- 13 Aug 2012
- Architecture
- Craftsmanship
Over the last several years we’ve seen a whole range of ideas regarding the architecture of systems. These include:
在过去几年中,我们已经看到了关于系统架构的各种观点,如下所示:
- Hexagonal Architecture (a.k.a. Ports and Adapters) by Alistair Cockburn and adopted by Steve Freeman, and Nat Pryce in their wonderful book Growing Object Oriented Software
- Onion Architecture by Jeffrey Palermo
- Screaming Architecture from a blog of mine last year
- DCI from James Coplien, and Trygve Reenskaug.
- BCE by Ivar Jacobson from his book Object Oriented Software Engineering: A Use-Case Driven Approach
Though these architectures all vary somewhat in their details, they are very similar. They all have the same objective, which is the separation of concerns. They all achieve this separation by dividing the software into layers. Each has at least one layer for business rules, and another for interfaces.
虽然这些架构在细节上略有不同,但它们其实非常相似。它们都有相同的观点,即关注分离。它们都通过软件分层来实现分离。每种都至少有业务规则层和接口层。
Each of these architectures produce systems that are:
- Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.
- Testable. The business rules can be tested without the UI, Database, Web Server, or any other external element.
- Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules.
- Independent of Database. You can swap out Oracle or SQL Server, for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.
- Independent of any external agency. In fact your business rules simply don’t know anything at all about the outside world.
每种架构按照如下方式生成出系统:
1、独立于框架。架构不依赖于功能丰富的库。这允许你把框架当做工具来使用,而不是强行把你的系统塞进框架有限的约束里去。
2、可测试性。业务规则可以不使用UI,DB,Web Server或者其他外部元素即可测试。
3、独立于UI。UI可以很容易更改,改变时不影响系统的其他部分。举例来说,Web UI能够被控制台UI代替,此时不影响业务规则。
4、独立于DB。你可以在Oracle,SQL Server,Mongo, BigTable,CouchDB或者其他数据库之间切换。你的业务规则并不绑定于特定的数据库。
5、独立于外部机制。实际上,你的业务规则不应该知道任何外部世界。
The diagram at the top of this article is an attempt at integrating all these architectures into a single actionable idea.
文章开始的图,是将所有这些架构要点集成到一个可操作的想法中的尝试。
The Dependency Rule
The concentric circles represent different areas of software. In general, the further in you go, the higher level the software becomes. The outer circles are mechanisms. The inner circles are policies.
内圆代表了软件的不同领域。一般来说,越向内软件层次就越高。外圆是战术(机制),内圆是战略(策略)。
The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. 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.
推动整个架构运行最重要的是依赖规则。该规则是指源码只能向内依赖。内圆不知道外圆的任何信息。需要特别指出的是,在外圆中声明的东西不应被内圆的代码所提及,包括函数,类,变量,或者任何其他已命名的软件实体。
By the same token, data formats used in an outer circle should not be used by an inner circle, especially if those formats are generate by a framework in an outer circle. We don’t want anything in an outer circle to impact the inner circles.
同样的,外圆中使用的数据格式不应被内圆使用,尤其是如果这些格式是由外圆的框架生成的时候。我们不希望外圆有任何东西影响到内圆。
Entities
Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.
实体封装了企业业务规则。一个实体可以是带有方法的对象,或者是一组数据结构和函数。只要实体在企业中可被许多不同的应用使用即可,其他的没什么关系。
If you don’t have an enterprise, and are just writing a single application, then these entities are the business objects of the application. They encapsulate the most general and high-level rules. They are the least likely to change when something external changes. For example, you would not expect these objects to be affected by a change to page navigation, or security. No operational change to any particular application should affect the entity layer.
如果你不是企业级应用,而仅仅是写一个简单的应用程序,那这些实体可认为是应用对象的业务逻辑。它们封装了最基本的和高层的规则。它们在外部变化时几乎不变。举个例子,你不希望页面导航或者安全机制的更改去影响到业务逻辑,对吧?任何特定应用的更改都不应影响到实体层。
Use Cases
The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case.
这一层的软件包含了应用的具体业务规则。它封装和实现了系统所有的用例。这些用例编排实体的数据流入和流出,指导这些实体使用企业侧的业务规则来实现用例目标。
We do not expect changes in this layer to affect the entities. We also do not expect this layer to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. This layer is isolated from such concerns.
我们不希望这层的变化影响到实体。我们也不希望本层被外部(诸如DB、UI或者任何框架)所影响。此层与这部分是隔离的。
We do, however, expect that changes to the operation of the application will affect the use-cases and therefore the software in this layer. If the details of a use-case change, then some code in this layer will certainly be affected.
但是,我们希望应用操作的改变能够影响到用例以及此层中的软件。如果用例的细节改变了,那么本层中的代码肯定会受到影响。
Interface Adapters
The software in this layer is a set of adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the Database or the Web. It is this layer, for example, that will wholly contain the MVC architecture of a GUI. The Presenters, Views, and Controllers all belong in here. The models are likely just data structures that are passed from the controllers to the use cases, and then back from the use cases to the presenters and views.
本层的软件是一组适配器。适配器用于将最适用于用例和实体的数据格式,转换成最适用于类似DB或者Web的数据格式。举例来说,GUI的MVC架构属于此层。表示器,视图和控制器都属于此层。模型更接近于数据结构,这些数据结构从控制器传递到用例,然后从用例返回给表示器和视图。
Similarly, data is converted, in this layer, from the form most convenient for entities and use cases, into the form most convenient for whatever persistence framework is being used. i.e. The Database. No code inward of this circle should know anything at all about the database. If the database is a SQL database, then all the SQL should be restricted to this layer, and in particular to the parts of this layer that have to do with the database.
类似的,在此层,数据从最适用于实体和用户,转换到最适用于持久化框架,比如DB。内圆的任何代码都不应该知道关于DB的信息。如果DB是SQL 数据库,那么所有的SQL语句应该被局限于此层,特别是与数据库有关的层的各个部分。
Also in this layer is any other adapter necessary to convert data from some external form, such as an external service, to the internal form used by the use cases and entities.
同样的,在这一层中,还需要其他适配器来将数据从一些外部形式(如外部服务)转换为用例和实体使用的内部表单。
Frameworks and Drivers.
The outermost layer is generally composed of frameworks and tools such as the Database, the Web Framework, etc. Generally you don’t write much code in this layer other than glue code that communicates to the next circle inwards.
最外层一般是由框架和工具组成的,比如DB,Web框架等等。一般来说,除了向内圆传递的胶水代码之外,在这个层中不会编写太多代码。
This layer is where all the details go. The Web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.
这一层更多的是细节性的东西。Web是细节,DB是细节。我们将这些都放在外层以减少他们的危害。
Only Four Circles?
No, the circles are schematic. You may find that you need more than just these four. There’s no rule that says you must always have just these four. However, The Dependency Rule always applies. Source code dependencies always point inwards. As you move inwards the level of abstraction increases. The outermost circle is low level concrete detail. As you move inwards the software grows more abstract, and encapsulates higher level policies. The inner most circle is the most general.
不,这些圆仅仅是个示意图。你可能发现你或许需要超过四个。没有规则说你必须恰好是四个。但是,依赖原则总是适用的。源码依赖总是向内依赖。越向内,抽象层次越高。外圆是低层次的具体细节。越向内,软件越抽象,封装了越高层的策略。最内圆层次最高。
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.
我们通常使用依赖倒置原则(DIP)来解决这种明显的矛盾。比如像Java这种语言,我们可以安排接口和继承关系,这样源码可以在跨越边界的正确点上与控制流反向。
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.
相同的手法可以用于跨越架构中的边界。我们利用动态多态性创建源代码依赖以反向控制流,这样我们就可以遵循依赖规则,不用管控制流的方向是什么。
What data crosses the boundaries.
Typically the data that crosses the boundaries is simple data structures. You can use basic structs or simple Data Transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hashmap, or construct it into an object. The important thing is that isolated, simple, data structures are passed across the boundaries. We don’t want to cheat and pass Entities or Database rows. We don’t want the data structures to have any kind of dependency that violates The Dependency Rule.
典型的跨越边界的数据是简单的数据结构。你愿意的话,可以用基础结构或者简单的DAO对象,或者可以只是被函数调用的参数。或者你可打包成hashmap,或者构建到一个对象中。重要的是隔离,简单,数据结构能够跨越边界。我们不想用实体或者DB的记录行来行骗。我们不想让数据结构具有任何违反依赖规则的依赖项。
For example, many database frameworks return a convenient data format in response to a query. We might call this a RowStructure. We don’t want to pass that row structure inwards across a boundary. That would violate The Dependency Rulebecause it would force an inner circle to know something about an outer circle.
举例来说,很多DB框架在查询结果中返回一种方便的数据结构,可能取名为RowStructure。我们不想传递行结构向内穿越边界。这将违反依赖原则,因为它强制内圆知道外圆的一些信息。
So when we pass data across a boundary, it is always in the form that is most convenient for the inner circle.
因此,当我们传递数据穿越边界的时候,它应该是对内圆最适用的形式。
Conclusion
Conforming to these simple rules is not hard, and will save you a lot of headaches going forward. By separating the software into layers, and conforming to The Dependency Rule, you will create a system that is intrinsically testable, with all the benefits that implies. When any of the external parts of the system become obsolete, like the database, or the web framework, you can replace those obsolete elements with a minimum of fuss.
遵守这些简单的规则并不难,而且会帮你避免很多麻烦。通过将软件分层并遵从依赖规则,你将创建一个本质可测试的系统,这包含所有的好处。当系统的任何外部部件如数据库或Web框架过时,您可以用最小的代价替换那些过时的元素。