GoF以外のプログラミング・デザインパターン #05 Layered Architecture

2025/11/26

プログラミング 学習

t f B! P L
eyecatch 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構造で構築するなど組み合わせの工夫をすると、効果的ですね。 でも、デメリットで挙げた、レイヤー間をまたぐ処理が多い場合は、依存性が増して、その後の更新などの作業が効率悪くなる可能性も高いので、こうした点に注意するというのがポイントですね。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ