以下の内容はhttps://www.m3tech.blog/entry/microservices-schema-sharing-skemより取得しました。


AI プロダクトのマイクロサービス開発でスキーマ共有に困ったので CLI「skem」を作った

こんにちは、山本(@hiro_o918)です。 この記事はAI・機械学習チームブログリレー 2 日目の記事です。 1 日目は北川さんの「gokartに型を入れるためにspotifyやfacebookのOSSにコントリビュートした話」でした。

www.m3tech.blog

内容とは関係ないうちの犬

はじめに

AI チームでは、LLM を活用した AI プロダクトを開発しています。 こうしたプロダクトでは LLM の推論に時間がかかるため非同期処理が必須になったり、機能ごとに独立した API として切り出したりすることが多く、自然とサービスが分割されていきます。

サービスが分割されると、API の定義だけでなく PubSub のメッセージスキーマなど、サービス間で共有すべきスキーマが増えていきます。 Protocol Buffers や OpenAPI Specification で定義をコード化するのがベストプラクティスですが、これらの定義ファイルをリポジトリ間でどう管理・共有するかという問題は意外と明確な解決策がありません。

そこで今回は、この課題を解決するために自作した CLI ツール「skem」を紹介します。

マイクロサービスにおけるインタフェース共有の課題

なぜインタフェース共有が難しいのか

マイクロサービスでは、サービス A がサービス B の API を呼び出す際に、その定義(proto ファイルや OpenAPI spec)を参照してコード生成を行うことが一般的です。 これらの定義ファイルをサービス B のリポジトリで管理している場合、サービス A からどのように参照するかという問題が生じます。

よく採られるアプローチとその課題をまとめると、次のようになります。

アプローチ 課題
手動コピー バージョンの不整合が発生しやすい。コピー忘れや、どのバージョンを参照しているかが不明瞭になる
Git submodule 仕組みとして重く、更新忘れや detached HEAD 状態でのコンフリクトが起きやすい。チームへの習熟コストも高い
buf proto ファイルの管理には便利だが、OpenAPI spec など任意の定義ファイルを管理するには不向き
monorepo への移行 インタフェース共有の問題は解決するが、既存の開発フローを大きく変える必要があり、移行コストが大きい

理想の要件

これらの課題を踏まえると、求められる要件は次のとおりです。

  • リポジトリ全体ではなく、特定のファイル/ディレクトリだけを取得できる
  • 特定のツールや環境に依存せず、任意のファイルを管理できる
  • 取得後にコード生成等の後処理を自動実行できる

skem の概要

skem は、リモート Git リポジトリから特定のファイルだけをダウンロードし、hooks を実行できる Rust 製の軽量 CLI ツールです。

主な機能は次の3つです。

  1. Sparse checkout によるファイル取得: Git の sparse checkout を使い、必要なファイルだけを効率的に取得。リポジトリ全体をクローンしないため高速
  2. lockfile による再現性の担保: .skem.lock に取得時のコミット SHA を記録し、同じバージョンを再現可能
  3. hooks による後処理の自動化: ファイル取得後に任意のシェルコマンドを実行できる。proto ファイルや OpenAPI spec のコード生成など、取得と連動した処理を自動化

skem のシンプルさについて

skem が担うのは「指定したファイルを取得すること」と「hooks を実行すること」だけです。 コード生成やビルドは hooks に委ね、ツール自体はシンプルに保つ設計にしています。

protoc や openapi-generator など既存のツールと自由に組み合わせられるのが利点です。

使い方

インストール

curl -fsSL https://raw.githubusercontent.com/hiro-o918/skem/main/install.sh | bash

cargo が使える環境であれば、ソースからもインストールできます。

cargo install skem

設定ファイル(.skem.yaml)

まず skem init でサンプルの設定ファイルを生成し、プロジェクトに合わせて編集します。

deps:
  - name: proto-schemas
    repo: "https://github.com/hiro-o918/skem.git"
    rev: "main"
    paths:
      - "examples/schemas/proto/"
    out: "./vendor/proto"
    hooks:
      - "echo 'Proto files updated'"

  - name: openapi-schemas
    repo: "https://github.com/hiro-o918/skem.git"
    rev: "main"
    paths:
      - "examples/schemas/openapi/"
    out: "./vendor/openapi"

設定の各フィールドは次のとおりです。

フィールド 必須 説明
deps[].name 依存の識別名
deps[].repo Git リポジトリ URL
deps[].rev ブランチ、タグ、コミット SHA(省略時は HEAD)
deps[].paths 取得するパスのリスト
deps[].out ファイルの出力先ディレクトリ
deps[].hooks 各依存の同期後に実行するコマンド
post_hooks すべての依存の同期後に実行するコマンド

同期の実行

設定が完了したら skem sync で同期します。

$ skem sync
Synchronizing 2 dependencies...
  openapi-schemas is up to date, skipping.
Successfully synchronized 1 dependencies.
Lockfile updated: .skem.lock

lockfile と比較して変更があった依存のみを取得するため、不要なクローンが走りません1

インタラクティブモードで依存を追加

リポジトリ内のどのファイルを取得するか分からない場合は、インタラクティブモードが便利です。

skem add --repo https://github.com/hiro-o918/skem.git

--paths--out を省略すると、リポジトリのファイル一覧が fuzzy select で表示され、対話的に取得したいファイルを選択できます。 選択後、出力先ディレクトリを入力すれば .skem.yaml に設定が追記されます。

lockfile による再現性と CI での活用

同期が完了すると .skem.lock が生成・更新されます。

locks:
  - name: proto-schemas
    repo: "https://github.com/hiro-o918/skem.git"
    rev: "main"
    sha: e6f5da00c99fdfc6b746a4bb4e288a0cac66135a
  - name: openapi-schemas
    repo: "https://github.com/hiro-o918/skem.git"
    rev: "main"
    sha: e6f5da00c99fdfc6b746a4bb4e288a0cac66135a

lockfile をコミットしておくことで、チーム全員が同じバージョンのファイルを取得できます。

skem check を使うと、lockfile とリモートの差分を確認できます。 更新がある場合は非ゼロで終了するため、CI で定期実行すれば lockfile の更新漏れを検知できます。

$ skem check

hooks の活用例

hooks には同期後に実行したいコマンドを記述します。 環境変数 SKEM_SYNCED_FILES に同期されたファイルのパスが渡されるため、変更があったファイルだけを対象に処理できます。

deps:
  - name: proto-schemas
    repo: "https://github.com/hiro-o918/skem.git"
    paths:
      - "examples/schemas/proto/"
    out: "./vendor/proto"
    hooks:
      - "protoc --go_out=./gen $SKEM_SYNCED_FILES"

典型的な使いどころは次のとおりです。

  • proto ファイル取得後に protocbuf generate でコード生成
  • OpenAPI spec 取得後に openapi-generator でクライアント生成
  • buf lintspectral lint でスキーマのバリデーション

すべての依存の同期が完了した後に実行したい処理は post_hooks に書きます。 「各依存のバリデーションは hooks、全体のビルドは post_hooks」という使い分けができます。

まとめ

AI プロダクト開発では、サービスの分割が進むにつれてスキーマ管理の煩雑さが増していきます。 skem はそこに対して「ファイルの取得と hooks の実行だけを担う」というシンプルな解を提供しています。 まだ発展途上のツールですが、同じ課題を感じている方の参考になれば嬉しいです。 使ってみた感想や改善提案は GitHub の Issue や PR でお待ちしています。

We are hiring!

AI チームでは、プロダクト開発における技術的な課題を自ら見つけ、アジャイルな検証と OSS 開発を通じて解決していくことに興味があるエンジニアを募集しています。 ご気軽にカジュアル面談からでも応募をお待ちしております!

エンジニア採用ページはこちら

jobs.m3.com

カジュアル面談もお気軽にどうぞ

jobs.m3.com

インターンも常時募集しています

open.talentio.com


  1. 初回実行時や --force オプション使用時はすべての依存を同期します



以上の内容はhttps://www.m3tech.blog/entry/microservices-schema-sharing-skemより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14