Go는 초기화되지 않은 변수에 zero values라고 하는 것이 담긴다.
예를 들어, bool 타입에는 false, int라면 0이 기본적으로 저장되는 식이다.
대충 아래와 같은 코드가 있다면, 우리는 클라이언트에서 optional하게 title이나 checked가 전달될 것을 예상하고 코드를 짜야 한다.
HTTP 메서드에서 PATCH는 일부만을 변경하기 위해 사용되는 메서드이기 때문에, title만 업데이트 하고 싶은 경우나 checked만 업데이트 하고 싶은 경우도 대응해야 하는 것이다.
type Body struct {
Title string `json:"title"`
Checked bool `json:"checked"`
}
func (handler *Handler) Update(c echo.Context) error {
ID := c.Param("id")
id, err := strconv.Atoi(ID)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
body := &Body{}
item := &Item{}
if err := c.Bind(body); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// ...
}
여기 위의 Body
는 bool
값을 그대로 받고 있는데, 만약 checked
값이 주어지지 않는다면 body
에 Bind
되는 과정에서 (JSON이 struct 객체로 언마샬(unmarshalling) 되는 과정에서) false
가 들어가게 된다.
그렇게 되면 아주 애매한 문제가 생긴다. 즉, 런타임에서 '클라이언트가 false
를 보낸 것인지', 'go가 zero value로 false로 초기화한 것인지' 알 수 없게 되는 것이다. 즉, 클라이언트가 명시적으로 false
를 보낸 경우와, checked
를 누락해서 보낸 경우가 구분되지 않는다.
간단한 해결방법은 struct에서 bool 대신 bool의 포인터(pointer)를 이용하는 것이다.
type Body struct {
Title string `json:"title"`
Checked *bool `json:"checked"`
}
이렇게 하면 클라이언트가 {"checked": false}
를 전송한 경우, body.Checked = false
가 담기게 되고, 클라이언트가 checked를 누락하고, 예컨데 {"title": "new title"}
만 보낸 경우, body.Checked = nil
이 된다.
그렇다면, DB에 update는 어떻게 할까? GORM과 echo/v4를 기준으로 정리해본다.
func (handler *Handler) Update(c echo.Context) error {
ID := c.Param("id")
id, err := strconv.Atoi(ID)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
body := &Body{}
item := &Item{}
if err := c.Bind(body); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if err = c.Validate(body); err != nil {
return err
}
if len(body.Title) == 0 && body.Checked == nil {
return echo.NewHTTPError(http.StatusBadRequest, "At least title or checked should be provided")
}
updateDto := make(map[string]interface{}, 2)
if len(body.Title) != 0 {
updateDto["Title"] = body.Title
}
if body.Checked != nil {
updateDto["Checked"] = body.Checked
}
err = handler.repo.Model(item).Where("id = ?", uint64(id)).Updates(updateDto).Error
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, item)
}
updateDto
라는 map
객체를 생성하고, title
과 checked
가 있는 경우들을 조건분기하며 추가해 나가면 된다.