依存性逆転の原則(DIP)
code:go
依存性逆転の原則(DIP)の実装
1. Handler層の実装
// handler/handler.go
type Core struct { // ← 具象(実装
// usecase.Publisherのusecaseが小文字なのはなぜか?ディレクトリ名は小文字ということ?
publisherUsecase usecase.Publisher // ← useaseのインターフェースに依存
}
✅ 正しい理解ーーーーーーーーーーーーーーーーーーーーーーーーーーー
usecaseが小文字の理由:
- usecaseはパッケージ名(ディレクトリ名)
- Goではパッケージ名は小文字が慣習
- usecase.Publisherは「usecaseパッケージのPublisherインターフェース」という意味
// パッケージ名.型名の構造
usecase.Publisher
↑ ↑
パッケージ名 型名(インターフェース)
(小文字) (大文字始まり=Public)
// 実際のディレクトリ構造
core/
usecase/ # パッケージ名(小文字)
publisher.go # type Publisher interface(大文字)
publisher_impl.go # type publisher struct(小文字=private)
1. Publisher interface(大文字)
- 外部パッケージから参照可能
- 契約として公開
2. publisher struct(小文字)
- 同じパッケージ内からのみ参照可能
- 実装詳細を隠蔽
- NewPublisher()経由でのみインスタンス化
//-------------------------------------------------------
// handler/publisher.go
// handlerのpubliserの実装
func (h Core) CreatePublisher(...) {
// usecaseのcreateをインターフェース経由で呼び出し
response, err := h.publisherUsecase.Create(ctx, input)
}
2. UseCase層のインターフェース定義
// usecase/publisher.go
type Publisher interface { // ← 抽象(インターフェース)
// インターフェースにはメソッドを定義する
// メソッドの戻り値はusecaseのpublisher_implが実態を返す
Create(ctx context.Context, input *input.CreatePublisher) (*output.CreatePublisher, error)
}
3. UseCase層の実装
// usecase/publisher_impl.go
type publisher struct { // ← 具象(実装)
publisherQuery query.Publisher
publisherRepo repository.Publisher
}
func (p *publisher) Create(...) (*output.CreatePublisher, error) {
// 実際の実装
}
依存の方向
従来の依存(依存性逆転なし)❌
Handler → UseCase実装 → Repository実装 → DB
(上位層が下位層の具象に直接依存)
依存性逆転あり ✅
Handler → UseCase(I) ← UseCase実装
↓
Repository(I) ← Repository実装
- (I) = インターフェース
- 矢印は依存の方向
なぜ「逆転」なのか
通常の依存
// ❌ 具象に直接依存
type Core struct {
publisherUsecase *publisher // 具体的な実装に依存
}
逆転した依存
// ✅ 抽象に依存
type Core struct {
publisherUsecase usecase.Publisher // インターフェースに依存
}
「逆転」の意味:
- 本来なら上位層(Handler)が下位層(UseCase実装)に依存する
- でもインターフェースを挟むことで、両方がインターフェースに依存する形に逆転
メリット
1. テスタビリティ
// テスト時にモックに差し替え可能
mockUsecase := mock.NewMockPublisher(ctrl)
handler := Core{
publisherUsecase: mockUsecase, // モックを注入
}
2. 柔軟性
// 実装を変更してもインターフェースが同じなら影響なし
// 例:DBをPostgreSQLからMongoDBに変更
type publisherMongoDB struct { ... } // 新実装
func (p *publisherMongoDB) Create(...) { ... } // 同じインターフェース
3. 依存関係の明確化
// Wireで依存性注入
func InitializeHandler() *handler.Core {
wire.Build(
handler.NewCore, // Handlerはインターフェースを要求
usecase.NewPublisher, // 実装を提供
// ...
)
}
まとめ
- HandlerはUseCaseインターフェースを呼び出す
- UseCaseの実装がそのインターフェースを満たす
- これにより、HandlerはUseCase実装の詳細を知らなくて良い
- テスト時はモック、本番は実装と差し替え可能