3、软件分层架构概述:分层架构的演进、典型分层及每层职责

软件分层架构是系统需求向可维护、可扩展、可测试的代码结构映射时最核心的骨架。无论是单体应用、微服务,还是嵌入式固件,分层思想都贯穿始终。本章将梳理分层架构从“大泥球”到“整洁架构”的演进脉络,深入剖析表现层、业务层、持久层、基础设施层的职责边界,并结合一个订单系统的实战案例,展示如何将需求条目精准映射到各层,同时指出常见的设计误区和复盘要点。

软件分层架构演进与典型分层职责 大泥球 MVC 三层架构 DDD分层 整洁架构 表现层 (UI) 页面 / API / 命令行 / 消息 业务层 (Service / Domain) 用例编排 / 业务规则 / 状态流转 事务边界 / 权限校验 持久层 (Repository / DAO) ORM映射 / 数据访问 / 缓存策略 基础设施层 (Infrastructure) 数据库 / 消息队列 / 文件系统 / 外部API 日志 / 配置 / 监控 / 认证 关键原则 • 上层依赖下层抽象 • 下层不反向依赖上层 • 同层之间禁止横向调用 • 业务层不感知数据库 • 持久层不处理业务逻辑 • 基础设施可被替换 • 依赖倒置(DIP)保障 • 接口归属上层定义 • 分层是逻辑概念 • 物理部署可合并 • 演进优于预先设计

3.1 分层架构的演进脉络

分层架构并非凭空产生,而是软件复杂度不断增长后自然演化的结果。早期系统开发中,开发者常将界面渲染、业务判断、SQL语句直接混写在同一个函数或页面中,这种被称为“大泥球”的架构在需求简单时尚可运转,一旦业务规则增多、界面频繁调整,任何改动都可能引发连锁故障。随后出现的MVC模式将界面输入输出(View)、用户交互控制(Controller)与数据模型(Model)分离,初步解决了表现层与业务逻辑的纠缠问题。但MVC并未严格约束业务逻辑与数据访问的边界,Model中常常同时包含业务规则和数据库操作。

随着企业级应用兴起,经典三层架构(表现层、业务层、数据访问层)成为主流。这种分层明确了“上层调用下层、下层不反向依赖”的规则,使得团队可以并行开发不同层次。然而,三层架构在应对复杂领域逻辑时暴露出不足:业务规则散落在Service类中,缺乏领域模型支撑,导致需求变更时难以定位影响范围。领域驱动设计(DDD)的分层模型进一步细化为用户界面层、应用层、领域层和基础设施层,强调领域层承载核心业务逻辑,基础设施层通过接口反向依赖实现依赖倒置。近年来,整洁架构(Clean Architecture)和六边形架构将这种思想推向极致,通过端口与适配器模式,让业务核心完全独立于框架、数据库和UI。

架构阶段核心特征主要问题适用场景
大泥球无分层,代码混排维护成本高,变更风险大原型验证、一次性脚本
MVC分离视图与控制,模型仍混杂业务与数据访问边界模糊小型Web应用
三层架构表现/业务/数据访问严格分层领域逻辑贫血,业务规则易散落传统企业应用、管理后台
DDD分层领域层独立,基础设施反向依赖学习曲线陡峭,建模成本高复杂业务系统、金融、电商
整洁架构依赖规则由外向内,核心完全独立过度设计风险,小项目成本高大型长期演进系统
经验提醒:分层架构的演进本质是“关注点分离”的不断深化。选择何种分层粒度,应基于团队技术能力、业务复杂度和系统生命周期。不要为了追求架构先进性而盲目引入DDD或整洁架构,简单场景下经典三层架构往往更高效。

3.2 典型分层及各层职责详解

在系统需求映射实践中,最常用的分层模型是四层结构:表现层、业务层、持久层和基础设施层。每层都有明确的职责边界和依赖方向,理解这些边界是避免架构腐化的关键。

表现层(Presentation Layer)负责与用户或外部系统交互,包括Web页面、REST API、命令行工具、消息监听器等。其核心职责是接收输入、解析参数、调用业务层接口、渲染输出结果。表现层不应包含任何业务规则,例如“订单金额超过1000元需要审批”这类逻辑绝不能写在Controller中。表现层需要处理参数校验、会话管理、异常转换(将业务异常转为HTTP状态码或错误码),但校验仅限格式和必填项,不涉及业务状态判断。

业务层(Business Layer)是系统的核心,承载用例编排、业务规则、状态流转和事务边界。业务层通常由Service类或领域对象组成,它接收表现层的请求,协调多个领域对象或持久化操作,确保业务不变量得到满足。例如“创建订单”用例需要校验库存、计算价格、生成订单号、扣减库存,这些步骤的编排和事务控制都在业务层完成。业务层应依赖持久层接口而非具体实现,这样即使替换数据库也不影响业务代码。

持久层(Persistence Layer)负责数据的存取和映射,包括ORM实体、Repository接口及实现、DAO类。持久层将数据库表记录转换为业务对象,或将业务对象持久化到存储介质。持久层不应包含业务判断,例如“根据用户等级计算折扣”属于业务逻辑,不应出现在SQL或Repository实现中。持久层需要处理连接管理、事务传播、缓存策略(一级缓存、二级缓存),但缓存失效规则应由业务层触发。

基础设施层(Infrastructure Layer)为其他层提供通用技术支持,包括数据库连接池、消息队列客户端、文件存储、日志框架、配置中心、认证授权组件等。基础设施层通常以工具类或框架组件的形式存在,被其他层调用。在DDD和整洁架构中,基础设施层还负责实现持久层接口、消息发布接口等,通过依赖注入将具体技术细节注入业务层。

核心原理:分层架构的依赖规则是“上层依赖下层的抽象,而非具体实现”。业务层定义Repository接口,持久层实现该接口,基础设施层负责装配。这种依赖倒置使得业务层可以独立测试,也使得技术选型可以灵活替换。

3.3 案例拆解:订单系统的分层映射

以一个典型的B2C订单系统为例,需求描述为:“用户下单时,系统校验库存充足后扣减库存,计算订单总价(商品单价×数量-优惠金额),生成待支付订单,并发送消息通知仓储系统准备发货。”我们将该需求映射到各层。

表现层(OrderController):接收HTTP POST /api/orders请求,解析JSON参数(商品ID、数量、用户ID),进行基础格式校验(商品ID非空、数量大于0),调用业务层OrderService.createOrder(),将返回的订单ID封装为201响应。表现层不关心库存如何扣减,也不计算价格。

业务层(OrderService):实现createOrder()方法,按以下步骤编排:

  1. 调用InventoryRepository.getStock(productId)查询库存
  2. 判断库存是否充足,不足则抛出InsufficientStockException
  3. 调用InventoryRepository.deductStock(productId, quantity)扣减库存
  4. 调用ProductRepository.getPrice(productId)获取单价
  5. 计算总价:price × quantity - discount(discount由PromotionService计算)
  6. 构建Order对象,状态设为PENDING_PAYMENT
  7. 调用OrderRepository.save(order)持久化订单
  8. 调用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验证消息发送
风险提示:该案例中,如果业务层直接调用持久层的具体类(如OrderRepositoryImpl),则无法独立测试业务逻辑,且替换数据库时需要修改业务代码。务必通过接口依赖,并使用依赖注入框架管理实现类。

3.4 常见误区与总结复盘

在实际项目中,分层架构的落地常出现以下误区:

  • 贫血模型:业务层变成纯粹的“事务脚本”,所有逻辑写在Service中,领域对象仅作为数据载体。这会导致业务规则无法复用,且需求变更时Service类急剧膨胀。
  • 跨层调用:表现层直接调用持久层或基础设施层,绕过业务层。例如在Controller中直接使用JdbcTemplate查询数据,虽然短期内代码量减少,但破坏了分层边界,使业务规则无法统一管控。
  • 持久层包含业务逻辑:在SQL中编写复杂条件判断,或在Repository实现中计算折扣、判断状态。这导致业务规则分散,且难以进行单元测试。
  • 过度设计:为每个简单操作都定义接口、实现类、工厂类,导致类数量爆炸。分层应服务于需求,而非为了分层而分层。
  • 事务边界错位:将事务注解放在表现层或持久层,导致事务粒度过大或过小。事务应定义在业务层的方法上,确保用例级别的原子性。

总结复盘:分层架构的核心价值在于“隔离变化”——表现层变化不影响业务规则,数据库替换不影响业务代码,框架升级不影响核心逻辑。在需求映射过程中,应遵循以下步骤:首先识别用例边界,确定业务层方法;其次定义业务层所需的接口(持久化、消息、外部服务);然后实现持久层和基础设施层;最后编写表现层适配。每完成一个用例,通过单元测试和集成测试验证分层边界是否被遵守。分层不是银弹,但它为系统的长期演进提供了坚实的结构基础。团队应定期检查代码依赖方向,使用ArchUnit等工具自动化校验分层规则,防止架构腐化。

实践建议:在项目初期,可以先用经典三层架构快速交付,当业务复杂度上升时,再逐步引入DDD的分层细化。关键是保持依赖方向正确,让业务层始终处于核心位置,技术细节向外围扩散。