企业级分层架构

构建可扩展的企业级应用架构,实现代码分层与模块化设计。

📚 学习目标

学完这篇文章后,你将能够:

  • 理解三层架构的设计理念
  • 掌握本项目的架构实现
  • 学会分离关注点(Separation of Concerns)
  • 了解各层的职责和最佳实践
  • 掌握代码组织原则

前置知识

在开始学习之前,建议先阅读:

你需要了解:

  • 模块化编程概念
  • SOLID 原则(可选)

1️⃣ 三层架构概述

1.1 什么是三层架构?

三层架构是一种软件设计模式,将应用程序分为三个逻辑层:

1.2 各层职责

层级目录职责示例
表现层app/api/处理 HTTP 请求/响应验证请求格式、返回响应
业务逻辑层app/services/业务逻辑、数据验证会话创建、聊天处理
数据访问层app/database/CRUD 操作插入、更新、删除

1.3 架构优势

优势

  • 📦 职责清晰,易于维护
  • 🧪 每层可独立测试
  • 🔄 代码复用性高
  • 🚀 团队协作效率高

劣势

  • 📝 初期代码量较多
  • ⏱️ 简单场景可能过度设计

2️⃣ 本项目的三层架构

2.1 架构图

2.2 目录结构

app/
├── api/                        # 表现层(API Routes)
│   ├── chat/
│   │   ├── route.ts           # 聊天 API
│   │   └── sessions/
│   │       └── route.ts       # 会话管理 API
│   └── artifacts/
│       └── [id]/
│           └── route.ts       # 工件 API

├── services/                   # 业务逻辑层
│   ├── chat.service.ts        # 聊天服务
│   ├── session.service.ts     # 会话服务
│   ├── artifact.service.ts    # 工件服务
│   └── README.md              # 服务层文档 ([app/services/README.md](https://github.com/loock-ai/langgraphjs-chat-app/blob/main/app/services/README.md))

└── database/                   # 数据访问层
    ├── sessions.ts            # 会话数据操作
    ├── artifacts.ts           # 工件数据操作
    └── messages.ts            # 消息数据操作

3️⃣ 表现层(Routes)

3.1 职责

表现层负责:

  • ✅ 接收 HTTP 请求
  • ✅ 验证请求格式
  • ✅ 调用服务层
  • ✅ 返回 HTTP 响应
  • ✅ 处理错误和异常

3.2 示例:会话管理 API

查看实现:app/api/chat/sessions/route.ts

import { NextRequest, NextResponse } from 'next/server';
import { sessionService } from '@/app/services';
import { withAuth } from '@/app/middleware/auth';
import type { SessionType } from '@/app/database/sessions';

/**
 * GET /api/chat/sessions
 * 获取当前用户的所有会话列表
 * 支持按 type 过滤: ?type=chat|deepresearch
 */
export const GET = withAuth(async (request: NextRequest, auth) => {
  try {
    const { searchParams } = new URL(request.url);
    const type = searchParams.get('type') as SessionType | undefined;

    // 调用服务层
    const sessions = await sessionService.getAllSessions(auth.client, type);
    return NextResponse.json({ sessions });
  } catch (e) {
    return NextResponse.json(
      { error: '获取会话列表失败', detail: String(e) },
      { status: 500 },
    );
  }
});

/**
 * POST /api/chat/sessions
 * 创建新会话
 */
export const POST = withAuth(async (request: NextRequest, auth) => {
  try {
    const { name, type = 'chat' } = await request.json();

    // 调用服务层
    const result = await sessionService.createSession(
      { name, type },
      auth.user.id,
      auth.client,
    );
    return NextResponse.json(result);
  } catch (e) {
    return NextResponse.json(
      { error: '新建会话失败', detail: String(e) },
      { status: 500 },
    );
  }
});

/**
 * DELETE /api/chat/sessions
 * 删除会话
 */
export const DELETE = withAuth(async (request: NextRequest, auth) => {
  try {
    const { id } = await request.json();

    if (!id) {
      return NextResponse.json({ error: '缺少 id' }, { status: 400 });
    }

    // 调用服务层
    await sessionService.deleteSession({ id });
    return NextResponse.json({ success: true });
  } catch (e) {
    return NextResponse.json(
      { error: '删除会话失败', detail: String(e) },
      { status: 500 },
    );
  }
});

/**
 * PATCH /api/chat/sessions
 * 重命名会话
 */
export const PATCH = withAuth(async (request: NextRequest, auth) => {
  try {
    const { id, name } = await request.json();

    if (!id || !name) {
      return NextResponse.json({ error: '缺少参数' }, { status: 400 });
    }

    // 调用服务层
    await sessionService.updateSessionName({ id, name });
    return NextResponse.json({ success: true });
  } catch (e) {
    return NextResponse.json(
      { error: '更新会话失败', detail: String(e) },
      { status: 500 },
    );
  }
});

代码分析

  • ✅ 只处理 HTTP 相关逻辑
  • ✅ 参数验证(如 id、name)
  • ✅ 错误捕获和统一响应
  • ✅ 调用服务层,不直接操作数据库

4️⃣ 业务逻辑层(Services)

4.1 职责

业务逻辑层负责:

  • ✅ 业务规则和逻辑
  • ✅ 数据验证
  • ✅ 调用数据访问层
  • ✅ 事务管理
  • ✅ 缓存处理

4.2 示例:会话服务

查看实现:app/services/session.service.ts

import * as sessionDb from '@/app/database/sessions';
import type { Session, SessionType } from '@/app/database/sessions';

/**
 * 会话服务
 * 负责会话的业务逻辑和验证
 */
export class SessionService {
  /**
   * 获取用户的所有会话
   */
  async getAllSessions(client: any, type?: SessionType): Promise<Session[]> {
    // 业务逻辑:过滤会话类型
    return await sessionDb.findSessions(client, type);
  }

  /**
   * 创建新会话
   */
  async createSession(
    data: { name?: string; type: SessionType },
    userId: string,
    client: any,
  ): Promise<Session> {
    // 业务逻辑:设置默认值
    const name = data.name || (data.type === 'chat' ? '新对话' : '新研究');

    // 业务逻辑:验证
    if (!userId) {
      throw new Error('用户 ID 不能为空');
    }

    // 调用数据层
    return await sessionDb.createSession(
      {
        name,
        type: data.type,
        user_id: userId,
      },
      client,
    );
  }

  /**
   * 删除会话
   */
  async deleteSession({ id }: { id: string }): Promise<void> {
    // 业务逻辑:验证 ID
    if (!id) {
      throw new Error('会话 ID 不能为空');
    }

    // 调用数据层
    await sessionDb.deleteSession(id);
  }

  /**
   * 更新会话名称
   */
  async updateSessionName({
    id,
    name,
  }: {
    id: string;
    name: string;
  }): Promise<void> {
    // 业务逻辑:验证参数
    if (!id || !name) {
      throw new Error('ID 和名称不能为空');
    }

    // 业务逻辑:名称长度验证
    if (name.length > 100) {
      throw new Error('名称不能超过 100 个字符');
    }

    // 调用数据层
    await sessionDb.updateSession({ id, name });
  }
}

// 导出单例
export const sessionService = new SessionService();

代码分析

  • ✅ 业务规则(默认名称、长度验证)
  • ✅ 参数验证(userId、name)
  • ✅ 调用数据层
  • ✅ 导出单例模式

5️⃣ 数据访问层(Database)

5.1 职责

数据访问层负责:

  • ✅ CRUD 操作
  • ✅ 数据库查询
  • ✅ 数据格式转换
  • ❌ 不包含业务逻辑
  • ❌ 不包含验证逻辑

5.2 示例:会话数据操作

查看实现:app/database/sessions.ts

import type { Session, SessionType } from '@/app/database/sessions';

/**
 * 查询所有会话
 */
export async function findSessions(
  client: any,
  type?: SessionType,
): Promise<Session[]> {
  let query = client
    .from('sessions')
    .select('*')
    .order('created_at', { ascending: false });

  if (type) {
    query = query.eq('type', type);
  }

  const { data, error } = await query;

  if (error) throw error;
  return data || [];
}

/**
 * 创建会话
 */
export async function createSession(
  data: {
    name: string;
    type: SessionType;
    user_id: string;
  },
  client: any,
): Promise<Session> {
  const { data: session, error } = await client
    .from('sessions')
    .insert({
      name: data.name,
      type: data.type,
      user_id: data.user_id,
    })
    .select()
    .single();

  if (error) throw error;
  return session;
}

/**
 * 删除会话
 */
export async function deleteSession(id: string, client: any): Promise<void> {
  const { error } = await client.from('sessions').delete().eq('id', id);

  if (error) throw error;
}

/**
 * 更新会话
 */
export async function updateSession(
  { id, name }: { id: string; name: string },
  client: any,
): Promise<void> {
  const { error } = await client.from('sessions').update({ name }).eq('id', id);

  if (error) throw error;
}

代码分析

  • ✅ 纯 CRUD 操作
  • ✅ 使用 Supabase Client
  • ✅ 返回数据库格式
  • ❌ 无业务逻辑
  • ❌ 无参数验证

6️⃣ 架构原则与最佳实践

6.1 硬性规则

根据项目文档 AGENTS.md

Routes (app/api/**/route.ts)
    ↓ calls
Services (app/services/*.service.ts)
    ↓ calls
Database (app/database/*.ts)

绝对禁止

  • ❌ Route 直接调用 Database(跳过 Service 层)
  • ❌ Service 层没有业务逻辑(直接透传)
  • ❌ Database 层包含业务逻辑

示例 - 错误

// ❌ 错误:Route 直接调用 Database
export const POST = withAuth(async (request, auth) => {
  const { name } = await request.json();

  // 直接操作数据库
  const { data } = await auth.client
    .from('sessions')
    .insert({ name, user_id: auth.user.id });

  return NextResponse.json({ session: data });
});

示例 - 正确

// ✅ 正确:Route → Service → Database
export const POST = withAuth(async (request, auth) => {
  const { name } = await request.json();

  // 调用服务层
  const session = await sessionService.createSession(
    { name },
    auth.user.id,
    auth.client,
  );

  return NextResponse.json({ session });
});

6.2 职责分离

层级职责示例
RoutesHTTP 处理解析请求、返回响应
Services业务逻辑验证、规则、事务
Database数据操作CRUD 查询

6.3 代码组织

命名规范

// Services: *.service.ts
chat.service.ts;
session.service.ts;
artifact.service.ts;

// Database: *.ts(无后缀)
sessions.ts;
artifacts.ts;
messages.ts;

// Routes: route.ts
app / api / chat / route.ts;
app / api / sessions / route.ts;

导出模式

// Services: 单例模式
export const sessionService = new SessionService();

// Database: 导出函数
export async function createSession(data, client) { ... }
export async function findSessions(client) { ... }

7️⃣ 实战案例:完整的 CRUD 流程

7.1 需求

创建一个任务管理功能:

  • GET /api/tasks - 获取任务列表
  • POST /api/tasks - 创建任务
  • PUT /api/tasks/[id] - 更新任务
  • DELETE /api/tasks/[id] - 删除任务

7.2 实现

Routes 层

// app/api/tasks/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withAuth } from '@/app/middleware/auth';
import { taskService } from '@/app/services';

export const GET = withAuth(async (request, auth) => {
  try {
    const tasks = await taskService.getAllTasks(auth.client);
    return NextResponse.json({ tasks });
  } catch (e) {
    return NextResponse.json({ error: '获取任务失败' }, { status: 500 });
  }
});

export const POST = withAuth(async (request, auth) => {
  try {
    const { title, description } = await request.json();

    if (!title) {
      return NextResponse.json({ error: '标题不能为空' }, { status: 400 });
    }

    const task = await taskService.createTask(
      { title, description },
      auth.user.id,
      auth.client,
    );

    return NextResponse.json({ task });
  } catch (e) {
    return NextResponse.json({ error: '创建任务失败' }, { status: 500 });
  }
});

Services 层

// app/services/task.service.ts
import * as taskDb from '@/app/database/tasks';

export class TaskService {
  async getAllTasks(client: any) {
    return await taskDb.findTasks(client);
  }

  async createTask(
    data: { title: string; description?: string },
    userId: string,
    client: any,
  ) {
    if (!userId) {
      throw new Error('用户 ID 不能为空');
    }

    return await taskDb.createTask(
      {
        title: data.title,
        description: data.description,
        user_id: userId,
      },
      client,
    );
  }

  async updateTask(id: string, data: Partial<Task>, client: any) {
    if (!id) {
      throw new Error('任务 ID 不能为空');
    }

    return await taskDb.updateTask(id, data, client);
  }

  async deleteTask(id: string, client: any) {
    if (!id) {
      throw new Error('任务 ID 不能为空');
    }

    await taskDb.deleteTask(id, client);
  }
}

export const taskService = new TaskService();

Database 层

// app/database/tasks.ts
export async function findTasks(client: any) {
  const { data, error } = await client
    .from('tasks')
    .select('*')
    .order('created_at', { ascending: false });

  if (error) throw error;
  return data || [];
}

export async function createTask(data: any, client: any) {
  const { data: task, error } = await client
    .from('tasks')
    .insert(data)
    .select()
    .single();

  if (error) throw error;
  return task;
}

export async function updateTask(id: string, data: any, client: any) {
  const { error } = await client.from('tasks').update(data).eq('id', id);

  if (error) throw error;
}

export async function deleteTask(id: string, client: any) {
  const { error } = await client.from('tasks').delete().eq('id', id);

  if (error) throw error;
}

💡 练习题

  1. 选择题:三层架构中,业务逻辑层应该在哪个目录?

    • A. app/api/
    • B. app/services/
    • C. app/database/
    • D. app/components/
  2. 分析题:查看本项目的 app/api/chat/sessions/route.ts,说明它如何遵循三层架构。

  3. 代码题:指出以下代码的问题并修正:

    export const POST = withAuth(async (request, auth) => {
      const { name } = await request.json();
      const { data } = await auth.client.from('sessions').insert({ name });
      return NextResponse.json({ session: data });
    });
  4. 实践题:实现一个完整的博客系统(文章、评论、标签),遵循三层架构。


📚 参考资源

项目文档

相关文件


✅ 总结

三层架构

  • Routes(表现层)- HTTP 处理
  • Services(业务逻辑层)- 业务规则
  • Database(数据访问层)- CRUD 操作

硬性规则

  • ✅ Route → Service → Database
  • ❌ Route 直接调用 Database
  • ❌ Service 没有业务逻辑
  • ❌ Database 包含业务逻辑

职责分离

  • Routes: HTTP 请求/响应
  • Services: 验证、规则、事务
  • Database: CRUD 查询

最佳实践

  • 单例模式导出 Services
  • 函数导出 Database 操作
  • 统一的错误处理
  • 清晰的命名规范

下一步:阅读最后一篇文章《完整项目功能复盘》,总结整个项目的技术要点。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page