はじめに
こんにちは!any株式会社でエンジニアをしている「しばちゃん @k-shiba-chan 」です!
この記事はanyプロダクトチームAdventCalendar2025、22日目です!
この記事では、レイヤードアーキテクチャについて各層のサンプルコード(TypeScript)を書きながら説明していきます!
きっかけ
プロジェクトのREADME.mdなどを見ると、よく「このプロジェクトでは、レイヤードアーキテクチャを採用しています」みたいに書いてあったりしませんか?
「あー聞いたことはある」「けど、具体的になんだっけ?」みたいな状態から脱却するため、まとめることにしました!
想定読者
- 「レイヤードアーキテクチャって何?」と聞かれて、うまく説明できない方
- アーキテクチャという言葉は聞いたことがあるけど、実際どう使うのかピンときていない方
- 現在レイヤードアーキテクチャに触れ始めたばかりで、各層の役割を整理したい方
なぜアーキテクチャを学ぶ必要があるのか
個人的に新しいプロジェクトに参画したとき、まず理解すべきなのがアーキテクチャ(設計の全体像) だと思っています。
アーキテクチャを知らないと起こること
1. コードを読むのに時間がかかる
- 処理の流れを追う方法が分からない
- 「この機能の実装、どこに書いてあるんだろう?」と迷子になる
例:図書館で料理本を探しているのに、文学コーナーをずっと探しているような状態
2. 実装する場所を間違える
- 見当違いな場所に実装してしまう
- 責務が違う層にコードを書いてしまう
- レビュワーに指摘されて手戻りが発生する
3. チーム開発の効率が悪くなる
- レビュワーの負担が増える
- コードレビューのコメントが増えて、マージまで時間がかかる
レイヤードアーキテクチャを学ぶメリット
レイヤードアーキテクチャの大枠を知っておくことで、以下のメリットがあると考えます。
1. コードを読む効率が上がる
「この処理は大体ドメイン層にあるはず」と見当がつくようになる
2. 適切な場所に実装できる
各層の責務を理解しているので、迷わず実装できる
3. プロジェクト特有のアーキテクチャも理解しやすい
基本を知っていれば、応用版のアーキテクチャも理解しやすい
レイヤードアーキテクチャとは
レイヤードアーキテクチャは、ソフトウェアを役割ごとに層(レイヤー)を分けて整理する設計方法です。
役割ごとに層を分けることで、コードを整理し、保守しやすくします。
具体的には、以下の4つの層に分けて実装します。
- プレゼンテーション層:ユーザーとのやり取り
- アプリケーション層:アプリケーションとしての処理の流れ
- ドメイン層:ビジネスのルールや知識
- インフラ層:データベースや外部サービスとの連携
プレゼンテーション層
プレゼンテーション層は、ユーザーとアプリケーションの接点となる層です。
Webアプリケーションであれば、APIのエンドポイント(Controller)がこの層に該当します。
責務
- リクエストを受け取る
- アプリケーション層(ユースケース)を呼び出す
- レスポンスを返す
// presentation/todo-controller.ts
import { CreateTodoUseCase } from '../application/create-todo.usecase';
export class TodoController {
constructor(private readonly createTodoUseCase: CreateTodoUseCase) {}
async createTodo(requestBody: { title: string }): Promise<{ status: number; body: unknown }> {
const { title } = requestBody;
const todo = await this.createTodoUseCase.execute(title);
return {
status: 201,
body: todo,
};
}
}
アプリケーション層
アプリケーション層は、ユースケース(何をするか)を実現する層です。
「Todoを作成する」「Todoを完了にする」といった、アプリケーションとしての処理の流れを定義します。
責務
- ドメイン層のオブジェクトを組み合わせて使う
- リポジトリを使ってデータを永続化する
- 「何をするか」を定義する(「どうやるか」はドメイン層に任せる)
// application/create-todo.usecase.ts
import { Todo, TodoRepository } from '../domain/todo';
import { randomUUID } from 'crypto';
export class CreateTodoUseCase {
constructor(private readonly todoRepository: TodoRepository) {}
async execute(title: string): Promise<Todo> {
// ID生成
const id = randomUUID();
// ドメイン層の Todo を生成
const todo = new Todo(id, title);
// リポジトリを使って保存
await this.todoRepository.save(todo);
return todo;
}
}
ドメイン層
ドメイン層は、ビジネスのルールや知識を表現する層です。
「Todoのタイトルは必須」「完了済みのTodoは再度完了にできない」といった、ビジネス上の制約やルールを実装します。
責務
- ビジネスルールの実装
- データの整合性を保つ
- 常に正しい状態のオブジェクトのみ存在させる
// domain/todo.ts
export class Todo {
constructor(
public readonly id: string,
private _title: string,
private _completed: boolean = false,
) {
this.validateTitle(_title);
}
get title(): string {
return this._title;
}
get completed(): boolean {
return this._completed;
}
// ビジネスルール:タイトルの検証
private validateTitle(title: string): void {
if (!title || title.trim().length === 0) {
throw new Error('タイトルは必須です');
}
if (title.length > 100) {
throw new Error('タイトルは100文字以内です');
}
}
// ビジネスルール:完了処理
complete(): void {
if (this._completed) {
throw new Error('既に完了しています');
}
this._completed = true;
}
}
// ドメイン層のリポジトリインターフェース
export interface TodoRepository {
save(todo: Todo): Promise<void>;
findById(id: string): Promise<Todo | null>;
}
インフラ層
インフラ層は、データベースや外部サービスとの連携を担当する層です。
ドメイン層で定義されたリポジトリのインターフェースを、実際の技術(PostgreSQL、MySQLなど)を使って実装します。
責務
- データの永続化(保存・取得)
- 外部APIとの通信
- ファイルシステムへのアクセス
// infrastructure/in-memory-todo-repository.ts
import { Todo, TodoRepository } from '../domain/todo';
export class InMemoryTodoRepository implements TodoRepository {
private store = new Map<string, Todo>();
async save(todo: Todo): Promise<void> {
this.store.set(todo.id, todo);
}
async findById(id: string): Promise<Todo | null> {
return this.store.get(id) ?? null;
}
}
まとめ
この記事では、レイヤードアーキテクチャの基本と各層の役割について、TypeScriptのサンプルコードを交えて解説しました!
各層の役割の振り返り
- プレゼンテーション層:リクエスト/レスポンスの入出力
- アプリケーション層:ユースケース(何をするか)の実現
- ドメイン層:ビジネスルール(どうやるか)の実装
- インフラ層:データベースなどの技術的な実装
レイヤードアーキテクチャは、ソフトウェア設計の基礎となる考え方です!
基礎を理解しておくことで、ドメイン駆動設計(DDD) などの、より実践的な設計手法も学びやすくなります!
筆者もこれからドメイン駆動設計について学んでいく予定なので、また記事にまとめていきたいと思います!
最後まで読んでいただき、ありがとうございました!
