在开发软件时,我们总是渴望体系结构,因为我们希望我们的软件在未来可以很容易地进行更改。
因此,架构出于可维护性的考虑而介入。在这里,可维护性意味着我们可以在不干扰旧代码的情况下轻松地合并更改。
软件中的每一个新增功能都伴随着风险:破坏程序的风险、可维护性、刚性、依赖性等等,有很多种不同程度的风险,我们称之为技术债务。
技术债务与可维护性成反比关系,使用我们想要降低风险的架构风格,或者我应该说在添加新功能时保持一致。
我们体验到,当我们开始编写代码时,添加功能是很容易的,而逐渐地,除非我们为该项目使用适当的架构,否则添加功能会变得越来越困难。
在一句话中,六角形建筑说:核心业务逻辑通过契约与应用程序的其他部分通信。
您的软件开发逻辑应该通过契约或Java接口与其他部分通信,即数据库、任何JMS队列、任何平面文件、HTTP请求、FTP等。
这种设计的好处在于,无论输入或输出是什么,每个通道都必须实现相同的契约/接口来与我们的软件对话。因此,从我们的角度来看,所有通道都是相同的,因为所有通道都实现相同的合同,因此我们的软件可以处理任何类型的输入和输出。
轻松集成任何通道,如平面文件、RDBMS、NoSQL、http、FTP、JMS等。
我们的软件可以很容易地进行测试,因为在需要实现合同时很容易创建模拟。
添加新需求意味着添加插件或执行合同。
适当分离关注点。
通过合同保持IOC的高水平和低水平谈判。
有时我们称六边形架构为端口和适配器或洋葱架构。对于这些样式,我们为每个通道都有一个端口,例如一个数据库。
我们要执行的任务包括:
履行合同,与我们的软件对话。正如数据库API所说的,JDBC本身是一个契约,或者Hibernate本身是一个框架,我们需要创建一个GOF适配器模式,在该模式中我们可以实现一种策略,将JDBC相关的操作转换为我们的契约相关的操作。
将此适配器插入我们的端口以与此通道通信。
package com.example.architecture.hexagonal;
public interface IPersists<T,TCOMMAND> {
public void save(T t,TCOMMAND commandObject);
public void delete(T t,TCOMMAND commandObject);
}
这是与我们软件对话的总合同。任何输出通道都应该实现该约定。
在这里,我举了一个示例,说明它如何与将数据保存在数据库中的JPA实体进行对话。
package com.example.architecture.hexagonal;
public class DataBaseChannelAdapter implements IPersists<EmployeeDomainObject,EmployeeCommand>{
public void save(EmployeeDomainObject t, EmployeeCommand commandObject) {
String underLyingJPAEntity = commandObject.getEntityClass();
System.out.println("call save on " + underLyingJPAEntity);
}
public void delete(EmployeeDomainObject t, EmployeeCommand commandObject) {
String underLyingJPAEntity = commandObject.getEntityClass();
System.out.println("call delete on " + underLyingJPAEntity);
}
}
因为JPA实体使用一些与JPA相关的注释,所以我没有将此JPA实体包括在我们的域中。我使用JPA框架作为我们软件域的外部通道,DataBaseChannelAdapter采用我们的核心域Employee对象和Command对象,该对象告诉适配器调用哪个JPA实体。
package com.example.architecture.hexagonal;
public class EmployeeDomainObject {
public String name;
public String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "EmployeeDomainObject [name=" + name + ", address=" + address
+ "]";
}
}
我们的核心域Employee对象不依赖于任何框架,因此我可以通过适配器将任何框架插入其中。例如,我可以轻松地将DatabaseAdapter更改为FileAdepter,以便将我们的核心域对象保存到File中。
package com.example.architecture.hexagonal;
public class EmployeeCommand{
public String entityClass;
public String getEntityClass() {
return entityClass;
}
public void setEntityClass(String entityClass) {
this.entityClass = entityClass;
}
@Override
public String toString() {
return "EmployeeCommand [entityClass=" + entityClass + "]";
}
}
此命令对象帮助适配器将核心域对象转换为基础输出通道(数据库或文件)。
package com.example.architecture.hexagonal;
public class EmployeeDomainDao<T,TCommand>{
IPersists<T,TCommand> adapter;
public void save(T t,TCommand commandObject) {
adapter.save(t, commandObject);
}
public void delete(T t,TCommand commandObject) {
adapter.delete(t, commandObject);
}
public IPersists<T, TCommand> getAdapter() {
return adapter;
}
public void setAdapter(IPersists<T, TCommand> adapter) {
this.adapter = adapter;
}
}
这是我们的核心域持久层。基于适配器,它将调用确切的输出通道并持久化我们的域对象。
package com.example.architecture.hexagonal;
public class Main {
public static void main(String[] args) {
EmployeeDomainDao<EmployeeDomainObject,EmployeeCommand> dao = new EmployeeDomainDao<EmployeeDomainObject,EmployeeCommand>();
IPersists<EmployeeDomainObject,EmployeeCommand> adapter= new DataBaseChannelAdapter();
dao.setAdapter(adapter);
EmployeeDomainObject emp = new EmployeeDomainObject();
emp.setName("Shamik Mitra");
emp.setAddress("India,Kolkata");
EmployeeCommand command = new EmployeeCommand();
command.setEntityClass("com.employeemanagement.entity.EmployeeJpaEntity");
dao.save(emp, command);
dao.delete(emp, command);
}
}
Object StateEmployeeDomainObject [name=Shamik Mitra, address=India,Kolkata]
call save on com.employeemanagement.entity.EmployeeJpaEntity
Object StateEmployeeDomainObject [name=Shamik Mitra, address=India,Kolkata]
call delete on com.employeemanagement.entity.EmployeeJpaEntity
图片由谷歌提供
应用程序域:正如我前面所说的,它是所有与软件相关的业务逻辑和验证进行的软件。这是每个外部模块通过合同进行对话的模块。
应用/中介层:有点像服务层,它采用框架层或外层,并根据域层契约进行必要的修改,与域层进行对话,或者以同样的方式将结果从域返回给框架层。它位于域和框架层之间。
框架层:此层用于输入/输出通道,也称为。并使用适配器来适配数据,并根据我们的软件合同对其进行转换。