はじめに
API の個人開発を進めている中で、Golang の勉強をサボりつつなんかいい感じの DB マイグレーションツールが欲しいなと調べていたところ、Prismaというものを見つけました。
GraphQL を利用して DB マイグレーションを行ったり、一部の言語で型安全な ORM として利用できるそうです。
細かい説明は他の記事に譲るとして、ここでは開発環境の構築を行います。
ただし、ローカルはあんまり汚したくないので、できるだけ仮想環境でやりましょう。
コンテナ構成
Prisma の公式ページに アーキテクチャが載っています。
クライアントは API サーバーとやりとりを行い、API サーバーと DB の間に一枚 Prisma を挟む形ですね。
これをローカル環境で再現しようとすると、以下の図の形になるかと思います。
Docker Compose で API サーバー、Prisma サーバー、DB の3マシンを起動し、ブラウザからは API サーバーにのみアクセスする形になります。
ブラウザからは Prisma サーバーおよび DB には直接アクセスできないわけですね。
今回構築する開発環境では、より作業を行いやすいようにもっとゆるく作ります。
具体的には下図の通りです。
最初の図から、Prisma Cli が増えています。また、Prisma サーバーにアクセスできるようになっていますね。
詳細は後述しますが、Prisma サーバーにはブラウザから DB 操作を行うことができるツールが付属しています。
本番環境でこのツールを使えるようにするのは当然 NG ですが、開発環境では何かと便利なので、ポートを解放して使えるようにします。
ディレクトリ構成
最終的なディレクトリ構成はこんな感じです。
親ディレクトリ
├── api-server
│ └── dockerfile
├── app
│ ├── Gopkg.lock
│ ├── Gopkg.toml
│ ├── datamodel.prisma
│ ├── generated
│ │ └── prisma-client
│ │ └── prisma.go
│ ├── main.go
│ ├── prisma.yml
│ └── vendor
├── docker-compose.yml
└── prisma-cli
└── dockerfile
一般的な Golang のディレクトリ構成とは大幅に異なりますが、これは Docker Compose による操作や指定をわかりやすくするためです。
通常のプロジェクトではちゃんと Golang の仕様や思想に従ったディレクトリ構成をお勧めします。
API サーバーの設定
特筆することはないです。
今回は Golang を使って API サーバーを建てる予定なので、依存性管理ができるようにdepを入れておきましょう。
FROM golang:1.13-alpine3.10
WORKDIR /go/src/app
RUN apk update \
&& apk add git \
&& go get -u github.com/golang/dep/cmd/dep
Prisma Cli の設定
Prisma には Cli が公式で用意されています。
Homebrew で公開されているものもありますが、今回は npm ライブラリを利用しましょう。
FROM node:13.1-alpine3.10
WORKDIR /usr/src/app
RUN npm install -g yarn \
&& yarn global add prisma
Docker Compose 設定
Prisma サーバーと MySQL については、公式のページにサンプルがあります。これを参考にしつつ書いていきましょう。
version: "3"
services:
api-server:
build: ./api-server
container_name: api-server
tty: true
ports:
- "8080:8080"
volumes:
- ./app:/go/src/app
prisma-cli:
build: ./prisma-cli
container_name: prisma-cli
tty: true
volumes:
- ./app:/usr/src/app
prisma-server:
image: prismagraphql/prisma:1.34
container_name: prisma-server
restart: always
ports:
- "4466:4466"
environment:
PRISMA_CONFIG: |
port: 4466
databases:
default:
connector: mysql
host: database
port: 3306
user: root
password: prisma
database:
image: mysql:5.7
container_name: database
environment:
MYSQL_ROOT_PASSWORD: prisma
volumes:
- mysql:/var/lib/mysql
volumes:
mysql: ~
いくつか注意点があります。
- API サーバーと Prisma Cli に
tty: true
をつける- API サーバーと Prisma Cli は実行し続けるコマンドを持たないため、起動完了後に停止してしまう
- 停止したコンテナにはアクセスできないので、停止せずにアクセスできる状態を保つための設定
- Prisma サーバーに
restart: always
をつける- Prisma サーバーは起動すると DB への接続を試みる
- MySQL は起動に時間がかかるため、初回の接続は失敗する
- 接続失敗するとコンテナが落ちてしまうので、接続できるまで再起動し続けるための設定
設定ができたらルートディレクトリに移動し、起動しましょう。
$ docker-compose up -d
起動処理のログは$ docker-compose logs
で確認できます。ログから Prisma サーバーの起動を確認できたら、ブラウザでlocalhost:4466
にアクセスしてみましょう。アクセスできれば OK です。
DB の操作
では、構築した環境で DB を操作してみましょう。
まずは Prisma Cli に入ります。ディストリビューションは Alpine なので、bash の代わりに ash を使います。
$ docker-compose exec prisma-cli ash
次にプロジェクトおよび DB の初期化を行います。
$ prisma init --endpoint http://prisma-server:4466
$ prisma deploy
ここで、エンドポイントとしてhttp://prisma-server:4466
を指定しています。
prisma-server
は docker-compose.yml で指定した Prisma サーバーのサービス名ですね。Docker Compose では、サービス名を利用してサービス間で通信を行うことができます。つまり、ここでは「Prisma サーバーの 4466 ポート」をエンドポイントとして指定しているのです。
prisma deploy
は datamodel.prisma
に従って DB のマイグレーションを行います。この辺りの書き方はGraphQLに従っているようなので、そちらを参考にしてください。
初期化が終わったら、データ構造の確認をしてみましょう。ブラウザからlocalhost:4466/_admin
にアクセスしてみてください。datamodel.prisma
の通りにデータ構造が作成できていれば OK です。
このページからは DB の操作を行うことができます。Prisma Adminとか言うらしいです。
試しにいくつかデータを登録してみましょう。画面右中央近くの+
をクリックし、画面右側に出てくるタブに値を入力するだけ。最後に画面右下のボタンで保存すれば完了です。
最後に Golang 用の Prisma Client を生成して終わりましょう。Prisma Cli から Prisma Client を生成ってギャグかな?
prisma.yml
に Golang 用の Prisma Client 生成設定を追加して、生成コマンドを実行します。
endpoint: http://prisma-server:4466
datamodel: datamodel.prisma
generate:
- generator: go-client
output: ./generated/prisma-client/
$ prisma generate
うまくいけば、上記の output で指定したディレクトリにファイルが生成されているはずです。
API サーバー
それでは、生成した Prisma Client を使って API サーバーを作成しましょう。
先ほどと同じようにシェルを使って API サーバーに入ってもいいのですが、私は VSCode プラグインのRemote - Containersをお勧めします。
なんでもいいので、シェルが使える状態になったら OK です。
まずは先ほど生成した Prisma Client を使ってデータを取得してみましょう。公式ページのこの辺りやこの辺りを参考に進めていきます。
$ cd /go/src/app
$ dep init
$ touch main.go
package main
import (
"app/generated/prisma-client"
"context"
"fmt"
)
func main() {
client := prisma.New(nil)
ctx := context.TODO()
users, err := client.Users(nil).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Println(users)
}
はいできました。Golang は学習中なのでクオリティは察してください。
とりあえず実行してみましょう。
$ go run main.go
[{ck3ez5b7e00110766n4zr6wro Alice} {ck3ez5b8u001507666qcbs2qy Bob} {ck3ez5b9b001907668hf3pgyw Charles}]
DB に登録した「Alice」「Bob」「Charles」の情報が取得できていますね。ck3e~
の文字列は自動生成された ID です。
あとはこれを HTTP リクエストに対して Json 形式でレスポンスできれば OK です。この記事とこの記事を参考に進めていきましょう。
package main
import (
"app/generated/prisma-client"
"context"
"log"
"net/http"
"reflect"
"github.com/ant0ine/go-json-rest/rest"
)
var ctx = context.TODO()
var client = prisma.New(nil)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/users", GetUsers),
)
if err != nil {
log.Fatal(err)
panic("Failed to setup router.")
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
func GetUsers(w rest.ResponseWriter, r *rest.Request) {
users, err := client.Users(nil).Exec(ctx)
if err != nil {
handleError(w, err)
return
}
usersMap := usersToMap(users)
w.WriteJson(usersMap)
}
func handleError(w rest.ResponseWriter, err error) {
log.Fatal(err)
result := map[string]string{"error": "server error"}
w.WriteJson(result)
w.WriteHeader(http.StatusInternalServerError)
}
func usersToMap(users []prisma.User) map[string]map[string]string {
result := make(map[string]map[string]string)
for _, user := range users {
tag := reflect.TypeOf(user).Field(1).Tag.Get("json")
result[user.ID] = map[string]string{tag: user.Name}
}
return result
}
出来上がったら、実行してアクセスしてみましょう。アクセス先はlocalhost:8080/users
です。
$ go run ./main.go
{
"ck3ez5b7e00110766n4zr6wro": {
"name": "Alice"
},
"ck3ez5b8u001507666qcbs2qy": {
"name": "Bob"
},
"ck3ez5b9b001907668hf3pgyw": {
"name": "Charles"
}
}
こんな感じの Json レスポンスが取得できれば OK です!
おわりに
というわけで、仮想環境に Prisma サーバーを軸にした開発環境を構築できました。これを発展させていけば、簡単な API サーバー程度ならすぐ実装できるんじゃないでしょうか。
とはいえ GraphQL の詳細や Prisma Client の利用方法についてはほとんど触れていませんし、ちゃんと運用するためにはもっと勉強が必要でしょうね。覚えることはどんどん増えていきます。
今回は無印の Prisma を利用しましたが、今後リリース予定の Prisma2 が現在開発されています。
まだ Golang 非対応(2019/11/30)だったので今回は利用しませんでしたが、早いところ Golang で使えるようになってほしいですね。
参考資料
Prisma 関連
prisma - 最速 GraphQL Server 実装
Prisma
Prisma Docs
Prisma 公式サーバーイメージ
Prisma2
Docker 関連
いい加減 docker-compose で links を使うのをやめて network でコンテナ間名前解決をする
Dockerfile のベストプラクティス
Compose ファイル・リファレンス
Golang 関連
Go にはディレクトリ構成のスタンダードがあるらしい。
VSCode で Go の Modules 設定
golang で REST API をやってみた ①
Go-Json-Rest
Standard Go Project Layout
Go の構造体にメタ情報を付与するタグの基本
Golang
dep(Golang の依存性管理ツール)