縮めてPOKEMON!!

https://github.com/hackz-megalo-cup/microservices-app

Go

TypeScript

Kubernetes

AWS

PostgreSQL

リアルなプログラミング言語POKEMONとレイドバトル!

さどるふ

warisuno

zatu

kfff

推しアイデア

プログラミング言語が3DでリアルなPOKEMONに変身! レイドバトルで協力してPOKEMONを倒し、個性豊かなアイテムを駆使して捕獲しよう!

作った背景

プログラミング言語をPOKEMONにしたらおもしろいとおもった。

推し技術

①マイクロサービスでドメイン領域ごとに独立実装 ②HTTP3に対応したWebTransportで高速リアルタイム通信 ③Kubernetes

プロジェクト詳細

POlymorpwhism KEnel MOdule Network (ポリモーフィズム・カーネル・モジュール・ネットワーク)

縮めてPOKEMON!!

推し技術!

Event Storming!

Event Stormingとは

複雑なビジネスプロセスやドメインの知識を共有、可視化、そして理解するための協同作業ベースのモデリング手法

なぜEvent Stormingを行ったか

今回のプロダクトを作るにあたり、プロセスが複雑であることが予想していた。 なので、誰がどのような行動を行い、どんなイベントが起こり、どんな流れで進行していくのかを把握する必要があると思い行なった。 始点はログインするところから始まり、終点はポケモンを捕まえるところ

初めてEvent Stormingを行ったが、考えないといけないことが多く大変ではあったが、チームメンバー間での認識のすり合わせや、考慮もれが潰れていったのでとても効果を感じた。 image image

Microservices!

こだわりポイント

Event Stormingで洗い出したドメイン領域ごとに、サービスを切っているため独立して作業を進めることが可能。 また、Apache Kafka互換のメッセージングブローカーであるRedPandaを使っており、Pub/Subのようになっているため、呼び出し元を知らなくて済む疎結合な構成になってい流。 他には、Argocdを使いGitHubを監視して自動でデプロイ(GitOps)したり、分散トレーシングのための監視基盤、 ゲームサーバー用にAgonesを使っていたり、k8sのCNIはカーネル空間で動くeBPFのCiliumを使用している。

基本的に、通信はgRPCを使っているが、ゲーム部分においてはHTTP/3 上で動くWebTransportを使用 (なお、iPhoneでは使えない; w ;)。 一部、WebTransportが使えない層のために、fallbackでWebSocketに繋いでいる。

image

k8s(Amazon EKS)

auth / gateway / capture / item / lobby / masterdata / raid-lobby / projector / game-server / frontend の全サービスをAmazon EKS上にデプロイ。ArgoCDがGitHubのmainブランチを監視し、マージと同時に自動でクラスタへ反映するGitOps構成を採用。k8sのCNIにはeBPFベースのCiliumを使用しており、カーネル空間でのパケット処理により低レイテンシなサービス間通信を実現している。ゲームサーバーはAgonesで管理し、バトルセッションごとにPodを動的にアロケーション・ライフサイクル管理している。

CI基盤

commit前、PR、mainマージ後にCIが動く これで本番環境でのエラーを未然に防げた

  • commit前: pre-commitフックでlint / format(Go: golangci-lint・gofmt、Frontend: Biome)を自動実行
  • PR時: contract(buf lint + breaking change検出)/ go-check(ビルド+テスト+race検出)/ frontend-check(tsc + Vite build)/ node-check の4ジョブが並列で走る
  • mainマージ後: 上記に加えて nix-build(各サービスのDockerイメージをghcr.ioにpush)と render-manifests(K8s YAMLをNixidyで自動生成)が動き、ArgoCDが差分を検知して本番クラスタへ自動デプロイ

頑張ったこと

  • イベントソーシング + Sagaパターンの共通基盤化: 全Goサービスに event_store / outbox_events / idempotency_keys / snapshots の4テーブルを統一仕様で実装。Transactional Outboxパターンにより「DBへの書き込みとKafkaへの発行が不可分」な設計を実現し、at-least-once配信保証を達成した
  • 耐障害設計の3層積層: サービス間呼び出しにCircuit Breaker(gobreaker)→ Bulkhead → Retry(指数バックオフ)を積層し、部分障害がカスケードしない構成をplatformパッケージとして共通化した
  • Proto-firstな開発フロー: buf v2でProtoを唯一の真とし、Go(connect-go)とTypeScript(connect-query)の両クライアントを自動生成。型安全性をフロントエンドからバックエンドまで貫通させた
  • WebTransport + WebSocketのfallback二重構成: リアルタイムバトルはHTTP/3上のWebTransportを基本とし、非対応ブラウザにはWebSocketで自動フォールバック。Agonesによるゲームサーバーの動的管理と組み合わせて、スケーラブルなバトル基盤を構築した
  • MSWモックによるフロントエンド単体開発: VITE_USE_MOCK=true でバックエンド不要のフロントエンド開発環境を整備し、サービス間の依存を断ち切って並列開発を加速した

難しかったこと

  • マイクロサービス間のデータ整合性: サービスごとに独立したDBを持つため、複数サービスをまたぐ操作(例:捕獲時のアイテム消費→ポケモン追加)でどう整合性を保つかが難しかった。Sagaパターンの補償トランザクションで解決したが、失敗シナリオの洗い出しとinvocation.failedinvocation.compensatedのイベントフロー設計に時間がかかった
  • eBPF(Cilium)とAgonesの組み合わせ: GameServerのUDP通信をk8sネットワーク上で正しくルーティングするためのCNI設定とAgones側のポート管理の兼ね合いが複雑で、パケットがどこで落ちているか追跡に苦労した
  • WebTransportのブラウザ対応: HTTP/3 + QUICが前提のWebTransportはiOS Safariで動作せず、最終的にWebSocketへのfallbackを実装したが、双方の接続状態管理・メッセージスキーマの共通化が設計上難しかった
  • 分散トレーシングのコンテキスト伝播: OpenTelemetryのトレースコンテキストをgRPC(connect-rpc)・Kafka(Watermill)・HTTPの3経路すべてで正しく伝播させる設定が難航した

苦労したこと

  • Goの開発とマイクロサービスが始めてで開発に入るまでのキャッチアップに時間がかかった
  • Devinのレビューが優秀過ぎて毎回指摘される
  • Godotにコメントにピリオドが無いことを指摘される
  • WSL特有のパス系のエラーに苦しんだ
  • armでは動くのにx86だとうごかねぇ。。
    • WSLエラー: Windowsのファイルパス(C:\...)とLinuxパス(/mnt/c/...)の混在でDockerのvolumemountが壊れ、原因特定に時間を溶かした
    • CPUアーキテクチャの違い:
    • Nix周り:
    • Devinのレビューが厳しい: コードの品質は上がったが精神的に削られた
    • Godot: コメントアウト末尾のピリオド欠如を毎回指摘された
    • EKS:

さどるふ

@thirdlf03