3、软件分层架构概述:分层架构的演进、典型分层及每层职责
软件分层架构是系统需求向可维护、可扩展、可测试的代码结构映射时最核心的骨架。无论是单体应用、微服务,还是嵌入式固件,分层思想都贯穿始终。本章将梳理分层架构从“大泥球”到“整洁架构”的演进脉络,深入剖析表现层、业务层、持久层、基础设施层的职责边界,并结合一个订单系统的实战案例,展示如何将需求条目精准映射到各层,同时指出常见的设计误区和复盘要点。
3.1 分层架构的演进脉络
分层架构并非凭空产生,而是软件复杂度不断增长后自然演化的结果。早期系统开发中,开发者常将界面渲染、业务判断、SQL语句直接混写在同一个函数或页面中,这种被称为“大泥球”的架构在需求简单时尚可运转,一旦业务规则增多、界面频繁调整,任何改动都可能引发连锁故障。随后出现的MVC模式将界面输入输出(View)、用户交互控制(Controller)与数据模型(Model)分离,初步解决了表现层与业务逻辑的纠缠问题。但MVC并未严格约束业务逻辑与数据访问的边界,Model中常常同时包含业务规则和数据库操作。
随着企业级应用兴起,经典三层架构(表现层、业务层、数据访问层)成为主流。这种分层明确了“上层调用下层、下层不反向依赖”的规则,使得团队可以并行开发不同层次。然而,三层架构在应对复杂领域逻辑时暴露出不足:业务规则散落在Service类中,缺乏领域模型支撑,导致需求变更时难以定位影响范围。领域驱动设计(DDD)的分层模型进一步细化为用户界面层、应用层、领域层和基础设施层,强调领域层承载核心业务逻辑,基础设施层通过接口反向依赖实现依赖倒置。近年来,整洁架构(Clean Architecture)和六边形架构将这种思想推向极致,通过端口与适配器模式,让业务核心完全独立于框架、数据库和UI。
| 架构阶段 | 核心特征 | 主要问题 | 适用场景 |
|---|---|---|---|
| 大泥球 | 无分层,代码混排 | 维护成本高,变更风险大 | 原型验证、一次性脚本 |
| MVC | 分离视图与控制,模型仍混杂 | 业务与数据访问边界模糊 | 小型Web应用 |
| 三层架构 | 表现/业务/数据访问严格分层 | 领域逻辑贫血,业务规则易散落 | 传统企业应用、管理后台 |
| DDD分层 | 领域层独立,基础设施反向依赖 | 学习曲线陡峭,建模成本高 | 复杂业务系统、金融、电商 |
| 整洁架构 | 依赖规则由外向内,核心完全独立 | 过度设计风险,小项目成本高 | 大型长期演进系统 |
3.2 典型分层及各层职责详解
在系统需求映射实践中,最常用的分层模型是四层结构:表现层、业务层、持久层和基础设施层。每层都有明确的职责边界和依赖方向,理解这些边界是避免架构腐化的关键。
表现层(Presentation Layer)负责与用户或外部系统交互,包括Web页面、REST API、命令行工具、消息监听器等。其核心职责是接收输入、解析参数、调用业务层接口、渲染输出结果。表现层不应包含任何业务规则,例如“订单金额超过1000元需要审批”这类逻辑绝不能写在Controller中。表现层需要处理参数校验、会话管理、异常转换(将业务异常转为HTTP状态码或错误码),但校验仅限格式和必填项,不涉及业务状态判断。
业务层(Business Layer)是系统的核心,承载用例编排、业务规则、状态流转和事务边界。业务层通常由Service类或领域对象组成,它接收表现层的请求,协调多个领域对象或持久化操作,确保业务不变量得到满足。例如“创建订单”用例需要校验库存、计算价格、生成订单号、扣减库存,这些步骤的编排和事务控制都在业务层完成。业务层应依赖持久层接口而非具体实现,这样即使替换数据库也不影响业务代码。
持久层(Persistence Layer)负责数据的存取和映射,包括ORM实体、Repository接口及实现、DAO类。持久层将数据库表记录转换为业务对象,或将业务对象持久化到存储介质。持久层不应包含业务判断,例如“根据用户等级计算折扣”属于业务逻辑,不应出现在SQL或Repository实现中。持久层需要处理连接管理、事务传播、缓存策略(一级缓存、二级缓存),但缓存失效规则应由业务层触发。
基础设施层(Infrastructure Layer)为其他层提供通用技术支持,包括数据库连接池、消息队列客户端、文件存储、日志框架、配置中心、认证授权组件等。基础设施层通常以工具类或框架组件的形式存在,被其他层调用。在DDD和整洁架构中,基础设施层还负责实现持久层接口、消息发布接口等,通过依赖注入将具体技术细节注入业务层。
3.3 案例拆解:订单系统的分层映射
以一个典型的B2C订单系统为例,需求描述为:“用户下单时,系统校验库存充足后扣减库存,计算订单总价(商品单价×数量-优惠金额),生成待支付订单,并发送消息通知仓储系统准备发货。”我们将该需求映射到各层。
表现层(OrderController):接收HTTP POST /api/orders请求,解析JSON参数(商品ID、数量、用户ID),进行基础格式校验(商品ID非空、数量大于0),调用业务层OrderService.createOrder(),将返回的订单ID封装为201响应。表现层不关心库存如何扣减,也不计算价格。
业务层(OrderService):实现createOrder()方法,按以下步骤编排:
- 调用InventoryRepository.getStock(productId)查询库存
- 判断库存是否充足,不足则抛出InsufficientStockException
- 调用InventoryRepository.deductStock(productId, quantity)扣减库存
- 调用ProductRepository.getPrice(productId)获取单价
- 计算总价:price × quantity - discount(discount由PromotionService计算)
- 构建Order对象,状态设为PENDING_PAYMENT
- 调用OrderRepository.save(order)持久化订单
- 调用MessagePublisher.publish("order.created", orderId)发送消息
业务层通过接口依赖持久层和基础设施层,不直接操作数据库或MQ。事务边界由@Transactional注解控制,确保扣库存和保存订单在同一事务中。
持久层(OrderRepositoryImpl):实现OrderRepository接口,使用MyBatis或JPA将Order对象映射到orders表。提供save()、findById()等方法,内部处理SQL语句和结果集转换。持久层不包含“库存不足”判断,也不计算优惠金额。
基础设施层:提供DataSource配置、RabbitMQTemplate封装、Redis缓存客户端、Logback日志配置等。其中MessagePublisher接口由基础设施层的RabbitMQMessagePublisher实现,通过Spring注入到业务层。
| 需求条目 | 映射层 | 具体实现 | 验证方式 |
|---|---|---|---|
| 校验库存充足 | 业务层 | 库存判断逻辑 | 单元测试:库存不足时抛异常 |
| 扣减库存 | 持久层 | UPDATE stock SET quantity=quantity-#{qty} | 集成测试:验证库存减少 |
| 计算订单总价 | 业务层 | 价格计算与优惠规则 | 单元测试:不同折扣场景 |
| 生成待支付订单 | 持久层 | INSERT INTO orders | 数据库断言 |
| 发送消息通知 | 基础设施层 | MQ消息发布 | Mock验证消息发送 |
3.4 常见误区与总结复盘
在实际项目中,分层架构的落地常出现以下误区:
- 贫血模型:业务层变成纯粹的“事务脚本”,所有逻辑写在Service中,领域对象仅作为数据载体。这会导致业务规则无法复用,且需求变更时Service类急剧膨胀。
- 跨层调用:表现层直接调用持久层或基础设施层,绕过业务层。例如在Controller中直接使用JdbcTemplate查询数据,虽然短期内代码量减少,但破坏了分层边界,使业务规则无法统一管控。
- 持久层包含业务逻辑:在SQL中编写复杂条件判断,或在Repository实现中计算折扣、判断状态。这导致业务规则分散,且难以进行单元测试。
- 过度设计:为每个简单操作都定义接口、实现类、工厂类,导致类数量爆炸。分层应服务于需求,而非为了分层而分层。
- 事务边界错位:将事务注解放在表现层或持久层,导致事务粒度过大或过小。事务应定义在业务层的方法上,确保用例级别的原子性。
总结复盘:分层架构的核心价值在于“隔离变化”——表现层变化不影响业务规则,数据库替换不影响业务代码,框架升级不影响核心逻辑。在需求映射过程中,应遵循以下步骤:首先识别用例边界,确定业务层方法;其次定义业务层所需的接口(持久化、消息、外部服务);然后实现持久层和基础设施层;最后编写表现层适配。每完成一个用例,通过单元测试和集成测试验证分层边界是否被遵守。分层不是银弹,但它为系统的长期演进提供了坚实的结构基础。团队应定期检查代码依赖方向,使用ArchUnit等工具自动化校验分层规则,防止架构腐化。