DEV Community

Pallat Anchaleechamaikorn
Pallat Anchaleechamaikorn

Posted on

Go generics ใน gin Handler

#go

เนื่องจาก Go 1.18 เริ่มใช้ generic แล้ว ซึ่งใน block ก่อนหน้านี้ ผมก็นำเอา blog ต้นฉบับของ contributors มาแปลคร่าวๆให้ได้อ่านกัน ทำให้ได้รู้ usecases ของการใช้ generic ว่าปกติจะใช้กับ ฟังก์ชั่นที่ ทำงานกับ map, slice หรือ channel เป็นหลัก และเอาจริงๆ หลายๆ usecases ที่คิดกันออกมา มันอาจจะทำงานกับ interface ได้ดีกว่า ที่จะพยายามยัดเยียดเอา generic ใส่เข้าไปเพื่อความเท่

วันนี้ก็เลยลองใช้ generic มาช่วยในการสร้าง gin HandlerFunc เพื่อให้เราสามารถเขียน handler ได้ง่ายขึ้นด้วยหน้าตาประมาณนี้

func Handler(request) (response,error)
Enter fullscreen mode Exit fullscreen mode

ประโยชน์ก็คือ มันจะช่วยให้เราเขียนเทสให้ handler ง่ายขึ้น โดยไม่ต้อง mock gin.Context

โดยเราจะวางโครงสร้างโปรเจ็คเราแบบนี้

Image description

  • domain ภายในนี้จะเป็นที่รวม business logic ทั้งหมด
  • ginhandlerfunc เป็น converter ที่จะช่วยแปลง handler แบบที่เราอยากได้ ไปเป็น gin.HandlerFunc

เรามาดูหน้าตาของ handler ที่ทำเสร็จแล้วกันก่อนว่าจะมีหน้าตาแบบไหน

type CustomerRequest struct {
    Name string `json:"name"`
}
type CustomerResponse struct {
    Name string `json:"name"`
}

func CustomerHandler(request CustomerRequest) (CustomerResponse, error) {
    fmt.Println("CustomerHandler", request)
    return CustomerResponse{Name: request.Name}, nil
}

func New() gin.HandlerFunc {
    return ginhandlerfunc.NewBindRequestOKResponse(CustomerHandler)
}
Enter fullscreen mode Exit fullscreen mode

ซึ่งเมื่อได้ code หน้าตาแบบนี้แล้ว เวลาเราจะเขียน business logic ก็เขียนแค่ใน CustomerHandler ได้เลย

ทีนี้เราลองมาดูใน ginhandlerfunc.NewBindRequestOKResponse กันว่ามีวิธีเขียนอย่างไร

func NewBindRequestOKResponse[Request, Response any](handler func(Request) (Response, error)) gin.HandlerFunc {
    return func(c *gin.Context) {
        var request Request
        if err := c.ShouldBindJSON(&request); err != nil {
            c.AbortWithError(http.StatusBadRequest, err)
            return
        }
        response, err := handler(request)
        if err != nil {
            c.AbortWithError(http.StatusInternalServerError, err)
            return
        }
        c.JSON(http.StatusOK, response)
    }
}
Enter fullscreen mode Exit fullscreen mode

นี่ก็คือหน้าตาของมัน โดยเราใช้ generic เข้ามาช่วยในส่วนการประกาศ Request และ Response ให้ ซึ่งวิธีนี้จะทำไม่ได้หากไม่มีการใช้ generic เข้ามาช่วย
และสามารถไปดู ตัวอย่างที่มากกว่านี้ได้ที่ https://github.com/pallat/generic_gin_handler

ซึ่งจะมีตัวอย่างของ handler ที่รับ parameter ผ่าน URI Param และการสร้าง handler struct มาเป็น handler ของเราในกรณีที่ต้องมี dependencies อื่นๆ

นี่เป็นเพียงแค่โค้ดตัวอย่างเบื้องต้น หวังว่าจะเป็นประโยชน์ ไม่ว่าจะนำไปใช้ หรือสร้างแรงบันดาลใจให้เขียนท่าอื่นๆที่เหมาะสมกับสไตล์ของตัวเองกันนะครับ

Top comments (0)