はじめに
この記事では、Webアプリ設計で迷いがちな
「どの層に何を書かないか」
を軸に層ごとの責務を整理します。
Webアプリケーション開発では、複数の層に分けて設計することが一般的です。
しかし
「どの層に何を書くべきか」
「どこに書かないか」
が曖昧になると、コードが複雑化し保守が困難になります。
層ごとの責務と「書かないこと」をまとめることで、
設計をシンプルに保てるのではと整理してみました。
Webアプリの一般的な層構造
-
プレゼンテーション層(View / ViewModel / Controller)
- UI表示、入力の受け取り、ユーザーとのやり取り
-
ユースケース層(UseCase / Service)
- ビジネスフロー、ユースケース単位の処理
-
アプリケーション層(Model / DTO / Repository)
- データアクセス、DTO変換
-
ドメイン層(Entity / ビジネスルール)
- ビジネスルール、業務ロジック
-
インフラ層(DB / 外部サービス)
- 永続化や外部APIとの通信
簡易図(例)
[View] --> [Controller/UseCase] --> [Domain/Entity] --> [Repository/DB]
各層の関心ごとと「書かないこと」
Webアプリ開発では、各層が明確な責務を持つことで保守性や拡張性が高まります。
ここでは 層ごとの主な関心ごと と 書かないこと(禁止事項) を整理します。
| 層 | 主な関心ごと | 書かないこと |
|---|---|---|
| View | UI表示、ユーザー入力の受け取り | DBアクセス、業務ロジック |
| ViewModel | 表示用データの保持、UIバインディング | DBアクセス、ビジネスルール |
| Controller | 外部入力の受け取り、UseCase呼び出し | UI表示形式、DB詳細 |
| UseCase | ユースケース単位の業務フロー処理 | UIの見た目、低レベルのDB操作 |
| Application | データアクセスの調整、DTO変換 | 業務フロー全体、UI処理 |
| Model (DTO / ViewModel用データ) | データ構造の定義、転送用データ表現 | 業務ロジック、外部アクセス |
| Domain / Entity | ビジネスルール、業務ロジック | UIやDBアクセスの詳細 |
| Infrastructure | 永続化、外部API通信 | UIロジック、業務ルール |
注意
プロジェクトによって層の分け方は異なるため、ここで示したルールは一例です。
チームやプロジェクトの状況に合わせて柔軟に適用してください。
難しさと注意点
層を詳細に分割することで責務は明確になりますが、
同時に以下の難しさや注意点も存在します。
-
小規模プロジェクトでは過剰設計になりやすい
層を細かく分けすぎると、コード量が増えて学習コストも高まります。
シンプルな構造の方が効率的な場合もあります。 -
チームの理解度が必要
ControllerとUseCaseを分けても、
チームメンバーが「どちらに書くべきか」を理解していないと逆に混乱します。
共通認識を持つことが重要です。 -
プロジェクトの性質に依存
大規模・長期的な開発では分割のメリットが大きいですが、
小規模・短期開発ではシンプルな構造の方が効率的です。
補足説明
-
View
- 画面に表示するデータや入力の受け取りに集中。
- ビジネスロジックやDB操作は書かない
-
ViewModel
- 表示用データを保持し、UIとバインディングする役割。
- DBアクセスやビジネスルールは関心外
-
Controller
- ユーザー操作やリクエストを受け取り、適切なUseCaseを呼び出す。
- UIの見た目やDBの詳細操作は扱わない
-
UseCase
- ユースケース単位の業務フローを実行。
- DomainやRepositoryを呼び出して処理を進める。
- UIや低レベルのDB操作は関心外
-
Application
- データアクセスや外部サービスとのやり取りを調整。
- DTO変換などを担当。
- 業務全体のフローやUI処理は書かない
-
Model (DTO / ViewModel用データ)
- データ構造を定義し、転送や表示用に利用。
- 業務ロジックや外部アクセスは含めない
-
Domain / Entity
- ビジネスルールや業務ロジックを中心に設計。
- UIやDBの具体的な操作は扱わない
-
Infrastructure
- DBや外部サービスとのやり取りを担当。
- ビジネスルールやUIロジックは書かない
ありがちな失敗例と改善例
NG例: ViewModelでDBアクセス
// NG: ViewModelで直接DBアクセス
public class ProductViewModel {
public List<Product> Products { get; set; }
public void Load() {
using(var db = new AppDbContext()) {
Products = db.Products.ToList();
}
}
}
OK例: Repository + UseCase
// Repository
public class ProductRepository {
public List<Product> GetAll() {
using(var db = new AppDbContext()) {
return db.Products.ToList();
}
}
}
// UseCase
public class ProductUseCase {
private readonly ProductRepository _repo;
public ProductUseCase(ProductRepository repo) {
_repo = repo;
}
public List<Product> GetProducts() {
return _repo.GetAll();
}
}
// Controller
public class ProductController {
private readonly ProductUseCase _useCase;
public ProductController(ProductUseCase useCase) {
_useCase = useCase;
}
public IActionResult Index() {
var products = _useCase.GetProducts();
return View(products);
}
}
関心の分離を意識するメリット
層ごとの責務を明確にし、
「書かないこと」を決めることで得られるメリットは多くあります。
-
保守性の向上
各層の責務が明確になるため、修正や追加の影響範囲を限定できる -
テスト容易性の向上
層ごとに独立した責務を持つことで、単体テストやモック化がしやすくなる -
複雑化しても責務が明確
大規模化しても「どこに何を書くか」が整理されているため、コードが混乱しにくい -
開発者間での誤解防止
チームメンバー間で「この層には何を書かないか」を共有することで、
設計方針のズレを減らせる -
拡張性の確保
小規模〜中規模の開発でも、将来的な機能追加やリファクタリングに対応しやすい
まとめ
関心の分離を意識することで、設計の迷いが減り、
チーム全体の開発効率や品質が向上します。
「この層には書かないこと」を決めることは、シンプルですが強力なルールです。