hokuishi.be

Goa と sqlc, Atlas で快適な Web API サーバ開発

2022/12/15

この記事はフラー株式会社 Advent Calendar 2022の 15 日目の記事です。
14 日目 の記事は @is_hoku で「Goa v3 の気をつけポイント」でした。

目次

はじめに

Web API サーバを開発する時に問題になることの一つに、コードで管理されていない外部のデータとコード自体の乖離があると思います。 大まかに考えて以下の 2 つが思いつきます。

  • API スキーマとコードの乖離
  • テーブル定義とコードの乖離

API スキーマとテーブル定義の変更を毎回忘れずにコードに反映して、実装が正しいことを把握しておくように努めることはバグの原因になりそうです。 そこで RDB を使う前提で上記の問題を解決する方法を模索しました。(そして今も模索中)

コードは is-hoku/goa-sample を見てください。

API スキーマとの乖離

これは Goa を使えば (一応) 解決です。しかもプレゼンテーション層をあまり考えないで開発できるので良いです。

テーブル定義との乖離

sqlc を使って解決しました。 sqlc はテーブル定義、クエリ定義、設定ファイルから型安全なコードを生成してくれるため、テーブル定義は常にコードと離れず型安全です。テーブル定義だけ変更してコードを変更し忘れたり、テーブル定義の型とコードの型が間違っていたりしてバグを仕込むことが無さそうで健康に良いと思います。

Atlas を導入

これで API スキーマとテーブル定義がコードで管理されて、そのコードからデータが生成されるため安全になりました。 しかし DB とのやりとりの部分をもう少し快適にしたい欲求が出てきました。

テーブル定義については以下のような欲求があります。

  • テーブル定義が正しいことの確認を定義を記述している段階でできると嬉しい (複雑なリレーションが間違っていないかなど)
  • ER 図を毎回手作業で更新したくない
  • マイグレーションで楽をしたい (Versioned Migration はやや面倒?)

1 つ目と 2 つ目はテーブル定義から ER 図が生成できると解決できそうです。 Prisma から ER 図を生成する 感じでできないかと考えていたら、 Atlas というツール (DDL) を見つけました。 Atlas DDL でテーブル定義を書いたら ER 図を生成できるそうです (と思ったら最新バージョンでは対応していないらしい (参考)、気長に待つかバージョンを下げて使用します)。

また 3 つ目のマイグレーションについてですが、 Versioned Migration は開発速度重視のハッカソンや個人開発、(データが) 小規模なアプリケーションにおいては少し煩雑に感じます (大規模なサービスでロールバック・ロールフォワードなどが必要な場合はありそうです)。 マイグレーションファイルを作成して、 golang-migrate などでマイグレートするより、 DDL の更新の差分をそのまま適用してくれる Declarative Migration の方を使いたい場合があります。 Atlas は Declarative Migration をサポートしているため、 Atlas DDL でテーブル定義を更新すればマイグレーションができます。 しかし sqlc を使うためにテーブル定義の SQL ファイルが必要なので、

Atlas DDL でテーブル定義を記述 → マイグレート → DB から現在のスキーマの SQL ファイルを作成

をするスクリプトを書きました。

ここまでの一連の作業を make で実行できるようにしました。実行している内容は以下です。

(Atlas DDL でテーブル定義と SQL でクエリ定義を記述したら)

  1. Atlas で DB に対してマイグレート
  2. DB の現在のテーブル定義のスキーマを SQL ファイルとして吐く
  3. sqlc でテーブル定義・クエリ定義の SQL ファイルから Go のコードを生成

まとめ

Goa で Web API サーバ開発をする際に sqlc と Atlas を用いることで、開発が少し快適になりました。 DB のデータとテーブル定義、コードが乖離しないように Atlas DDL という一つの情報源からそれぞれのデータやファイルを生成するようにしました。 これによって開発をする時にプレゼンテーション層とインフラストラクチャ層 (の DB の部分) で気にすることが少なくなって、ビジネスロジックのコードにより集中できるような構成になり良い感じです。
よく考えてみると、あらゆるデータをテキストファイルで管理することと一つの情報源から複数の異なるデータ構造のデータを生成することが大事な気がします。

16 日目は @Taip00n さんで「ターミナルでスターウォーズを見るべ」です!