Ginのチュートリアルから派生させて、PostgreSQLへの保存まで
GolangのフレームワークであるGinを学習しようと思ったときのファーストステップとして、Googleが出しているGinのチュートリアルをやってみるのはオススメです。
しかし、上記の方法はメモリにデータを保持しているため、テーブルへの保存などには触れられていません。
なので、上記のチュートリアルから派生させて、ローカルで立てたPostgreSQLに対してのデータの保存を行ってみました。
その備忘録です。
リポジトリはこちら
オニオンアーキテクチャも意識しながら書いてみたのですが、怪しいところもあるので、是非コメントでご教授いただけると幸いです。
SQL-Migrate
今回はORMapperとしてSQLBoilerを利用します。
SQLBoilerにはマイグレーション機能がついていないので、SQL-Migrateを利用して、マイグレーションを行ってから、SQLBoilerでの型定義を行います。
(チュートリアルでは、Priceはfloat64なので、少数も使えますが、今回はintにして少数を利用しない方針で実装しています。)
DB接続に必要な環境変数は、docker-compose.ymlで定義しています。
上記を設定したら、SQL-Migrateをインストールして
go install github.com/rubenv/sql-migrate/...@v1.2.0
マイグレーションのコマンドを実行すると、テーブルが作成されます。
sql-migrate up
SQLBoiler
上記で説明した通り、ORMapperにはSQLBoilerを利用しました。
会社で利用しているのがGormなので、Gorm以外のORMapperを使ってみたいという安直な理由です。
設定用のtomlファイルを以下のように定義しました。
パラメータが少ないように思えますが、ドライバ名(上記だとpsql
)を先頭に付けた環境変数は、SQLBoilerの設定パラメータとして扱われるので、主なテーブルの接続情報に関してはSQL-Migrateと同様、docker-compose.ymlに記載しています。
sqlboilerのコマンドを叩くと、SQL-Migrateで作成したテーブルの情報を元に構造体が作成されます。
sqlboiler psql
Domain
チュートリアルでは、アルバムの情報のCRUD操作を行っています。
なので、まずはアルバムのドメインを作成します。
本来はNewAlbum
内でバリデーションなどをやるべきですが、今回は時間の都合で省略してます。
Repository
オニオンアーキテクチャを意識して、リポジトリのインターフェースを実装してから、PostgreSQL用のリポジトリを書いていきます。
インターフェース
PosgreSQL用のリポジトリ
また、この時点でエラードメインも書きました。
理由としては、リポジトリの独自定義のエラーをユースケース層に意識させたくなかったからです。
例としてFindById
を挙げます。
func (repo *AlbumPostgresRepository) FindById(ctx *gin.Context, id string) (*domain.Album, error) {
m, err := models.FindAlbum(ctx, repo.db, id)
if err == sql.ErrNoRows {
return nil, domain.NewNotFoundError("album not found")
}
if err != nil {
return nil, domain.NewInvalidInputError("failed find album")
}
album, _ := domain.NewAlbum(m.ID, m.Title, m.Artist, m.Price)
return album, nil
}
FindAlgumで、データが取得できなかった場合、err == sql.ErrNoRows
がtrueになるわけですが、sql.ErrNoRowsは永続層に閉じ込めておきたいハンドリングだったので、自分で独自のエラー型を定義して、リポジトリ層からはその独自のエラー型を返しています。
Usecase
今回は明確なユースケースが想定されているわけではありませんが、List、Get、Create、Update、Deleteをそれぞれユースケースと見立てて実装しました。
例としてCreateのユースケースを掲載しておきます。
Presentation
プレゼンテーション層で、APIリクエストをユースケース層に渡すマッピングを行います。
デモアプリなので、エラーの場合はすべてInternalServerError
で返すようになっています。(サボりました。)
また、現在はCreateとUpdateのAPIリクエストの型がAlbum構造体と一緒なのですが、ドメイン構造や今後の開発を考えると、リクエストの型は別にすべきだろうと考え、リクエスト用の型を定義しています。
また、 Ginのルーティングの関係で、引数が*gin.Context
だけの関数しかルーティングに渡せないので、プレゼンテーション層ではそれを意識して書いています。
Main.go
main.goで、今まで書いてきたそれぞれのレイヤーを定義しながら、RestAPIのルーティングに対応させていきます。
DIを考慮したコードを自動生成してくれるwireというツールが出ているので、実務ではこれを使うべきかなと思いますが、今回は自分の書いたコードを検証しながらAPIを作成していたので、自分で各レイヤーの構造体を定義しています。
ここまで実装すると、APIとして実行できるようになります。
おわり
Ginのチュートリアルも手軽にGinの使い方がわかる、いいチュートリアルでしたが、そのチュートリアルをここまで深ぼって書いてみると、プロダクトを1から作るイメージが湧きました。
今後Ginに入門したいと思っている方は是非一度挑戦してみては如何でしょうか?
Discussion