ドメイン設計
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-280文字
- H3Index: 有効なH3セルIDであること
- 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. イベント駆動
- 重要なビジネスイベントは明示的に発行
- 将来の拡張(通知、集計など)に備える