The diagram shows where the implementations reside, not the interfaces. The gateway interfaces reside with the use cases.
该图显示了实现驻留的位置,而不是接口。网关接口驻留在用例中。
The diagram is a more elaborate version of Alistair Cockburn's Hexagonal Architecture, which is a lot more precise about the use of adapters, especially in respect to CDI.
该图是Alistair Cockburn的六边形体系结构的一个更精细的版本,它对适配器的使用要精确得多,特别是在CDI方面。
Essentially, the idea is to turn dependencies inward: Use Cases may depend upon Entities (i.e., import domain related packages and use domain data types; Cockburn does not describe these as separate circles, but rather as "the application"). Adapters on the left may call Use Cases (i.e., execute or abort them, e.g. using the Command pattern), while Use Cases in turn may call interfaces (such as service or repository facades, which are implemented by Adapters on the right), using the principle of Dependency Inversion to preserve the inbound direction.
本质上,这个想法是将依赖关系转向内部:用例可能依赖于实体(即,导入与域相关的包并使用域数据类型;Cockburn没有将它们描述为单独的圆,而是将其描述为“应用程序”)。左侧的适配器可以调用用例(即,执行或中止它们,例如使用命令模式),而用例反过来可以调用接口(例如,由右侧的适配器实现的服务或存储库外观),使用依赖关系反转的原则来保持入站方向。
If you're confused about "left" and "right": In Cockburn's picture, the hexagon has adapters on the left (input/interaction mechanisms) and on the right (persistence, external systems, services). You may think of it as a 3-layer-architecture diagram turned 90 degrees, where the domain layer does not depend on the persistence layer, but the other way around.
如果你混淆了“左”和“右”:在Cockburn的图片中,六边形的左侧(输入/交互机制)和右侧(持久性、外部系统、服务)都有适配器。您可能会认为它是一个旋转了90度的3层体系结构图,其中域层不依赖于持久层,反之亦然。
Again, Cockburn's explanation, while perhaps a bit harder to grasp due to the unusual hexagonal image, is the more precise one, because it doesn't try to include everything and concentrates on the dependency fundamentals.
同样,科克伯恩的解释,虽然可能由于不同寻常的六角形图像而更难理解,但更准确,因为它没有试图包括一切,而是专注于依赖的基本原理。
In your BC drawing your are missing a surrounding circle outside UC. That circle is infraestructure, where you should place the Repository implementation of the Repository interface of the Domain Model. The implementation access the DB using a technology (SQL for example).
在你的BC图中,你遗漏了UC外面的一个圆圈。这个圈子就是基础设施,您应该将域模型的Repository接口的Repository实现放在其中。该实现使用一种技术(例如SQL)访问数据库。
So the circles are from inside to outside:
所以圆圈是从里到外的:
- Domain Model (entities, value objects, repositories(interfaces), etc)
- Application Layer (use cases).
- Infraestructure (UI,repositories(implementation),etc)
Dependencies points inwards and not just the next circle inside (infraestructure depends both on application layer and domain model).
依赖关系指向内部,而不仅仅是内部的下一个循环(基础架构取决于应用层和域模型)。
I use hexagonal architecture though.
不过,我使用的是六角形建筑。
The gateway interfaces must reside in the use cases layer and implemented in the interface adapters layer to ensure the dependency rule.
网关接口必须驻留在用例层中,并在接口适配器层中实现,以确保依赖规则。
Interface Adapters Layer || Use Cases Layer
||
+-----------------+ || <implements> +-------------+ <uses> +---------+
|JDBCEntityGateway| --------++--------------> +EntityGateway| <------ | UseCase |
+-----------------+ || +-------------+ +---------+
||
This pattern can be applied to every architectural boundary. The higher level layer defines an interface to tell what it needs and not how it is done. The lower level layer implements that interface and thus defines how it is done. Maybe that's why the layer is named interface adapters
. As a result of this you can change the way of how something is done by providing another implementation. You might recognize now that this is the open-close principle.
这种模式可以应用于每一个建筑边界。更高级别的层定义了一个接口来告诉它需要什么,而不是它是如何完成的。较低级别的层实现该接口,从而定义如何实现该接口。也许这就是该层被命名为接口适配器的原因。因此,您可以通过提供另一个实现来更改某些操作的方式。你现在可能认识到这就是开合原则。
But keep in mind that the interface should be a stable abstraction of what the use case needs. Don't put implementation specific things in that interface, e.g. if you specify a find method like this List<Eintity> find(String where)
. Becauese the where
string is a detail, perhaps a SQL or JPQL string part. You should rather introduce a EntityCriteria
that describes the selection criteria in an implementation independent way.
但请记住,接口应该是用例所需内容的稳定抽象。不要将特定于实现的内容放在该接口中,例如,如果您指定了如下List
Find(字符串where)这样的Find方法。因为WHERE字符串是一个细节,可能是一个SQL或JPQL字符串部分。相反,您应该引入一个EntityCriteria,它以独立于实现的方式描述选择标准。
I find this question and the discussion under it very interesting because we have a comment from the user @RobertMartin and the fact that his answer adds more confusion than clarity.
我觉得这个问题和下面的讨论非常有趣,因为我们收到了用户@RobertMartin的评论,他的答案增加了更多的困惑,而不是清晰。
For the sake of consistency, and keeping only one source of truth, I will not use interpretations of the clean architecture in other articles across the internet I will also ignore the answer under this question by the user with the name @RobertMartin that states that
为了保持一致性,并且只保留一个真理来源,我不会在互联网上的其他文章中使用对干净架构的解释,我也会忽略这个问题下@RobertMartin用户的答案,该用户声明
The diagram shows where the implementations reside, not the
interfaces. The gateway interfaces reside with the use cases.
because it also does not match everything that was written in the book "Clean Architecture: A Craftsman’s Guide to Software Structure and Design by Robert C. Martin (2017)".
因为它也不符合罗伯特·C·马丁在《清洁架构:软件结构和设计工匠指南》(2017)一书中所写的所有内容。
Taking everything above into consideration as if we had only the book I will answer the question from my point of view, my interpretation of the clean architecture that was described in the book and from my personal experience of successful use of the implementation of the "clean architecture".
考虑到以上一切,就好像我们只有这本书一样,我将从我的角度回答这个问题,从我对书中所描述的清洁建筑的理解,以及我成功地使用“清洁建筑”的个人经验。
Let's start with this:
让我们从这个开始:
...following the visual rules, Use Cases cannot use the Gateways (while
they need to) and frameworky-stuff can use them (while it should not).
You know I was thinking about the same question for a week, when I tried to implement the closest implementation of the clean architecture possible, by following the description from the book word for word, because usually, you do not notice the problem until you try to decouple layers.
您知道,当我试图通过逐字遵循书中的描述来实现尽可能接近的干净体系结构的实现时,我花了一周时间思考同样的问题,因为通常情况下,直到您尝试解耦各层,您才会注意到问题。
Let's look at some related quotes from the book, and see how it is described there:
让我们来看看这本书中的一些相关语录,看看书中是如何描述的:
A use case is a description of the way that an automated system is used.
It specifies the input to be provided by the user, the output to be
returned to the user, and the processing steps involved in producing
that output.
A use case is an object. It has one or more functions that implement
the application-specific business rules. It also has data elements
that include the input data, the output data, and the references to
the appropriate Entities with which it interacts. Entities have no
knowledge of the use cases that control them.
DATABASE GATEWAYS
Between the use case interactors and the database are the database
gateways. These gateways are polymorphic interfaces that contain
methods for every create, read, update, or delete operation that can
be performed by the application on the database. For example, if the
application needs to know the last names of all the users who logged
in yesterday, then the UserGateway interface will have a method named
getLastNamesOfUsersWhoLoggedInAfter that takes a Date as its argument
and returns a list of last names. Recall that we do not allow SQL in
the use cases layer; instead, we use gateway interfaces that have
appropriate methods. Those gateways are implemented by classes in the
database layer.
What is "Between the use case interactors and the database"? Of course "Interface adapters" layer, so the book gives us an instruction on implementing a gateway interface and now we know that it should be in the interface adapters layer and implemented in the outer frameworks and drivers layer.
什么是“用例交互角色和数据库之间”?当然是“接口适配器”层,所以这本书给了我们关于实现网关接口的说明,现在我们知道它应该在接口适配器层中,并在外部框架和驱动程序层中实现。
But how to implement Use Cases without importing gateway interfaces from the outer interface adapters layer?
但是,如何在不从外部接口适配器层导入网关接口的情况下实现用例呢?
To mitigate this issue I decided to follow a technique called "Ports and Adapters" (also known as "Hexagonal Architecture" or "Ports and Adapters Architecture"), which is an architectural pattern that can help to achieve a higher degree of separation between the inner core and the outer layers. In this pattern, the inner core defines interfaces (ports) that are implemented by the outer layers (adapters), and the dependencies flow from the inner core to the adapters, without the need for the inner core to import any specific interface from the outer layers.
为了缓解这个问题,我决定遵循一种称为“端口和适配器”(也称为“六角形体系结构”或“端口和适配器体系结构”)的技术,这是一种体系结构模式,可以帮助实现内部核心和外层之间更高程度的分离。在此模式中,内部核心定义由外层(适配器)实现的接口(端口),依赖项从内部核心流向适配器,而不需要内部核心从外部层导入任何特定接口。
I am afraid it will be hard to describe what I mean without a clear visualization, so here's an example using the Ports and Adapters pattern in the Clean Architecture in the Flutter project:
如果没有清晰的可视化,恐怕很难描述我的意思,所以这里有一个在Flutter项目的Clean Architecture中使用Ports and Adapters模式的例子:
Application Business Rules
应用程序业务规则
Use case:
使用案例:
import 'package:entities/entities.dart';
/// [GetDogsResourceUseCase] is a class with functions that have prominent
/// positions within the architecture, and they will have names that clearly
/// describe their function.
/// From the [GetDogsResourceUseCase], it is impossible to tell whether the
/// application is delivered on the web, on a thick client, or a console,
/// or is a pure service.
abstract class GetDogsResourceUseCase {
const GetDogsResourceUseCase();
Stream<Resource<List<Dog>>> callAsStream([Params params]);
Future<Resource<List<Dog>>> callAsFuture([Params params]);
}
Interface adapters
接口适配器
Use case adapter:
用例适配器:
import 'package:dogs_app_interface_adapters/dogs_app_interface_adapters.dart';
import 'package:dogs_app_use_cases/dogs_app_use_cases.dart';
import 'package:entities/entities.dart';
import 'package:injectable/injectable.dart';
/// A [GetDogsResourceUseCaseAdapter] specifies the [Params]
/// to be provided by the user, the [Resource] to be returned to the user, and
/// the processing steps involved in producing that output [Resource].
@Injectable(as: GetDogsResourceUseCase)
class GetDogsResourceUseCaseAdapter implements GetDogsResourceUseCase {
const GetDogsResourceUseCaseAdapter(this._dogsGateway);
final DogsGateway _dogsGateway;
@override
Stream<Resource<List<Dog>>> callAsStream([
Params input = const Params(),
]) {
//TODO: implement
// step 1
//_paramsGateway.getSavedDogsAsStream,
// step 2
//_dogsGateway.requestDogs,
// step 3
//_dogsGateway.saveDogs,
}
@override
Future<Resource<List<Dog>>> callAsFuture([
Params input = const Params(),
]) {
//TODO: implement
// step 1
//_dogsGateway.getSavedDogsAsFuture,
// step 2
//_dogsGateway.requestDogs,
// step 3
//_dogsGateway.saveDogs,
}
Gateway:
网关:
import 'package:entities/entities.dart';
abstract class DogsGateway {
Future<List<Dog>> requestDogs([
Params params = const Params(),
]);
Stream<List<Dog>> getSavedDogsAsStream([
Params params = const Params(),
]);
Future<List<Dog>> getSavedDogsAsFuture([
Params params = const Params(),
]);
Future<void> saveDogs(List<Dog> dogs);
}
Frameworkds & Drivers
Frameworkds和驱动程序
Gateway implementation:
网关实施:
@Injectable(as: DogsGateway)
class DogsGatewayImpl implements DogsGateway {
const DogsGatewayImpl(this._restClient, this._dogsDao);
final RestClient _restClient;
final DogsDao _dogsDao;
@override
Stream<List<Dog>> getSavedDogsAsStream([
Params params = const Params(),
]) {
//TODO: implement
//_dogsDao.streamDogs
}
@override
Future<List<Dog>> getSavedDogsAsFuture([
Params params = const Params(),
]) {
//TODO: implement
//_dogsDao.getDogs
}
@override
Future<List<Dog>> requestDogs([
Params params = const Params(),
]) {
//TODO: implement
//_getDogsResponse(params)
}
@override
Future<void> saveDogs(List<Dog> dogs) {
// TODO: Save remote dogs in a local database
//_dogsDao.updateDogs
}
As you can see I achieved implementation of the same diagram that was presented in the book, without violating the dependency rules.
正如您所看到的,我实现了本书中提供的相同图表的实现,而不违反依赖关系规则。
Let's move to your next question:
让我们进入您的下一个问题:
Am I missing something?
I am afraid that either the book missing something or the diagram missing something and since I am writing this answer 6 years after publishing the book, and Robert C. Martin has not published an edited version of the "Clean Architecture" yet, we will have to use our imagination to think about what is best for our project and how it will better fit our needs creating our own interpretation of the clean architecutre.
我担心这本书或图表遗漏了一些东西,由于我是在这本书出版6年后才写下这个答案的,而且罗伯特·C·马丁还没有出版《清洁建筑》的编辑版本,我们将不得不用我们的想象力来思考什么对我们的项目最好,以及它如何更好地满足我们的需求,创造我们自己的清洁建筑解释。
And your last question:
最后一个问题是:
And if not, is there a more correct way to represent the rules of The
Clean Architecture visually?
As a matter of fact, there is. I ended up representing the diagram more correctly, at least in a way that has practical implementation and proof of concept that it can be implemented in a real project, eliminating confusion and adding <i> sign where the object represents an abstract interface.
事实上,是有的。我最终更正确地表示了图表,至少以一种具有实际实现和概念证明的方式,证明它可以在真实的项目中实现,消除了混淆,并在对象表示抽象接口的地方添加了符号。
更多回答
That solves "UseCases need to be able to access the gateways" though leaves open "Gateway implementations should not be able to use UseCases". Or should they?
这解决了“Use Case需要能够访问网关”的问题,尽管“Gateway实现不应该能够使用Use Case”。还是他们应该这么做?
@JeroenDeDauw You could always separate the interfaces in their own module to avoid use cases being visible. However, somewhere in your application you will always be able to access all public types, hence you have the option of writing "spaghetti code". But should you? IMO. some "encapsulation" have to be enforced by convetion rather than compiler support.
@JeroenDeDauw您总是可以在它们自己的模块中分离接口,以避免可见的用例。然而,在您的应用程序中的某个地方,您将始终能够访问所有公共类型,因此您可以选择编写“意大利面代码”。但你应该这样做吗?国际海事组织。一些“封装”必须通过传输而不是编译器支持来实施。
@Lars-Erik my question is purely about the diagram and visual representation of the architectural rules. So not about how to follow these rules in a codebase.
@Lars-Erik我的问题纯粹是关于架构规则的图表和视觉表示。所以不是关于如何在代码库中遵循这些规则。
Here is a quote from the book "Clean Architecture by Robert C. Martin": "Between the use case interactors and the database are the database gateways. These gateways are polymorphic interfaces that contain methods for every create, read, update, or delete operation that can be performed by the application on the database. ...Those gateways are implemented by classes in the database layer." Between "use cases and the database" is the "interface adapters". So book clearly says that gateway interfaces are in the "interface adapters" layer and are implemented in the "frameworks & drivers" layer.
这里引用Robert C.Martin的《Clean Architecture》一书中的一句话:“用例交互组件和数据库之间是数据库网关。这些网关是多态接口,包含应用程序可以在数据库上执行的每个创建、读取、更新或删除操作的方法。……这些网关是由数据库层中的类实现的。”在“用例和数据库”之间的是“接口适配器”。因此,书中明确指出,网关接口位于“接口适配器”层,并在“框架和驱动程序”层实现。
Can you explain how it falls short for applications that are not server side web-apps? (Or link to an explanation)
你能解释一下它为什么不适用于非服务器端Web应用程序吗?(或指向解释的链接)
To answer the follow-up: 1) In highly distributed systems, the flow of information is often multi-directional and requires "hopping in an out" of the described circles. There may be interactors or presenters involved, but there don't have to be. There are also often party of the system that contain read-only services and views (read models), and state is (re-)calculated from events. You will find some of the principles embedded in "Clean Architecture", of course, but reality will differ.
回答后续问题:1)在高度分布式的系统中,信息流通常是多方向的,需要从所描述的圈子中跳入和跳出。可能会有互动演员或主持人参与,但不一定要有。还经常有系统的一方包含只读服务和视图(读取模型),并且状态是根据事件(重新)计算的。当然,你会发现“清洁建筑”中嵌入的一些原则,但实际情况会有所不同。
2) In Enterprise environments, you have domain knowledge, presenter and view logic in highly partitioned, often legacy systems that will cover some of the flow of information, but can not as neatly separated as "an adapter that calls an api". It is, again, good to apply the underlying principles (e.g., strive for domain separation and well-defined anti-corruption layers with clean interfaces), but requires more fine-grained approaches to make it work.
2)在企业环境中,您在高度分区的、通常是遗留的系统中拥有领域知识、演示者和视图逻辑,这些系统将覆盖一些信息流,但不能像“调用API的适配器”那样整齐地分离。同样,应用基本原则是好的(例如,争取域分离和定义良好的反腐败层和干净的接口),但需要更细粒度的方法才能使其发挥作用。
3) in single page apps and mobile apps, which Martin conveniently abstracts away as "devices", you will often have local domain logic and state, which has to be synchronized with the web services in this diagram. Some of the interactions may look as described, but there are more complex data flow scenarios involved. Once again, the underlying principles are useful, but the solution described by this diagram falls short.
3)在单页应用程序和移动应用程序中,Martin很方便地将其抽象为“设备”,您经常会有本地域逻辑和状态,这必须与图中的Web服务同步。有些交互可能看起来像所描述的那样,但涉及到更复杂的数据流场景。再说一次,基本原则是有用的,但此图所描述的解决方案不够完善。
I think it is dangerous to assume that there is only one true way of designing "Clean Architecture", and thus every other way must be "unclean". For different system components, different requirements and different environments, different rules will apply. What's prudent in one scenario may turn out to be a hindrance in others. Identifying useful patterns and principles is a good thing - but always remember that architecture is a game of tradeoffs and compromises and by its very nature decidedly non-binary.
我认为,假设只有一种真正的方法来设计“清洁建筑”是危险的,因此所有其他方法都一定是“不干净的”。对于不同的系统组件、不同的要求和不同的环境,将适用不同的规则。在一种情况下谨慎的做法可能会在另一种情况下成为障碍。确定有用的模式和原则是一件好事--但请始终记住,体系结构是一个权衡和妥协的游戏,从本质上讲,它绝对不是二进制的。
Given that in my diagram there can be no UI code inside of a Bounded Context, I'm not sure why the infrastructure would ever depend on the UseCases. In fact to me it seems implementations of repositories should never use a UseCase. Do you see things differently?
考虑到在我的图表中,有界上下文中不能有UI代码,我不确定为什么基础设施会依赖于用例。事实上,在我看来,存储库的实现永远不应该使用UseCase。你对事情有不同的看法吗?
If you don't have UI inside your BC, then just don't put any UI component in the infraestructure circle. The infraestructure depends on the use cases because there are some application layer concerns that are implemented by the infraestrucure (transactions, security, and in general any concern that is not specific of the BC domain).Along with those concerns,in the infraestructure circle also live the implementation of domain specific interfaces (like repositories).The dependencies rules of the architecture allows you to access use cases from the implementation of repositories,but you shouldn't
如果您的BC中没有UI,那么就不要在基础结构圈中放置任何UI组件。基础架构依赖于用例,因为有一些由基础架构实现的应用层关注点(事务、安全,以及通常不特定于BC域的任何关注点)。与这些关注点一起,基础架构圈中还存在域特定接口(如存储库)的实现。体系结构的依赖关系规则允许您从存储库的实现访问用例,但您不应该
It's up to the developer not to do those kind of things. But the architecture in circles I told you (onion architecture) allow to do them. That's why I prefer Ports and Adapters (aka Hexagonal Architecture).
这取决于开发人员是否不做这种事情。但我告诉你的圈子里的架构(洋葱架构)允许这样做。这就是我更喜欢端口和适配器(也就是六角形架构)的原因。
我是一名优秀的程序员,十分优秀!