Upgrade to Pro — share decks privately, control downloads, hide ads and more …

秩序を保つためのレイヤードアーキテクチャ

 秩序を保つためのレイヤードアーキテクチャ

2025/10/23 BuiLT

Avatar for ふくすけ

ふくすけ

October 23, 2025
Tweet

More Decks by ふくすけ

Other Decks in Programming

Transcript

  1. Expressとは const express = require('express') const app = express() const

    port = 3000 app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) }) https://expressjs.com/ 2025/10/23 | BuiLT 4
  2. MVCフレームワークとの違い Ruby on Rails フルスタックフレームワーク (厚いフレームワーク) 強力な MVC(Model-View-Controller) アーキテクチャ DRY

    (Don't Repeat Yourself), CoC (Convention over Configuration) の原則 「app/models にはモデル」 「app/controllers にはコントローラー」 が最初から決まっている Active Record という強力なORMが組み込まれている Rails Wayという強い思想 Rails Wayがあることでアーキテクチャに悩む必要がない 2025/10/23 | BuiLT 5
  3. Express アンチパターン router.post('/register', async (req: Request, res: Response) => {

    // ① バリデーション const { email, password, age } = req.body; if (!email || password.length < 8) { return res.status(400).json({ message: 'Invalid input' }); } // ④ DB アクセス ( 存在確認) const existingUser = await prisma.user.findUnique({ where: { email } }); if (existingUser) { return res.status(409).json({ message: 'Email exists' }); } // ③ ビジネスロジック const hashedPassword = await bcrypt.hash(password, 10); let initialPoints = 50; if (age < 25) { initialPoints = 100; // キャンペーン適用! } // ... ( さらに複雑なロジックが続く) ... // ④ DB アクセス ( 作成) const newUser = await prisma.user.create({ data: { email, password: hashedPassword, points: initialPoints }, }); // ⑤ 外部連携 ( 副作用) await sendWelcomeEmail(newUser.email); // ① レスポンス res.status(201).json(newUser); }); 2025/10/23 | BuiLT 7
  4. レイヤードアーキテクチャとは データアクセス層 ビジネスロジック層 プレゼンテーション層 User Request Controller Service Repository Database

    1. Controller (Presentation) 層: HTTPリクエスト・レスポンスのみを取り扱う 2. Service (Business Logic) 層: ビジネスロジック (HTTPやDBのことは知らない) 3. Repository (Data Access) 層: データの保存・取得方法だけを知っている 依存は一方向 (Controller → Service → Repository) 2025/10/23 | BuiLT 10
  5. レイヤードアーキテクチャの導入 データアクセス層 ビジネスロジック層 マッパー層 プレゼンテーション層 User Request Router Controller Mapper

    Service Repository Database 1. Router (Presentation) 層: HTTPリクエストのルーティング 2. Controller (Presentation) 層: HTTPリクエスト・レスポンスのみを取り扱う 3. Mapper 層: APIと内部モデルの型変換 4. Service (Business Logic) 層: ビジネスロジック 5. Repository (Data Access) 層: Firestoreへのアクセス 2025/10/23 | BuiLT 12
  6. Router (Presentation) 層 import {Router as createRouter} from "express"; import

    { userController } from "@/controllers/userController"; const router = createRouter(); router.get("/users", userController.index); router.post("/users", userController.create); router.get("/users/:userId", userController.show); router.patch("/users/:userId", userController.update); router.delete("/users/:userId", userController.destroy); ルーティングのみ担当 2025/10/23 | BuiLT 13
  7. Controller (Presentation) 層 import { Request, Response } from "express";

    import { userService } from "@/services/userService"; import { toCreateUserInput, toUserResponse } from "@/mappers/userMapper"; const userController = { async create(req: Request, res: Response) { const createUserInput = toCreateUserInput(req.body); const user = await userService.createUser(createUserInput); res.status(201).json(toUserResponse(user)); }, }; HTTPリクエスト・レスポンスのみ担当 Mapper層を呼び出して型変換を行いService層を呼び出す バリデーションもここで行う 2025/10/23 | BuiLT 14
  8. Mapper 層 (Request Mapper) const toCreateUserInput = (body: CreateUserBody) =>

    { const data: CreateUserInput = { name: body.name.trim(), email: body.email.toLowerCase().trim(), age: body.age, }; return data; }; trimやtoLowerCaseなどの前処理や必要に応じて型変換を行う 2025/10/23 | BuiLT 15
  9. 型変換の例 APIレスポンスの型 id: string; name: string; createdAt: string; // ISO

    文字列 updatedAt: string; // ISO 文字列 内部モデルの型 id: string; name: string; createdAt: Timestamp; // Firestore のTimestamp オブジェクト updatedAt: Timestamp; // Firestore のTimestamp オブジェクト 型変換が必要! 2025/10/23 | BuiLT 16
  10. Mapper 層 (Response Mapper) const toUserResponse = (user: User): UserResponse

    => { return { id: user.id, name: user.name, email: user.email, age: user.age, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt.toISOString(), }; }; APIレスポンス用に型変換を行う 2025/10/23 | BuiLT 17
  11. Service (Business Logic) 層 import { userRepository } from "@/repositories/userRepository";

    import { CreateUserInput } from "@/models/userModel"; const createUser = (input: CreateUserInput) => { // ビジネスロジック const user = await userRepository.create(input); return user; }; ビジネスロジック (権限チェック、外部API連携など) Repository層を呼び出す 2025/10/23 | BuiLT 18
  12. Repository (Data Access) 層 const getUsers = async (): Promise<User[]>

    => { const snapshot = await firestore.collection("users").get(); return snapshot.docs.map(doc => doc.data() as User); }; DB (Firestore) へのアクセス 2025/10/23 | BuiLT 19
  13. ## ディレクトリ構成 src/ ├── controllers/ // コントローラー層 ├── mappers/ //

    データ変換層(request/response の変換) ├── repositories/ // データアクセス層 ├── routes/ // ルーティング層 └── services/ // ビジネスロジック層 2025/10/23 | BuiLT 22
  14. ## レイヤードアーキテクチャ 本システムは、責務を明確に分離したレイヤー構造を採用しています。 Client Request ↓ Express Application ↓ Routes

    Layer ↓ Controllers Layer ↓ Mappers Layer ↓ Services Layer ↓ Repositories Layer ↓ Firestore 2025/10/23 | BuiLT 23
  15. まとめ 薄いフレームワークではアーキテクチャ設計が求められる 巨大なindex.tsを避ける レイヤードアーキテクチャで秩序を保つ 各層の責務が明確 → 保守性向上 テストが容易 → 品質向上

    変更の影響範囲が限定 → 変更容易性向上 アーキテクチャのドキュメンテーションが重要 コーディングエージェントにも正しいアーキテクチャを理解させる 2025/10/23 | BuiLT 24