[译]如何组织代码
软件设计最佳实践,逐层发现包、按功能发现包以及六角形架构/端口和适配器。
在这篇博文中,我将探讨如何构建我们的代码并讨论最佳实践,涵盖三种不同的方法:按层打包、按功能打包以及六边形架构/端口和适配器及其优缺点。
在探索构建代码的不同方法之前,我们需要了解基本的软件设计原则:
- 内聚力:指模块内各类之间的相互关联程度。
- 耦合度:指不同模块之间的依赖程度。
- 模块化:指软件系统被划分为独立模块的程度。每个模块都封装了一组特定的功能,并设计为独立工作,同时通过明确定义的接口相互交互。
- 抽象:隐藏实现细节并仅通过接口公开必要的功能。
- 关注点分离:设有不同的部分,每个部分解决一个特定的关注点。
- 封装:将数据和方法捆绑到单个模块或类中以隐藏内部细节。
让我们仔细看看内聚力和耦合力?
内聚力描述了软件的集中程度。它与单一责任原则密切相关。
- 高内聚力意味着模块内的类紧密相关并具有共同的、明确定义的目的。
- 低内聚力意味着模块内的类之间关系松散,缺乏明确的目的且职责不相关。
要遵循的最佳实践是实现模块之间的高内聚和松散耦合。
松耦合被认为是结构良好和设计良好的计算机系统的标志,与高内聚力相结合,可带来较高的可读性和可维护性。
现在,让我们探索构建代码的不同方式。首先,我将逐层介绍包,然后逐功能介绍包,并比较两者。之后,我们将探索端口和适配器模式。
层封装
它代表一个项目结构,其中类被组织成多个层,每个层负责一组特定的功能。
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
典型的层次包括:
- 表示层:此层负责处理用户交互并向用户呈现信息。它通常包括与用户界面、控制器和视图相关的组件。
- 服务层:该层包含业务逻辑并提供表示层所需的数据。
- 领域包:此包包含领域实体。
- 数据访问层:此层负责处理数据库的数据持久化和检索。
- 基础设施包:此包提供支持应用程序运行的服务。它可能包括日志记录、配置、安全和其他跨切关注点的组件。
以下是使用逐层打包的一些缺点:
- 低内聚力:不相关的类被分组到同一个包中。
- 高耦合
- 封装性差:大多数类都是公共的,因此我们不能将类作为包私有的,因为其他层需要它们。
- 模块化程度低:由于每个包都包含与特定层相关的类,因此以后很难将代码分解为微服务。
- 可维护性差:由于类分散在各个包中,因此很难找到所需的类。
- 它提倡数据库驱动设计而不是领域驱动设计。
按功能打包
它代表一种基于特性或功能而非层来组织代码的结构。在这种方法中,每个包代表一个独特且独立的特性。
目标是将与特定功能相关的所有组件(例如控制器,服务,存储库和域类)组合到一个包中。
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
使用这种结构的一些好处:
- 高内聚力
- 低耦合
- 强封装:允许某些类将其访问修饰符设置为包私有(package-private)而不是公共(public)。
- 高模块化:由于每个包都包含与特定功能相关的类,因此以后很容易将代码分解为微服务。
- 可维护性:由于功能所需的所有类都在同一个包中,因此减少了在包之间导航的需要。
- 促进领域驱动设计
端口和适配器模式(六边形架构)
六边形架构,也称为端口和适配器,是 Alistair Cockburn 博士在 2005 年撰写的一篇文章中引入的一种软件架构模式。
六边形架构
创建无需 UI 或数据库即可运行的应用程序,以便您可以针对…运行自动回归测试。
阿利斯泰尔·科克伯恩
该模式通过保持核心业务逻辑独立于外部细节并且不与数据库、用户界面或外部服务等外部依赖项紧密耦合来促进关注点的隔离/分离。
这使得测试、维护和发展系统变得更加容易。
在此模式下:
- 域/核心:表示应用程序的业务逻辑或域(应用程序的核心)。
- 端口:端口是核心定义的允许与外部组件交互的接口。这些接口可以包括服务、存储库或任何外部依赖项的接口。
- 适配器:适配器是端口的实现。它们将核心应用程序连接到外部组件,例如数据库、用户界面和外部服务。适配器可以特定于不同的技术或协议。
- 主要参与者:系统用户,例如 webhook、UI 请求或测试脚本。
- 次要参与者:由应用程序使用,这些服务要么是存储库(例如数据库),要么是接收者(例如消息队列)。
六边形: 六边形象征着核心应用程序位于中心,周围环绕着适配器。此形状表示核心与其外部依赖项之间的明确分离。
顶层包结构应如下所示:
src/main/
java
mina
dev
<servicename>
adapters
config
core
<ServiceApplication>.java
根包应该只包含以下包:
core
包含服务的所有领域逻辑。它可能包含子包。- 端口应该位于核心包中:端口只是核心声明的、供适配器调用或实现的接口。
adapters
软件包包含所有适配器实现代码。它可能包含子软件包,用于按单个适配器或按技术组织适配器代码。config
包包含用于将不同组件连接在一起的配置类。
包依赖规则:
- 根包可能依赖于所有其他包。
- 软件包
config
可能依赖于core
和adapters
。 - 可能
adapters
取决于core
但不取决于config
。 Core
可能不依赖于任何其他包。
您可以在我的 github 存储库中找到更多详细信息和示例:https://github.com/minarashidi/transfer-service-hexagonal-architecture/tree/main/src/main/java/com/rashidi/transferservice
我希望这篇文章能帮助您更好地理解不同的代码结构。
我非常希望听到您的想法和评论。欢迎分享您的见解或提出任何问题。感谢您的阅读!
Related content
- Spring Boot Tutorials
- [译]Spring Security 和 JWT 入门
- [译]测试 Spring Boot 应用程序:最佳实践和框架
- Spring Boot集成SpringDoc生成Api文档
- Spring Boot项目创建Docker镜像并运行应用
- [译]OAuth2 with Spring 第1部分:了解基本概念
- [译]OAuth2 with Spring 第2部分:授权服务器入门
- [译]OAuth2 with Spring 第3部分:使用Spring授权服务器授予authorization_code OIDC客户端
- [译]OAuth2 with Spring 第4部分:Spring授权客户端与Google授权服务器的社交登录演示
- [译]OAuth2 with Spring 第5部分:使用PKCE保护您的Spring Boot应用程序以增强安全性