MVC + Service + Repositoryを復習
業務でto B向けの商品卸システムをLaravelで構築することになったのでざっと復習しました。
最後にLaravelに触れたのはもう半年近く前、最近はDockerばかりやってました。
今回まとめたのはMVCではなく、MVC + Service + Repositoryというアーキテクチャです。
MVCだとどうしてもControllerにビジネスロジックが集まってしまい、可読性が…。
そう思い、MVC + Service + Repository構成を私は好んで採用しています。
では早速見ていきましょう。
Controller
リクエストの受付とレスポンスの返却
クライアントからのHTTPリクエスト(GET、POSTなど)を受け取ります。
リクエストに含まれるパラメータやヘッダー情報を取得します。
Service や Repository を呼び出して必要な処理を実行し、結果を取得します。
取得した結果に基づいて、適切なレスポンス(HTML、JSONなど)を生成し、クライアントに返します。
Serviceの呼び出しと連携
ビジネスロジックを実行するために、Service を呼び出します。
Service から受け取った結果を、View に渡したり、レスポンスとして整形したりします。
必要に応じて、複数の Service を連携させて複雑な処理を実現します。
Viewの選択とデータの受け渡し
レスポンスとして表示する View を選択します。
Service から受け取ったデータを View に渡します。
View が必要な情報を Service から取得できるように、必要な情報を Controller 経由で渡します。
認証
認証ミドルウェアを利用して、リクエストの認証を行います。
認可が必要な場合は、Service を呼び出して認可処理を行います。
認証・認可の結果に基づいて、適切なレスポンスを返したり、View を選択したりします。
フォームリクエストの処理
フォームリクエストを利用して、リクエストパラメータのバリデーションを行います。
バリデーションエラーがあれば、エラーメッセージとともに元の画面にリダイレクトします。
Controllerがしないこと
ビジネスロジックの実装
ビジネスロジックは Service に委譲します。
Controller はビジネスロジックの内容を知らなくても動作するように設計します。
データベースへの直接アクセス
データベースへのアクセスは Repository に委譲します。
Controller はデータベースの構造やクエリを知らなくても動作するように設計します。
複雑な計算処理
複雑な計算処理は Service に委譲します。
Controller は計算処理の内容を知らなくても動作するように設計します。
例:サンプルコード
// app/Http/Controllers/ProductController.php
namespace App\\Http/Controllers;
use App\\Services\\ProductService;
use Illuminate\\Http\\Request;
class ProductController extends Controller
{
public function __construct(protected ProductService $productService)
{}
public function index()
{
$products = $this->productService->getAllProducts();
return view('products.index', ['products' => $products]);
}
public function show(Request $request, $id)
{
$product = $this->productService->getProductById($id);
return view('products.show', ['product' => $product]);
}
// ... (他のアクション)
}
Service
ビジネスロジックの実装
アプリケーション固有のビジネスルールや処理フローを実装します。
複数のモデルにまたがる処理や、複雑な計算処理などを担当します。
データベースのトランザクション管理が必要な場合は、Service 内でトランザクションを制御します。
Repository の呼び出しと連携
データベースとのやり取りが必要な場合は、Repository を呼び出します。
Repository から受け取ったデータを加工したり、複数の Repository を組み合わせて複雑なデータ処理を行ったりします。
バリデーション
ユーザーからの入力値や、外部API から取得したデータなどを検証します。
Laravel のバリデーション機能やフォームリクエストを活用して、効率的にバリデーションを実装します。
外部API連携
外部サービスとの連携が必要な場合は、API リクエストの送信、レスポンスの解析、エラーハンドリングなどを Service 内で行います。
通知・メール送信
ユーザーへの通知やメール送信処理を行います。
Laravel の通知機能やメール送信機能を活用して、効率的に実装します。
その他の処理
計算処理、ファイル操作、画像処理、PDF 生成、検索処理、タスクスケジューリング、イベント処理、キュー処理、WebSocket 通信など、アプリケーションに必要な様々な処理を Service 内で実装します。
Service がしないこと
リクエストの受付とレスポンスの返却
これらの処理は Controller の責務です。
Service は HTTP リクエストやレスポンスを直接扱うことはありません。
View の選択とデータの受け渡し
これらの処理も Controller の責務です。
Service は View の内容を知らなくても動作するように設計します。
例:サンプルコード
// app/Services/ProductService.php
namespace App\\Services;
use App\\Repositories\\ProductRepository;
class ProductService
{
public function __construct(protected ProductRepository $productRepository)
{}
public function getAllProducts()
{
return $this->productRepository->getAllProducts();
}
public function getProductById($id)
{
return $this->productRepository->getProductById($id);
}
public function createProduct(array $data)
{
// バリデーション
// ...
return $this->productRepository->createProduct($data);
}
// ... (他のメソッド)
}
Repository
データベースアクセスの抽象化
Eloquent ORM やクエリビルダを使用して、データベースに対する CRUD (Create, Read, Update, Delete) 操作を行います。
データベースの具体的な実装(MySQL、PostgreSQL など)を意識することなく、統一的なインターフェースでデータにアクセスできます。
データベースの変更があった場合でも、Repository のインターフェースを変更するだけで、Service や Controller のコードに影響を与えずに対応できます。
データ取得
Service から受け取った条件に基づいて、データベースから必要なデータを検索・取得します。
Eloquent のリレーションを利用して、関連するデータもまとめて取得できます。
必要に応じて、取得したデータを加工・整形してから Service に返します。
データ保存・更新・削除
Service から受け取ったデータをデータベースに保存、更新、または削除します。
トランザクション処理が必要な場合は、Repository 内でトランザクションを制御します。
データの存在確認
指定された条件に合致するデータがデータベースに存在するかどうかを確認します。
主キーやユニーク制約を利用して、データの一意性を保証します。
Repository がしないこと
ビジネスロジックの実装
ビジネスロジックは Service の責務です。
Repository はデータの取得・保存・更新・削除といった基本的な操作のみを行います。
バリデーション
バリデーションは Service の責務です。
Repository は受け取ったデータの妥当性をチェックしません。
複雑な計算処理
複雑な計算処理は Service の責務です。
Repository はデータの取得・保存・更新・削除といった基本的な操作のみを行います。
例:サンプルコード
// app/Repositories/ProductRepository.php
namespace App\\Repositories;
use App\\Models\\Product;
class ProductRepository
{
public function getAllProducts()
{
return Product::query()
->where('status', 'published')
->orderBy('created_at', 'desc')
->get();
}
public function getProductById($id)
{
return Product::findOrFail($id);
}
public function createProduct(array $data)
{
return Product::create($data);
}
// ... (他のメソッド)
}
Model
データ構造の表現
データベーステーブルの各カラムに対応するプロパティを持ちます。
テーブル間のリレーションシップを定義します(belongsTo, hasMany, belongsToMany など)。
アクセサやミューテタを使って、データの取得や設定を制御します。
データベース操作
Eloquent ORM のメソッドを使用して、データベースに対する CRUD 操作(Create, Read, Update, Delete)を行います。
save(), update(), delete() などのメソッドで、簡単にデータを保存、更新、削除できます。
where(), orderBy(), limit() などのメソッドで、柔軟なクエリを構築できます。
バリデーションルール定義 (一部)
データベースに保存する前に、データのバリデーションルールを定義できます。
$rules
プロパティや$casts
プロパティを使って、バリデーションルールやデータ型を指定します。
バリデーションの実行自体は Service が担当しますが、Model にバリデーションルールを定義することで、データの整合性を保ちやすくなります。
ビジネスロジック (一部)
モデルに関連するシンプルなビジネスロジックは、Model 内に実装することもあります。
例えば、商品の価格計算や、ユーザーのポイント計算など、特定のモデルに強く依存するロジックは、Model 内に記述することがあります。
ただし、複雑なビジネスロジックや、複数のモデルにまたがるロジックは、Service に実装する方が適切です。
Model がしないこと
リクエストの受付とレスポンスの返却
これらの処理は Controller の責務です。
Model は HTTP リクエストやレスポンスを直接扱うことはありません。
View の選択とデータの受け渡し
これらの処理も Controller の責務です。
Model は View の内容を知らなくても動作するように設計します。
複雑なビジネスロジックの実装
複雑なビジネスロジックは Service の責務です。
Model はデータの構造と基本的な操作に集中します。
例:サンプルコード
// app/Models/Product.php
namespace App\\Models;
use Illuminate\\Database\\Eloquent\\Model;
class Product extends Model
{
protected $fillable = ['name', 'description', 'price', 'status'];
public function category()
{
return $this->belongsTo(Category::class);
}
public function reviews()
{
return $this->hasMany(Review::class);
}
// ... (他のメソッド)
}
View
データの表示
Controller から受け取ったデータを、HTML タグやテンプレートエンジン(Blade など)を使って整形し、表示します。
リスト表示、詳細表示、フォームなど、様々な形式でデータを表示できます。
必要に応じて、JavaScript を使用して動的な表示やインタラクションを追加します。
ユーザー入力の受付
フォーム要素(input、textarea、select など)を使って、ユーザーからの入力を受け付けます。
入力されたデータは Controller に送信され、バリデーションや保存処理が行われます。
部分テンプレートの利用
ヘッダー、フッター、サイドバーなど、共通で使用する部分を部分テンプレートとして定義し、複数の View で再利用できます。
これにより、コードの重複を減らし、保守性を高めることができます。
コンポーネントの利用
Laravel の Blade コンポーネント機能を利用して、再利用可能な UI コンポーネントを作成できます。
コンポーネントは、独自のロジックやスタイルを持つことができ、View の構造を整理し、可読性を向上させることができます。
View がしないこと
ビジネスロジックの実装
ビジネスロジックは Service の責務です。
View はデータの表示に集中し、ビジネスロジックを含まないようにします。
データベースへの直接アクセス
データベースへのアクセスは Repository の責務です。
View はデータベースの構造やクエリを知らなくても動作するように設計します。
複雑な計算処理
複雑な計算処理は Service の責務です。
View は計算処理の結果を受け取り、表示するだけです。
例:サンプルコード
{{-- resources/views/products/index.blade.php --}}
@extends('layouts.app')
@section('content')
<h1>商品一覧</h1>
<ul>
@foreach ($products as $product)
<li>
<a href="{{ route('products.show', $product->id) }}">{{ $product->name }}</a>
- {{ $product->price }}円
</li>
@endforeach
</ul>
@endsection
最後に
ということで、それぞれが担当すべき責務についてまとめてみましたが如何でしょうか?
MVC + Service + Repository のアーキテクチャを採用することで、コードの可読性や保守性が向上し、開発効率が向上することが期待できるのではと思います。