架构整洁的重要性

152 阅读6分钟

"The Clean Architecture" 是由 Robert C. Martin(“Uncle Bob”)提出的一种软件架构设计模式,旨在通过分层的方式提高代码的可维护性、可测试性和独立性。

整洁架构和六边形架构、洋葱架构概念上类似,其目的都是将核心业务用例和技术细节分离。但是,整洁架构提出了更多的约束条件,下图为整洁架构的分层模式。

Clean Architecture Layers Robert C. Martin, Clean Architecture

1. # 目的

清晰的架构帮助我们解决,或者至少缓解这些常见的架构问题:

  • 决策过早:在项目刚开始时,我们对需要解决的问题了解最少,却往往在此时就做出决策。

  • 难以更改:当发现新需求时,我们不得不在“快速修补”与“昂贵且痛苦的重新设计”之间做出选择,而前者通常会胜出。最佳的架构应当允许延迟对特定解决方案的承诺,并让我们有机会改变主意。

  • 框架驱动:框架是工具,应被使用而非遵从。然而,框架常要求我们做出承诺,而它们本身却未承诺支持我们。框架可能会朝不同方向发展,最终让我们受其规则和特性限制。

  • 数据库驱动:我们常以数据库为中心,先设计数据库,再围绕它创建一个CRUD系统。结果是,我们在各处都依赖数据库对象,将一切都视为表、行和列。

  • 专注于技术细节:谈到架构时,我们往往只提技术细节,比如“它是运行在Tomcat中的servlet,用Oracle数据库和Spring框架”。

  • 难以查找信息:系统中难以快速找到所需内容,导致每次修改都变得更长、更痛苦。

  • 业务逻辑分散:业务逻辑散布在多个层中,查看某项功能的工作原理时,唯一选择是调试整个代码库。更糟的是,逻辑往往在多个地方重复。

  • 业务与技术耦合:业务规则依赖具体的技术,如果后续替换某个技术组件成本非常大。

2. 特征

  • 独立性:使得业务逻辑和外部依赖关注点分离。

  • 可测试性:易于单元测试和集成测试。

  • 细节分离:独立于 UI、数据库、框架。低层细节都是可以被替换的

  • 易维护:降低模块之间的耦合,便于修改和扩展。

3. 依赖规则

整洁架构将软件分为不同层次,内圈代表高层策略,外圈代表具体细节。通常,越接近核心,抽象程度和稳定性越高;越靠近外层,细节实现越具体。

  • 核心层(实体和用例)不应依赖外部层(接口适配器和框架)。
  • 所有的依赖都应该从外部向内流动,确保技术细节不影响核心业务逻辑。

4. 分层逻辑

Entities

  • 关键业务实体和行为,比如电商领域中的订单、客户、商品

  • 普通的 java 对象

  • 非数据模型 ,业务模型要和数据模型区分开

Use Cases

  • 表示您的业务操作,即您可以使用应用程序执行的操作。每个业务操作都有一个用例
  • 纯业务逻辑,普通 Java
  • 为应用某些逻辑所需的数据定义接口。一个或多个数据提供者将实现该接口,但用例不知道数据来自哪里
  • 用例不知道是谁触发了它,可能是 web api 、grpc、dubbo、akka 等入口处理器
  • 抛出业务异常

Interface Adapters

  • 将业务模型适配为外部数据模型

  • 将外部模型适配为内部数据模型

  • 防腐层

  • 可选的

Frameworks and Drivers.

  • 从多个来源(数据库、网络设备、文件系统、第三方等)检索和存储数据

  • 实现用例定义的接口

  • 使用最合适的框架(无论如何它们都会在这里被隔离)

  • 是与应用程序交互的方式,通常涉及交付机制(例如 REST API、计划作业、其他系统)

  • 触发用例并将结果转换为适合交付机制的格式

5. 分层结构

单模块结构:

{{业务领域}}-Service
├── main
│   ├── configaction
│   ├── Main.java
├── core
│   ├── entity
│   │   ├── {{实体}}: Customer
│   │   ├── {{实体}}Factory (Optional): CustomerFactory
│   ├── usecase
│   │   ├── model
│   │   │   ├── {{Action}}Input
│   │   │   ├── {{Action}}Output
│   │   ├── exception
│   │   ├── repository
│   │   │   ├── {{Entity}}Repository
│   │   ├── {{Action}}UseCase.java: CreateCustomerUseCase.java
├── infrastructure
│   ├── store
│   │   ├── {{StoreType}}
│   │   │   ├── model
│   │   │   ├── {{StoreType}}{{实体}}Repository: MysqlCustomerRepository
│   ├── client
│   │   ├── {{Entity}}Client
│   │   ├── model
│   ├── http
│   │   ├── {{Entity}}RestResource
│   │   │   ├── model
│   │   │   │   ├── {{Action}}Req
│   │   │   │   ├── {{Action}}Resp
│   │   │   ├── exception
│   │   │   ├── mapper
│   ├── dubbo
│       ├── {{}}-Dubbo-Client
│       │   ├── {{Entity}}RpcResource

{{业务领域}}-Client

多模块结构:

将单模块一级 package 拆分为子模块,如果需要可以进一步细化 infrastructure 子模块结构。

{{业务领域}}-Service
├── pom.xml (父模块 POM,管理公共依赖和子模块)
├── main (启动模块,包含应用启动类)
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   ├── configaction
│   │   │   │   ├── Main.java
├── core (核心业务逻辑模块)
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   ├── entity
│   │   │   │   │   ├── Customer.java
│   │   │   │   │   ├── CustomerFactory.java
│   │   │   │   ├── usecase
│   │   │   │   │   ├── model
│   │   │   │   │   │   ├── CreateCustomerInput.java
│   │   │   │   │   │   ├── CreateCustomerOutput.java
│   │   │   │   │   ├── exception
│   │   │   │   │   ├── repository
│   │   │   │   │   │   ├── CustomerRepository.java
│   │   │   │   │   ├── CreateCustomerUseCase.java
├── infrastructure (基础设施模块)
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   ├── store
│   │   │   │   │   ├── mysql
│   │   │   │   │   │   ├── MysqlCustomerRepository.java
│   │   │   │   ├── client
│   │   │   │   │   ├── CustomerClient.java
│   │   │   │   ├── http
│   │   │   │   │   ├── CustomerRestResource.java
│   │   │   │   │   ├── model
│   │   │   │   │   │   ├── CreateCustomerReq.java
│   │   │   │   │   │   ├── CreateCustomerResp.java
│   │   │   │   │   ├── exception
│   │   │   │   │   ├── mapper
│   │   │   │   ├── dubbo
│   │   │   │   │   ├── CustomerRpcResource.java

依赖约束

  • Entities 不依赖任何其他层。

  • Use Cases 可以依赖 Entities。

  • Infrastructure 可以依赖 Use Cases 和 Entities。

  • Main 可以依赖任何其他层,但不允许被其他层依赖。

Entities (核心业务实体)

模块:core -> entity

  • 功能:定义核心的业务实体和领域逻辑。

  • 模块解释

    • {{实体}}: 领域实体(如 Customer),表示业务的核心对象。
    • {{实体}}Factory: (可选)用于创建或管理实体对象的工厂类,封装复杂的初始化逻辑。

Use Cases (应用用例层)

模块:core -> usecase

  • 功能:定义应用的业务用例,编排实体的行为以满足用例的需求。

  • 模块解释

    • {{Action}}UseCase.java(如 CreateCustomerUseCase.java):用例类,表示一个具体的业务操作(如创建用户)。

    • model:

      • {{Action}}Input:用例的输入参数。
      • {{Action}}Output:用例的输出结果。
    • repository: 抽象接口(如 CustomerRepository),用于定义与数据存储交互的行为。

    • exception: 用例特定的异常,捕获或处理业务逻辑中的错误场景。


Infrastructure (基础设施层)

模块: infrastructure -> client / http / store ``/ dubbo

  • 功能:负责将外部输入(如 HTTP 请求、RPC 调用等)转换为应用层可以理解的形式,或者将应用层的输出转换为外部需要的形式。

  • 模块解释

    • Client(如 CustomerClient

      • 与外部服务或模块进行交互,封装第三方 API 调用逻辑。
    • HTTP(如 CustomerRestResource

      • 提供 HTTP 接口(如 REST API)。
      • {{Action}}Req / {{Action}}Resp:定义 HTTP 请求和响应的模型。
      • exception: 处理 HTTP 层的特定异常。
      • mapper: 映射器,用于在 HTTP 层模型和用例模型之间转换。
    • Store(如 MysqlCustomerRepository

      • 持久化逻辑的具体实现,负责将数据存储在数据库中。
      • 实现 core -> usecase -> repository 中定义的接口。
    • Dubbo 相关模块

      • {{Entity}}RpcResource:RPC 调用的入口,作为 Dubbo 服务的暴露点。
      • {{}}-Dubbo-Client:封装 Dubbo 客户端依赖。

Main (集成层)

模块:` main ```

  • 功能:上帝类,负责将整个子组件集成运行起来。

6. 案例分析

clean-architecture-java

7. 实施成本分析

  • 团队需要具备整洁架构的概念

  • 初始设计成本较高。

  • 过度分层可能导致额外的复杂性。

  • 为了增加扩展性。代码数会上升

8. 总结

架构是为了解决复杂性,应根据项目需求适当简化。Clean Architecture 提供了良好的分层结构,是开发复杂应用的一种优雅选择。