概要
クリーンアーキテクチャーってよく聞くけど、なんやねんって思う人いますよね💦
ベテランさんとかもなかなか教えてくれたりしないです。
なのでその内容を小学生にもわかるレベルで、解説していきます。
今回はRailsで実装してますが、
皆さんが使っている言語に置き換えて学んでくださいね!
AI時代だからこそ設計
コードを書く価値は無くなってきたので、設計を学ぶ意義は大きいと思います!
設計を語れる方が大事な時代だからです!
🏠 小学生にもわかる説明
「料理屋さん」で考えてみましょう!料理屋さんには色々な人がいますよね。
- 👤 お客さん(画面・ユーザー)
- 📋 ホール係(コントローラー)→ 注文を聞く人
- 👨🍳 シェフ(ユースケース)→ 料理のルールを決める人
- 🥩 食材(エンティティ)→ 本物の食べ物・大事なもの
- 🏪 倉庫・冷蔵庫(リポジトリ/DB)→ 食材を保管する場所
大事なルールは「シェフは食材のことだけ考える!」
シェフは「今日のメニューは何か」「どう料理するか」だけ考えます。「食材がどの倉庫にあるか」は考えません。倉庫が変わっても(MySQLからPostgreSQLに変えても)シェフの仕事は変わらない!これがクリーンアーキテクチャーの核心です。
🎯 ドメインとビジネスルールって何?
ドメインとビジネスルールってエンジニア界隈では当たり前のように使われていて、
なんのこっちゃってなりますよね。それを簡単にして説明します。
ドメイン=「その世界のこと」
「ドメイン」とは、そのアプリが解決しようとしている現実世界の問題領域のことです。
- ECサイトなら「商品を売り買いする世界」
- 病院システムなら「診察・予約・カルテを管理する世界」
- サッカーゲームなら「サッカーというスポーツの世界」
ビジネスルール=「その世界のルール」
そしてビジネスルールとは、その世界に存在する技術と無関係なルールです。
ECサイトの例:
- 在庫が0なら注文できない
- 会員でないとセール価格は見られない
- 注文確定後はキャンセルできない
サッカーゲームの例:
- オフサイドなら得点は無効
- 試合時間は90分
- 退場したら10人で戦う
これらのルールは、DBがMySQLでもPostgreSQLでも、
RailsでもNext.jsでも変わりません。
どれだけ技術力が高かろうが、AIを使おうが、そもそもサッカーのルールを知らなかったらサッカーゲームは作れない。
どれだけ技術力が高かろうが、AIを使おうが、そもそもサッカーのルールを知らなかったらサッカーゲームは作れない。
これがドメイン知識の本質です。
なので、ドメインが大きかったり複雑になると、どれだけ優秀なエンジニアでも最初の立ち上がりは遅くなります。
「なんでこんなに時間かかるの?」と思ったとき、それはエンジニアの技術力の問題ではなく、そのビジネスのルールを一から学んでいる最中だからかもしれません。
こういう背景、ぜひわかってください!(特に非エンジニアの方へ)
エンティティはドメインの言葉で話す
画像で見た「ビジネスルール」を、コードで表現するとこうなります。
だからエンティティには「ビジネスルール」だけを書くんです!
class Order
# 「注文確定後はキャンセルできない」というビジネスルール
def cancelable?
status == "pending"
end
# 「在庫が0なら注文できない」というビジネスルール
def purchasable?(stock)
stock > 0
end
end
💉 依存性の注入(DI)って何?
「依存性の注入」という言葉、難しそうですよね。でも考え方はシンプルです。
「必要な道具は外から渡してもらう」 というだけです。
料理屋さんで考えてみましょう
注入しない場合(ダメな例)
シェフが自分で「よし、今日は〇〇スーパーから食材を買ってくる!」と決めてしまっている状態。
- スーパーが閉まったら詰む
- テスト(試食)のときも本物の食材を買いに行かないといけない
- 別のスーパーに変えたいときはシェフのスケジュールを全部変えないといけない
注入する場合(良い例)
オーナーが「今日はこの食材を使って」とシェフに渡す。
- シェフはどこから来た食材でも料理できる
- テストのときはダミーの食材(モック)を渡せばOK
- 食材の調達先が変わってもシェフは何も知らなくていい
シェフは「どの倉庫を使うか」を自分で決めない。オーナーが「今日はこの食材を使って」と渡してくれる。これが依存性の注入です。
コードで見ると
# ダメな例:シェフが自分でスーパーに買いに行く
class RegisterUser
def initialize
@user_repository = UserRepository.new # 自分で決めてしまう
end
end
# 良い例:オーナーから食材を渡してもらう
class RegisterUser
def initialize(user_repository: UserRepository.new)
@user_repository = user_repository # 外から受け取る
end
end
外から渡してもらうことで、本番は本物のリポジトリ、
テスト時はモックに簡単に差し替えられます。
-
補足
この「外」とはコントローラーのことです。
シェフ(ユースケース)は倉庫(リポジトリ)を自分で選ばない。
呼び出す側が渡してくれるので、差し替えが自由にできます。
🛤 Railsでのディレクトリ構成
Railsは通常MVCですが、クリーンアーキテクチャーを取り入れるとこうなります。
app/
├── entities/ # 🥩 純粋なビジネスルール(Railsに依存しない!)
├── use_cases/ # 👨🍳 ユースケース(シェフ)
├── repositories/ # 🏪 データの取り出し口(インターフェイス変換)
├── controllers/ # 📋 ホール係(薄く保つ!)
└── models/ # DBとのマッピング(ActiveRecord)
1️⃣ エンティティ(食材)― Railsに依存しない純粋なクラス
# app/entities/user_entity.rb
class UserEntity
attr_reader :id, :name, :email, :age
def initialize(id:, name:, email:, age:)
@id = id
@name = name
@email = email
@age = age
end
# ビジネスルールはここに書く
def adult?
age >= 18
end
def valid_email?
email.include?("@")
end
end
ActiveRecord を継承しません。DBのことを一切知らないのがポイントです。
2️⃣ リポジトリ(倉庫係)― DBとエンティティをつなぐ
# app/repositories/user_repository.rb
class UserRepository
# DBからデータを取ってきて、エンティティに変換する
def find(id)
record = User.find(id) # ← ActiveRecordモデル(普通のRailsモデル)
to_entity(record)
end
def save(user_entity)
record = User.find_or_initialize_by(id: user_entity.id)
record.update!(
name: user_entity.name,
email: user_entity.email
)
to_entity(record)
end
def find_all
User.all.map { |record| to_entity(record) }
end
private
# ActiveRecordのレコードをエンティティに変換(DBの世界→ビジネスの世界)
def to_entity(record)
UserEntity.new(
id: record.id,
name: record.name,
email: record.email
)
end
end
3️⃣ ユースケース(シェフ)― ビジネスのルールを実行する
# app/use_cases/register_user.rb
class RegisterUser
# リポジトリを外から受け取る(依存性の注入)
def initialize(user_repository: UserRepository.new)
@user_repository = user_repository
end
def call(name:, email:)
# ビジネスルールのチェック
entity = UserEntity.new(id: nil, name: name, email: email)
raise "メールアドレスが不正です" unless entity.valid_email?
# 保存(どこに保存するかは知らない!)
@user_repository.save(entity)
end
end
ユースケースはDBがMySQLかPostgreSQLかを知りません。リポジトリを差し替えるだけでOKです。
4️⃣ コントローラー(ホール係)― 薄く保つ!
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
# コントローラーはユースケースを呼ぶだけ
use_case = RegisterUser.new
user = use_case.call(
name: params[:name],
email: params[:email]
)
render json: { id: user.id, name: user.name }, status: :created
rescue => e
render json: { error: e.message }, status: :unprocessable_entity
end
end
「薄く保つ」とは、コントローラーにビジネスロジックを書かないということです。
注文を聞いて、シェフに渡して、結果をお客さんに返す。それだけ。
コントローラーは「受け取って渡す」だけ。ビジネスロジックは書きません。
🏪 アプリが大きくなると、なんでいいの?
責務の分離には2つの嬉しい副産物があります。
① テストしやすくなる
シェフ(ユースケース)だけ単体でテストできる。テスト用のニセ倉庫(モック)を渡せばOK。
② 変更が怖くなくなる
冷蔵庫をMySQLからPostgreSQLに変えても「倉庫係(リポジトリ)だけ直す」で済む。シェフのコードは1行も触らなくていい。
🏠 小さい家なら「なんでも置き場」でもいい。でも大きな家になったら「リビング」「キッチン」「寝室」をちゃんと分けないと、何がどこにあるかわからなくなる。
補足(責務の分離)
責務の分離は、コードの整理整頓のことです。
責務の分離をNext.jsで実践した例はこちらで解説しています👇
新人エンジニアは知らないとやばい😱 「責務の分離」ってなーに?🤔( フロントエンドで解説)
🎯 結論:クリーンアーキテクチャーとは?
ここまで読んでいただいた方なら、もう感覚的にわかっているはずです。
クリーンアーキテクチャーとは、変更があっても影響範囲が小さく、設計が崩れないようにシステムを設計する考え方です。
そのために2つのルールを守ります。
① 責務を分離する(コードの整理整頓)
シェフはシェフの仕事だけ、倉庫係は倉庫の仕事だけ知っていればいい。
「自分の仕事以外は知らなくていい」状態を作ることで、どこかを変えても他が壊れません。
② 内側の人は外側のことを知らない
シェフ(ビジネスロジック)は、DBがMySQLかPostgreSQLかを知らなくていい。
倉庫が変わってもシェフの仕事は変わらない。
小さいうちは「なんでも一人でやる」でも動きます。でもお店が大きくなったとき、最初から役割分担が決まっているお店と一人が全部知っているお店では、成長スピードが全然違います。
それがクリーンアーキテクチャーを学ぶ理由です。
🤔 採用すべきでないケース
クリーンアーキテクチャーは銀の弾丸ではありません。
むしろ小さいうちに導入すると過剰設計になることもあります。
部活で考えると
最初は3人でとりあえずやってみる(MVP・PoC)
友達3人で「とりあえずサッカーやろうぜ!」となったとき、
ポジションもフォーメーションも細かいルールも決めないですよね。
まず蹴ってみる。それでいい。
部員が30人になって大会に出るとなったら...
「ポジション」「戦術」「練習メニュー」をちゃんと決めないと試合にならない。
1人が「攻撃も守備も全部やる!」では限界がくる。
アプリ開発で言うと
採用しなくていいケース:
- MVP(Minimum Viable Product):最低限の機能で検証するフェーズ
- PoC(Proof of Concept):技術・アイデアの実現可能性を確かめる段階
- 仕様が頻繁に変わる初期フェーズ
- 個人開発の小さなアプリ
- チームがクリーンアーキテクチャーに慣れていない
採用を検討すべきケース:
- チーム開発で複数人が同じコードを触る
- 長期運用を前提としたプロダクト
- DBや外部サービスの切り替えが想定される
- テストをしっかり書きたい
最初はMVPでシンプルに作って、アプリが育ってきたタイミングで
「そろそろポジション決めないとな」と感じたら導入を検討しましょう。
🎯 まとめ
| よくあるRails | クリーンアーキテクチャー |
|---|---|
| モデルに全部書く(Fat Model) | エンティティ・ユースケースに分ける |
| コントローラーが複雑 | コントローラーは薄く |
| DBを変えたらバグだらけ | リポジトリだけ直せばOK |
| テストしにくい | 各層を独立してテスト可能 |
Fat Modelとは、Railsのモデルにビジネスロジックが詰め込まれすぎて、肥大化した状態のことです。
最初は「難しい…」と感じるかもしれませんが、アプリが大きくなるほど真価を発揮します!小さなアプリには過剰設計になることもあるので、チームや規模に合わせて取り入れ方を調整してみてください。
使用したAI
Claude Code
ChatGPT




