
1. 项目概述为什么我们还在谈论分层架构干了这么多年软件研发从单体应用到微服务从瀑布到敏捷各种架构模式和概念层出不穷。但无论技术栈怎么变有一个词你几乎在每个项目的技术评审会上都能听到那就是“分层架构”。它听起来有点老生常谈甚至有些“过时”毕竟现在大家都在聊领域驱动设计、事件驱动、六边形架构这些更时髦的概念。然而我敢说能把分层架构真正理解透彻、用对地方的团队其实并不多。很多时候我们只是机械地套用“Controller-Service-Dao”的三层模板却很少深入思考每一层到底应该承担什么职责层与层之间应该如何清晰地划清界限。这个项目我们就来一次彻底的“庖丁解牛”把系统分层架构从里到外、从上到下拆解清楚。它绝不是一个简单的概念复述而是基于我踩过无数坑、重构过多个臃肿系统后沉淀下来的一套实战心法。我们会探讨分层架构的本质是什么它解决了哪些核心痛点以及在不同的业务场景和技术背景下如何灵活地设计和调整你的分层策略。无论你是刚入行的新手希望建立一个清晰的架构认知还是经验丰富的老手想优化现有系统的结构这篇文章都能给你带来一些实实在在的启发和可落地的建议。2. 分层架构的核心思想与价值主张2.1 本质关注点分离与单一职责分层架构最根本的思想源于软件工程的两个黄金法则关注点分离和单一职责原则。它的目标是把一个复杂的系统按照不同的职责和抽象级别垂直地切割成若干个层次。每一层都像一个专业的车间只负责处理特定类型的工作。比如有的车间专门负责接待客户接收请求有的车间负责核心的加工逻辑业务处理还有的车间专门负责与仓库打交道数据存取。这样做的好处是显而易见的。首先它极大地降低了系统的认知复杂度。一个新加入的开发者不需要一下子理解整个庞然大物他只需要先搞清楚自己负责的那一层是如何工作的以及它与上下相邻层是如何交互的即可。其次它提升了系统的可维护性。当需要修改某个功能时比如改变数据存储方式你的修改范围通常会被限制在数据访问层而不会波及到上层的业务逻辑或展示逻辑。最后它增强了可测试性。你可以很容易地对每一层进行独立的单元测试通过模拟Mock其依赖的上下层确保该层逻辑的正确性。注意分层不是目的而是手段。分层的终极目标是让代码结构清晰、易于理解和修改。千万不要为了分层而分层生搬硬套出一个“过度设计”的架构那反而会增加不必要的复杂性。2.2 核心价值应对变化与团队协作分层架构的价值在系统演进和团队协作中体现得淋漓尽致。软件需求是永恒变化的今天可能用MySQL明天可能要用Elasticsearch做全文检索后天接口协议可能要从REST换成GraphQL。一个良好的分层架构能够将这些变化隔离在特定的层次内。例如所有与外部系统数据库、缓存、消息队列、第三方API的交互都应该被收敛到最底层的“基础设施层”或“数据访问层”。当数据库从MySQL迁移到PostgreSQL时你只需要修改这一层中的具体实现类如更换数据库驱动和调整部分SQL方言而上层的业务逻辑代码完全无需感知。同样如果前端展示要从Web页面换成手机App你通常只需要修改最顶层的“表示层”或“接口层”核心业务规则依然稳固。从团队协作角度看分层天然地划分了工作边界。前端团队可以专注于表示层后端业务团队深耕业务逻辑层而数据团队或架构团队可以负责基础设施层的稳定性和性能。大家基于清晰的接口契约进行协作并行开发效率自然更高。3. 经典分层模型深度拆解虽然分层思想可以灵活应用但经过多年实践有几个经典模型被广泛认可和采用。理解它们是设计自己系统分层的基础。3.1 三层架构经久不衰的基石这是最广为人知的分层模型尤其在企业级Web应用中极为常见。表示层也称为展现层或用户界面层。它负责直接与用户交互接收用户的输入并将处理结果以适当的形式HTML页面、JSON数据、XML等展示给用户。在Web领域这通常对应着我们的Controller、API Gateway或者前端框架如Vue、React中的视图组件。它的核心职责是协议处理和数据渲染/组装不应该包含任何业务规则。业务逻辑层这是系统的“大脑”和核心价值所在。它包含了所有的业务规则、业务流程、领域模型和核心计算逻辑。这一层应该对“表示层”如何展示数据、“数据访问层”如何存储数据一无所知。它只关心“做什么业务”不关心“怎么展示”和“数据在哪”。通常以Service、Manager、Domain Service等形式存在。数据访问层也称为持久层。它负责与数据源打交道包括数据库、缓存、文件系统、外部API等。它的职责是提供一套统一的接口供业务逻辑层调用以完成数据的增删改查。这一层封装了所有数据访问的细节比如SQL语句、NoSQL的查询语法、网络请求等。常见的形态是Dao、Repository、Mapper。三层架构的交互流程用户请求到达表示层 - 表示层进行必要的参数校验和格式转换 - 调用业务逻辑层的某个服务方法 - 业务逻辑层执行复杂的业务规则期间可能需要调用数据访问层获取或保存数据 - 数据访问层与数据库交互 - 结果逐层返回最终由表示层渲染输出。3.2 四层架构领域模型的引入随着业务复杂度的提升三层架构的一个突出问题暴露出来业务逻辑层容易变成“上帝类”或“事务脚本”的集合缺乏对核心业务概念的清晰表达。于是在业务逻辑层和数据访问层之间引入了领域层形成了四层架构。表示层职责不变。应用层这是一个薄层有时也被归入业务逻辑的一部分。它的主要职责是协调领域层的多个对象完成一个特定的用例或用户故事。它不包含业务规则只包含工作流逻辑。例如“用户下单”这个用例应用层会依次调用“订单”领域对象的创建方法、“库存”领域对象的检查方法、“支付”领域对象的触发方法。它更像是用例的编排者。领域层这是系统的核心中的核心。它包含了领域模型实体、值对象、聚合根、领域服务以及领域规则。这一层是纯粹的业务知识表达与技术实现完全无关。它定义了“业务是什么”而不是“如何实现业务”。一个设计良好的领域层应该是高度内聚、低耦合的并且可以被独立理解和测试。基础设施层它相当于原来三层架构中数据访问层的扩展。它不仅负责数据持久化实现领域层定义的Repository接口还负责其他技术细节如发送邮件、调用外部HTTP服务、消息队列的生产消费等。它为上层应用层、领域层提供技术能力的实现。四层架构尤其是领域驱动设计下的分层强制我们将业务复杂性封装在领域层使得核心业务逻辑非常稳定而将易变的技术细节推到了外围的基础设施层。3.3 五层及更多层应对特定复杂度在某些超大型系统或特定场景下可能会看到更多层次。例如接口层/API网关层在微服务架构中通常会在最外层有一个独立的API网关层负责路由、认证、限流、监控等跨切面关注点让内部的服务专注于业务。任务调度层对于有复杂后台作业的系统可能会抽象出一个独立的层来管理定时任务、异步作业的调度和执行。集成层在需要与大量异构外部系统对接的企业服务总线架构中会有一个专门的集成层来处理协议转换、数据映射和消息路由。关键在于每增加一层都应该有明确和强烈的理由——通常是为了处理一种新的、独立的关注点。层数不是越多越好每多一层就多一份抽象成本和层间调用的开销。4. 分层设计的关键决策与实操要点知道了有哪些层下一步就是如何设计它们。这里有几个至关重要的决策点。4.1 层间依赖原则单向依赖与依赖倒置这是分层架构能否保持清晰的核心纪律。最理想的状态是单向依赖即上层模块依赖下层模块而不能反向依赖。在三层架构中表示层依赖业务逻辑层业务逻辑层依赖数据访问层。这保证了关注点的单向流动。但有时业务逻辑层需要定义数据访问的接口比如UserRepository而具体实现如UserRepositoryImpl在数据访问层。这就产生了业务逻辑层“依赖”一个由下层实现的接口的假象。为了解决这个问题引入了依赖倒置原则。具体做法是将接口定义放在一个双方都能访问的公共契约模块或直接放在业务逻辑层。业务逻辑层只依赖这个接口。数据访问层基础设施层实现这个接口。通过依赖注入在运行时将数据访问层的实现实例“注入”到业务逻辑层。这样源码依赖方向就从“业务逻辑层 - 数据访问层”变成了“业务逻辑层 - 接口 - 数据访问层实现”实现了依赖关系的倒置但控制流和职责关系依然清晰。4.2 数据传递对象的选择DO、DTO、VO、BO层与层之间传递数据用什么对象这里水很深用错了会导致层边界模糊。DO数据对象与数据库表结构一一对应通常由MyBatis等ORM框架使用。它只存在于数据访问层。BO业务对象在业务逻辑层内部流转是领域模型的核心体现。它包含了业务数据和相关行为方法。DTO数据传输对象用于跨层传输数据特别是在表示层与业务逻辑层之间。它的设计完全由接口需求驱动可能是一个BO的子集、超集或组合。DTO应该是“贫血”的即只有属性字段和简单的getter/setter没有业务逻辑。VO视图对象用于表示层向客户端渲染数据。它可能包含一些展示逻辑比如日期格式化、状态码转中文等。实操心得我强烈建议在层间传递时显式地定义和使用DTO而不是直接将DO或BO传递到表示层。这虽然增加了一些转换代码可以使用MapStruct等工具自动化但它严格捍卫了分层边界。表示层的变化比如增加一个展示字段只需要修改DTO和转换逻辑不会污染到核心的BO或DO。同理数据库表结构的变化也只会影响DO不会通过层间传递直接波及到上游。4.3 事务边界与层的关系事务管理放在哪一层这是一个常见的困惑。根据单一职责原则事务的本质是一个跨数据操作的原子性保证它是一个技术性、基础设施性的关注点。因此事务的控制权不应该分散在业务逻辑的各个角落。最佳实践将事务的声明放在应用层或传统的Service层的最外层方法上。因为应用层协调一个完整的用例这个用例往往需要作为一个原子操作。例如“转账”这个用例包含扣款和加款两个步骤必须在同一个事务中。业务逻辑层或领域层的各个领域对象和方法应该专注于业务规则而不需要关心自己是否运行在事务中。通过Spring的Transactional注解在应用层方法上声明事务是清晰且合理的做法。5. 分层架构的常见陷阱与反模式即使知道了正确做法实践中也容易掉进一些坑里。下面是我总结的几个高频“翻车”现场。5.1 贫血模型与充血模型之辩这是业务逻辑层设计中最经典的陷阱。在传统的三层架构里我们很容易写出“贫血模型”即业务逻辑层的Service类非常庞大包含了所有的业务逻辑而对应的Java Bean所谓的BO或DO只是一堆属性的集合没有任何行为方法。这实际上是一种面向过程的编程只不过把过程包装在了类里。反模式示例// 贫血的Order对象 public class Order { private Long id; private BigDecimal amount; private String status; // 只有getter/setter } // 庞大的OrderService包含了所有关于Order的操作逻辑 Service public class OrderService { public void placeOrder(OrderDTO dto) { // 校验逻辑 // 计算逻辑 // 状态变更逻辑 // 保存逻辑 // ... 所有代码都在这里 } }正确的方向是走向“充血模型”这是领域驱动设计的核心。将属于订单这个业务实体的行为和规则封装到Order这个领域对象内部。改进示例// 充血的Order领域实体 public class Order { private Long id; private BigDecimal amount; private OrderStatus status; private ListOrderItem items; // 业务行为下单 public static Order place(Customer customer, ListOrderItem items) { // 在校验、计算等逻辑 Order order new Order(); order.status OrderStatus.CREATED; order.items items; order.calculateTotalAmount(); // 内部方法计算总额 return order; } // 业务行为支付 public void pay(Payment payment) { if (!this.status.canPay()) { throw new IllegalStateException(订单当前状态不可支付); } // 执行支付逻辑 this.status OrderStatus.PAID; } // 其他属于订单本身的行为... }这样OrderService就变得很薄它可能只负责一些跨实体的协调工作或者作为领域服务的门面。核心逻辑都在领域对象里更符合面向对象的设计思想也更容易测试和维护。5.2 层渗透最致命的架构腐蚀这是破坏分层清晰度的头号杀手。它指的是某一层的职责“渗透”到了其他层。常见症状包括SQL出现在Service层业务逻辑层直接拼接SQL字符串或使用JdbcTemplate绕过了数据访问层。业务逻辑出现在Controller层在Controller里做了大量的数据校验、计算、状态判断Service层变成了简单的数据透传。领域对象直接暴露给前端将DO或BO直接通过接口返回给前端导致数据库表结构或内部业务模型暴露一旦变化前端直接崩溃。在DO或DTO里写业务逻辑试图在数据传输对象里封装业务方法混淆了数据载体和业务实体的界限。如何防范建立严格的代码审查和架构守护规则。利用ArchUnit这类架构测试工具可以编写规则来强制约束例如“所有Controller类不能直接依赖JpaRepository”“Service类中的方法不能以select、update、from等SQL关键词开头”。5.3 过度分层与“流水线式”开发另一个极端是过度分层。我曾经见过一个项目一个简单的查询请求数据流经过了Controller - Facade - AppService - DomainService - Manager - Dao - Mapper。每一层都只是简单调用下一层没有任何实质性的职责。这被称为“流水线式”或“传话筒式”架构。这种架构的危害在于开发效率低下改一个小功能需要修改多个文件。理解成本高跟踪一个调用链路像是在走迷宫。性能损耗每一层都可能意味着一次对象转换或代理开销。判断标准如果你无法清晰地说出某一层存在的独特价值它处理了哪种其他层无法处理的关注点那么这一层很可能就是多余的。对于大多数业务系统四层架构接口层、应用层、领域层、基础设施层已经足够应对其复杂性。6. 分层架构的演进与现代化实践分层架构不是一成不变的。在现代软件开发中它需要与其他架构模式和工程实践相结合。6.1 与六边形架构/整洁架构的结合六边形架构端口与适配器和整洁架构是分层思想的进一步发展。它们都强调“核心业务逻辑独立于外部世界”。你可以将传统的“领域层”视为架构的核心。所有外部的依赖数据库、UI、第三方服务都通过“端口”接口来定义并由“适配器”具体实现在基础设施层实现。在这种视角下传统的“表示层”和“数据访问层”都变成了“适配器”它们位于架构的最外围。应用层和领域层位于核心。依赖关系永远是从外层指向内层核心层对外层一无所知。这极大地提升了核心业务的稳定性和可测试性。6.2 在微服务中的分层应用在微服务架构中每个服务内部依然推荐采用分层架构。但是服务的边界成为了更高层级的“层”。这时分层有了新的含义服务间API层定义服务对外的契约Protobuf/OpenAPI。服务内部分层每个微服务内部仍然可以按照四层架构来组织代码。共享内核对于多个服务都需要使用的核心领域模型如账户、商品可以提炼成独立的库JAR包作为共享的“领域层”避免重复建设和模型不一致。微服务下的分层更要警惕“分布式单体”反模式——即服务拆开了但代码结构和数据模型依然是高度耦合的通过隐式的共享数据库关联。这比单体架构更难维护。6.3 前端的分层思考分层思想同样适用于前端。一个复杂的前端应用如使用React/Vue也可以分为视图层纯UI组件负责渲染和用户交互事件。状态/逻辑层管理应用状态如使用Vuex、Pinia、Redux和处理前端业务逻辑如表单校验、数据格式化。服务/API层封装所有对后端API的调用处理请求/响应拦截、错误处理等。工具/工具层提供通用的工具函数、常量、配置等。清晰的前端分层能让代码更易于维护和测试尤其是在大型前端项目中。7. 实战从一个需求开始设计分层理论说再多不如看一个实例。假设我们要开发一个“在线书店”的“用户购书”功能。需求分析用户选择书籍加入购物车结算时生成订单扣减库存调用支付接口支付成功后通知用户。识别核心领域用户、书籍、购物车、订单、库存、支付。其中订单是核心聚合根。分层设计接口层提供RESTful API如POST /orders。负责接收创建订单的请求包含书籍ID、数量等进行基础参数校验非空、格式并调用应用层服务。应用层OrderApplicationService。它接收接口层传来的DTO协调领域层完成“创建订单”这个用例。它的伪代码如下Transactional public OrderResultDTO placeOrder(PlaceOrderCommand command) { // 1. 通过领域服务或仓库获取“书籍”聚合根检查是否存在 Book book bookRepository.findById(command.getBookId()); // 2. 通过领域服务检查库存是否充足库存可能是一个独立的领域服务 inventoryService.checkStock(book.getId(), command.getQuantity()); // 3. 调用“订单”领域工厂或方法创建订单聚合根核心业务逻辑在此 Order newOrder Order.create(command.getUserId(), book, command.getQuantity()); // 4. 保存订单触发领域事件如OrderCreatedEvent orderRepository.save(newOrder); // 5. 发布领域事件触发后续流程如扣减库存、发送通知等可异步 domainEventPublisher.publish(new OrderCreatedEvent(newOrder)); // 6. 返回给接口层的DTO return OrderAssembler.toDTO(newOrder); }领域层Order实体包含订单状态、金额、关联用户和书籍信息。有create(),pay(),cancel()等方法。Book实体。InventoryService领域服务封装库存检查规则。OrderCreatedEvent领域事件。基础设施层OrderRepositoryImpl实现OrderRepository接口使用JPA或MyBatis操作数据库。InventoryServiceImpl实现InventoryService接口可能调用另一个库存微服务的API。DomainEventPublisherImpl实现事件发布接口可能使用Spring Event或消息队列如RabbitMQ。PaymentClientImpl实现支付接口调用第三方支付网关。EmailSenderImpl实现邮件发送接口。通过这个例子你可以看到每一层的职责非常清晰。需求变更比如增加优惠券逻辑主要修改点在领域层Order实体或新增一个Coupon领域服务和应用层的协调逻辑其他层变动很小。8. 工具、度量与持续改进设计好了分层如何保证它在项目迭代中不被破坏架构守护工具如前所述使用ArchUnit编写测试用例来强制约束包之间的依赖关系、类命名规范、注解使用等。把它集成到CI/CD流程中违反架构规则的代码无法合并。代码度量关注一些能反映架构健康的指标。层间依赖耦合度使用工具如SonarQube、JDepend分析包依赖图确保没有循环依赖和违规的跨层依赖。类的职责如果一个Service类有几千行代码它很可能承担了过多职责需要考虑拆分。抽象稳定度高层模块如领域层接口应该比低层模块如基础设施实现更稳定。如果高层模块频繁变更说明抽象可能有问题。持续重构分层架构不是一蹴而就的。随着业务发展当初的划分可能不再合理。要勇于重构。常见的重构方向包括提取领域层从庞大的Service中识别并抽取出核心的领域模型和行为。下沉基础设施将散落在各处的技术代码如HTTP调用、缓存操作收敛到独立的基础设施包中。引入防腐层当与一个设计糟糕的外部系统对接时在基础设施层内建立一个“防腐层”将外部系统的模型转换为你内部清晰的领域模型避免“坏味道”侵入核心。最后我想分享一个最深的体会分层架构的本质是一种沟通和管理的工具。它通过约定俗成的规则让团队对系统的结构达成共识让新人能快速融入让修改的影响范围可控。它没有银弹不能解决所有的软件复杂度问题但它提供了一个坚实、可讨论、可演进的基础框架。当你和团队成员在争论一段代码应该放在哪一层时你们正是在对系统的核心设计进行深入的思考这本身就是架构活动最大的价值所在。不要追求教科书式的完美分层而要追求一个适合你当前团队和业务阶段、并且能随着发展而灵活调整的、活的分层结构。