全栈必知:从小单体到大系统,模块怎么长出来的?

软件架构的进化史,就是和「复杂度」相爱相杀的过程!

  • 萌新期:就一个人单干,业务超简单,能跑起来就算成功!
  • 成长期:业务越来越复杂,团队也人挤人,主打一个拒绝重复和混乱
  • 大佬期:高并发、强安全一个不少,必须把模块拆得明明白白,还要有超强的扩展能力

业务刚开始,"能用" 才是王道;等业务体量上来了,"有序" 才是 YYDS!模块设计的精细度和职责划分,也得跟着一路升级。

本文将沿着 "小型单体→中型应用→大型微服务" 的路径,结合不同规模项目的必备模块与可选模块,解析模块设计如何逐步适应不同阶段的需求。

划重点:本文模块名称参考 NestJS、Spring Boot、Express 这些常见后端框架,按职责分层,方便不同技术栈的小伙伴都能看懂~

先统一「语言」:模块分层到底是啥?

不同框架(NestJS/Spring Boot/Express)的模块命名可能不一样,但职责层次相通。先明确这几个核心模块,避免后面聊懵:

层次 核心模块 通俗理解
请求入口层 Controller + Middleware/Interceptor 「前台接待」:接请求、做通用处理(日志 / 鉴权)
业务逻辑层 Service + Guard + Pipe/DTO 「后厨加工」:核心逻辑、权限判断、数据校验
数据访问层 Model/DAO + ORM 「仓库管理」:定义数据格式、操作数据库
横切支持层 Logger/Config/Cache/Queue 等 「后勤保障」:日志、配置、缓存、异步任务等

有了这个「分层字典」,后面聊不同阶段的模块设计就清晰多了。

一、小型单体:用最少的模块「跑起来」再说

典型场景:个人博客(文章 CRUD + 登录)、小商城(商品 + 下单)、数据看板(读库展示)

特点:2 张表撑全场,1-2 人开发,CRUD 为主,能跑就行

必备模块:只留「核心链路」

  1. Controller:请求「接收站」

比如用户点「登录」,它接住username和password,直接扔给 Service,自己不做复杂处理。

javascript 复制代码
// 伪代码:极简Controller
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const result = userService.login(username, password); // 直接甩给Service
  res.send(result);
});
  1. Service:业务「加工车间」

只关心「按规则处理数据」,不管数据从哪来、往哪去。例如,在一个电商订单处理系统中,订单计算折扣的 Service 伪代码如下:

ini 复制代码
function calculateDiscount(order) {
    // 获取订单总金额
    totalAmount = order.totalAmount;
    // 假设满100减20的规则
    if (totalAmount >= 100) {
        discount = 20;
    } else {
        discount = 0;
    }
    return discount;
}
  1. Model/DAO:数据「搬运工」
    • Model:定义数据格式(比如用户必须有id和password);
    • DAO:写 SQL 操作数据库(比如「用 username 查用户」)。
  1. 极简配置与错误处理
    • Config:存数据库地址、端口(哪怕写死在代码里,先跑通);
    • Error Handler:出错时至少返回500,别让页面白屏。

可选模块:别给自己加戏

  • ORM?表少的话直接写原生 SQL 更快(比如select * from user where name = ?);
  • 日志?console.log加个时间戳,调试够用就行。

核心逻辑:Controller → Service → DAO,一条线走到底,多余抽象都是负担。

二、中型应用:加模块是为了「少踩坑」

用户从几百涨到几万,业务从「增删改查」变成「带流程的业务」(比如下单要扣库存、减余额、记日志),团队也扩到 3-10 人 ------ 这时候「混乱」会拖慢所有人。

典型痛点

  • 重复代码爆炸:每个 Controller 都写「登录检查」「操作日志」,改一处要同步改 10 个地方;
  • 数据格式混乱:前端今天传userName,明天传user_name,Service 里全是兼容逻辑;
  • 错误返回随机:有的接口返回字符串,有的返回对象,前端吐槽「又崩了」。

该加哪些模块?「缺啥补啥」

在小型项目基础上,按需加模块解决具体问题,就像面馆扩大后加保安、质检员:

  1. Middleware:给所有请求「设关卡」

把「登录检查」「日志记录」这些通用逻辑抽出来,所有请求先过一遍 Middleware。

scss 复制代码
// 伪代码:登录检查Middleware
app.use((req, res, next) => {
  if (!req.cookies.token) {
    return res.status(401).send('请先登录');
  }
  next(); // 通过则进下一步
});

改一次 Middleware,全系统生效,再也不用在每个 Controller 里重复写校验。

  1. DTO + Pipe:给数据「画红线」
less 复制代码
// DTO定义(NestJS示例)
class CreateOrderDto {
  @IsNumber() // 必须是数字
  goodsId: number;
  @IsNumber()
  @Min(1) // 至少1件
  quantity: number;
}

这样 Service 里就不用处理格式问题,专心写「扣库存」逻辑。

    • DTO:定义前端传参规则(比如创建订单必须有goodsId(数字)、quantity(>0));
    • Pipe:自动校验参数(比如quantity是负数就直接返回错误)。
  1. 其他可选模块
    • 增强日志:按模块输出(比如「订单模块」「用户模块」),记录接口耗时(方便排查慢接口);
    • 内存缓存:给高频接口(比如商品详情)加缓存,减少数据库压力(比如用Map存热点数据);
    • 分环境配置:开发 / 测试 / 生产用不同数据库地址,避免硬编码(比如用dotenv区分环境)。

核心逻辑:用模块解决「重复」和「混乱」,让团队协作更顺,而不是为了「架构好看」。

下图展示架构示意图,可选模块也全部列出,其中

  • 核心业务模块 - 绿色系
  • 安全与校验模块 - 蓝色系
  • 基础设施模块 - 紫色系
  • 数据流相关 - 橙色系

三、大型系统:模块要「扛住压力」

到了高并发场景(比如电商秒杀、社交平台),团队拆成多个子团队,系统开始往分布式 / 微服务走 ------ 这时候模块要支撑「高可用」「强安全」和「跨团队协作」。

典型痛点

  • 单体扛不住:流量高峰时数据库崩了,整个系统瘫痪;
  • 权限管理复杂:不同角色能访问的接口完全不同(比如游客看商品,管理员改价格);
  • 跨服务排查难:一个请求经过 5 个服务,出问题不知道在哪一环卡壳。

模块要「精细化」,但核心链路不变

在中型基础上,加更专业的模块应对压力:

  1. Guard:接口权限「细粒度控制」

比 Middleware 更灵活,能按「接口 + 角色」控制权限。比如:

less 复制代码
// Guard示例(NestJS)
@Injectable()
class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const user = context.switchToHttp().getRequest().user;
    return user.role === 'admin'; // 只有管理员能访问
  }
}
// 用在需要权限的接口上
@Controller('goods')
class GoodsController {
  @Post()
  @UseGuards(AdminGuard) // 只有管理员能创建商品
  create() {}
}
  1. 分布式工具链
    • 分布式缓存(Redis):多实例共享缓存,避免重复查库;
    • 消息队列(Kafka/RabbitMQ):秒杀时先把订单请求扔进队列,异步处理(防止系统被冲垮);
    • 配置中心(Nacos):多环境动态更新配置(比如突然改 Redis 地址,不用重启服务);
    • 分布式追踪(Jaeger):画一条请求链路图,看经过哪些服务、在哪耗时最长。
  1. 更细的异常处理

用 Filter 细分异常类型(比如参数错、权限错、数据库错),配合全局 Error Handler 返回标准化错误(前端再也不用猜格式)。

下图展示架构示意图,可选模块也全部列出,其中

  • 核心业务模块 - 绿色系
  • 安全与校验模块 - 蓝色系
  • 基础设施模块 - 紫色系
  • 数据流相关 - 橙色系

常见误区:这些模块不是一回事!

(1)Guard、Pipe 是中间件吗?

不是。它们的分工和工作时机完全不同:

模块 工作内容 工作时机 通俗比喻
Middleware 处理所有请求的通用逻辑 最早接触请求,能改请求 / 响应 小区大门保安(查健康码、登记)
Guard 判断 "有没有权限访问" 中间件之后,控制器之前 会议室门禁(没卡不让进)
Pipe 校验数据格式、转换数据类型 守卫之后,控制器处理数据前 快递安检机(检查包裹里有没有违禁品)

(2)Pipe 和 DTO 重复吗?

完全不重复。DTO 是 "规则手册",Pipe 是 "执法人员":

  • DTO 规定 "密码必须≥8 位"(静态规则);
  • Pipe 负责检查 "这个密码是不是真的≥8 位"(动态执行)。

举例:LoginDTO写着 "password 长度≥8",PasswordPipe则在每次登录时实际检查,如果不够就返回错误。

(3)为什么大型项目必须有 Cache 和 Queue?

  • Cache:高并发场景下,数据库每秒能处理的请求有限(比如 1 万次 / 秒),而 Cache(如 Redis)能处理百万级请求 / 秒,通过缓存热点数据(如首页商品)可避免数据库过载;
  • Queue:当用户下单后需要发送短信、生成物流单等耗时操作时,直接在接口中处理会导致响应变慢,用 Queue 异步处理可让接口快速返回,后续由消费端慢慢处理任务。

最后:架构演进的「底层逻辑」

从单体到分布式,模块越拆越多,但核心永远是「解决具体问题」:

阶段 核心目标 模块设计原则
小型单体 快速落地 核心链路(Controller→Service→DAO)+ 极简支撑
中型应用 减少重复、规范协作 补横切模块(Middleware/DTO/Pipe)
大型系统 高可用、可运维、跨团队协作 精细化模块(Guard / 队列 / 分布式工具)

记住:架构不是「炫技」,而是「实事求是」------

  • 业务简单时,别强行加一堆模块(摆摊不用建写字楼);
  • 业务复杂后,别舍不得拆模块(大公司必须分部门)。

你现在的项目处于哪个阶段?遇到过哪些「架构坑」?欢迎评论区聊聊~ 👇

相关推荐
寅时码1 分钟前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
CF14年老兵3 分钟前
🚀 React 面试 20 题精选:基础 + 实战 + 代码解析
前端·react.js·redux
CF14年老兵4 分钟前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
think1234 分钟前
带你走进Spring Cloud的世界
spring boot·后端·spring cloud
十五_在努力7 分钟前
参透 JavaScript —— 彻底理解 new 操作符及手写实现
前端·javascript
拾光拾趣录22 分钟前
🔥99%人答不全的安全链!第5问必翻车?💥
前端·面试
没逻辑26 分钟前
Goroutine 死锁定位与调试全流程
后端
IH_LZH27 分钟前
kotlin小记(1)
android·java·前端·kotlin
无限大629 分钟前
Java 随机数生成:从青铜到王者的骚操作指南
后端·程序员
lwlcode35 分钟前
前端大数据渲染性能优化 - 分时函数的封装
前端·javascript