Java微服务六边形结构简介

本文将在 Java 中实现 Hexagonal Architecture 的基本概念。

六边形 ​架构 ​​:

六边形架构是一种用于设计软件的架构模式。它旨在创建以核心业务逻辑或领域为中心的松散耦合的可互换软件组件。

原则:

在六边形架构中,应用程序包含三个部分:

  1. 用户端或驱动端
  2. 业务逻辑
  3. 服务器端或驱动端

Java微服务六边形结构简介

用户端:

这是用户或任何外部应用程序与应用程序交互的部分。换句话说,应用程序的用户界面。 这部分的例子:

  1. 控制台或命令行
  2. HTTP API 层和一组处理请求的控制器
  3. 一个定时任务

在这里,我们有调用业务流程的参与者。所以这是应用程序的驱动方面。

业务逻辑:

这部分构成了应用程序的核心。它为来自用户界面的请求提供服务。根据请求,它执行一些特定的逻辑,请求逻辑所需的资源,最后将预定义的响应发送回用户界面。定义业务逻辑的主要组件是:

  • 实体是域对象。他们不知道他们存储在哪里。
  • 存储库具有与服务器端通信并返回单个实体或实体列表的方法列表。
  • 交互器是实现业务逻辑、验证等的特定于领域的服务类。用户端通过交互器调用业务流程。

服务器端:

这一面由支持业务逻辑的服务组成。它们中的每一个都有特定的用途,并为业务逻辑层提供资源/数据。 这部分的例子:

  1. 数据源,如关系数据库(例如 PostgreSQL)、非关系或基于文档的数据库(例如 MongoDB)、文件系统、JSON API 数据源、GraphQL 数据源等。
  2. 另一个服务或​微服务

所以,在这里,我们有由业务逻辑驱动的参与者。

Java微服务六边形结构简介

松耦合和互换性:

现在我们已经了解了系统的基本部分,我们深入研究了部分之间的耦合。到目前为止,我们知道:

  • 用户端 -> 业务逻辑通信通过交互器发生
  • 业务逻辑 -> 服务器端通信通过存储库发生

因此,只要用户端和交互者之间的契约是固定的,用户端的任何参与者,无论是命令行还是 HTTP API 控制器,都可以调用相同的业务流程。类似地,业务逻辑可以从任何类型的数据源或服务接收实体,只要数据源实现存储库中列出的相同方法即可。换句话说,这些通信必须有一个固定的合同。

在 Java 中,这些合约是使用接口实现的。因此,我们已经可以得出结论,存储库只不过是接口。虽然,用户端可以轻松地从 Interactor 或 Service 类调用方法,而无需 Interactor 类实现接口。但是 Interactor 类可以有其他用户端不应该知道的方法。

总而言之,用户端通过业务逻辑中定义的接口来驱动业务逻辑。业务逻辑通过同样在业务逻辑中定义的接口驱动服务器端。

在六边形架构中,我们称接口为ports。

但是,来自用户端和服务器端的不同参与者有不同的技术实现和数据格式。因此,这些参与者必须调整他们的数据格式和逻辑以从端口或接口调用方法。这就是为什么它们在六边形架构中也被称为适配器。

因此,六边形架构有时被称为端口和适配器架构。

Java微服务六边形结构简介

Java 中的示例:

为了理解上述原理,我们将以一个将书籍列表写入标准控制台的命令行应用程序为例。为此,应用程序将在外部文件系统中搜索书籍。组件如下图所示。

Java微服务六边形结构简介

让我们实现应用程序的不同方面:

1、业务逻辑或领域:

这是我们的Book 实体:

<b>public</b> <b>class</b> Book {
<b>private</b> <b>int</b> id;
<b>private</b> String name;
<b>private</b> String author;
<b>public</b> Book(<b>int</b> id, String name, String author) {
<b>this</b>.id = id;
<b>this</b>.name = name;
<b>this</b>.author = author;
} <font><i>// getter 和 setter </i></font><font>
}
</font>

接口/端口:这是我们的接口:

图书服务:

<b>public</b>  IBookService { 
List<Book> getBookList();
}

书库:

<b>public</b>  BookRepository { 
List<Book> getAllBooks ();
}

交互器或服务类。我们将其称为BookService:

<b>public</b> BookService implements  IBookService { 
<b>private</b> BookRepository bookRepository; 公共 BookService(BookRepository bookRepository) {
<b>this</b>.bookRepository = bookRepository;
} @Override
<b>public</b> List<Book> getBookList() {
<b>return</b> bookRepository.getAllBooks();
}
}

用户端:

这是我们的 ConsoleAdapter。我们将其称为BookConsoleUI:

<b>public</b> <b>class</b> BookConsoleUI {
<b>private</b> IBookService bookService;
<b>public</b> BookConsoleUI(IBookService bookService) {
<b>this</b>.bookService = bookService;
}
<b>public</b> <b>void</b> showBooks() {
List<Book> books = bookService.getBookList();
printBooksToConsole(books);
} <b>public</b> <b>void</b> printBooksToConsole(List<Book> books) {
<font><i>// 将书籍打印到控制台的逻辑在这里</i></font><font>
}
}
</font>

服务器端:

这是我们的文件适配器。我们将其称为FileDataSource:

<b>public</b> FileDataSource implements  BookRepository {    
@Override
<b>public</b> List<Book> getAllBooks() {
<b>return</b> getBooksFromFile().stream()
.map(<b>this</b>::mapBookStringToBook)
.collect(Collectors.toList());
}
<b>public</b> List<String> getBooksFromFile() {
<font><i>// 从文件中读取图书数据的代码放在这里</i></font><font>
}
<b>private</b> Book mapBookStringToBook(String bookString) {
String[] bookDetails = bookString.split(</font><font>";"</font><font>);
<b>return</b> <b>new</b> Book(Integer.parseInt(bookDetails[0]), bookDetails[1], bookDetails[2]);
}
}
</font>

一切都放在一起:

让我们看看如何将应用程序的每一端连接在一起:

<font><i>// 实例化 FileAdapter </i></font><font>
FileDataSource fileDataSource = <b>new</b> FileDataSource();
</font><font><i>// 将数据源插入 Interactor </i></font><font>
BookService bookService = <b>new</b> BookService(fileDataSource);
</font><font><i>// 将交互器或服务插入 ConsoleUI </i></font><font>
BookConsoleUI userInterface = <b>new</b> BookConsoleUI(bookService);
</font><font><i>// 通过用户界面驱动业务逻辑</i></font><font>
userInterface.showBooks();
</font>

在这里,我们可以看到,如果我们想要交换数据源,我们只需要实例化该数据源并将其插入 BookService,因为 BookService 将 BookRepository 的引用作为其构造函数参数。

发表评论

相关文章