整合数据库
数据库在外层,但在现实中如何工作呢?
你需要在用例层创建一个技术无关接口,并在网关层实现它。我想这就是为什么那一层被称为接口适配器,因为你在此处调整内部层次定义的接口。例如:
public interface OrderRepository {
public List<Order> findByCustomer(Customer customer);
}
实现在网关层
public class HibernateOrderRepository implements OrderRepository {
...
}
在运行时,您将实现实例传递给用例的构造函数。由于该用例仅依赖于接口(如上例中的OrderRepository
),因此您不必对网关实现进行源代码依赖。
通过扫描您的导入语句,您可以看到这一点。
其中一个用例是管理人员。管理人员是保存/检索/..人员(=> CRUD操作),但为了做到这一点,Usecase需要与数据库通信。但这将违反依赖规则
不,那不会违反依赖规则,因为用例定义了它们需要的接口。数据库只是实现它。
如果使用maven管理应用程序依赖项,则会看到数据库jar模块依赖于用例而不是相反。
+-----+ +-----------+
| db | --> | use cases |
+-----+ +-----------+
将这些用例接口提取为一个独立的模块可能更好。这可以防止db模块依赖于用例模块的依赖项。
那么模块依赖关系就会变成这样
+
| db |
+
两个选项都是依赖关系的反转,否则依赖关系会像这样:
+
| db | <
+
整合Web层
如果我收到了一个GET /person/{id}请求,我的微服务应该这样处理吗?
不应该这样做,因为Web层访问了DB层。更好的方法是Web层访问控制器层,控制器层访问用例层,用例层访问存储库,可以是数据库存储库,也可以是任意外部系统。
为了保持依赖反转,您必须使用接口将层解耦,如上所示。
因此,如果您想将数据传递到内部层,则必须在内部层引入定义获取所需数据的方法的接口,并在外部层中实现它。换句话说,您需要将外部层适配到内部层。我想这就是为什么Bob大叔称此层为接口适配器的原因。
在控制器层中,您将指定一个接口,如下所示
public interface ControllerParams {
public Long getPersonId();
}
在Web层中,您可以像这样实现您的服务
@Path("/person")
public PersonRestService {
private SomeController someController;
@Get
@Path("{id}")
public void getPerson(PathParam("id") String id){
try {
Long personId = Long.valueOf(id);
someController.someMethod(new ControllerParams(){
public Long getPersonId(){
return personId;
}
});
} catch (NumberFormatException e) {
}
}
}
乍一看似乎是样板代码,但请记住,您可以让rest框架将请求反序列化为Java对象。而这个对象可能实现了ControllerParams
。
如果您始终遵循依赖反转规则和清洁架构,您将永远不会在内部层中看到外部层类的导入语句。
那么我们为什么要做这个努力呢?
清洁架构的目的是主要业务类不依赖任何技术或环境。由于依赖关系从外部到内部层,外部层发生变化的唯一原因是因为内部层发生变化或者如果您要更换外部层的实现技术。例如Rest -> SOAP。
罗伯特·C·马丁在第5章面向对象编程的结尾部分在依赖反转的部分告诉我们:
采用这种方法,使用面向对象语言编写系统的软件架构师可以对系统中所有源代码依赖项的方向拥有绝对掌控权。 他们不受约束地将这些依赖项与控制流对齐。 无论哪个模块进行调用,哪个模块被调用,软件架构师都可以将源代码依赖性指向任何方向。
这就是力量!
控制流和源代码依赖关系
我猜开发人员经常会对控制流和源代码依赖之间的区别感到困惑。
控制流描述运行时调用的顺序。它引入的依赖关系称为运行时依赖项。
源代码依赖关系是指源代码中出现的类型所依赖的关系。在像Java这样的语言中,需要导入类型。这就是为什么导入语句几乎包括所有源代码依赖项的原因。我说几乎所有,因为同一包中的类型不需要导入。
依赖反转意味着源代码依赖关系与控制流相反。依赖反转为我们提供了创建插件体系结构的机会。每个接口都是插入点,可以出于领域、技术或测试原因交换。
编辑
网关层 = 接口OrderRepository => OrderRepository-Interface不应该在UseCases中吗?因为我需要在那个层级上使用CRUD操作?
是的,OrderRepository
接口应该在用例层中定义。我们经常犯的一个错误是认为接口属于实现者。但是,接口属于客户端。客户端告诉接口它想要什么,但保持开放如何完成。
还要考虑应用接口隔离原则并定义特定于用例的接口,例如PlaceOrderUseCaseRepository
接口,而不仅仅是每个用例都使用的OrderRepository
。
您应该这样做的原因是防止通过公共接口耦合用例,并遵守单一责任原则。专门用于一个用例的存
public interface PlaceOrderRepository {
public void storeOrder(Order order);
}
还有一个使用案例的界面可能是这样的:
public interface CancelOrderRepository {
public void removeOrder(Order order);
}