Goaにおける複数のサービスの連携

Goaを使用したスケーラブルなマイクロサービスアーキテクチャの設計と実装

実際のアプリケーションでは、複数のサービスが連携して完全なシステムを形成することが 一般的です。Goaは単一のプロジェクト内で複数のサービスを設計および実装することを 容易にします。このガイドでは、複数のサービスを効果的に作成および管理するプロセスを 説明します。

複数のサービスを理解する

Goaにおけるサービスは、特定の機能を提供する関連エンドポイントの論理的なグループを 表します。単純なアプリケーションでは1つのサービスで十分かもしれませんが、 大規模なアプリケーションでは機能を複数のサービスに分割することで恩恵を受けることが 多いです。このアプローチにより、APIエンドポイントのより良い組織化、関心事の明確な 分離、より容易な保守とテスト、独立したデプロイ機能、および細かいセキュリティ 制御が可能になります。

サービスアーキテクチャパターン

マルチサービスシステムを設計する際、サービスは通常、フロントサービスと バックサービスの2つのカテゴリーに分類されます。これらのパターンを理解することで、 スケーラブルで保守可能なアーキテクチャを設計するのに役立ちます。

サービスの組織化

Goaは、サービスの設計と生成されたコードをどのように組織化するかについて柔軟性を 提供します。主な2つのアプローチ、統合設計と独立設計を見てみましょう。

統合設計アプローチ

統合アプローチは、サービス固有の実装を維持しながら、すべてのサービスを単一の設計 階層の下にまとめます。以下がその仕組みです:

// design/design.go - トップレベルの設計ファイル
package design

import (
    _ "myapi/services/users/design"    // 各サービスは独自の設計を持つ
    _ "myapi/services/products/design"
    . "goa.design/goa/v3/dsl"
)

var _ = API("myapi", func() {
    Title("My API")
    Description("マルチサービスAPIの例")
})

各サービスは、全体のAPIに貢献する独自の設計ファイルを維持します:

// services/users/design/design.go - サービス固有の設計
package design

import (
    . "goa.design/goa/v3/dsl"
    "myapi/design/types"
)

var _ = Service("users", func() {
    // サービス固有の設計
})

このアプローチはコード生成と型の共有を一元化します:

  • 生成されたコードはルートのgen/ディレクトリに配置される
  • 単一のgoa genコマンドですべてのサービスコードを生成
  • 共有型は自動的にすべてのサービスで利用可能
  • システムは統一されたOpenAPI仕様を維持
  • チームは簡単にサービス間の一貫性を維持できる

独立設計アプローチ

独立アプローチは、各サービスを独立したユニットとして扱います:

// services/users/design/design.go - 独立したサービス設計
package design

import (
    . "goa.design/goa/v3/dsl"
)

var _ = API("users", func() {
    Title("ユーザーサービス")
    Description("ユーザー管理API")
})

var _ = Service("users", func() {
    // サービス固有の設計
})

このアプローチはサービスの独立性を最大化します:

  • 各サービスは独自のgen/ディレクトリを維持
  • サービスは独立して生成およびバージョン管理が可能
  • 各サービスは独自のOpenAPI仕様を持つ
  • サービスは簡単に別のリポジトリに移動できる
  • チームは異なるサービスで独立して作業できる

トランスポートの考慮事項

トランスポートプロトコルの選択は、サービス間の相互作用に大きな影響を与えます。 各アプローチの利点を見てみましょう:

gRPCサービス

gRPCは以下を通じて内部サービス通信に優れています:

  • 効率的なバイナリプロトコル伝送
  • プロトコルバッファーによる強力な型付けインターフェース
  • サービスディスカバリーとロードバランシングの組み込み
  • 双方向ストリーミング機能
  • gRPCゲートウェイを通じたHTTP/JSON統合

HTTPサービス

HTTPは以下を提供することで外部向けサービスに適しています:

  • ユニバーサルなクライアント互換性
  • 豊富なツールとミドルウェアのエコシステム
  • 馴染みのあるRESTパターン
  • 容易なデバッグとテスト
  • Webアプリケーションとの自然な適合

リポジトリ構造

適切に組織化されたリポジトリは、チームがコードベースをナビゲートおよび 維持するのに役立ちます。以下は推奨される構造です:

myapi/
├── README.md          # システム概要とセットアップガイド
├── design/            # 共有設計要素
│   ├── design.go      # 統合アプローチのトップレベル設計
│   └── types/         # 共有型定義
├── gen/               # 生成されたコード(統合アプローチ)
│   ├── http/          # HTTPトランスポート層コード
│   ├── grpc/          # gRPCトランスポート層コード
│   └── types/         # 生成された共有型
├── scripts/           # 開発とデプロイメントスクリプト
└── services/          # サービス実装
    ├── users/         # 例:ユーザーサービス
    │   ├── cmd/       # サービス実行可能ファイル
    │   ├── design/    # サービス固有の設計
    │   ├── gen/       # 生成されたコード(独立アプローチ)
    │   ├── handlers/  # ビジネスロジック
    │   └── README.md  # サービスドキュメント
    └── products/      # 例:製品サービス
        └── ...

サービス通信パターン

サービス間の相互作用を設計する際は、以下の一般的なパターンを考慮してください:

フロントサービスとバックサービス

サービスは通常、以下の2つのカテゴリーに分類されます:

  1. フロントサービス:外部向けサービスで:

    • 幅広いクライアント互換性のためにHTTPをトランスポートとして使用
    • バックサービスへのリクエストのオーケストレーションに焦点を当てる
    • 外部リクエストの認証と認可を処理
    • 観測可能性のコンテキスト(トレース、メトリクス)を開始
    • 浅い実装で広範なAPIを定義
  2. バックサービス:内部サービスで:

    • パフォーマンスの利点のためにしばしばgRPCを使用
    • コアビジネスロジックを実装
    • プライベートなアイデンティティメカニズム(例:spiffe)を使用する場合がある
    • 既存の観測可能性コンテキストに貢献
    • 深い実装で焦点を絞ったAPIを定義

一般的なアーキテクチャパターンは、プラットフォームの機能を外部クライアントに 公開する少数のフロントサービス(時には1つだけ)と、実際のビジネスロジックを 処理する複数のバックサービスを持つことです。

スクリプトと自動化

scripts/ディレクトリは、一般的な開発とデプロイメントタスクの自動化を提供します。 これらのスクリプトは統合アプローチと独立アプローチの両方に適応し、選択した アーキテクチャに関係なくサービスを簡単に管理できるようにします。

開発スクリプト

コア開発スクリプトは、コード生成、ビルド、およびテストを処理します:

# scripts/gen.sh - コード生成スクリプト
#!/bin/bash
if [ "$1" == "" ]; then
    # 統合アプローチ:すべてのサービスを生成
    goa gen myapi/design
else
    # 独立アプローチ:特定のサービスを生成
    cd services/$1 && goa gen myapi/services/$1/design
fi

# scripts/build.sh - ビルドスクリプト
#!/bin/bash
if [ "$1" == "" ]; then
    # すべてのサービスをビルド
    for service in services/*/; do
        service=${service%*/}
        echo "Building ${service##*/}..."
        go build -o bin/${service##*/} ./$service/cmd/${service##*/}
    done
else
    # 特定のサービスをビルド
    go build -o bin/$1 ./services/$1/cmd/$1
fi

# scripts/test.sh - テストランナー
#!/bin/bash
if [ "$1" == "" ]; then
    # すべてのサービスと共有コードをテスト
    go test ./... -v
else
    # 特定のサービスをテスト
    go test ./services/$1/... -v
fi

デプロイメントスクリプト

デプロイメントスクリプトは、サービスの実行とコンテナデプロイメントを処理します:

# scripts/run.sh - ローカルサービスランナー
#!/bin/bash
if [ "$1" != "" ]; then
    # 特定のサービスを実行
    ./bin/$1
else
    # 利用可能なサービスを一覧表示
    echo "利用可能なサービス:"
    ls bin/
fi

# scripts/deploy.sh - Kubernetesデプロイメント
#!/bin/bash
if [ "$1" != "" ]; then
    deploy_service() {
        echo "$1をデプロイ中..."