続・Golangを使って簡単なwebバックエンドを書いた (意訳: Prismaを使ったDBスキーマ管理とGolangにおけるセッションハンドリング)

# はじめに
どうも。~~飛び上がり自殺をしようとしたら一般気象学 第2版補訂版(著: 小倉 義光, 2016)を持った奇妙な関西弁を話す謎の生物に止められたため、今度はドリルでモホロビチッチ不連続面まで掘削し、その過程の地熱と地圧で死ねないか考えている者です。~~

前回、[Golangで簡単なWebバックエンドを書いた]の記事では、次の2点の問題について取り上げました。

* 認証周り (セッション管理)がちょーっと弱い。
* データベースのマイグレーションがDjangoやRailsのように実用性のあるものではない。

今回はこの2点を頑張って対処してみます。

## セッション管理の問題
この問題ついては別途、ライブラリを作りました。👉[gauth]

このライブラリは「ガウス」とか「ゲウス」とか「ギャウス」とか読んであげてください。

[gauth]はベースとなる技術として、JWTを使っています。JWTについては調べていただくことにして、このライブラリによって、

* セッション管理
* ログインが必須なリソースの保護

ができるようになりました。めでたしめでたし

## DBの問題
GolangでORMを行う場合、[Gorm]の利用が筆頭に上がるかと思います。が、**このライブラリの自動マイグレーションはフィールドの追加とテーブルの追加以外の事ができません。** 例えば、gormで次の事をした場合、マイグレーションを自分で書き上げなければなりません。

* NULL許容だったフィールドをNULL非許容にした
* フィールドやテーブルをリネームした
* フィールドを消した
* すでにあるフィールドにインデックス属性をつけた

Djangoはここら辺が非常によくできていて、相当に特殊な場合を除いて、`python manage.py makemigrations`を実行するだけで、これらの問題に対処するマイグレーションファイルをいい感じに書いてくれます。**が、Golangではそういったいい感じのDBマイグレーションの生成支援ツールがない。**

・・・ **よし、作るか**。

等と普段の僕なら考えるのですが、巷ではSpec-Drivenな開発手法が流行っているようです。Zノーテーション万歳！と言いたいところですが、そこまで高度な形式仕様の記述は必要としていません。**欲しいのは何かしらの言語で記述されたデータモデルの仕様をDBのスキーマに落としてくれるツールだ！！しかも実用的なマイグレーションつきで！！**

というわけで探したら・・・ **ありました！！[Prisma]が！！**

## [Prisma]とは
公式サイトより引用すると、

> Prisma replaces traditional ORMs

との事です。つまり、一般的なORMを置き換えることを目的としたツールという事になります。

### [Prisma]の機能

Prismaがもつ機能として、次の機能があります:

* DBとの調停
* **[GraphQL]ベースのDSLによってモデルの仕様を書くことができる**
* **実用的なマイグレーション** (なお2.0ではマイグレーションのバージョニングが追加されるという。)
* DSLによって記述されたモデルデータをGolangなりTypescriptなりのクライアントとして落とし込める
* **Adminパネル**

・・・Djangoが使えない(というか巷ではNodeJSやGolangなどでWebバックエンドを開発することが多いそうですが)場合は間違いなく[Prisma]はモデル管理ツールの筆頭候補になりそうですね。素晴らしい。

### 使ってみる

と、いうわけで[Prisma]を使ってみます。

#### 1. Prismaクライアントのインストール

クライアント自体は[NPM]に置かれています。と、いうわけで、おもむろにコンソールを開いて次のコマンドでクライアントをインストールします。

```
npm i -g prisma
```

#### 2. サーバーのインストール

[Prisma]のサーバーはDocker化されています。つまり、`docker-compose.yml` あたりで開発に必要な構成を定義して開発を行うとようにしても良いのですが、今回は単純なコードサンプルだけなので、[Postgres]と[Prisma]をdockerコンテナとして動かします:

```docker-compose.yml
version: '3'

services:
  db:
    image: postgres:alpine
    restart: always
    environment:
      POSTGRES_PASSWORD: go-sample
      POSTGRES_USER: go-sample
  prisma:
    image: prismagraphql/prisma:1.31
    restart: always
    depends_on:
      - db
    ports:
      - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        databases:
          default:
            connector: postgres
            host: db
            port: 5432
            user: go-sample
            password: go-sample
```

今回、使用するサーバーのバージョンは1.31ですが、**これより新しいバージョンの[アプリ]がリリースされた場合はそれを使用するようにしてください。**

##### [Prisma]のサーバーを終了するとSIGKILLが発生する件について

頑張って[Prisma]と[Postgres]の`docker-compose.yml`を書き、さあやっと遊べますよヤッター⭐と思ったのですが、なんとdocker-composeを終了する時に[Prisma]が終了コードコード137を返すではありませんか！！

```
go-gql-sample_prisma_1 exited with code 137 <-- アッー！！
db_1       | 2019-04-28 03:24:13.906 UTC [1] LOG:  received smart shutdown request
db_1       | 2019-04-28 03:24:13.907 UTC [1] LOG:  background worker "logical replication launcher" (PID 24) exited with exit code 1
db_1       | 2019-04-28 03:24:13.907 UTC [19] LOG:  shutting down
db_1       | 2019-04-28 03:24:13.915 UTC [1] LOG:  database system is shut down
go-gql-sample_db_1 exited with code 0
```

この問題、まさに以前Qiitaで記事にしたことのある問題ドンピシャなのです:

* [Docker Containerの終了時にExit Code 137 (SIGKILL)が出る時の対処法]

**と、いうわけでこの問題に対処します。** まず、原因箇所を特定するため、`docker-compose ps`を実行して[Prisma]の中でどういったプロセスが実行されているのか調べます:

```
          Name                         Command              State            Ports         
-------------------------------------------------------------------------------------------
go-gql-sample_db_1          docker-entrypoint.sh postgres   Up       5432/tcp              
go-gql-sample_prisma_1      /bin/sh -c /app/start.sh        Up       0.0.0.0:4466->4466/tcp
```

この出力では、`go-gql-sample_prisma_1`が[Prisma]のコンテナになります。そして、このコンテナが実行しているコマンドはどうやらシェルで書かれているようですね。というわけでその中身を見てみましょう。

```/app/start.sh
#!/bin/bash
set -e
/app/prerun_hook.sh
/app/bin/prisma-local  # <-- これ!
```

はいビンゴ！つまり`/app/bin/prisma-local`は新しく作成されたプロセス内で実行されます。(i.e. SIGINTが伝わってこない) そして同様に`/app/bin/prisma-local`についてもここに書きたいところですが、**当該のスクリプトをここに書くのは長過ぎるので**、結論のみを書くと、`/app/bin/prisma-local`はちゃんと`exec`を使ってサーバーを起動しておりました。

というわけで、`/app/start.sh`の内容を次のように書き換え、新しくDockerイメージを作成します:

```/app/start.sh
#!/bin/sh -e
# -*- coding: utf-8 -*-

/app/prerun_hook.sh
exec /app/bin/prisma-local  # execビルトインコマンドはプロセスを置き換える
```

```prismasvr.dockerfile
FROM prismagraphql/prisma:1.31
COPY ./prisma-patch.sh /app/start.sh
CMD [ "/app/start.sh" ]
```

```docker-compose.yml
version: '3'

services:
  db:
    image: postgres:alpine
    restart: always
    environment:
      POSTGRES_PASSWORD: go-sample
      POSTGRES_USER: go-sample
  prisma:
    build:
      context: ./
      dockerfile: prismasvr.dockerfile
    restart: always
    depends_on:
      - db
    ports:
      - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        databases:
          default:
            connector: postgres
            host: db
            port: 5432
            user: go-sample
            password: go-sample
```

上記の変更を行った後、`docker-compose up`と`docker-compose stop`を行って様子を見てみましょう。

```
go-gql-sample_prisma_1 exited with code 143
db_1         | 2019-04-30 06:42:17.810 UTC [1] LOG:  received smart shutdown request
db_1         | 2019-04-30 06:42:17.813 UTC [1] LOG:  background worker "logical replication launcher" (PID 24) exited with exit code 1
db_1         | 2019-04-30 06:42:17.813 UTC [19] LOG:  shutting down
db_1         | 2019-04-30 06:42:17.835 UTC [1] LOG:  database system is shut down
go-gql-sample_db_1 exited with code 0
```

と、このように、[Prisma]を正常に終了させるようにする事ができました。

尚、これはバグなのでGithubに[プルリク]を出しています。**マージされるかどうかは不明。**

#### 3. prisma.ymlの作成

[Prisma]には環境設定ファイルが存在し、これを以下のコマンドで簡単に作成しておきます:

```
prisma init --endpoint http://localhost:4466
```

このコマンドを実行すると、`prisma.yml`と`datamodel.prisma`の2つのファイルが作成されます。前者が環境設定ファイル、後者がデータモデル定義ファイルとなります。また、データモデルの定義には複数のファイルを指定することができ、そのようにする場合は`prisma.yml`を次のように書き換える必要があります。

```prisma.yml
endpoint: http://prisma:4466
datamodel:
  - models/user.prisma
  - models/payment.prisma
  - models/etc.prisma
```

しかし、今回の場合、記述するべきモデルは一つだけなので、デフォルトの設定でも問題ありません。

#### 4. データモデルの定義

さて、[Prisma]のバグに簡易パッチを当て、ようやくデータモデルの定義に移ることができます。先にも述べたように、[Prisma]のモデル定義のDSLは[GraphQL]の`type`に各種ディレクティブを付け加えたものになっています。

そして、今回の簡単な認証バックエンドに必要なモデルは一つ。ユーザー名とパスワードのハッシュを格納する`User`モデルのみです。これを定義します:

```graphql:datamodel.prisma
type User {
  id: ID! @unique @id
  username: String! @unique
  password: String!
}
```

上記コードで独特な点は、`@unique`と`@id`で、次の効果があります。

* `@unique` はSQLで言うところの`UNIQUE`制約、つまり、「保存されているデータの当該カラムの情報はユニークでなければならない」という制約です。
* `@id` はIDで型付けされたフィールドのみ有効な語句で、これが指定される事によって当該のフィールドの値は[Prisma]によって自動生成され、ユーザがAdminパネルで編集することはできません。

これらのディレクティブに加え、`Date`型には`@createdAt`等のディレクティブなども用意されています。詳細については[ヘルプ]をご覧頂くとして、取り敢えず上記コードで`User`モデルを定義することができました。

#### 5. モデルの展開

次に、モデルを[Prisma]に展開させます。とはいえ、やることは単純に次のコマンドを実行するだけです:

```shell
prisma deploy
```

#### 6. クライアントの作成

次に、バックエンドから[Prisma]を操作するためのクライアントを作成します。今回は[GraphQL]ベースのバックエンドなので、作成すべきコードは**次の2つです**:

* [GQLGen]のための[GraphQLスキーマ定義]
* Resolver :arrow_right: [Prisma] のためのGo言語で書かれた[Prisma]クライアント

これらのコードを作成するには、`prisma.yml`に少し変更を加えます:

```prisma.yml
endpoint: http://prisma:4466
datamodel:
  - models/user.prisma
generate: # 👈👇この項目を追加する
  - generator: go-client
    output: ./backend/prisma
  - generator: graphql-schema
    output: ./backend/schemata/generated.graphql
hooks:
  post-deploy:
    - prisma generate # 👈 prisma deployした時に自動的にクライアントも更新させる
```

上記変更を行った後、`prisma deploy` あるいは、`prisma generate`を実行すると、`./backend/prisma`にGo用クライアントが、`./backend/schemata/generated.graphql`に[GraphQL] スキーマが生成されます。

#### 7. ゴリゴリ書く
あとはゴリゴリとバックエンドを書くだけです:star:
また、書いたコードは[ここ]にあります。ありがとうございました。

# 終わりに

とりあえず、[Prisma]の使い方をざっくり書いてみましたが、コードを修正してプルリク送るよりもQiitaに記事をアウトプットするほうが労力がかかるように思えました。~~独創的な死に方で自殺する前に考えた事をコードに落とし込むAIと考えたことをブログ記事にするAIはよ！！~~

では。

[Golangで簡単なWebバックエンドを書いた]: https://qiita.com/hyamatan/items/5b32d7996e944c6836b6
[gauth]: https://github.com/hiroaki-yamamoto/gauth
[Gorm]: https://github.com/jinzhu/gorm
[Prisma]: https://www.prisma.io/
[NPM]: https://www.npmjs.com/package/prisma
[アプリ]: https://github.com/prisma/prisma
[Postgres]: https://www.postgresql.org/
[Docker Containerの終了時にExit Code 137 (SIGKILL)が出る時の対処法]: https://qiita.com/hyamatan/items/0b7df8927ddbab603104
[先の記事]: https://qiita.com/hyamatan/items/0b7df8927ddbab603104
[プルリク]: https://github.com/prisma/prisma/pull/4464
[ヘルプ]: https://www.prisma.io/docs/datamodel-and-migrations/datamodel-POSTGRES-knum/
[GraphQL]: https://graphql.org/
[GraphQLスキーマ定義]: https://graphql.org/learn/schema/
[GQLGen]: https://gqlgen.com/
[ここ]: https://github.com/hiroaki-yamamoto/go-gql-sample