クリーンアーキテクチャとは
概要
ビジネスルールを中心に置き、外の技術に依存しない設計
詳細
アーキテクチャの中心は「ドメイン(ビジネスルール)。
UI・DB・外部APIなどはすべて「外側の関心事」として扱われ、
依存の方向は常に内向き(=内側は外側を知らない)
層構造について
各層の役割
| 層 |
主な責務 |
外側or内側 |
外部との役割 |
具体例 |
| Framwworks&Drivers |
技術的な土台。外部ライブラリやフレームワーク層 |
最も外側 |
Webフレームワーク,DB,外部API,UI |
Gin,Gorm,Echo,MySQL,gRPCなど |
| Interface Adapters |
内側と外側の橋渡し。データ形式変換・I/O制御。 |
外側寄り |
JSON,SQL,HTTP形式に合わせる |
HTTP Handler,DB RepositoryImpl |
| Use Cases(アプリケーション) |
ビジネスルールの組み立て。トランザクション単位の整理 |
内側 |
DBやUIを意識しない |
CreateUserUseCase,PlaceOrderUsecase |
| Entities(ドメイン) |
ビジネスルールの中核。純粋なロジック。 |
最も内側 |
技術的な依存ゼロ |
User,Order,CalculateTotal() |
Goでのディレクトリ構成例
/internal
/domain
user.go // Entity
user_repository.go // Repository interface
/usecase
create_user.go // ユースケース
/interface
handler/http_user.go // HTTPハンドラ
/infrastructure
mysql/user_repo.go // MySQL実装
/cmd/api/main.go // DIで結線(wireやfxなど)
「内側」と「外側」とは具体的にどこな箇所の事か
内側
ビジネス(Entities)に近いものほど内側
外側
技術や実装に近いものほど外側
特徴・メリット
- 1.依存方向が一方通行(内向き)
- 内側(ドメイン)は外側(DB,Webフレームワークなど)を知らない。
- 外側が「インターフェースを実装」して内側に提供する形。
- 2.テストしやすい
- ドメイン層やユースケースそうを「モック化されたリポジトリ」でテストできる。
- DB接続なしでビジネスロジックの単体テストが可能。
- 3.技術変更に強い
- フレームワークやDBを差し替えてもドメインに影響しない。
- 例:Gin-Fiberに変えてもユースケース層はそのまま。
- 4.長期保守に向く
- 「ビジネスルール」と「技術的実装」分離されているため、新機能追加やリファクタ時も影響範囲が明確。
Entities(ドメイン)とは
概要
システムの中で最も「本質的なビジネスルール」を表す層で、
外部技術(DB・UI・フレームワーク)に依存しない純粋なロジックを記述する場所。
詳細説明
ビジネス的に重要なルール・制約・状態遷移を保持する。
外部要因(DB,API,UI)に影響されないようにすることでどんな技術を使ってもルールが壊れないようにする。
具体例
間違った例
type User struct {
ID int `json:"id" gorm:"primary_key"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
良い例
package domain
import "errors"
type User struct {
id int
name string
email string
password string
}
// コンストラクタ: 不正な状態で生成されないように
func NewUser(name, email, password string) (*User, error) {
if name == "" {
return nil, errors.New("name is required")
}
if len(password) < 8 {
return nil, errors.New("password must be at least 8 characters")
}
return &User{
name: name,
email: email,
password: password,
}, nil
}
// ドメインロジック(ビジネスルール)
func (u *User) ChangeEmail(newEmail string) error {
if newEmail == "" {
return errors.New("email cannot be empty")
}
u.email = newEmail
return nil
}
func (u *User) Name() string { return u.name }
func (u *User) Email() string { return u.email }
このように書くと
- gormやjsonタグがない→非技術依存
- バリデーションや制約(ビジネスルール)を内包
- 外部から直接フィールドを触れない(カプセル化)
- goはフィールド名の先頭文字を大文字にしないと外部モジュールから接続できない