如何结构化我们的代码

软件设计最佳实践,探讨按层、按特性和六边形架构/端口和适配器的代码结构。

在这篇文章中,我将探讨如何组织我们的代码并讨论最佳实践,涵盖三种不同的方法:按层、按特性和六边形架构/端口和适配器,以及它们的优缺点。

在探讨不同的代码结构方法之前,我们需要了解基本的软件设计原则:

  • 内聚性(Cohesion):指模块内部的类彼此相关的程度。
  • 耦合性(Coupling):指不同模块之间的依赖程度。

1*2Q8ye2K8hYRtNOMHWxoOBQ.png

  • 模块性(Modularity):指软件系统被分成独立模块的程度。每个模块封装了一组特定的功能,并被设计成能够独立工作,通过明确定义的接口与其他模块交互。
  • 抽象化(Abstraction):隐藏实现细节,只通过接口暴露必要的功能。
  • 关注点分离(Separation of Concerns):具有明确定义的各个部分,每个部分处理特定的关注点。
  • 封装性(Encapsulation):将数据和方法捆绑到单个模块或类中,以隐藏内部细节。

让我们更仔细地看看内聚性和耦合性?

内聚性描述软件的 关注点有多集中它与单一职责原则密切相关。

  • 高内聚性 意味着模块内的类彼此密切相关,并共享一个共同的、明确定义的目的。
  • 低内聚性 意味着模块内的类关系不紧密,缺乏明确的目的,具有无关的责任。

遵循的最佳实践是追求高内聚性和松耦合

1*6DpCfIP-T09sZBfxu8G2DA.png

松散的耦合被认为是计算机系统良好结构和良好设计的迹象,与高内聚性结合使用时,可实现高可读性和可维护性。

1*WVS54T-5rqEvL7xf1AeADA.png

现在,让我们探讨不同的代码组织方式。首先,我将介绍按层组织,然后是按特性组织,对比这两者。之后,我们将探讨六边形架构/端口和适配器模式。

1*fmBZBkeP4RAmM9DwFoynyQ.png

按层组织

它表示一个项目结构,其中类被组织到多个层中,每个层负责一组特定的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
src
├── main
│ ├── java
│ │ └── com
│ │ └── app
│ │ ├── service
│ │ │ └── UserService.java
│ │ │ └── OrderService.java
│ │ │ └── ProductService.java
│ │ ├── domain
│ │ │ └── User.java
│ │ │ └── Order.java
│ │ │ └── Product.java
│ │ ├── repository
│ │ │ └── UserRepository.java
│ │ │ └── OrderRepository.java
│ │ │ └── ProductRepository.java
│ │ ├── controller
│ │ │ └── UserController.java
│ │ │ └── OrderController.java
│ │ │ └── ProductController.java

典型的层包括:

  1. 表示层(Presentation Layer):负责处理用户交互并向用户呈现信息。通常包括与用户界面、控制器和视图相关的组件。
  2. 服务层(Service Layer):包含业务逻辑并提供演示层所需的数据。
  3. 领域包(Domain Package):该包包含领域实体。
  4. 数据访问层(Data Access Layer):该层处理数据到/从数据库的持久化和检索。
  5. 基础设施包(Infrastructure Package):该包提供支持应用程序操作的服务。它可能包括用于日志记录、配置、安全等横切关注点的组件。

使用 按层组织 的一些缺点:

  • 低内聚性:不相关的类被组合到同一个包中。
  • 高耦合性
  • 封装性差:大多数类是公共的,因此我们无法将类设置为包私有,因为它们在其他层中是需要的。
  • 低模块性:由于每个包包含与特定层相关的类,因此很难将代码分解为后来的微服务。
  • 可维护性差:由于类散布在不同的包中,很难找到正在寻找的类。
  • 它促进了数据库驱动设计,而不是领域驱动设计。

按特性组织

它表示一种根据功能或特性而不是层次结构组织代码的结构。在这种方法中,每个包代表一个独特且独立的功能。

目标是将与特定特性相关的所有组件(如控制器、服务、存储库和领域类)组合到一个包中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
src
├── main
│ ├── java
│ │ └── com
│ │ └── app
│ │ ├── user
│ │ │ ├── UserController.java
│ │ │ ├── UserService.java
│ │ │ └── UserRepository.java
│ │ ├── order
│ │ │ ├── OrderController.java
│ │ │ ├── OrderService.java
│ │ │ └── OrderRepository.java
│ │ ├── product
│ │ │ ├── ProductController.java
│ │ │ ├── ProductService.java
│ │ │ └── ProductRepository.java

使用此结构的一些好处包括:

  • 高内聚性
  • 低耦合性
  • 强封装性:允许某些类将其访问修饰符设置为包私有而不是公共。
  • 高模块性:由于每个包包含与特定功能相关的类,因此很容易将代码分解为后来的微服务。
  • 可维护性:减少了在包之间导航的需求,因为所有与特性相关的类都在同一个包中。
  • 促进了领域驱动设计

六边形架构/端口和适配器模式

六边形架构,也称为端口和适配器,是由阿利斯泰尔·科克本博士在他2005年的一篇文章中介绍的软件架构模式。

该模式通过保持核心业务逻辑独立于外部细节,并不紧密耦合于数据库、用户界面或外部服务等外部依赖,促进了关注点的隔离/分离。

这使得测试、维护和发展系统更加容易。

1*wF_VJcN5dpwCQB_sx1z2_w.png

在此模式中:

  1. 领域/核心(Domain / Core)

    代表应用程序的业务逻辑或领域(应用程序的核心)。

  2. 端口(Ports)

    端口是核心定义的接口,允许与外部组件进行交互。这些可以包括服务、存储库或任何外部依赖的接口。

  3. 适配器(Adapters)

    适配器是端口的实现。它们将核心应用程序连接到数据库、用户界面和外部服务等外部组件。适配器可以针对不同的技术或协议进行特定的实现。

  4. 主要操作者(Primary actors)

    系统的使用者,如webhook、UI请求或测试脚本。

  5. 次要操作者(Secondary actors)

    被应用程序使用的这些服务是 Repository(例如数据库)或 Recipient(例如消息队列)。

1*AUdml5HCo92xf8WgHkZfRA.gif

六边形形状:

六边形形状象征着核心应用程序位于中心,周围是适配器。这个形状代表了核心与其外部依赖之间的明确分离。

顶级包结构应如下所示:

1
2
3
4
5
6
7
8
9
src/main/
java
mina
dev
<servicename>
adapters
config
core
<ServiceApplication>.java

根包应只包含包:coreadaptersconfig

  • core 包含服务的所有领域逻辑。它可以包含子包。
  • 端口应该位于 core 包中:端口只是由核心声明的要调用或由适配器实现的接口。
  • adapters 包含所有适配器实现代码。它可以包含子包以按单个适配器或技术组织适配器代码。
  • config 包含用于连接不同组件的配置类。

包依赖规则:

  • 根包可以依赖于所有其他包。
  • config 包可以依赖于 coreadapters
  • adapters 可以依赖于 core,但不可以依赖于 config
  • core 不得依赖于其他任何包。

希望这篇文章能够帮助您更好地理解不同的代码结构。

系统设计概念系列文章

计算机的层次化架构

每个开发者都应该知道的7个原则

6个系统设计的基本概念

数据库:系统设计的核心

图解系列

系统设计中的缓存技术:完整指南

关系数据库的全景图

Redis 全景解析

当然架构设计、全景图解系列还有很多,快来关注一起学习吧~

请我吃🍗