架构风格和架构模式
架构风格(Architectural Styles)
Roy Thomas Fielding 在论文 Architectural Styles and
the Design of Network-based Software Architectures 中对架构风格(Architectural Styles)的描述是:
An architectural style is a coordinated set of architectural constraints that restricts the roles/features of architectural elements and the allowed relationships among those elements within any architecture that conforms to that style.
架构风格是一组协调的架构约束,它限制架构元素的角色/功能以及符合该风格的任何架构中这些元素之间允许的关系。
即架构风格描述了一个架构所具有的的特征(约束),具有这个特征(约束)的系统就是特定的架构风格。
Vaughn Vernon 在《实现领域驱动设计》书中对架构风格的描述是:
An architectural style is to architecture what a design pattern is to a specific design. It is an abstraction of those aspects that are common to different concrete implementations, enabling discussion of their relevant benefits without getting lost in technical detail. There are many different styles of distributed systems architecture, including client-server and distributed objects.
架构风格之于架构就像设计模式之于设计一样。它将不同架构实现所共有的东西抽象出来,使得我们在谈及到架构时不至于陷入技术细节中。分布式系统架构存在着多种架构风格,包括客户端-服务器架构风格和分布式对象架构风格。
微服务(Microservices)就是一种架构风格。James Lewis 和 Martin Fowler 在 Microservices Guide 中是这样描述微服务的:
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.
简而言之,微服务架构风格 是一种将单个应用程序开发为一组小型服务的方法,每个服务都在自己的进程中运行并用轻量级机制(通常是 HTTP 资源 API)进行通信。 这些服务围绕业务功能构建,并可通过全自动部署机制独立部署。 这些服务的集中管理是最低限度的,这些服务可能用不同的编程语言编写并使用不同的数据存储技术。
而微服务器架构风格需要具有的特征(约束)就是:
- 通过服务来实现独立自治的组件(Componentization via Services)
- 围绕业务能力构建(Organized around Business Capabilities)
- 产品化思维(Products not Projects)
- 轻量级通讯机制(Smart endpoints and dumb pipes)
- 分散治理(Decentralized Governance)
- 数据去中心化(Decentralized Data Management)
- 基础设施自动化(Infrastructure Automation)
- 容错性设计(Design for failure)
- 演进式设计(Evolutionary Design)
架构模式(Architectural Patterns)
维基百科对架构模式的定义是:
An architectural pattern is a general, reusable resolution to a commonly occurring problem in software architecture within a given context.
架构模式是针对给定上下文中软件架构中常见问题的通用的、可重用的解决方案。
关键词是”解决方案”。这也是架构模式和架构风格最大的区别。架构风格讲的是一个架构具有哪些特征,而架构模式则是讲的如何实现出一个具有这些特征的架构。
举个例子,与微服务架构风格对应的模式,微服务架构模式则是指至少包含了下面这些功能的架构:
- 弹性伸缩
- 服务发现
- 配置中心
- 服务网关
- 负载均衡
- 服务安全
- 跟踪监控
- 降级熔断
分层架构(Layered Architecture)
定义
Eric Evans 于 2003 年在《领域驱动设计:软件核心复杂性应对之道》中提到了一个可用于领域驱动设计的分层架构:
为了应对软件的复杂性,需要将软件的不同部分,如用户界面、业务逻辑、数据库操作等关注点进行分离,让分离出来的关注点既能专注于实现软件不同的部分,也能维持各个部分之间复杂的关系。根据行业经验和惯例,普遍使用分层的方式来分离关注点。
常见分层有下面几个,每个层都包含了与该层关注点相关的代码:
用户界面层(User Interface)
专注于为软件的”用户”提供信息和响应”用户”的请求等交互操作。
这里的”用户”既可以是人类,也可以是其他应用程序,如脚本。交互的方式既包括典型的用户图形图像界面(User Graphic Interface),也包括 Restful API。
应用层(Application Layer)
专注于对外暴露软件能完成哪些用例,并在此层中通过编排领域层(Domain Layer)中的领域对象来实现这些用例。
此层不包含任何业务逻辑,也不会存在与业务相关的条件判断语句。
能在此层做的还有:调用其它微服务、拼装结果、安全认证、权限校验、事务控制、发送或订阅领域事件等。
领域层(Domain Layer)
专注于实现业务逻辑,表达业务概念,处理业务状态信息以及业务规则这些行为,此层是整个项目的重点。
此层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。
基础设施层(Infrastructure)
专注于为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。
常见的功能是提供数据库持久化。
实现经典的分层架构需遵循下面这些规则:
- 所有的依赖项都是朝一个方向:从用户界面层(User Interface)到基础设施层(Infrastructure)。
- 与某层相关的逻辑不能放到其他层中。比如:用户界面层(User Interface)中不能包含与业务逻辑或数据库相关的代码。
改进
Vaughn Vernon 在《实现领域驱动设计》书中给出了一个使用了依赖倒置原则(Dependency Inversion Principle)改进后的分层架构:
改进的方式就是:
we would have a Repository implemented in Infrastructure for an interface defined in Domain. Focusing on the Domain Layer, using DIP enables both the Domain and Infrastructure to depend on abstractions (interfaces) defined by the domain model.
我们可以在领域层中定义资源库接口,然后在基础设施层中实现该接口。我们将关注点放在领域层上,采用依赖倒置原则,使领域层和基础设施层都只依赖于由领域模型定义的抽象接口。
总结
分层架构的核心思想是通过为软件分层的方式来达到分离关注点的目的,从而降低软件的复杂性。
分层的实际实现是通过调整源代码的组织结构来实现的。这意味着分层架构除了为我们提供有关如何组织源代码的指导外,并没有为我们提供关于软件设计和实现的其他方面的信息。
分层架构是为了组织代码以实现关注点分离,仅此而已。
分层架构可以进一步细分为严格分层架构和松散分层架构。严格分层架构不允许耦合跨层,而松散分层架构则允许耦合跨层。
其他资料
六边形架构(Hexagonal Architecture)
六边形架构由 Alistair Cockburn 于 2005 在其个人博客中提出。
六边形架构将软件分为了里外两部分:
- 里面部分是与业务逻辑相关的,软件的核心,由分层架构中的应用层(Application Layer)和领域层(Domain Layer)组成;
- 外面部分是与应用逻辑相关的,软件的实现,由分层架构中的其他层组成。
- 里面和外面通过端口以及对应的实现适配器连接起来,所以也称为端口适配器(Ports & Adapters)。
六边形架构之所以叫六边形是为了强调里面部分通过多个端口与外面部分连接,而避免大家像分层架构层层依赖那样一维的去思考。
实现六边形架构需遵循下面这些规则:
- 里面部分不能对外面部分有任何依赖。
- 外面部分不能有任何用例或者领域逻辑。
- 根据端口实现的任何适配器都能使用。
总结
六边形架构的核心思想有两点:
- 将软件的核心(业务逻辑相关的部分)与所有的与实现相关的(应用逻辑相关的部分)依赖分离,核心干净纯粹,测试和修改业务逻辑都变得更容易。
- 以端口和适配器的角度思考。使得更改 UI 和数据库等适配器不会对端口有影响。
最后需要注意,六边形架构在代码组织方面没有做出任何假设,并且与分层架构类似,它并没有告诉我们太多关于系统设计本身的信息。
其他资料
- Hexagonal Architecture Is Powerful
- Ports and Adapters Pattern (Hexagonal Architecture)
- Ports & Adapters Architecture
洋葱架构(Onion Architecture)
定义
洋葱架构由 Jeffrey Palermo 于 2008 年在其个人博客中提出。
洋葱架构并不是一个全新的架构,更像是使用了依赖倒置原则(Dependency Inversion Principle)改进后的严格分层架构与六边形架构的结合。
洋葱架构在端口适配器模式的基础上,将内层的应用程序核心分为了多层,并使用依赖倒置原则(Dependency Inversion Principle)对内部分层进行优化,从内到外依次是:
- 领域模型层(Domain Model)。整个架构的中心,包含具有状态和行为的领域模型,这一层没有任何外部依赖。
- 领域服务层(Domain Service)。本质是领域模型存储库接口的定义层,提供领域模型的查找和保存。接口在此层定义,但是接口的实现则是在最外层实现。
- 应用服务层(Application Service)。使用领域模型层和领域服务层来实现软件支持的用例。
- UI(User Interface)、基础设施(Infrastructure)以及测试用例(Tests)层。UI 使用应用服务层提供的服务来展示 UI,测试用例则是调用应用服务层提供的服务接口来进行测试,基础设施层则是实现了领域服务层中定义的领域模型存储库接口。
实现洋葱架构需遵循下面这些规则:
- 应用程序围绕独立的对象模型构建(The application is built around an independent object model)
- 内层定义接口。外层实现接口(Inner layers define interfaces. Outer layers implement interfaces)
- 层向中心耦合(Direction of coupling is toward the center)
- 所有应用程序核心代码都可以与基础设施分开编译和运行(All application core code can be compiled and run separate from infrastructure)
洋葱架构与经典分层架构的区别:
- 在经典分层架构中,一个层只能调用其正下方的层。而在洋葱架构中,任何外层可以调用任何内层。
总结
洋葱架构的核心思想是依赖倒置原则(Dependency Inversion Principle)的应用和内层不能对外层有直接的依赖。
其他资料
Boundary-Control-Entity
定义
Boundary-Control-Entity(BCE) 架构由 Ivar Jacobson 于1992年在 Object-Oriented Software Engineering: A use case driven approach 中提出。
Boundary-Control-Entity(BCE)也叫 Entity-Control-Boundary(ECB)或 Entity-Boundary-Control(EBC)。
Boundary
外界与系统交互的接口。
Control
包含所有不适合放在 Entity 和 Boundary 的行为。
Robert C. Martin 对 Interactor 的描述是包含特定于应用程序的业务规则(The interactors on the other hand, have application specific business rules)。
Entity
包含与问题域相关的数据和行为。
Robert C. Martin 对 Entity 的描述是包含与应用无关的的业务逻辑(contain application independent business rules)。
其他资料
整洁架构(Clean Architecture)
整洁架构由 Robert C. Martin 于 2012 年在总结下面的这些架构的基础上提炼出来的:
- Hexagonal Architecture (a.k.a. Ports and Adapters)
- Onion Architecture
- Screaming Architecture
- DCI, The DCI Architecture: A New Vision of Object-Oriented Programming
- BCE
整洁架构将系统分层了四层(但不是只能有四层),通常用圆圈表示:
实体(Entities)
实体层封装了系统范围内的关键业务逻辑规则。一个业务实体可以是一个带有方法的对象,也可以是一组数据结构和函数的集合。只要它能被系统中的其他不同应用复用就可以。
可以将这里的实体视作为 DDD 中的领域实体。
用例(Use Cases)
用例层包含了应用程序特定的业务规则,它封装和实现了整个系统的所有用例场景。
需要注意的是,每个类都只实现一个用例。
接口适配器(Interface Adapters)
这层中的软件是一组适配器,用于将数据从最适合用例和实体的格式转换为最适合某些外部机构(例如数据库或 Web)的格式。MVC 架构、Presenter、View 和 Controller 都在这层。
框架和驱动(Frameworks and Drivers)
最外层一般由数据库、Web框架等框架和工具组成。
一般情况下,我们不会在这一层编写太多代码,但明确说明这些工具在架构中的位置和优先级非常重要。
圆圈之间存在很强的依赖规则:内圈中的代码不能直接引用外圈中的一段代码。所有向外的通信都应该通过接口进行。这与洋葱架构中的依赖关系规则完全相同。
除了层之外,Clean Architecture 还提供了一些有关需要实现的类的提示。在架构图的右下角,从控制器到用例的控制流通过输入端口接口,并通过输出端口接口流向演示器。这可确保用例和用户界面正确解耦。
总结
整洁架构的核心思想有两点:
- 严格遵守依赖倒置原则。
- 用例导向。每个用例一个类,促进了代码的垂直切片。
其他资料
- DCI Architecture Is Visionary
- DCI - Data Context Interaction
- Clean Architecture Is Screaming
- Clean Architecture
- Clean Architecture: Standing on the shoulders of giants
一些不错的总结
摘录自来自:Clean Architecture - Make Your Architecture Scream
Make Your Architecture Scream
Policy and Level
- A computer program is a statement of policy - known inputs and expected outputs
- Most systems have many statements of separate policies
-Business rules, formatting, ETL, etc - Policies that change for the same reasons should be grouped into the same components and vice versa
- Goal is to create acyclic dependency graphs where items with similar policies are at the same level, and dependencies are at the edges
- Direction of dependencies is based on the level of components they connect
- Level is the distance from the inputs and outputs
- The further the policy is from the inputs and outputs, the higher the level
- Direction of dependencies is based on the level of components they connect
- Data flows and source code dependencies do not always point in the same direction
- Dependencies should be decoupled from data flow but coupled to the level
- The importance of this is the fact that the higher level components are now reusable with different input and output sources
- Policies that change for the same reason or at the same time are bounded by the SRP or CCP
- Higher level components typically change less frequently than the lower level components
- Lower level components (those that change frequently) should be plugins to the higher level components
Business Rules
- “Business rules are rules or procedures that make or save the business money” (whether or not they were implemented on a computer)
- Critical business rules - rules critical to the business itself, with or without a computer, for example, calculating interest on a loan.
- Critical business data - critical data that would exist even if there wasn’t an automated system.
- Critical business rules and data are tightly bound and therefore a good space for an object, also called…
Entity
- An object that contains critical business rules and critical business data
- These should be separated from every other concern in the application
- No dependencies on databases, 3rd party dependencies, user interfaces, etc.
- These objects are pure business.
Use Cases
- There are additional business rules that are not “critical” - they define how the automated system should work, but would have no impact on a manual business operation
- Use case - description of how an automated system is to be used
- These indicate how and when a critical business entity should be invoked
- These also indicate the inputs and outputs but not where they come from (database, UI, etc.)
- The how of data gets in and out is irrelevant to the use case
- Entities have no knowledge of how use cases use them
- Follows the Dependency Inversion Principle - higher level components know nothing of the lower level components - the direction is inverted
- Use cases are specific to a single application
- Entities are generalizations
Request and Response Models
- Use cases accept simple request objects for input and return simple response objects for outputs
- They SHOULD NOT depend on any frameworks or other dependencies - simple objects
- Seems like a good idea to return a reference to an entity object - do NOT do this
- Entity objects are higher level components that will change for different reasons so stick to the simple request / response objects
- Coupling them together violates the Common Closer and Single Responsibility Principles
So, Business Rules …
- “Business rules are the reason a software system exists”
- Should remain pristine
- Are independent and reusable
Screaming Architecture
- Upon initial inspection, does your application structure scream Spring, or does it scream Healthcare software?
- Architecture should scream the use cases of the application, not the frameworks or dependencies in the application
- “If your architecture is based on frameworks, then it cannot be based on your use cases”
- A house’s architecture is focused on usability, not whether the house is built of bricks or stucco
- Again, going back to deferring decisions like that until further down the road
- The “web” is a delivery mechanism - it’s your IO layer - much like a mobile app, desktop application or any other
The Theme and Purpose
- The architecture is all about structures that support the use cases of the application
- Architecture is not a framework nor is it supplied by a framework.
- Good architecture allows you to defer and delay decisions as well as make it easy to change your mind about those decisions.
Frameworks are Tools, NOT Ways of Life
- Look at frameworks with skepticism - you don’t want to adopt the be all end all position
- They should NOT dictate your application architecture
- How should you use it?
- How should you protect yourself from it?
Testable Architectures
- If you’ve done your job right, then unit testing should be easy to do as everything was decoupled properly
- Entity objects will be plain old objects with no external dependencies
- Use case objects will coordinate the use of entity objects, again with no infrastructure dependencies
Does your Architecture Scream?
- Your architecture should quickly identify the purpose of your system.
They: “We see some things that look like models - where are the views and controllers?“
You: “Oh, those are details that needn’t concern us at the moment. We’ll decide about them later.“
The Clean Architecture
Uncle Bob’s version of the Clean Architecture diagram:
Our re-imagining of it as a cone:
- It’s really about the Separation of Concerns
- Dividing software into layers
- Should be independent of frameworks
- They should be testable
- They should be independent of a UI
- They should be independent of a database
- They should be independent of interfaces to 3rd party dependencies
- The Clean Architecture Diagram
- Innermost: “Enterprise / Critical Business Rules” - Entities
- Next out: “Application business rules” - Use Cases
- Next out: “Interface adapters” - Gateways, Controllers, Presenters
- Outer: “Frameworks and drivers” - Devices, Web, UI, External Interfaces, DB
- Moving inward, the level of abstraction and policy increase
- The innermost circle is the most general/highest level
- Inner circles are policies
- Outer circles are mechanisms
- Inner circles cannot depend on outer circles
- Outer circles cannot influence inner circles
Entities
- Entities should be usable by many applications (critical business rules) and should not be impacted by anything other than a change to the critical business rule itself
- They encapsulate the most general/high-level rules.
Use Cases
- Use cases are application specific business rules
- Changes should not impact the Entities
- Changes should not be impacted by infrastructure such as a database
- The use cases orchestrate the flow of data in/out of the Entities and direct the Entities to use their Critical Business Rules to achieve the use case
Interface Adapters
- Converts data from data layers to use case or entity layers
- Presenters, views and controllers all belong here
- No code further in (use cases, entities) should have any knowledge of the db
Frameworks and Drivers
- These are the glue that hook the various layers up
- The infrastructure details live here
- You’re not writing much of this code, i.e. we use SQL Server, but we don’t write it.
Crossing Boundaries
- Flow of control went from the controller, through the application use case, then to the presenter
- Source code dependencies point in towards the use cases
- Dependency Inversion Principle
- Use case needs to call a presenter - doing so would violate the dependency rule - inner circles cannot call (or know about) outer circles….
- The use case would need to call an interface
- The implementation of that interface would be provided by the interface adapter layer - this is how the dependency is inverted
- This same type of inversion of control is used all throughout the architecture to invert the flow of control
- The implementation of that interface would be provided by the interface adapter layer - this is how the dependency is inverted
- The use case would need to call an interface
- Use case needs to call a presenter - doing so would violate the dependency rule - inner circles cannot call (or know about) outer circles….
Data Crossing Boundaries
- Typically data crossing the boundaries consist of simple data structures
- DO NOT PASS ENTITY OBJECTS OR DATA ROWS!
- This would violate the dependency rules
- Data is passed in the format that is most convenient to the inner circle / layer
- These are isolated, simple data structures
- Meaning our DTOs needed to cross the boundaries should belong in the inner circle, or at least their definition (interface, abstract class)
Conclusion
- Conforming to the rules is not difficult (but requires work) and will set you up to be able to plug and play pieces in the future
其他常见的术语
阻抗失衡(Impedance Mismatch)
Impedance mismatch is a term used in computer science to describe the problem that arises when two systems or components that are supposed to work together have different data models, structures, or interfaces that make communication difficult or inefficient.
In the context of databases, impedance mismatch refers to the discrepancy between the object-oriented programming (OOP) model used in application code and the relational model used in database management systems (DBMS). While OOP models are designed to represent data as objects with properties and methods, relational models represent data as tables with columns and rows.