BETA

Golang×Prisma×MySQL 開発環境を Docker Compose で構築!

投稿日:2019-11-30
最終更新:2019-11-30
※この記事は外部サイト(https://qiita.com/drafts/25187ce3541ec3686...)からのクロス投稿です

はじめに

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 deploydatamodel.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 の依存性管理ツール)

その他

Remote Containers
GraphQL
draw.io(製図に利用)

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

だらだら更新していきたい技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!