はじめに:本記事の狙い
クリーンアーキテクチャ(以下、CA)での設計しか知らない私が、Ruby on RailsやLaravelに代表されるMVCフレームワークを用いた開発へスムーズに適応できるよう、両者の概念を(無理やり)対応付け、思考法を整理することが本記事の狙いである。
クリーンアーキテクチャとMVCの概念対応
まず、CAの各層・概念が、一般的なMVCフレームワークにおいてどのコンポーネントに対応するのかを以下に示す。
クリーンアーキテクチャの概念 | MVCフレームワークでの主な実装先 |
---|---|
エンティティ (Entity) |
Model (ActiveRecord モデル) |
値オブジェクト (Value Object) | PORO/POPO をModel内で利用 |
リポジトリ (Repository) |
Model (ActiveRecord が責務を兼任) |
ユースケース / ドメインサービス |
Service Object (app/services など) |
コントローラー (Controller) |
Controller (app/controllers ) |
プレゼンター (Presenter) |
Presenter/Decoratorパターン (app/presenters など) |
ルーター (Router) |
ルーティングファイル (config/routes.rb など) |
データベース (Database) |
ORM (ActiveRecord とデータベース設定) |
以下では、「ユーザー登録機能」をRuby on Railsで実装する例を交えながら、各概念の対応関係を詳述する。
1. ドメイン層 (Domain Layer)
ドメイン層は、ビジネスの核となるルールやロジックを担う。MVCフレームワークでは、これらの責務は主にModelや、必要に応じて作成されるServiceクラスに配置される。
エンティティ (Entity)
一意なIDを持ち、状態が変化するビジネスオブジェクト(例: User
, Product
)である。
-
MVCでの対応:
RailsのActiveRecordやLaravelのEloquentといったO/Rマッパーのモデルクラスが、エンティティの役割を兼任するのが最も一般的である。app/models
に配置されるこれらのクラスが、ビジネスルール(validates
など)と永続化のロジックを両方持つ。
実装例: ActiveRecordによるエンティティ
# app/models/user.rb
# ApplicationRecordはActiveRecord::Baseを継承している
class User < ApplicationRecord
# エンティティとしてのビジネスルール(状態を検証する)
validates :name, presence: true
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
# 永続化に関するロジックもここに含む
has_secure_password
end
このUser
クラスは、ActiveRecordの機能を使ってデータベースとの連携を担いつつ、ビジネスルールを定義するエンティティとしての役割も兼ね備えている。
値オブジェクト (Value Object)
単なる値としての性質を持つオブジェクト(例: Money
, ZipCode
)である。
-
MVCでの対応:
Active Record
のcomposed_of
(Rails) や、カスタムキャスト (Laravel) を利用してModelの属性として実装可能だ。または、単純なPORO (Plain Old Ruby Object) / POPO (Plain Old PHP Object) として定義し、Model内で利用する方法も一般的である。
実装例: ZipCode
値オブジェクト
# app/models/value_objects/zip_code.rb
# (カスタムディレクトリを作成して管理する)
class ZipCode
attr_reader :value
def initialize(value)
raise ArgumentError, 'Invalid zip code format' unless value.match?(/\A\d{3}-\d{4}\z/)
@value = value
end
def to_s
@value
end
end
この値オブジェクトをUser
モデル内で利用することで、単なる文字列ではない、意味と制約を持った属性として扱うことができる。
ドメインサービス (Domain Service)
特定のエンティティに属さない、複数のエンティティをまたぐドメイン固有のロジックである。
-
MVCでの対応: サービスオブジェクト (
Service Object
) や サービスクラス (Service Class
) として実装する。後述のユースケースと合わせて実装されることも多い。
2. ユースケース層 (Use Case Layer)
アプリケーション固有のビジネスルールや、具体的な操作手順を定義する。
ユースケース (Use Case)
システムの操作(例: 「ユーザーを作成する」)を表現する。
-
MVCでの対応: ドメインサービスと同様に、サービスオブジェクトやサービスクラスで実装するのが一般的だ。Controllerからこのサービスクラスを呼び出すことで、Controllerを薄く保つ(Fat Controllerの回避)ことができる。
app/services
のようなディレクトリを作成し、UserRegistrationService
のように具体的な処理をクラスとして定義する。
実装例: ユーザー登録ユースケース
# app/services/user_registration_service.rb
class UserRegistrationService
Result = Struct.new(:success?, :user, :errors, keyword_init: true)
def initialize(params)
@params = params
end
def execute
user = User.new(@params)
if user.save
# 成功した場合の付随処理(ドメインサービス的な振る舞い)
send_welcome_email(user)
Result.new(success?: true, user: user)
else
# 失敗した場合
Result.new(success?: false, errors: user.errors.full_messages)
end
end
private
def send_welcome_email(user)
# Mailerクラスなどを呼び出す
puts "Welcome email sent to #{user.email}!"
end
end
3. アダプタ層 (Adapter Layer)
ドメイン層やユースケース層と、外部の技術(フレームワーク、UI)との間でデータ変換を行う。
コントローラー (Controller)
外部からのリクエストを受け取り、ユースケースを呼び出し、レスポンスを返す。
- MVCでの対応: MVCフレームワークのControllerがほぼ同じ役割を担う。ただし、CAほど責務が厳密ではなく、リクエストの受付、ユースケース(Service)の呼び出し、レスポンス(ViewのレンダリングやJSON返却)までを担当する。
実装例: UsersController
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
end
def create
# 1. ユースケース(Service)を呼び出す
result = UserRegistrationService.new(user_params).execute
# 2. 結果に応じてレスポンスを決定する
if result.success?
redirect_to user_path(result.user), notice: 'User was successfully created.'
else
@user = User.new(user_params)
flash.now[:alert] = result.errors.join(', ')
render :new, status: :unprocessable_entity
end
end
# ... 省略 ...
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
Controllerはビジネスロジックの詳細を知らず、UserRegistrationService
に処理を委譲している点が重要だ。
プレゼンター (Presenter)
ユースケースからの出力を、Viewに適した形に整形する。
- MVCでの対応: 必須ではないが、DecoratorパターンやPresenterパターンを用いて実装可能である。Viewのロジックが複雑化した場合に導入する。単純な場合は、Viewファイル内やヘルパーメソッドで整形を済ませることが多い。
実装例: UserPresenter
# app/presenters/user_presenter.rb
class UserPresenter < SimpleDelegator
# SimpleDelegatorを継承するとメソッド委譲を自動化できる
# View向けの整形ロジック
def registration_date
created_at.strftime('%Y年%m月%d日')
end
def name_with_honorific
"#{name} 様"
end
end
リポジトリ (Repository)
エンティティの永続化を抽象化するインターフェースである。
-
MVCでの対応: Active Recordモデルがこの役割を暗黙的に兼任している。
User.find(1)
やuser.save
といったメソッドが、リポジトリパターンの責務を果たしていると見なせる。そのため、リポジトリ層を明示的に作成することは少ない。
4. インフラ層 (Infrastructure Layer)
フレームワークやデータベースなど、具体的な技術要素である。
ルーター (Router)
リクエストを適切なコントローラーに振り分ける。
-
MVCでの対応: フレームワークが提供するルーティング機能そのものが対応する。(例: Railsの
config/routes.rb
)
実装例: ルーティング定義
# config/routes.rb
Rails.application.routes.draw do
resources :users, only: [:new, :create, :show]
end
データベース (Database)
データの永続化を行う具体的なストレージ。
-
MVCでの対応: フレームワークのデータベース設定とO/Rマッパー(ORM)が対応する。
Active Record
やEloquent
を通じて、開発者はSQLを意識せずにデータを操作できる。
結論:両者の思想の違いと橋渡し
CAの各概念は、MVCフレームワークの思想に合わせて、既存のコンポーネントに役割を統合したり、新たなディレクトリ(例: app/services
)を作成したりすることで対応可能である。
- ドメイン層・ユースケース層のロジック → Model と Service層 に集約。
- リポジトリの責務 → Model (Active Record) が兼任。
- プレゼンターの責務 → View/Helper または Decorator/Presenterパターンで対応。