その2 ファイル分割してみる - golang製Web Framework 「echo」事始め
続き
前回の予告どおり、echoのサンプルプロジェクトのcrudモジュールについて、ソースファイルの分割をしてみる。
なお、普段からgolangを書いている人にとってはつまらない内容かもしれない。
けれども、なかなかどうしてimport何かではまったりして楽しかった。
ディレクトリ構成
こんな感じにしてみた
$ tree crud ├── handler │ ├── create.go │ ├── delete.go │ ├── list.go │ └── update.go ├── model │ └── user.go └── router.go
分割
server.go から router.go へ
server.go
は router
だけをまとめて、 router.go
とした。
router.go
package main import ( "github.com/labstack/echo" "github.com/labstack/echo/middleware" "./handler" ) func main() { e := echo.New() // Middleware e.Use(middleware.Logger()) e.Use(middleware.Recover()) // Routes e.GET("/users", handler.ListUser) e.GET("/users/:id", handler.GetUser) e.POST("/users", handler.CreateUser) e.PUT("/users/:id", handler.UpdateUser) e.DELETE("/users/:id", handler.DeleteUser) // Start server e.Logger.Fatal(e.Start(":1324")) }
modelディレクトリ
modelディレクトリはその名の通り、モデルを格納している。
model/user.go
package model type ( User struct { ID int `json:"id"` Name string `json:"name"` } ) var ( Users = map[int]*User{} Seq = 1 )
var (
Users = map[int]*User{}
Seq = 1
)
は永続層で、本来DBに置き換わる領域。
handlerディレクトリ
handler
という名前はサンプルから取った名前。ビジネスロジックを格納する領域としており、 Service
とした方が良いかもしれない。
それぞれCRUDの役割を持つロジックを定義している。
$ tree crud ├── handler │ ├── create.go │ ├── delete.go │ ├── list.go │ └── update.go
create.go
package handler import ( "net/http" "github.com/labstack/echo" "../model" ) func CreateUser(c echo.Context) (err error) { u := &model.User{ ID : model.Seq, } if err := c.Bind(u); err != nil { return err } model.Users[u.ID] = u model.Seq++ return c.JSON(http.StatusCreated, u) }
router.go
の e.POST("/users", handler.CreateUser)
にて下記の様なPOST リクエストを受け取り、データを格納する。
curl -v POST -H "Content-Type: application/json" "http://localhost:1324/users" -d '{ "id" : 1, "name" : "Anthony Kiedis" }'
modelは ./model
というようにimportする。
GOPATH配下の場合は絶対パスを指定するのがお作法らしいが、今回の場合はGOPATH配下に構築はしていないので、相対パスで書いてしまっている。
ハマったコト
ID : model.Seq,
の末尾に ,
が必要な理由が分からなかった…(削除するとコンパイルエラー)
update.go
package handler import ( "net/http" "strconv" "github.com/labstack/echo" "../model" ) func UpdateUser(c echo.Context) (err error) { u := new(model.User) if err := c.Bind(u); err != nil { return err } id, _ := strconv.Atoi(c.Param("id")) model.Users[id].Name = u.Name return c.JSON(http.StatusOK, model.Users[id]) }
router.go
の e.PUT("/users/:id", handler.UpdateUser)
にて下記の様なPUT リクエストを受け取り、データを更新する。
curl -v -X PUT -H "Content-Type: application/json" "http://localhost:1324/users/1" -d '{ "name" : "Anthony Kiedis at Red Hot Chili Peppers" }'
ハマったコト
Golang/echoとは直接関係ないのだけど、Elasticsearchをcurlで触っていた時はコンテントタイプは不要だった。
しかし、今回 -H "Content-Type: application/json"
をちゃんと指定しないと、正しくJSONパラメータとして渡せなかった。
まあコレが普通なのだけど、イマイチ・・・。
list.go
package handler import ( "net/http" "strconv" "github.com/labstack/echo" "../model" ) func GetUser(c echo.Context) (err error) { id, _ := strconv.Atoi(c.Param("id")) return c.JSON(http.StatusOK, model.Users[id]) } func ListUser(c echo.Context) (err error) { return c.JSON(http.StatusOK, model.Users) }
router.go
の e.GET("/users", handler.ListUser)
や e.GET("/users/:id", handler.GetUser)
にて、下記の様なGET リクエストを受け取り、データを取得する。
curl -i -v GET "http://localhost:1324/users" curl -i -v GET "http://localhost:1324/users/1"
delete.go
package handler import ( "log" "net/http" "strconv" "github.com/labstack/echo" "../model" ) func DeleteUser(c echo.Context) (err error) { log.Println(model.Users) id, _ := strconv.Atoi(c.Param("id")) delete(model.Users, id) log.Println(model.Users) return c.NoContent(http.StatusNoContent) }
router.go
の e.DELETE("/users/:id", handler.DeleteUser)
にて、下記の様なDELETE リクエストを受け取り、データを削除する。
curl -i -v -X DELETE "http://localhost:1324/users/1"
おわり
まあこんな所でしょうか。
あとは
- DBへの永続化
- エラーハンドリング
- デプロイ
当たりの優先順位でやっていきたい。