コード生成

コマンドライン使用法、生成プロセス、カスタマイズオプションを含め、Goaがあなたのデザインからコードを生成する方法を学びます。

Goaのコード生成システムは、あなたのデザインを本番環境対応のコードに変換します。 単なるスキャフォールディングではなく、Goaはベストプラクティスに従い、API全体で 一貫性を維持する完全な、実行可能なサービス実装を生成します。

コード生成の利点

  • 一貫性:生成されたコードは一貫したパターンとベストプラクティスに従います
  • 型安全性:生成された実装全体で強力な型付けを実現
  • バリデーション:デザインルールに基づく自動リクエスト検証
  • ドキュメント:OpenAPI仕様とドキュメントの生成
  • トランスポートサポート:単一のデザインから複数のトランスポートプロトコルを生成
  • 保守性:デザインの変更が自動的に実装に反映

コード生成の概要

Goaのコード生成は、デザインファイルを取り込み、完全な、実行可能なサービス実装を生成します。

コマンドラインツール

インストール

Goaのコマンドラインツールを以下のようにインストールします:

go install goa.design/goa/v3/cmd/goa@latest

主要なコマンド

Goaは、サービスを生成しスキャフォールディングするための2つのコマンドを提供します。 すべてのコマンドはファイルシステムのパスではなく、Goパッケージのインポートパスを 期待します:

# ✅ 正しい:Goパッケージのインポートパスを使用
goa gen goa.design/examples/calc/design

# ❌ 誤り:ファイルシステムのパスを使用
goa gen ./design

コードの生成(goa gen

goa gen <design-package-import-path> [-o <output-dir>]

コード生成のための主要なコマンドです。以下を行います:

  • デザインパッケージを処理し、実装コードを生成
  • 毎回gen/ディレクトリを最初から再作成
  • デザインの変更後は毎回実行する必要があります
  • -oフラグで出力場所をカスタマイズ可能(デフォルトは./gen

例の作成(goa example

goa example <design-package-import-path> [-o <output-dir>]

スキャフォールディングコマンドで、以下を行います:

  • サービスの1回限りのサンプル実装を作成
  • サンプルロジックを含むハンドラースタブを生成
  • 新しいプロジェクトを開始する時に1回だけ実行すべき
  • デザイン変更後の再実行は想定されていません
  • 再実行してもカスタム実装を上書きしません

バージョンの表示(goa version

goa version

インストールされているGoaのバージョンを表示します。

生成プロセス

Goaのコード生成コマンドを実行すると、Goaは以下の体系的なプロセスに従って デザインを動作するコードに変換します:

デザインの読み込み

生成プロセスはいくつかのフェーズで行われます:

  1. ブートストラップ: まず、GoaはデザインパッケージとGoaパッケージをインポートする一時的なmain.go ファイルを作成します。この一時ファイルはコード生成をブートストラップするために 別プロセスとしてコンパイルされ実行されます。

  2. DSLの実行: デザインパッケージの初期化関数が最初に実行され、続いてDSL関数が実行されて メモリ内に式オブジェクトを構築します。これらの式は協調して、API設計全体を 表す包括的なモデルを作成します。

  3. バリデーション: バリデーション中、Goaは式ツリーが完全で適切に形成されていることを確認する 包括的なチェックを実行します。式間の必要な関係がすべて適切に定義されており、 デザインがすべてのルールと制約に従っていることを検証します。このバリデーション ステップは、コードが生成される前に潜在的な問題を早期に発見するのに役立ちます。

  4. コード生成: バリデーションが完了すると、Goaは有効な式をコードジェネレーターに渡します。 これらのジェネレーターは式データを使用してテンプレートをレンダリングし、 実際のコードファイルを生成します。生成されたファイルは、サービスとトランス ポート層ごとに整理されて、プロジェクトのgen/ディレクトリに書き込まれます。

生成のカスタマイズ

メタデータの使用

Meta関数を使用してコード生成の動作をカスタマイズできます。以下は生成に影響を 与える主要なメタデータタグです:

型生成の制御

"type:generate:force"タグを使用すると、メソッドから直接参照されていない場合でも 型の生成を強制できます。値は型を生成する必要のあるサービスの名前です。

var MyType = Type("MyType", func() {
    // 未使用でも型の生成を強制
    Meta("type:generate:force", "service1", "service2")
    
    Attribute("name", String)
})

パッケージと構造のカスタマイズ

"struct:pkg:path"タグを使用すると、型のパッケージとパスを指定できます。値は genパッケージからの相対パッケージパスです。

var MyType = Type("MyType", func() {
    // カスタムパッケージで型を生成
    Meta("struct:pkg:path", "types")
})

var MyStruct = Struct("MyStruct", func() {
    Attribute("ssn", String, func() {
        // フィールド名を上書き
        Meta("struct:field:name", "SSN")
        // カスタム構造体タグ
        Meta("struct:tag:json", "ssn,omitempty")
    })
})

Protocol Bufferのカスタマイズ

"struct:name:proto"タグを使用すると、型のプロトコルバッファーメッセージの名前を 指定できます。値はパッケージパス、メッセージ名、およびプロトコルバッファー型の インポートパスです。

var Timestamp = Type("Timestamp", func() {
    // protobufメッセージ名を上書き
    Meta("struct:name:proto", "MyProtoType")
    
    Field(1, "created_at", String, func() {
        // Googleのタイムスタンプ型を使用
        Meta("struct:field:proto", 
            "google.protobuf.Timestamp",
            "google/protobuf/timestamp.proto",
            "Timestamp",
            "google.golang.org/protobuf/types/known/timestamppb")
    })
})

OpenAPI生成

"openapi:generate"タグを使用すると、サービスのOpenAPI生成を無効にできます。 値は型を生成する必要のあるサービスの名前です。

"openapi:operationId"タグを使用すると、メソッドの操作IDを指定できます。 値はサービス名とメソッド名です。

"openapi:tag"タグを使用すると、サービスのOpenAPIタグを指定できます。 値はサービス名とタグ名です。

var _ = Service("MyService", func() {
    // このサービスのOpenAPI生成を無効化
    Meta("openapi:generate", "false")
    
    Method("MyMethod", func() {
        // カスタム操作ID
        Meta("openapi:operationId", "{service}.{method}")
        // OpenAPIタグを追加
        Meta("openapi:tag:Backend", "バックエンドAPI")
    })
})

一般的なメタデータの用途:

  • 生成される型の制御
  • 生成される構造体のフィールドとタグのカスタマイズ
  • パッケージの場所の上書き
  • プロトコルバッファー生成の設定
  • APIドキュメントのカスタマイズ

プラグインシステム

Goaのプラグインシステムを使用すると、コード生成プロセスを拡張およびカスタマイズ できます。プラグインは生成パイプラインの特定のポイントで介入し、機能の追加、生成 されたコードの変更、または完全に新しい出力の作成を可能にします。

プラグインの機能

プラグインは以下の3つの主要な方法でGoaと対話できます:

  1. 新しいDSLの追加
    プラグインは、Goaのコアのデザイン言語構造と連携して動作する追加のDSLを提供 できます。例えば、CORSプラグイン はクロスオリジンポリシーを定義するためのDSLを追加します:
var _ = Service("calc", func() {
    Description("電卓サービス")
    
    // CORSプラグインが追加するDSL
    cors.Origin("/.*localhost.*/", func() {
        cors.Headers("X-Shared-Secret")
        cors.Methods("GET", "POST")
    })
})
  1. 生成されたコードの変更
    プラグインはGoaが生成するファイルを検査および変更したり、完全に新しいファイルを 追加したりできます:プラグインのGenerate関数は、デザインが評価された後のコード 生成中にGoaによって呼び出されます。以下を受け取ります:

    • genpkg:生成されたコードが配置されるGoパッケージパス
    • roots:すべてのデザインデータを含む評価済みのデザインルート
    • files:Goaがこれまでに生成したファイルの配列

    この関数により、プラグインは生成されたファイルを検査および変更し、出力に完全に 新しいファイルを追加し、生成からファイルを削除し、デザインに基づいてコードを 変換できます。この関数の柔軟性により、プラグインは最終的に生成されるコードベース を完全に制御できます。

一般的なユースケース

プラグインは通常、以下のために使用されます:

  • 特定のプロトコルやトランスポート(CORSなど)のサポートを追加
  • 追加のドキュメント形式を生成
  • カスタムバリデーションルールを実装
  • クロスカッティングな関心事(ロギング、メトリクスなど)を追加
  • サポート用の設定ファイルを生成

プラグインの使用開始

既存のプラグインを使用するには:

  1. プラグインパッケージをインポート
  2. デザインでそのDSLを使用
  3. 通常通りgoa genを実行 - プラグインは自動的に統合されます
import (
    . "goa.design/goa/v3/dsl"
    cors "goa.design/plugins/v3/cors/dsl"
)

このセクションの内容

コマンドラインツール

インストール、使用法、ベストプラクティスを含め、Goaのコード生成用コマンドラインツールについて学びます。

生成プロセス

生成パイプライン、式の評価、出力構造を含め、Goaがあなたのデザインをコードに変換する方法を理解します。

生成されるサービスインターフェースとエンドポイント

サービスインターフェース、エンドポイント、トランスポート層を含め、Goaによって生成されるコードについて学びます。

生成されるクライアント

クライアントサイドのエンドポイントとクライアント構造体を含め、Goaによって生成されるクライアントコードについて学びます。

生成されるHTTPサーバーとクライアントのコード

サーバーとクライアントの実装、ルーティング、エラー処理を含め、Goaによって生成されるHTTPコードについて学びます。

生成されるgRPCサーバーとクライアントのコード

サービスインターフェース、エンドポイント、トランスポート層を含め、Goaによって生成されるコードについて学びます。

カスタマイズ

メタデータを使用してGoaのコード生成をカスタマイズおよび拡張する方法を学びます。

型とバリデーション

Goaが生成されるコードで型、ポインタ、バリデーションをどのように扱うかを理解する

ビューと結果型

ビューの計算とマーシャリングを含め、Goaが結果型のビューをどのように扱うかを理解する