https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
我对此模式有疑问。数据库位于外层,但实际上如何工作?例如,如果我有一个仅管理此实体的微服务:
person{
id,
name,
age
}
用例之一是管理人员。"管理人员"正在保存/检索/ ..人员(=> CRUD操作),但是要做到这一点,用例需要与数据库对话。但这将违反依赖性规则
The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards.
这甚至是一个有效的用例吗?
如果数据库位于外层,如何访问数据库? (依赖性转换?)
如果我收到GET /person/{id}请求,我的微服务应该这样处理吗?
但是,使用依赖倒置将违反
Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes, functions, classes. variables, or any other named software entity.
Crossing boundaries.
At the lower right of the diagram is an example
of how we cross the circle boundaries. It shows the Controllers and
Presenters communicating with the Use Cases in the next layer. Note
the flow of control. It begins in the controller, moves through the
use case, and then winds up executing in the presenter. Note also the
source code dependencies. Each one of them points inwards towards the
use cases.
We usually resolve this apparent contradiction by using the Dependency
Inversion Principle. In a language like Java, for example, we would
arrange interfaces and inheritance relationships such that the source
code dependencies oppose the flow of control at just the right points
across the boundary.
For example, consider that the use case needs to call the presenter.
However, this call must not be direct because that would violate The
Dependency Rule: No name in an outer circle can be mentioned by an
inner circle. So we have the use case call an interface (Shown here as
Use Case Output Port) in the inner circle, and have the presenter in
the outer circle implement it.
The same technique is used to cross all the boundaries in the
architectures. We take advantage of dynamic polymorphism to create
source code dependencies that oppose the flow of control so that we
can conform to The Dependency Rule no matter what direction the flow
of control is going in.
用例层是否应声明一个数据库接口,该接口将由数据库包实现(框架和驱动程序层)
如果服务器收到GET /persons/1请求,则PersonRest将创建一个PersonRepository并将此存储库+ ID传递给ManagePerson :: getPerson函数,getPerson不知道PersonRepository但知道其实现的接口,因此它不违反任何规则吗?
ManagePerson :: getPerson将使用该存储库来查找该实体,并将Person实体返回给PersonRest :: get,这将向客户返回Json Objekt吗?
不幸的是,英语不是我的母语,所以我希望你们能让我知道我是否理解正确的模式,并可能回答我的一些问题。
提前输入
我个人觉得干净的架构思想太复杂了,我更喜欢Onion架构,我已经使用该架构创建了一个示例项目
The Database is at outter Layer but how would that work in reality?
您在网关层中创建一个技术独立的接口,并在数据库层中实现它。例如。
public interface OrderRepository {
public List findByCustomer(Customer customer);
}
实现在db层中
public class HibernateOrderRepository implements OrderRepository {
...
}
在运行时,内层将注入外层实现。但是您没有源代码依赖性。
您可以通过扫描导入语句来查看。
And one of the use cases would be manage Persons. Manage Persons is saving / retrieving / .. Persons (=> CRUD operations), but to do this the Usecase needs to talk to a database. But that would be a violation of the Dependency rule
不,这不会违反依赖关系规则,因为用例定义了所需的接口。数据库只是实现它。
如果使用maven管理应用程序依赖关系,您将看到db jar模块取决于用例,反之亦然。但是最好将这些用例接口提取到自己的模块中。
然后模块依赖关系看起来像这样
+-----+ +---------------+ +-----------+
| db | --> | use-cases-api |
+-----+ +---------------+ +-----------+
那是依赖的倒置,否则看起来像这样
+-----+ +-----------+
| db |
+-----+ +-----------+
If i get a GET /person/{id} Request should my Microservices process it like this?
是的,那是违法的,因为Web层访问db层。更好的方法是Web层访问控制器层,控制器层访问用例层,依此类推。
为了保持依赖关系的反转,您必须使用上面显示的接口来分离层。
因此,如果要将数据传递到内部层,则必须在内部层引入一个接口,该接口定义了获取所需数据并在外部层中实现数据的方法。
在控制器层中,您将指定这样的接口
public interface ControllerParams {
public Long getPersonId();
}
在网络层中,您可以这样实现服务
@Path("/person")
public PersonRestService {
// Maybe injected using @Autowired if you are using spring
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) {
// handle it
}
}
}
乍一看,它似乎是样板代码。但是请记住,您可以让其余框架将请求反序列化为Java对象。并且该对象可能改为实现ControllerParams。
因此,如果您遵循依赖关系反转规则和干净的体系结构,您将永远不会在内层中看到外层类的import语句。
干净架构的目的是使主要业务类别不依赖于任何技术或环境。由于依存关系从外层指向内层,因此外层发生变化的唯一原因是由于内层发生了变化。或者,如果您交换外层的实现技术。例如。休息-> SOAP
那么我们为什么要这样做呢?
罗伯特·C·马丁(Robert C. Martin)在第5章"面向对象编程"中讲述了这一点。在依赖倒置一节的最后,他说:
With this approach, software architects working in systems written in OO languages have absolute control over the direction of all source code dependencies in the system. Thay are not constrained to align those dependencies with the flow of control. No matter which module does the calling and which module is called, the software architect can point the source code dependency in either diraction.
That is power!
我猜开发人员经常对控制流和源代码依赖性感到困惑。控制流通常保持不变,但是源代码依赖关系相反。这使我们有机会创建插件体系结构。每个接口都是插入点。因此可以互换,例如由于技术或测试原因。
编辑
gateway layer = interface OrderRepository => shouldnt the OrderRepository-Interface be inside of UseCases because i need to use the crud operations on that level?
我认为可以将OrderRepository移入用例层。另一个选择是使用用例的输入和输出端口。用例的输入端口可能具有类似存储库的方法,例如findOrderById,并将其调整为OrderRepository。为了持久性,它可以使用您在输出端口中定义的方法。
public interface UseCaseInputPort {
public Order findOrderById(Long id);
}
public interface UseCaseOutputPort {
public void save(Order order);
}
与仅使用OrderRepository的区别在于用例端口仅包含特定于用例的存储库方法。因此,它们仅在用例更改时更改。因此,他们只承担一个责任,因此您尊重接口隔离原则。
谢谢@RenLink的回答:)网关层=接口OrderRepository => OrderRepository-Interface是否应该位于UseCases内,因为我需要在该级别上使用crud操作?
关键元素是依赖倒置。内层都不应该依赖于外层。因此,例如,如果用例层需要调用数据库存储库,那么您必须在用例层内部定义存储库接口(只是一个接口,没有任何实现),并将其实现放在接口适配器层中。
您好,首先感谢您的回答! 我会在框架和驱动程序层的db包中放入什么?
以@BarneyStinson为例,实现数据库存储库接口
网关(接口适配器层)和db(框架和驱动程序)=>如果我使用java spring,我应该将存储库放在db中,因为它是一个框架,如果我不使用框架,并且我使用jdbc,则无需编写任何代码 例如我把它放在接口适配器层里面! 我真的不明白数据库和网关之间的区别