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

ドメイン設計

Bazbiiのドメインモデルとビジネスロジックの設計方針を説明します。

主要なドメインモデル

Actor(主体)

システム内の主体を表します。

type ActorKind string

const (
ActorKindUser ActorKind = "user"
ActorKindPartner ActorKind = "partner"
ActorKindSystem ActorKind = "system"
ActorKindBot ActorKind = "bot"
)

type ActorID = UUID
  • User: エンドユーザー
  • Partner: パートナー(店舗など)
  • System: システム生成
  • Bot: ボット

Post(投稿)

位置情報に紐づく投稿を表します。

type Post struct {
ID PostID
ActorID ActorID
ActorKind ActorKind
H3Index H3Index
Text string
Partner *PartnerMeta
CreatedAt time.Time
}
  • H3Index: H3地理インデックス(位置情報)
  • Text: 投稿本文
  • Partner: パートナー関連情報(オプション)

H3Index(位置情報)

Uber H3地理インデックスシステムを使用した位置表現。

  • 六角形のセルで位置を表現
  • 解像度(resolution)でセルサイズを調整
  • k-ringで周辺セルを取得可能

ドメインイベント

重要なビジネスイベントはドメインイベントとして発行します。

// 投稿作成成功
EventPostCreated = "post.created"

// 投稿削除
EventPostDeleted = "post.deleted"

// 投稿作成失敗(重要なエラー)
EventPostCreationFailed = "post.creation_failed"

イベント発行の指針

  • ✅ 重要なビジネスイベント(作成、削除など)
  • ✅ 監視が必要なエラー
  • ❌ 技術的な詳細(DB接続エラーなど)はログで十分

ビジネスルール

投稿作成のルール例

  1. テキスト長: 1-280文字
  2. H3Index: 有効なH3セルIDであること
  3. Actor: 有効なActorIDであること

権限ルール

  • 投稿は作成者のみ削除可能
  • パートナー投稿は公式バッジ表示

ポート(インターフェース)設計

Repository パターン

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

type PostReader interface {
FindByID(ctx context.Context, id PostID) (*Post, error)
ListByH3(ctx context.Context, h3Index H3Index, limit int) ([]Post, error)
}

Event Publisher

// server-core/shared/event_publisher.go
type EventPublisher interface {
Publish(ctx context.Context, event DomainEvent)
}

ドメインサービスの設計

サービス層の責務

  • ✅ ビジネスルールの実装
  • ✅ 複数のエンティティにまたがるロジック
  • ✅ トランザクション境界の管理
  • ❌ データベース操作(Repository経由)
  • ❌ HTTP/gRPC処理(ハンドラー層の責務)

実装例

// server-core/post/service.go
func (s *Service) CreateUserPost(ctx context.Context, in CreateUserPostInput) (PostID, error) {
// ビジネスロジック
p, err := NewPostDraft(in.H3Index, in.Text, in.ActorID)
if err != nil {
return PostID{}, err
}

// 永続化(Repository経由)
postID, err := s.repo.Insert(ctx, p)
if err != nil {
publisher.Publish(ctx, NewPostCreationFailedEvent(err, in.ActorID))
return PostID{}, fmt.Errorf("failed to insert post: %w", err)
}

// イベント発行
publisher.Publish(ctx, NewPostCreatedEvent(postID, in.ActorID))

return postID, nil
}

設計原則

1. 富んだドメインモデル

  • エンティティにビジネスロジックを持たせる
  • 単なるデータ構造ではなく、振る舞いを持つ

2. 不変性の活用

  • 一度作成されたエンティティは基本的に不変
  • 変更が必要な場合は新しいエンティティを作成

3. イベント駆動

  • 重要なビジネスイベントは明示的に発行
  • 将来の拡張(通知、集計など)に備える

関連ドキュメント