2、经典三层架构:表现层、业务逻辑层、数据访问层的职责与边界

说到分层架构,很多人第一反应就是「三层架构」。嗯,这确实是最经典、最基础的分层模式。我做了十几年架构,见过无数项目从单层慢慢演变成三层,也见过不少团队把三层搞成了「一锅粥」。今天咱们就好好聊聊,这三层到底该怎么分。

2.1 表现层:只负责「展示」和「交互」

表现层,也叫 UI 层。它的职责其实很单纯:把数据展示给用户,把用户的操作传给下层。说白了,它就是个「传话筒」。

我个人习惯把表现层想象成一个餐厅的服务员。服务员只负责点菜、上菜,他不会去后厨炒菜,也不会去仓库查库存。表现层也一样,它不该包含任何业务逻辑。

表现层该做的事:

  • 接收用户输入(点击、键盘、触摸)
  • 调用业务逻辑层的接口
  • 把返回的数据渲染到界面上
  • 做简单的输入校验(比如非空、格式)

我在项目中遇到过一种情况:前端同学为了省事,直接在页面里写了大量 if-else 判断订单状态。结果后来业务规则变了,改前端代码改到崩溃。这就是典型的「表现层越界」。

避坑指南:我曾经接手过一个项目,表现层里居然有数据库连接字符串。嗯,这已经不是越界了,这是「越狱」。表现层永远不要直接访问数据库,永远不要。

2.2 业务逻辑层:核心的「大脑」

业务逻辑层,也叫服务层、领域层。这是整个系统的核心。所有的业务规则、计算逻辑、流程控制,都应该放在这里。

你想想看,一个电商系统里,「下单」这个操作涉及多少逻辑?库存检查、价格计算、优惠券抵扣、积分累计、物流规则……这些都不该出现在表现层,也不该散落在数据访问层。它们应该集中管理在业务逻辑层。

我建议把业务逻辑层想象成公司的「运营部」。运营部制定规则、处理流程,前台(表现层)只负责接待,仓库(数据访问层)只负责存取货物。

个人经验:判断一个逻辑该不该放在业务层,有个简单方法——问自己:「如果换一个前端(比如从 Web 换成 App),这个逻辑还要不要?」如果要,那就放业务层。

来看一个简单的例子,这是业务逻辑层的典型代码风格:

public class OrderService {
    private final IOrderRepository orderRepo;
    private final IInventoryService inventoryService;
    private final IPricingService pricingService;

    public OrderResult createOrder(CreateOrderRequest request) {
        // 1. 校验库存
        if (!inventoryService.checkStock(request.getItems())) {
            return OrderResult.fail("库存不足");
        }

        // 2. 计算价格
        Money total = pricingService.calculate(request.getItems());

        // 3. 应用优惠
        total = pricingService.applyDiscount(total, request.getCouponCode());

        // 4. 创建订单
        Order order = new Order(request.getUserId(), request.getItems(), total);

        // 5. 保存
        orderRepo.save(order);

        return OrderResult.success(order);
    }
}

注意看,这个类里没有一行 SQL,也没有一行 HTML。它只关心「业务规则」本身。

2.3 数据访问层:只管「存取」

数据访问层,也叫持久层、DAO 层。它的职责更简单:把数据存进去,把数据取出来。至于这些数据怎么用、有什么业务含义,它一概不管。

说白了,数据访问层就是个「搬运工」。它把业务逻辑层的「对象」转换成数据库的「记录」,反过来也一样。

职责 不该做的事
CRUD 操作(增删改查) 业务规则判断
数据映射(ORM 转换) 权限校验
事务管理(基本级别) 数据计算
连接池管理 缓存策略(应由上层决定)

我见过最离谱的代码,是在数据访问层里写了一个「满减优惠」的计算逻辑。问为什么,答曰「这样查询的时候直接算好,上层用着方便」。嗯,方便是方便了,但后来优惠规则变了,改 SQL 改到想哭。

避坑指南:我曾经在一个项目里发现,数据访问层的方法名居然叫 getActiveUsersWithOrdersAndCalculateTotalSpent()。这名字本身就暴露了问题——它既做了查询,又做了计算。记住:数据访问层的方法名应该只描述「取什么数据」,而不是「拿数据干什么」。

2.4 三层之间的边界:依赖方向

三层架构有个铁律:依赖方向必须从上到下

  • 表现层 → 依赖 → 业务逻辑层
  • 业务逻辑层 → 依赖 → 数据访问层
  • 数据访问层 → 不依赖任何上层

为什么?因为下层是「稳定的基础设施」,上层是「易变的业务逻辑」。如果下层依赖上层,那上层一改,下层也得跟着改,这就失去了分层的意义。

我建议用「接口」来解耦。业务逻辑层定义接口,数据访问层实现接口。这样即使换数据库,业务逻辑层也不用动。

核心原则:

  • 表现层只认识业务逻辑层,不认识数据访问层
  • 业务逻辑层只认识数据访问层的接口,不认识具体实现
  • 数据访问层只认识数据库,不认识任何上层

2.5 常见误区:别把三层搞成「三层皮」

很多团队名义上用了三层架构,实际上只是「三层皮」——代码确实分在了三个文件夹里,但职责完全混乱。

我总结了几种典型症状:

  • 「胖表现层」:页面里塞满了业务逻辑,业务逻辑层反而成了空壳
  • 「瘦业务层」:业务逻辑层只是简单调用了数据访问层,没有任何业务处理
  • 「万能数据层」:数据访问层里既有 SQL 又有业务计算,还偶尔输出日志

为什么会这样?说白了,很多团队把「分层」理解成了「分文件夹」。真正的分层是「职责分离」,不是「代码搬家」。

个人经验:我判断一个团队是否真正理解三层架构,就看他们怎么处理「事务」。如果事务控制写在表现层,那基本是「三层皮」。正确做法是:事务边界在业务逻辑层,数据访问层只负责执行。

2.6 什么时候该用三层?什么时候不该用?

三层架构不是银弹。我见过有人写个「Hello World」也硬套三层,结果代码量翻了三倍,维护成本反而更高。

我建议这样判断:

场景 推荐架构 原因
简单 CRUD 应用 两层(表现层+数据层) 业务逻辑简单,三层反而冗余
复杂业务系统 三层 业务逻辑需要独立管理和测试
微服务架构 每个服务内部可用三层 服务间解耦,服务内分层
原型/快速验证 单层 先跑通再说,后续再重构

记住:架构是为业务服务的,不是为架构师的面子服务的。三层架构很好,但别为了用三层而用三层。

嗯,关于三层架构的职责与边界,今天就聊这么多。下一章咱们聊聊更进阶的分层模式——DDD 分层架构,看看它和经典三层有什么不同。