🧅
DDDとオニオンアーキテクチャ入門:肥大化したMVCから脱却するために
はじめに
Webアプリ開発でよく採用される「MVC」ですが、開発が進むと次のような問題に悩まされることはありませんか?
- モデル(ActiveRecordなど)が肥大化する
- ビジネスロジックがコントローラやサービスに分散する
- 仕様変更のたびに影響範囲が広がる
こうした問題を解決するアプローチの一つが DDD(ドメイン駆動設計) と、それを支える オニオンアーキテクチャ です。
本記事では、初心者にもわかるようにこれらの概念を整理し、Goなどでの適用例を紹介します。
※チームや開発者によって、考え方やルールは、異なります。
DDD(ドメイン駆動設計)とは?
DDD(Domain Driven Design) は「ソフトウェアを業務知識(ドメイン)に沿って設計する」アプローチです。
単にコードを整理するだけでなく、現実の業務を反映したモデルを中心に設計すること が特徴です。
キーワード
- エンティティ (Entity) : ユニークなIDを持ち、ライフサイクルを持つオブジェクト
- 値オブジェクト (Value Object) : IDを持たず、属性で定義される(例:金額、座標)
- 集約 (Aggregate) : 複数のエンティティや値オブジェクトをまとめる単位
- リポジトリ (Repository) : 集約を永続化・取得するインターフェース
- ドメインサービス : モデルに属さないビジネスルールを表現する
例)ECサイトの「注文(Order)」
- エンティティ:User, Order
- 値オブジェクト:Address, Money
- リポジトリ:OrderRepository
なぜDDDが必要なのか?
従来のMVC構成では、以下のような課題があります。
- モデルにビジネスロジックを詰め込みすぎる
- コントローラやサービスクラスに処理が散らばる
- 仕様変更に弱い
DDDでは「ドメイン(業務ルール)をコードの中心に据える」ことで、変更に強く、業務をコードに落とし込みやすくなります。
オニオンアーキテクチャとは?
DDDを支える設計思想の一つが オニオンアーキテクチャ です。
名前の通り「玉ねぎの層構造」を持ち、内側の層ほどドメインに近く、外側の層ほどインフラ依存になります。
構造イメージ
[ UI層 ] ← Web, API, CLIなど
[ アプリ層 ] ← ユースケース、サービス
[ ドメイン層 ] ← エンティティ、値オブジェクト、ドメインサービス
[ インフラ層 ] ← DB, 外部API, フレームワーク
特徴
- 依存方向は 外側 → 内側 のみ
- ドメイン層はフレームワークやDBに依存しない
- インフラを差し替えてもドメイン層のコードは影響を受けない
コード例(Goの場合)
ここでは簡単に ユーザー登録のユースケース をオニオンアーキテクチャで表現します。
ドメイン層
domain/user.go
package domain
type User struct {
ID uint
Name string
Email string
}
リポジトリのインターフェース
domain/user_repository.go
package domain
type UserRepository interface {
FindByEmail(email string) (*User, error)
Save(user *User) error
}
ユースケース(アプリケーション層)
usecase/user_service.go
package usecase
import "peer-match-ai/domain"
type UserService struct {
repo domain.UserRepository
}
func NewUserService(r domain.UserRepository) *UserService {
return &UserService{repo: r}
}
func (s *UserService) Register(name, email string) (*domain.User, error) {
existing, _ := s.repo.FindByEmail(email)
if existing != nil {
return nil, fmt.Errorf("user already exists")
}
user := &domain.User{Name: name, Email: email}
err := s.repo.Save(user)
return user, err
}
インフラ層(DB実装)
infra/user_repository.go
package infra
import (
"gorm.io/gorm"
"peer-match-ai/domain"
)
type GormUserRepository struct {
db *gorm.DB
}
func (r *GormUserRepository) FindByEmail(email string) (*domain.User, error) {
var user domain.User
err := r.db.Where("email = ?", email).First(&user).Error
return &user, err
}
func (r *GormUserRepository) Save(user *domain.User) error {
return r.db.Create(user).Error
}
オニオンアーキテクチャのメリット
- ドメイン中心:業務ロジックが整理され、仕様変更に強い
- テスト容易性:インフラをモック化できる
- 疎結合:DB・フレームワークの変更に強い
導入のコツ
- 最初から完璧なDDD/オニオンを目指さない
- まずは「重要なドメイン」から切り出す
- Controllerを薄くし、Usecase層を作ってみる
よくある落とし穴
- 設計に時間をかけすぎて開発速度が落ちる
- 不必要な抽象化でコードが読みにくくなる
- 「DDDを導入すること自体」が目的化する
まとめ
- DDD は「業務知識をコードに落とし込む設計思想」
- オニオンアーキテクチャ は「ドメイン中心のアーキテクチャ」
- 小さな範囲から導入すれば、肥大化したMVCを改善できる
- 実務でもテスト容易性・変更容易性が高まり、長期的にメリットが大きい
- 「MVCが限界かも」と思ったら、まずはユースケース層やリポジトリを切り出すところから始めてみましょう。
Discussion