在本教程中,我们将使用 Hexagonal Architecture 的原理,使用 CLI 使用者实现一个简单的 Java CMS 应用程序。主要思想是尽可能保持业务逻辑分离,并使用SOLID原则中的“ D”依赖反转原则来防止层之间的耦合。
它是一种围绕业务逻辑设计软件应用程序架构并将其与其他层解耦的方法。解耦是通过使用端口和适配器来处理的,这就是为什么 Hexagonal Architecture 也被称为Ports & Adapters的原因。
您可以看到由 Hexagonal 架构分层的典型应用程序的几个关键特征;
该项目包含 2 个根包:
对于每个域对象,在我们的例子中,我们有一个包来将端口和用例保存在域包中。在基础设施包中,我们为每个域对象提供了适配器包。
我们的领域模型,Article 是用来说明文章信息的,相关实现如下;
1 2 3 4 5 6 | public record Article( Long id, Long accountId, String title, String body ) {} |
这是一个简单的 Java POJO 类来声明文章信息,让我们看看如何在 Port 接口中编写合约
如果你想定义一个包含创建、检索和查询行为的端口,下面的接口将是一个不错的选择;
1 2 3 4 5 6 7 8 | public interface ArticlePort { Article create(ArticleCreate articleCreate); Article retrieve(Long articleId); List } |
看上面的接口就可以理解返回值是一个领域模型,但是关于函数参数呢?
应用程序由一个或多个用例组成,您可以在这些用例中使用端口作为依赖项,同时定义步骤以满足业务逻辑方面的要求。这里我们说端口,因为消费者不会直接使用它们,而是使用实现。
1 2 3 4 5 6 7 8 9 10 11 12 | public class ArticleRetrieveUseCase { final ArticlePort articlePort; public ArticleRetrieveUseCase(ArticlePort articlePort) { this.articlePort = articlePort; } public Article retrieve(ArticleRetrieve useCase){ return this.articlePort.retrieve(useCase.id()); } } |
在ArticleRetrieveUseCase类中,我们有一个检索用例函数,它使用ArticlePort并且永远不知道实际的实现是什么,因为它不依赖于具体的实现。
用例请求也在用例包中,这是文章创建操作的示例
1 2 3 4 5 | public record ArticleCreate( Long accountId, String title, String body ) {} |
在这部分之前,我们主要看到接口,在基础设施包中,我们将看到它们的实现。想想你依赖一个数据库,如果你想把数据持久化到数据库中,你需要使用 MySQL、mongo 或 Cassandra 等 JPA 相关的技术……这些技术特定的实现都是适配器,它们需要满足我们在端口实现中提供的签名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class ArticleImMemoryDataAdapter implements ArticlePort { private final ConcurrentHashMap @Override public Article create(ArticleCreate articleCreate) { long id = (articles.size() + 1); Article article = new Article(id, articleCreate.accountId(), articleCreate.title(), articleCreate.body()); articles.put(id, article); return article; } @Override public Article retrieve(Long articleId) { return articles.get(articleId); } @Override public List return articles.values().stream() .filter(a-> a.accountId().equals(articleQuery.accountId())) .collect(Collectors.toList()); } } |
在上面的示例中,您可以看到持久层只是一个内存映射,用于保存文章数据并通过该映射获取。
在大多数应用程序中,您会看到有 REST、gRPC 等接口让消费者通过该协议使用它。在这个例子中,我们将看到一个直接使用文章领域模型的公开用例的 CLI 界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class ArticleCli implements ArticlePort { private final ArticleCreateUseCase articleCreateUseCase; private final ArticleRetrieveUseCase articleRetrieveUseCase; private final ArticleQueryUseCase articleQueryUseCase; public ArticleCli(ArticleCreateUseCase articleCreateUseCase, ArticleRetrieveUseCase articleRetrieveUseCase, ArticleQueryUseCase articleQueryUseCase) { this.articleCreateUseCase = articleCreateUseCase; this.articleRetrieveUseCase = articleRetrieveUseCase; this.articleQueryUseCase = articleQueryUseCase; } @Override public Article create(ArticleCreate articleCreate) { ArticleCreate article = new ArticleCreate(articleCreate.accountId(),articleCreate.title(),articleCreate.body()); return this.articleCreateUseCase.create(article); } @Override public Article retrieve(Long articleId){ return this.articleRetrieveUseCase.retrieve(ArticleRetrieve.from(articleId)); } @Override public List return this.articleQueryUseCase.query(ArticleQuery.from(articleQuery.accountId())); } } |
ArticleCli依赖于ArticlePort,让我们看看我们如何在示例 Java 应用程序中构建和使用ArticleCli 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class Application { static Logger log = Logger.getLogger(Application.class.getName()); public static void main(String[] args) { ArticleImMemoryDataAdapter articleImMemoryDataAdapter = new ArticleImMemoryDataAdapter(); ArticleCli articleCli = new ArticleCli( new ArticleCreateUseCase(articleImMemoryDataAdapter), new ArticleRetrieveUseCase(articleImMemoryDataAdapter), new ArticleQueryUseCase(articleImMemoryDataAdapter)); Article article = articleCli.create(new ArticleCreate(5L, "Hexagonal in 5 Minutes", "Hexagonal architecture is initially suggested...")); log.info("Article is created " + article); Article articleDetails = articleCli.retrieve(1L); log.info("Article details "+articleDetails); List log.info("Found articles " + result); } } |
ArticleCli接受ArticlePort的任何实现,在我们的例子中是ArticleImMemoryDataAdapter。通过使用这种表示法,您可以轻松地在测试中构建假实现,而不是尝试在持久层中模拟第三方库。执行此主应用程序后,您将看到以下内容
1 2 3 4 5 6 | Oct 23, 2021 12:26:49 AM com.huseyin.hexagonal4j.Application main INFO: Article is created Article[id=1, accountId=5, title=Hexagonal in 5 Minutes, body=Hexagonal architecture is initially suggested...] Oct 23, 2021 12:26:49 AM com.huseyin.hexagonal4j.Application main INFO: Article details Article[id=1, accountId=5, title=Hexagonal in 5 Minutes, body=Hexagonal architecture is initially suggested...] Oct 23, 2021 12:26:49 AM com.huseyin.hexagonal4j.Application main INFO: Found articles [Article[id=1, accountId=5, title=Hexagonal in 5 Minutes, body=Hexagonal architecture is initially suggested...]] |
六边形架构帮助我们组织层以解耦域逻辑以拥有更多可维护的软件应用程序。这将我们的业务逻辑与外部层解耦,外部层通过在适配器中实现端口来访问域层。这将使您可以自由地将功能添加/更新/删除到您的业务逻辑中,而不必担心其他层上的问题。
您可以在此处访问示例的源代码:https://github.com/huseyinbabal/hexagonal-architecture
留言与评论(共有 0 条评论) “” |