
Layered Architecture(レイヤードアーキテクチャ)は、システムを機能ごとに階層(Layer)で分割して構築する設計思想です。
各レイヤーは責務(Responsibility)を限定し、上位層が下位層に依存する構造をとります。
ソフトウェアの保守性・拡張性・再利用性を高める代表的なアーキテクチャパターンです。
レイヤー構造
一般的なレイヤー構成(4層モデル)
Presentation Layer(表示層)
・ユーザーとの入出力(UI)を担当する層。
・画面、APIレスポンス、CLI出力など。
・下位層にビジネスロジックを直接書かない。
↓
Application Layer(アプリケーション層)
・ユースケース単位の処理フローを制御。
・「何を・どの順で行うか」を定義する。
・ドメイン層を呼び出して業務処理を実行。
↓
Domain Layer(ドメイン/ビジネスロジック層)
・システムの中核的なビジネスルールを表現。
・エンティティ、値オブジェクト、ドメインサービスを定義。
・他レイヤーに依存しない(独立性が最も高い)。
↓
Infrastructure Layer(インフラ層)
・DB、ファイル、ネットワークなど外部との接続部分を扱う。
・リポジトリ(Repository)や外部APIアクセスを実装。
・上位層にはインターフェースを介して提供。
サンプルコード
構成イメージ
📦 project/
┣ 📂 presentation/
┃ UI層(APIや画面)
┣ 📂 application/
┃ アプリケーション層(ユースケース制御)
┣ 📂 domain/
┃ ドメイン層(ビジネスルール)
┗ 📂 infrastructure/
インフラ層(データアクセス・外部連携)
presentation/todoController.js
HTTP入出力・APIハンドラ
// Presentation Layer
import { AddTodoUseCase, GetTodosUseCase } from "../application/todoUseCases.js";
export class TodoController {
constructor(todoRepository) {
this.addTodoUseCase = new AddTodoUseCase(todoRepository);
this.getTodosUseCase = new GetTodosUseCase(todoRepository);
}
async addTodo(req, res) {
const { title } = req.body;
await this.addTodoUseCase.execute(title);
res.json({ message: "Todo added successfully" });
}
async getTodos(req, res) {
const todos = await this.getTodosUseCase.execute();
res.json(todos);
}
}
application/todoUseCases.js
処理の流れを制御
// Application Layer
import { Todo } from "../domain/todo.js";
export class AddTodoUseCase {
constructor(todoRepository) {
this.todoRepository = todoRepository;
}
async execute(title) {
const todo = new Todo(title);
await this.todoRepository.save(todo);
}
}
export class GetTodosUseCase {
constructor(todoRepository) {
this.todoRepository = todoRepository;
}
async execute() {
return await this.todoRepository.findAll();
}
}
domain/todo.js
ビジネスルール(Todoの仕様)
// Domain Layer
export class Todo {
constructor(title) {
if (!title || title.trim() === "") {
throw new Error("Title is required");
}
this.title = title;
this.done = false;
}
toggle() {
this.done = !this.done;
}
}
infrastructure/todoRepository.js
データアクセス実装
// Infrastructure Layer
export class InMemoryTodoRepository {
constructor() {
this.todos = [];
}
async save(todo) {
this.todos.push(todo);
}
async findAll() {
return this.todos;
}
}
実行
import express from "express";
import { TodoController } from "./presentation/todoController.js";
import { InMemoryTodoRepository } from "./infrastructure/todoRepository.js";
const app = express();
app.use(express.json());
const todoRepository = new InMemoryTodoRepository();
const todoController = new TodoController(todoRepository);
app.post("/todos", (req, res) => todoController.addTodo(req, res));
app.get("/todos", (req, res) => todoController.getTodos(req, res));
app.listen(3000, () => console.log("Server running on http://localhost:3000"));
依存関係の原則
・依存の方向は上から下のみ(下位層→上位層は依存しない)。
・つまり「UI → アプリケーション → ドメイン → インフラ」の一方向。
・変更の影響範囲を限定できる。
メリット
・責務分離が明確で、各層の修正が他に波及しにくい。
・テストしやすく、モック化もしやすい。
・新しいUIやDBへの変更が容易。
デメリット
・層の数が多くなるとコード量・依存管理が複雑になる。
・単純なアプリにはオーバーエンジニアリングになることも。
・層をまたぐ処理が多いとパフォーマンスコストが増加。
類似・派生アーキテクチャ
| アーキテクチャ |
特徴 |
| Clean Architecture |
依存方向をドメイン中心に整理した拡張版 |
| Hexagonal (Ports & Adapters) |
外部I/Oをポート・アダプタで抽象化 |
| Onion Architecture |
ドメイン層を中心に円環構造で依存逆転 |
あとがき
機能ごとのレイヤー分けをするのが、MVCとは少し違う感覚になるのがわかります。
それぞれのレイヤー分けは、標準的ですが、ボリュームにそれぞれ大きな差が出てくるので、
一番膨れそうなドメインレイヤーの中をMVC構造で構築するなど組み合わせの工夫をすると、効果的ですね。
でも、デメリットで挙げた、レイヤー間をまたぐ処理が多い場合は、依存性が増して、その後の更新などの作業が効率悪くなる可能性も高いので、こうした点に注意するというのがポイントですね。
0 件のコメント:
コメントを投稿