Overview
小規模なプロダクトにおいてPythonでAPIサーバはしんどい(できるけどソースが美しくない)ので, 全然使ったことないけどGoでAPIサーバを立ててみたい
Description
普段お仕事ではDjango REST Framework, 趣味ではFlaskを使っているんですが, PythonでORマッパー使おうとするとSQLAlchemyとか使うハメになってしんどいです.
かといってDjangoを使うのはウェイトが重い...
peeweeとかいういい感じのライブラリもあるっぽいですが
ということでせっかくなのでGoで作ってみます
今回は, 要素を一意に特定できるid
と文字列text
を要素に持つPostを定義して, API経由でCRUD操作を行うようなアプリを作ります
GET: localhost:1323/posts -> 全postの取得
GET: localhost:1323/posts/:id -> id指定でpost取得
PUT: localhost:1323/posts/:id -> id指定でpost更新
POST: localhost:1323/posts/:id -> id指定でpost作成
DELETE: localhost:1323/posts/:id -> id指定でpost削除
Settings
DockerでGoの開発環境を整えます
とりあえず動かしたいという方は後述のソースをまるっとコピりましょう
環境構築について, こちらの記事がわかりやすいです.
Golang(Echo) x docker-composeでホットリロード用いた開発
ディレクトリ構造
.
├── Dockerfile
├── docker-compose.yml
└── src
├── main.go
├── go.mod
├── go.sum
├── controller
│ └── sqlite.go
└── view
└── post.go
プログラム本体
ディレクトリ構造に合わせてコピる
main.go
GET, POST, PUT, DELETEなど各メソッドを使える
package main
import (
"github.com/labstack/echo"
view "app/view"
)
func main() {
e := echo.New()
initRouting(e)
e.Logger.Fatal(e.Start(":1323"))
}
func initRouting(e *echo.Echo) {
e.GET("/posts", view.GetAllPosts)
e.GET("/posts/:id", view.GetPost)
e.POST("/posts", view.CreatePost)
e.PUT("/posts/:id", view.UpdatePost)
e.DELETE("/posts/:id", view.DeletePost)
}
post.go
json:id
でレスポンスのjsonのキー名を指定できる. デフォだとPascalCaseになっちゃうのがウザいので設定
gorm:hoge
で各属性を指定できる
これDB接続のところDecoratorみたいな感じで綺麗に共通化できないんでしょうか...
詳しい方教えて下さい
package view
import (
"net/http"
"github.com/labstack/echo"
. "app/controller"
)
type Post struct {
Id int `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
Text string `json:"text"`
}
func GetAllPosts(c echo.Context) error {
db := OpenSQLiteConnection()
defer db.Close()
db.AutoMigrate(&Post{})
var posts []Post
db.Find(&posts)
return c.JSON(http.StatusOK, posts)
}
func GetPost(c echo.Context) error {
db := OpenSQLiteConnection()
defer db.Close()
db.AutoMigrate(&Post{})
if id := c.Param("id"); id != "" {
var post Post
db.First(&post, id)
return c.JSON(http.StatusOK, post)
} else {
return c.JSON(http.StatusNotFound, nil)
}
}
func CreatePost(c echo.Context) error {
db := OpenSQLiteConnection()
defer db.Close()
db.AutoMigrate(&Post{})
post := new(Post)
if err := c.Bind(post); err != nil {
return err
}
db.Create(&post)
return c.JSON(http.StatusOK, post)
}
func UpdatePost(c echo.Context) error {
db := OpenSQLiteConnection()
defer db.Close()
newPost := new(Post)
if err := c.Bind(newPost); err != nil {
return err
}
if id := c.Param("id"); id != "" {
var post Post
db.First(&post, id).Update(newPost)
return c.JSON(http.StatusOK, post)
} else {
return c.JSON(http.StatusNotFound, nil)
}
}
func DeletePost(c echo.Context) error {
db := OpenSQLiteConnection()
defer db.Close()
if id := c.Param("id"); id != "" {
var post Post
db.First(&post, id)
db.Delete(post)
return c.JSON(http.StatusOK, post)
} else {
return c.JSON(http.StatusNotFound, nil)
}
}
sqlite.go
hoge.sqlite3はDBを保存したいパスを指定
:memory:
の方を使うとインメモリで使える
package controller
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
func OpenSQLiteConnection() *gorm.DB {
//db, err := gorm.Open("sqlite3", ":memory:")
db, err := gorm.Open("sqlite3", "hoge.sqlite3")
if err != nil {
panic("failed to connect database.")
}
db.LogMode(true)
return db
}
Docker関連ファイル
libc-dev, gccがないとrealize(ホットリロード用ライブラリ)がインストールできないのでインスコ
Dockerfile
FROM golang:1.13.5-alpine as build
WORKDIR /go/app
COPY src .
RUN apk add --no-cache git gcc libc-dev \
&& go build -o app \
&& go get gopkg.in/urfave/cli.v2@master \
&& go get github.com/oxequa/realize
FROM alpine
WORKDIR /app
COPY --from=build /go/app/app .
RUN addgroup go \
&& adduser -D -G go go \
&& chown -R go:go /app/app
CMD ["./app"]
docker-compose.yml
version: '3.5'
services:
app:
build:
context: .
target: build
volumes:
- ./src:/go/app
command: realize start --run --no-config
ports:
- 1323:1323
Run
モジュール管理用ファイルのgo.mod
を生成
$ docker run --rm -v `pwd`:/go/app -w /go/app golang:1.13.5-alpine go mod init app
各ファイルを作り終わったら次のコマンドでイメージのビルド&実行
$ docker-compose build
$ docker-compose up
~
app_1 | [02:18:27][APP] : Install started
app_1 | [02:18:28][APP] : Install completed in 0.446 s
app_1 | [02:18:28][APP] : Running..
app_1 | [02:18:28][APP] : ____ __
app_1 | [02:18:28][APP] : / __/___/ / ___
app_1 | [02:18:28][APP] : / _// __/ _ \/ _ \
app_1 | [02:18:28][APP] : /___/\__/_//_/\___/ v3.3.10-dev
app_1 | [02:18:28][APP] : High performance, minimalist Go web framework
app_1 | [02:18:28][APP] : https://echo.labstack.com
app_1 | [02:18:28][APP] : ____________________________________O/_______
app_1 | [02:18:28][APP] : O\
app_1 | [02:18:28][APP] : ⇨ http server started on [::]:1323
コンテナができたらプログラムが実行されます.
ソースコードを変更すると自動で更新されるのがいい感じです!
各メソッドが使えるかも試しておきましょう
curlでもいいですが, 直感的に使えるhttpieが便利です
# 全件取得(まだ0件)
$ http GET localhost:1323/posts
# 新規作成(idは自動インクリメント)
$ http POST localhost:1323/posts text=hoge
$ http GET localhost:1323/posts
# id=1のtextを更新
$ http PUT localhost:1323/posts/1 text=fuga
$ http GET localhost:1323/posts/1
# id=1を削除
$ http DELETE localhost:1323/posts/1
$ http GET localhost:1323/posts
Tips
go get
などコマンドを打ちたいときは
$ docker ps
で実行中のコンテナIDを調べて,
$ docker exec -it {CONTAINER_ID} sh
でコンテナ内に入れます
References
https://github.com/labstack/echo
https://github.com/jinzhu/gorm
Golang(Echo) x docker-composeでホットリロード用いた開発