10日で作る Go Basic API
「とりあえず動く API」を超えて、10日でどこまで“ちゃんとした”バックエンドを作れるのか。
このエントリでは、リンク集サービス Tsudzuri のバックエンド API を約10日間で作り上げたプロセスを、アーキテクチャ、ツール、CI/CD、Google Cloud、そして GitHub Copilot(AI エージェント) の活用という観点から振り返ります。
「Go で Web API を作ってみたい」「趣味開発でも設計と運用まで含めてきちんとやってみたい」という人向けに、コンパクトに全体像を掴めることを目指しています。
サービス概要と目標設定
Tsudzuri とは?
Tsudzuri は、複数人で共同編集できる リンク集(綴り) を作るためのサービスです。
- 匿名ゲストで開始可能
- 「綴り (Page)」を作成してリンクを追加・編集・並べ替え
- 招待コード/招待リンクで共有し、参加者全員が閲覧・編集可能
今回の記事では、フロントではなく バックエンド API サーバー(Go) にフォーカスします。
ゴール設定
ゴールは以下に絞り込みました。
- 最低限必要な Page / User のユースケース を一通り実装
- gRPC + gRPC-Gateway ベースで、HTTP と gRPC の両方から叩ける API
- PostgreSQL + ent でドメインを壊さない永続化
- OpenTelemetry + GCP を前提にしたトレーシング/ロギングの仕組み
- ローカル開発は Docker Compose + Makefile でワンコマンド起動
- GitHub Actions で CI(Lint/Test)と CD(Cloud Run デプロイ)までつなぐ
「あとから雑に作り直さなくていい」最小構成を狙い、設計と運用の“ライン”をはじめから決めて進めたのがポイントでした。
アーキテクチャ概要
全体構成
リポジトリ構成は、クリーンアーキテクチャをベースにしたレイヤード構成です。
cmd/api # エントリポイント (main, DI, OTEL)
config/ # 設定・環境変数
api/ # protobuf 定義 & 生成物
├── protobuf/
└── tsudzuri/
domain/ # ドメインモデル, リポジトリIF
├── page/
└── user/
usecase/ # ユースケース (アプリケーションサービス)
├── page/
└── user/
infrastructure/# DB, Firebase などの実装
├── api/
└── db/
presentation/ # gRPC ハンドラ, エラーコード
├── grpc/
└── errcode/
pkg/ # 共通ライブラリ (log, trace, cache, uuid…)
resources/ # DBマイグレーション
レイヤごとに責務をはっきり分離しています。
- presentation: gRPC / HTTP の入り口。リクエスト/レスポンス変換とエラーマッピング
- usecase: ユースケース単位のアプリケーションサービス
- domain: ビジネスルールとエンティティ、リポジトリインターフェース
- infrastructure: DB や外部サービス(Postgres, Firebase)の実装
Page / User ドメインの切り方
ドメインは Page と User に絞りました。
-
domain/page- Page エンティティ(タイトル、リンクのリスト、並び順など)
- Link エンティティ(URL, タイトル, メモ…)
- PageRepository インターフェース
-
domain/user- User エンティティ(ID, DisplayName, アイコンなど)
- UserRepository インターフェース
これに対して、
-
usecase/pageに Page 作成・編集・一覧・参加(join)・リンク操作など -
usecase/userに User 作成・ログイン・取得など
のユースケースを配置し、さらに presentation/grpc/page, presentation/grpc/user でハンドラを実装しています。
ポイント:
- ent のスキーマはインフラ層に閉じ込め、
domainからは見えないようにする - ハンドラは
usecaseのインターフェースだけを知る -
domainは application / infrastructure の詳細を一切知らない
これにより、「とりあえず動く」ではなく、「拡張しやすい」構成にできました。
開発フローとツール群
ローカル開発は Makefile + Docker Compose に寄せています。
-
make dev:airでホットリロード開発 -
make up/make down: API / DB / Swagger UI の起動・停止 -
make test: テスト用 DB コンテナを立ち上げてからgo test ./... -
make lint/make fmt:golangci-lintとgofumpt、およびbuf format -
make generate:go generate+buf generateで ent / protobuf 周りをまとめて生成
テスト実行時は docker compose で Postgres を自動起動し、pg_isready で待ち合わせてから go test を叩くようにしているので、テスト前の手作業がほとんど要らないのがポイントです。
DB は ent + PostgreSQL で、スキーマは infrastructure/db/ent、マイグレーション SQL は resources/migration/ にまとめています。
Google Cloud 上での運用設計
ここからは、「個人開発でも現実的に払えるコスト」 を意識しながら設計した GCP 周りを少し踏み込んで紹介します。
Cloud Run — コンテナ実行基盤の中心
この API は Cloud Run で動かすことを前提に設計しました。
- 完全マネージドで OS・パッチ・スケーリングを丸投げできる
- コンテナさえ作ればそのままデプロイできる
- 個人開発でも Free Tier + 従量課金で現実的なコスト
構成としては、Artifact Registry に push したイメージを Cloud Run サービス(tsudzuri など)で起動する、ごく標準的な形です。
個人利用なので、
- 最小インスタンス数 0(アイドル時コストゼロ、cold start は割り切る)
- 最大インスタンス数・同時実行数は控えめ
- CPU はリクエスト処理中のみ割り当て
といった「とりあえず安く動かす」設定からスタートしています。
Cloud SQL — 共通コアでできるだけ安い構成を目指す
DB は Cloud SQL for PostgreSQL を採用しています。ここでもコストをかなり意識しました。
- 1 プロジェクトに 「共通コア」的な Cloud SQL インスタンス を 1 つだけ作る
- その中に
tsudzuri専用のデータベースを作成 - 本番・検証を分けたい場合も、まずは同一インスタンス内に別 DB として切る
インスタンス自体は小さめのマシンタイプを選び、
- 自動バックアップ有効
- 高可用性(マルチゾーン)は最初は切っておく(RTO/RPO 要件を満たせるなら後から有効化)
といったあたりで「コストと安心感のバランス」を取っています。
開始/停止のスケジューリング
さらにコストを削りたい場合、夜間や使わない時間帯に Cloud SQL を止める選択肢もあります。
Cloud Scheduler + Cloud Functions(または Cloud Run Jobs)+ Cloud SQL Admin API を組み合わせることで、
- 「毎日 1:00 に停止」「毎朝 9:00 に起動」
といったスケジュール運用が可能です。
Service Account 設計 — CI/CD と Cloud Run / Cloud SQL
サービスアカウントは、ざっくり次の 2 役に分けています。
- CI/CD 用サービスアカウント
- GitHub Actions から Artifact Registry / Cloud Run にアクセスするための SA
- 例:
roles/artifactregistry.writer,roles/run.admin,roles/iam.serviceAccountUser
- Cloud Run 実行用サービスアカウント
- 実際に API コンテナが動くときに紐づける SA
- Cloud SQL 用に
roles/cloudsql.client、Secret Manager 用にroles/secretmanager.secretAccessorなどを付与
CI 側の SA キーは GitHub Actions の GCP_SA_KEY シークレットとして登録し、.github/workflows/ci.yaml から secrets: inherit で渡しています。
Artifact Registry — イメージ管理とライフサイクル
コンテナイメージは Artifact Registry に格納し、GitHub Actions からタグ付きで push しています。
古いイメージでストレージ課金が増えがちなので、Artifact Registry のライフサイクルポリシーで、
- 「最新数個だけ残し、それ以外は一定期間で削除」
といったルールを 1 つ入れておくと安心です。
Dockerfile は multi-stage build で、builder ステージでビルド → runtime ステージにバイナリだけコピーするごく基本的な構成ですが、それだけでもイメージサイズと cold start をだいぶ抑えられます。
OpenTelemetry / Logging — 見える化の土台
ファイルとしては cmd/api/otel.go, pkg/trace/, pkg/log/ あたりに設定がまとまっています。
- gRPC / HTTP ハンドラに OpenTelemetry のミドルウェアを挿入
- GCP のリソース情報(プロジェクト ID, サービス名など)を自動検出
- ローカル開発時は stdout exporter、本番は Cloud Trace exporter などに切り替え可能な構成
ログは zap ベースで JSON 形式にまとめておき、Cloud Run 上ではそのまま Cloud Logging に取り込まれます。
エラー時にはトレース ID をログに埋め込むようにしておくことで、
- 「このリクエストで何が起きたか」をログから辿る
- 同じ ID で Cloud Trace 上のスパンも追いかける
といった、運用時に嬉しいクロス参照がしやすくなります。
「最初から観測可能性を入れておく」ことで、後からのトラブルシュートやパフォーマンスチューニングが格段に楽になります。
CI / CD パイプライン
Lint & Test(CI)
.github/workflows/lint-and-test.yaml で、feature/** ブランチへの push をトリガーに CI を走らせています。
-
make install-toolsで.bin/にツールインストール -
make lintで静的解析 -
make testでユニットテスト
チーム開発を想定して、feature ブランチに push したら自動で検証が走る流れを最初から仕込んでいます。
Cloud Run へのデプロイ(CD)
CD は、再利用可能なワークフロー .github/workflows/ci.yaml を中心に構成しています。
- Docker イメージをビルド
- Artifact Registry へ push
- Cloud Run へデプロイ
- 必要に応じてトラフィックの切り替え
環境ごとにラッパーを用意することで、同じ CI ロジックを dev / prod で使い回せるようにしました。
-
dev-precheck.yaml-
precheckブランチへの push or 手動実行で dev 環境へデプロイ
-
-
cd.yaml-
v*.*.*タグの push をトリガーに prod 環境へデプロイ
-
Cloud Run に渡す環境変数は .github/env/dev.yaml / .github/env/prod.yaml のような YAML で管理し、 ci.yaml の inputs として渡します。
GitHub Copilot / AI エージェントを使った開発フロー
今回の開発では、GitHub Copilot(特に Chat / エージェント機能)をトライアルで登録しかなり積極的に使いました。正直これが無いと厳しかったと思います。
Copilot に任せたこと
- 既存のコードに合わせたテスト生成
- 例:
usecase/pageやdomain/userのテーブルドリブンテスト
- 例:
- Protobuf や ent スキーマの初期雛形
- OpenTelemetry の設定や gRPC ミドルウェアの配線案
- コード補完
特に、「このレイヤの責務はこうだから、ここにこういう関数を追加したい」 と説明すると、既存コードからパターンを学習してかなり良い初期案を出してくれます。
人間がやるべきこと
一方で、以下は人間側がしっかり判断したところです。
- ドメインモデリング(Page / User / Link の境界、ユースケースの切り方)
- トランザクション境界の設計(どこまでを 1 トランザクションにするか)
- エラー設計(
presentation/errcodeの粒度やマッピング方針) - CI/CD・GCP 全体像の設計
Copilot はコードを書くのは得意ですが、「そもそも何を作るべきか」「どんな責務の分割が美しいか」という設計は、やはり人間が主導した方がうまくいきました。
実際のやり取りイメージ
- 「
usecase/pageに Page の join ユースケースを追加したい。既存の create/list と同じテストスタイルでテストも追加して」 - 「この gRPC ハンドラから usecase を呼ぶ部分でエラー変換がダサいので、
presentation/errcodeに共通関数を足して整理して」 - 「Cloud Run 用の GitHub Actions を作りたい。
.github/workflows/ci.yamlを再利用ワークフローとして切り出して」
といった依頼を投げると、コードとワークフローを書いてくれます。そこに対して、「ここはトラフィック切り替えは別でやるから enable_traffic_update: false にして」など、プロダクトの事情を踏まえた微調整を人間が行う、という形でコラボしました。
まとめ
- 10日という短期間でも、設計と運用の「最低ライン」を最初に決めることで、後から伸ばせるバックエンド API を作れる
- Go / gRPC / ent / OpenTelemetry / Cloud Run / GitHub Actions という組み合わせは、個人開発でも十分現実的な選択肢
- GitHub Copilot などの AI エージェントを「ペアプロ相手」として使うと、実装や設定の初期案づくりが爆速になる
この記事が、開発を 「10日で形にしてみる」 ための参考になれば嬉しいです。
Discussion