メインコンテンツまでスキップ

実装パターン

バックエンドでよく使うパターンとベストプラクティスを説明します。

依存注入パターン

コンストラクタインジェクション

// server-core/post/service.go
type Service struct {
repo PostWriter
}

func NewService(repo PostWriter) *Service {
return &Service{repo: repo}
}

Context経由の依存注入

// イベント発行などの横断的関心事
publisher := EventPublisherFromContext(ctx)
publisher.Publish(ctx, event)

Repository パターン

インターフェース定義(core)

// server-core/post/ports.go
type PostWriter interface {
Insert(ctx context.Context, post Post) (PostID, error)
}

実装(platform)

// server-platform/datastore/postgres/post_repository.go
type PostRepository struct {
db *sql.DB
}

func (r *PostRepository) Insert(ctx context.Context, post post.Post) (post.PostID, error) {
// SQL実装
}

ドメインイベントパターン

イベント定義

// server-core/post/events.go
const EventPostCreated = "post.created"

func NewPostCreatedEvent(postID PostID, actorID ActorID) DomainEvent {
return NewEvent(EventPostCreated, map[string]any{
"post_id": postID.String(),
"actor_id": actorID.String(),
})
}

イベント発行

// サービス内で発行
publisher := EventPublisherFromContext(ctx)
publisher.Publish(ctx, NewPostCreatedEvent(postID, actorID))

Context の活用

ActorIDの伝播

// Gatewayで設定
ctx = ctxutil.WithActorID(ctx, actorID)

// API Serverで取得
actorID, ok := ctxutil.GetActorID(ctx)

トレースIDの伝播

// OpenTelemetryが自動的に伝播
// Traceparent ヘッダが自動付与

エラーハンドリング

ドメインエラー

// server-core/post/service.go
if err := validatePost(text); err != nil {
return PostID{}, fmt.Errorf("invalid post: %w", err)
}

インフラエラー

// platform実装でラップ
if err != nil {
return fmt.Errorf("failed to insert post: %w", err)
}

トランザクション管理

サービス層でのトランザクション

// トランザクションはplatform層で管理
// 必要に応じてトランザクションコンテキストを注入

テストパターン

モックの使用

// テストでモックRepositoryを注入
mockRepo := &MockPostWriter{}
service := post.NewService(mockRepo)

テーブル駆動テスト

tests := []struct {
name string
input CreateUserPostInput
wantErr bool
}{
{"正常系", CreateUserPostInput{...}, false},
{"テキスト長超過", CreateUserPostInput{...}, true},
}

関連ドキュメント