本文へスキップ

プロジェクト構造 ​

Spine CLIでプロジェクトを作成し、ユーザーリソースを追加します。生成されたファイルを確認しながら、アプリの構成コードを置く場所と、コントローラー、サービス、リポジトリの役割を学びます。

まずCLIのインストールを完了してください。最初のアプリに沿ってプロジェクトをすでに作成している場合は、生成手順を省略し、そのプロジェクトで続けられます。以下のコードのモジュールパスは、自分のプロジェクトのパスに読み替えてください。

CLIによるプロジェクトの生成 ​

プロジェクトを配置する親ディレクトリで実行します。同じ名前のmy-appディレクトリがすでにある場合は、別の名前を使ってください。

sh
spine version
spine new my-app --module example.com/my-app
cd my-app
go mod tidy
spine doctor
spine inspect

my-appディレクトリが生成され、依存関係が用意されます。doctorがツールとプロジェクト設定を診断し、inspectで既定のパスであるGET /healthの登録を確認できます。

生成される基本構造 ​

spine newは次のファイルを生成します。go.sumは、その後のgo mod tidyで作成されます。

text
my-app/
├── .gitignore
├── README.md
├── go.mod
├── main.go
├── spine.toml
└── internal/
    └── application/
        ├── application.go
        └── application_test.go

CLIの設定 ​

spine.tomlには、CLIがプロジェクトを見つけて実行するために必要な設定を記述します。対応するキーは次の4つです。データベース接続などのアプリ設定は別途管理します。

toml
module = "example.com/my-app"
entrypoint = "."
composition = "./internal/application"
port = 8080
キー役割
moduleGoのモジュールパス。go.modのモジュールパスと一致する必要があります。
entrypoint実行またはビルドする対象
compositionアプリ構成を検査し、生成したコードを自動登録する場所
port開発サーバーで使用するポート

実行とアプリ構成の分離 ​

main.goはアプリの構成関数を呼び出し、構成を検証してからサーバーを実行します。コンストラクタ、ルート、インターセプターは、internal/application/application.goのBuildApplicationに登録します。

go
package main

import (
    "log"

    "example.com/my-app/internal/application"
)

func main() {
    app, options, err := application.BuildApplication()
    if err != nil {
        log.Fatal(err)
    }
    if err := app.Validate(options); err != nil {
        log.Fatal(err)
    }
    if err := app.Run(options); err != nil {
        log.Fatal(err)
    }
}

BuildApplicationはサーバーを起動せずに、アプリと実行オプションを返します。生成されたコードは、既定のHTTPパスであるGET /healthを登録します。

go
package application

import (
    "os"

    "github.com/NARUBROWN/spine"
    "github.com/NARUBROWN/spine/pkg/boot"
    "github.com/NARUBROWN/spine/pkg/httpx"
)

type HealthController struct{}

func NewHealthController() *HealthController { return &HealthController{} }

func (c *HealthController) Get() httpx.Response[map[string]string] {
    return httpx.Response[map[string]string]{Body: map[string]string{"status": "ok"}}
}

func BuildApplication() (spine.App, boot.Options, error) {
    app := spine.New()
    app.Constructor(NewHealthController)
    app.Route("GET", "/health", (*HealthController).Get)

    address := ":8080"
    if port := os.Getenv("SPINE_PORT"); port != "" {
        address = ":" + port
    }
    return app, boot.Options{Address: address, HTTP: &boot.HTTPOptions{}}, nil
}

CLIによる機能の追加 ​

プロジェクトのルートでユーザーリソースを生成します。既存のプロジェクトですでに生成している場合は、このコマンドを省略します。

sh
spine generate resource user
text
src/user/
├── user_route.go
├── user_repository.go
├── user_service.go
└── user_controller.go

コマンドが終了すると、src/userに4つのファイルが生成されます。CLIは既定で、BuildApplicationに次の登録コードを追加します。生成結果を確認し、同じコードを手動で再度登録しないでください。

go
if err := user.Register(app); err != nil {
    return nil, boot.Options{}, err
}

user_route.goは、コンストラクタとGET /user/:idを登録します。ほかのファイルの役割は次のとおりです。

ファイル役割
user_controller.goHTTPリクエストを受け取ってサービスに渡し、レスポンスを返します。
user_service.goビジネスロジックを処理します。
user_repository.goデータアクセスを担当します。

リソースジェネレーターが生成するのは、IDを返すGETハンドラーの基本コードだけです。データベースモデルやデータを永続保存する機能は、自分で実装する必要があります。

型を定義した作成と取得の例が必要な場合は、別の名前でspine generate slice order --storage memory --field name:stringを使ってください。このコマンドのメモリストレージは開発用で、アプリを終了するとデータが失われます。

登録規則と検証 ​

CLIが登録コードを検査できるように、BuildApplication() (spine.App, boot.Options, error)の関数本体の最上位にapp := spine.New()を維持してください。登録コードを条件分岐、ループ、任意の補助関数の中に移さないでください。Register(app)が返したエラーも確認し、返す必要があります。

アプリ構成を検査してからサーバーを実行します。

sh
spine contract check
spine verify
spine dev

contract checkは、登録コードから抽出した実行契約を検査します。verifyは契約の検査と、gofmt、go vet、Goのテストを実行します。両方のコマンドがエラーなく終了すると、devが開発サーバーを起動します。実際のHTTPの動作と、外部のデータベースやメッセージブローカーへの接続は、実行環境で別途確認する必要があります。

次のステップ ​