validatonをどう書くか
handler
↓
usecase (usecase, input)
↓
domain (model, repository)
↑
infra
みたいな構造のWebアプリケーションを構築する場合、validationをどう書くかという話です。考慮する点としては、
- validation ロジック(定義)をどこに書くか
- validation をどこで実行するか
の2点を考えていく必要がある。
- については
- usecase の中でも入力された値を使うので、そこでもvalidationをすると二度手間になる。
- usecase より下では理想的で「綺麗な」値だけを考えたい
- usecase 層のinputを構造体として定義している
あたりを考慮すると、usecase層のinput定義にロジックを記述するのがいいように思う。
2.については、「usecaseにvalidationしていない値を持ち込みたくない」という考えを推し進めるとhandlerで個別にやりたくなってくる。
ただusecaseの先頭でやるとエラーをまとめてcatchできるメリットがあって、コード的には収まりがいい。
サンプルコード
上記の点を考慮したサンプルコードを以下のまとめる。サンプルコードではusecase層でvalidationを実行しつつ、handlerでやる場合もコメントアウトして併記している。
(色々端折ってます)
handler
func GetPagingList(c *echo.Context) error {
page, err := strconv.Atoi(ctx.QueryParam("page"))
if err != nil {
page = 1
}
perPage, err := strconv.Atoi(ctx.QueryParam("per_page"))
if err != nil {
perPage = 20
}
in := input.ListGet{
UserID: ctx.currentUserID,
Page: page,
PerPage: perPage,
}
// handlerでvalidationするならここで
//if err := in.Validate(); err != nil {
// return echo.NewHTTPError(http.StatusBadRequest, err.Error())
//}
list, err := usecase.GetList(in)
if err != nil {
switch err.(type) {
case *errors.InvalidParameter:
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
case *errors.NotFoundError:
return echo.NewHTTPError(http.StatusNotFound, "not found")
default:
return echo.NewHTTPError(http.StatusInternalServerError, "internal server error")
}
}
}
usecase.input
type ListGet struct {
Page int
PerPage int
UserID int64
}
func (c *ListGet) Validate() error {
if c.Page < 1 {
return errors.NewInvalidParameter("page parameter must be upper 0")
}
if c.PerPage < 1 {
return errors.NewInvalidParameter("per_page parameter must be upper 0")
}
if c.PerPage > 100 {
return errors.NewInvalidParameter("per_page parameter must be under or equal 100")
}
return nil
}
usecase
func GetList(in input.ListGet) (list *model.List, err error) {
if err = in.Validate(); err != nil {
return
}
// ~~ list 取得処理 ~~
}
最後に
サンプルにあるc.PerPage > 100
みたいなのって完全にドメインの知識だよね?というのはあって、ある程度仕方ないとはいえドメイン知識がusecaseに染み出してる感は否めません。
その辺りは「これはAPIの定義の話だ」と割り切ってしまえばそんなに違和感はないかもしれません。