烂翻译系列之学习领域驱动设计——第十章:设计启示
“It depends” is the correct answer to almost any question in software engineering, but not really practical. In this chapter, we will explore what “it” depends on.
“视情况而定”几乎是软件工程中任何问题的正确答案,但实际上并不实用。在这一章中,我们将探讨“它”取决于什么。
In Part I of the book, you learned domain-driven design tools for analyzing business domains and making strategic design decisions. In Part II, we explored tactical design patterns: the different ways to implement business logic, organize system architecture, and establish communication between a system’s components. This chapter bridges Parts I and II. You will learn heuristics for applying analysis tools to drive various software design decisions: that is, (business) domain-driven (software) design.
在本书的第一部分,你学习了用于分析业务领域和制定战略设计决策的领域驱动设计工具。在第二部分,我们探讨了战术设计模式:实现业务逻辑、组织系统架构以及在系统组件之间建立通信的不同方式。本章将第一部分和第二部分连接起来。你将学习如何运用启示将分析工具应用于驱动各种软件设计决策:即(业务)领域驱动(软件)设计。
But first, since this chapter is about design heuristics, let’s start by defining the term heuristic.
但是首先,由于本章是关于设计启示的,让我们首先定义“启示”这个词。
Heuristic
启示
A heuristic is not a hard rule that is guaranteed and mathematically proven to be correct in 100% of cases. Rather, it’s a rule of thumb: not guaranteed to be perfect, yet sufficient for one’s immediate goals. In other words, using heuristics is an effective problem-solving approach that ignores the noise inherent in many cues, focusing instead on the “swamping forces” reflected in the most important cues.
启示不是一条硬性规则,它并不是在100% 的情况下都是有保证的和在数学上证明是正确的。相反,它是一种经验法则:不能保证完美,但足以满足当前目标。换句话说,使用启示是一种有效的问题解决方法,它忽略了许多线索中固有的噪声,而是专注于最重要线索所反映的“压倒性力量”。
The heuristics presented in this chapter focus on the essential properties of the different business domains and on the essence of the problems addressed by the various design decisions.
本章中提出的探索法聚焦于不同业务领域的关键属性以及各种设计决策所解决的问题的本质。
翻译:本章中介绍的启示主要关注不同业务领域的核心属性以及各种设计决策所解决问题的本质。
Bounded Contexts
有界上下文
As you’ll recall from Chapter 3, both wide and narrow boundaries could fit the definition of a valid bounded context encompassing a consistent ubiquitous language. But still, what is the optimal size of a bounded context? This question is especially important in light of the frequent equation of bounded contexts with microservices.
正如你在第3章中所回忆的那样,宽边界和窄边界都符合有效边界上下文(包括一致性的通用语言)的定义。但是,边界上下文的最佳大小是什么?鉴于边界上下文与微服务经常被相提并论,这个问题尤为重要。
Should we always strive for the smallest possible bounded contexts? As my friend Nick Tune says:
我们应该总是追求尽可能小的有界上下文吗?正如我的朋友Nick Tune所说:
There are many useful and revealing heuristics for defining the boundaries of a service. Size is one of the least useful.
定义服务边界有许多有用且富有启发性的启示。但大小是其中最不实用的一个。
Rather than making the model a function of the desired size—optimizing for small bounded contexts—it’s much more effective to do the opposite: treat the bounded context’s size as a function of the model it encompasses.
与其让模型成为所需大小的函数——优化小的边界上下文——相反,更有效的做法是:将边界上下文的大小视为它所包含模型的函数。
Software changes affecting multiple bounded contexts are expensive and require lots of coordination, especially if the affected bounded contexts are implemented by different teams. Such changes that are not encapsulated in a single bounded context signal ineffective design of the contexts’ boundaries. Unfortunately, refactoring bounded context boundaries is an expensive undertaking, and in many cases, the ineffective boundaries remain unattended and end up accumulating technical debt (see Figure 10- 1).
影响多个有界上下文的软件变更成本高昂,并需要大量协调,尤其是当受影响的边界上下文由不同的团队实现。这种没有封装在单个边界上下文中的变更表明上下文边界的设计无效。不幸的是,重构有界上下文边界成本高昂,在许多情况下,无效的边界没有得到关注处理,最终会累积技术债务(见图10-1)。
Figure 10-1. A change affecting multiple bounded contexts
图10-1. 影响多个有界上下文的变更
Changes that invalidate the bounded contexts’ boundaries typically occur when the business domain is not well known or the business requirements change frequently. As you learned in Chapter 1, both volatility and uncertainty are the properties of core subdomains, especially at the early stages of implementation. We can use it as a heuristic for designing bounded context boundaries.
使有界上下文边界无效的更改通常发生在业务域不明确或业务需求经常更改的情况下。正如您在第1章中了解到的,波动性和不确定性都是核心子域的特性,特别是在实施的早期阶段。我们可以用它作为设计有界上下文边界的启示。
Broad bounded context boundaries, or those that encompass multiple subdomains, make it safer to be wrong about the boundaries or the models of the included subdomains. Refactoring logical boundaries is considerably less expensive than refactoring physical boundaries. Hence, when designing bounded contexts, start with wider boundaries. If required, decompose the wide boundaries into smaller ones as you gain domain knowledge.
较宽的有界上下文边界,或者包含多个子域的边界,使得对边界或所包含子域的模型的理解出现错误时更加安全。重构逻辑边界的成本远低于重构物理边界的成本。因此,在设计有界上下文时,首先从较宽的边界开始。如果需要,随着你对领域的了解加深,可以将较宽的边界分解为较小的边界。
This heuristic applies mainly to bounded contexts encompassing core subdomains, as both generic and supporting subdomains are more formularized and much less volatile. When creating a bounded context that contains a core subdomain, you can protect yourself against unforeseen changes by including other subdomains that the core subdomain interacts with most often. This can be other core subdomains, or even supporting and generic subdomains, as shown in Figure 10-2.
这种启示主要适用于包含核心子域的有界上下文,因为通用子域和支持子域都更加公式化且波动性较小。在创建一个包含核心子域的有界上下文时,你可以通过将核心子域最常交互的其他子域(可以是其他核心子域,甚至是通用子域和支撑子域)包含在内,来保护自己(有界上下文)免受不可预见的变化的影响。如图10-2所示。
Figure 10-2. Wide bounded context boundaries
图10-2. 较宽的有界上下文边界
Business Logic Implementation Patterns
业务逻辑实现模式
In Chapters 5–7, where we discussed business logic in detail, you learned four different ways to model business logic: the transaction script, active record, domain model, and event-sourced domain model patterns.
在第5至7章中,我们详细讨论了业务逻辑,你学习了四种不同的业务逻辑建模方式:事务脚本、活动记录、领域模型和基于事件源的领域模型模式。
Both the transaction script and active record patterns are better suited for subdomains with simple business logic: supporting subdomains or integrating a third-party solution for a generic subdomain, for example. The difference between the two patterns is the complexity of the data structures. The transaction script pattern can be used for simple data structures, while the active record pattern helps to encapsulate the mapping of complex data structures to the underlying database.
事务脚本和活动记录模式都更适合于具有简单业务逻辑的子域:例如,支撑子域或集成第三方解决方案的通用子域。这两种模式的区别在于数据结构的复杂性。事务脚本模式可用于简单的数据结构,而活动记录模式有助于封装复杂数据结构到底层数据库的映射。
The domain model and its variant, the event-sourced domain model, lend themselves to subdomains that have complex business logic: core subdomains. Core subdomains that deal with monetary transactions, are obligated by law to provide an audit log, or require deep analytics of the system’s behavior are better addressed by the event-sourced domain model.
领域模型及其变体,即基于事件源的领域模型,更适用于具有复杂业务逻辑的子域:核心子域。涉及货币交易、法律要求提供审计日志或需要深入分析系统行为的核心子域,更适合采用基于事件源的领域模型。
With all of this in mind, an effective heuristic for choosing the appropriate business logic implementation pattern is to ask the following questions:
考虑到所有这些,选择合适的业务逻辑实现模式的一个有效的启示是提出以下问题:
- Does the subdomain track money or other monetary transactions or have to provide a consistent audit log, or is deep analysis of its behavior required by the business? If so, use the event-sourced domain model. Otherwise... 该子域是否跟踪货币或其他金融事务,或者必须提供一致的审计日志,或者业务是否需要对其行为进行深入分析?如果是这样,请使用基于事件源的领域模型。否则...
- Is the subdomain’s business logic complex? If so, implement a domain model. Otherwise... 该子域的业务逻辑是否复杂?如果是,实现一个领域模型。否则...
- Does the subdomain include complex data structures? If so, use the active record pattern. Otherwise... 该子域是否包含复杂的数据结构?如果是,使用活动记录模式。否则...
- Implement a transaction script. 使用事务脚本模式。
Since there is a strong relationship between a subdomain’s complexity and its type, we can visualize the heuristics using a domain-driven decision tree, as shown in Figure 10-3.
由于子域的复杂性和其类型之间存在很强的关系,我们可以使用领域驱动决策树来可视化这些启示,如图10-3所示。
Figure 10-3. Decision tree for business logic implementation pattern
图10-3. 业务逻辑实现模式的决策树
We can use another heuristic to define the difference between complex and simple business logic. The line between these two types of business logic is not terribly sharp, but it’s useful. In general, complex business logic includes complicated business rules, invariants, and algorithms. A simple approach mainly revolves around validating the inputs. Another heuristic for evaluating complexity concerns the complexity of the ubiquitous language itself. Is it mainly describing CRUD operations, or is it describing more complicated business processes and rules?
我们可以使用另一种启示来定义复杂和简单业务逻辑之间的区别。这两种业务逻辑之间的界限并不是特别明显,但是很有用。一般来说,复杂的业务逻辑包括复杂的业务规则、不变性和算法。简单的业务逻辑主要围绕验证输入。另一种评估复杂性的启示关注通用语言本身的复杂性。它是主要描述 CRUD 操作,还是描述更复杂的业务流程和规则?
Deciding on the business logic implementation pattern according to the complexity of the business logic and its data structures is a way to validate your assumptions about the subdomain type. Suppose you consider it to be a core subdomain, but the best pattern is active record or transaction script. Or suppose what you believe is a supporting subdomain requires a domain model or an event-sourced domain model; in this case, it’s an excellent opportunity to revisit your assumptions about the subdomain and business domain in general. Remember, a core subdomain’s competitive advantage is not necessarily technical.
根据业务逻辑及其数据结构的复杂性来决定业务逻辑实现模式,是验证你对子域类型假设的一种方式。假设你认为它是核心子域,但最佳模式是活动记录或事务脚本。或者,假设你认为它是一个支撑子域却需要领域模型或基于事件源的领域模型;在这种情况下,这是一个极好的机会来重新审视你对子域和整个业务域的假设。记住,核心子域的竞争优势并不一定是技术性的。
Architectural Patterns
架构模式
In Chapter 8, you learned about the three architectural patterns: layered architecture, ports & adapters, and CQRS.
在第8章中,你学习了三种架构模式:分层架构、端口与适配器架构(Ports and Adapters)以及命令查询职责分离(CQRS)。
Knowing the intended business logic implementation pattern makes choosing an architectural pattern straightforward:
了解预期的业务逻辑实现模式使得选择架构模式变得简单:
- The event-sourced domain model requires CQRS. Otherwise, the system will be extremely limited in its data querying options, fetching a single instance by its ID only. 基于事件源的领域模型需要采用命令查询职责分离(CQRS)。否则,该系统在数据查询选项上将会极其受限,只能通过ID检索单个实例。
- The domain model requires the ports & adapters architecture. Otherwise, the layered architecture makes it hard to make aggregates and value objects ignorant of persistence. 领域模型要求端口和适配器架构。否则,分层架构使得聚合和值对象很难忽视持久化(导致与持久化的具体实现耦合)。
- The Active record pattern is best accompanied by a layered architecture with the additional application (service) layer. This is for the logic controlling the active records. 活动记录模式最好与带有额外应用(服务)层的分层架构一起使用。这是为了控制活动记录的逻辑。
- The transaction script pattern can be implemented with a minimal layered architecture, consisting of only three layers. 事务脚本模式可以使用最小化的分层架构实现,该架构仅由三个层组成(不含应用层)。
The only exception to the preceding heuristics is the CQRS pattern. CQRS can be beneficial not only for the event-sourced domain model, but also for any other pattern if the subdomain requires representing its data in multiple persistent models.
前面的启示的唯一例外是 CQRS 模式。CQRS不仅对基于事件源的领域模型有益,而且如果子域需要在多个持久化模型中展示其数据,则对任何其他模式(模型的模式,事务脚本、活动记录、领域模型)也有益。
Figure 10-4 shows a decision tree for choosing an architectural pattern based on these heuristics.
图10-4显示了基于这些启示来选择架构模式的决策树。
Figure 10-4. Architectural pattern decision tree
图10-4. 架构模式决策树
Testing Strategy
测试策略
The knowledge of both the business logic implementation pattern and the architectural pattern can be used as a heuristic for choosing a testing strategy for the codebase. Take a look at the three testing strategies shown in Figure 10-5.
业务逻辑实现模式和架构模式的知识可以作为为代码库选择测试策略的启示。看看图10-5所示的三种测试策略。
Figure 10-5. Testing strategies
图10-5. 测试策略
The difference between the testing strategies in the figure is their emphasis on the different types of tests: unit, integration, and end-to-end. Let’s analyze each strategy and the context in which each pattern should be used.
图中测试策略的不同之处在于它们侧重于不同类型的测试:单元测试,集成测试,端到端测试。让我们分析一下每一种测试策略适配于哪一种模式。
Testing Pyramid
金字塔测试
The classic testing pyramid emphasizes unit tests, fewer integration tests, and even fewer end-to-end tests. Both variants of the domain model patterns are best addressed with the testing pyramid. Aggregates and value objects make perfect units for effectively testing the business logic.
经典的测试金字塔强调单元测试,较少的集成测试,以及更少的端到端测试。领域模型模式的两种变体最好使用金字塔测试进行测试。聚合和值对象是用于有效测试业务逻辑的理想单元。
Testing Diamond
菱形测试
The testing diamond focuses the most on integration tests. When the active record pattern is used, the system’s business logic is, by definition, spread across both the service and business logic layers. Therefore, to focus on integrating the two layers, the testing diamond is the more effective choice.
测试菱形最重视集成测试。当使用活动记录模式时,系统的业务逻辑根据定义分布在服务层和业务逻辑层之间。因此,为了专注于整合这两个层,菱形测试是更有效的选择。
Reversed Testing Pyramid
倒金字塔测试
The reversed testing pyramid attributes the most attention to end-to-end tests: verifying the application’s workflow from beginning to end. Such an approach best fits codebases implementing the transaction script pattern: the business logic is simple and the number of layers is minimal, making it more effective to verify the end-to-end flow of the system.
倒金字塔测试最重视端到端测试:验证应用程序从开始到结束的工作流程。这种方法最适合实现事务脚本模式的代码库:业务逻辑简单且层数最少,这使得验证系统的端到端流程更为有效。
Figure 10-6 shows the testing strategy decision tree.
图10-6显示了测试策略决策树。
Figure 10-6. Testing strategy decision tree
图10-6. 测试策略决策树
Tactical Design Decision Tree
战术设计决策树
The business logic patterns, architectural patterns, and testing strategy heuristics can be unified and summarized with a tactical design decision tree, as depicted in Figure 10-7.
业务逻辑模式、架构模式和测试策略启示可以通过战术设计决策树进行统一和总结,如图10-7所示。
Figure 10-7. Tactical design decision tree
图10-7. 战术设计决策树
As you can see, identifying subdomains types and following the decision tree gives you a solid starting point for making the essential design decisions. That said, it’s important to reiterate that these are heuristics, not hard rules. There is an exception to every rule, let alone heuristics, that by definition are not intended to be correct in 100% of the cases.
如你所见,识别子域类型并遵循决策树为你提供了一个制定关键设计决策的坚实起点。然而,需要重申的是,这些都是启示,而不是硬性规则。每个规则都有例外,更不用说启示了,顾名思义,意味着并不是100%的正确。
The decision tree is based on my preference to use the simple tools, and resort to the advanced patterns—domain model, event-sourced domain model, CQRS, and so on—only when absolutely necessary. On the other hand, I’ve met teams that have a lot of experience implementing the event-sourced domain model and therefore use it for all their subdomains. For them it’s simpler than using different patterns. Can I recommend this approach to everyone? Of course not. In the companies I have worked for or consulted, the heuristics-based approach was more efficient than using the same solution for every problem.
这个决策树是基于我倾向于使用简单工具的偏好,并且只在绝对必要时才采用高级模式——如领域模型、基于事件源的领域模型、CQRS等。另一方面,我也遇到过很多团队,他们有丰富的基于事件源的领域模型实现经验,因此他们为所有子域都使用这种模式。对他们来说,这比使用不同的模式更简单。我能推荐这种方法给所有人吗?当然不能。在我工作过或咨询过的公司中,基于启示比为每个问题都采用相同解决方案更为高效。
At the end of the day, it depends on your specific context. Use the decision tree illustrated in Figure 10-7, and the design heuristics it is based on, as guiding principles, but not as a replacement for critical thinking. If you find that alternative heuristics fit you better, feel free to alter the guiding principles or build your own decision tree altogether.
最后,这取决于你的具体环境。使用图10-7所示的决策树,以及它所基于的设计启示,作为指导原则,但不要放弃批判性思维。如果你发现其他的启示更适合你,可以随意修改这些指导原则或构建完全属于自己的决策树。
Conclusion
总结
This chapter connected Parts I and II of the book to a heuristic-based decision framework. You learned how to apply the knowledge of the business domain and its subdomains to drive technical decisions: choosing safe bounded context boundaries, modeling the application’s business logic, and determining the architectural pattern needed to orchestrate the interactions of each bounded context’s internal components. Finally, we took a detour into a different topic that is often a subject of passionate arguments—what kind of test is more important—and used the same framework to prioritize the different tests according to the business domain.
本章将本书的第一部分和第二部分连接到一个基于启示的决策框架。您学习了如何应用业务领域及其子领域的知识来驱动技术决策:选择安全的有界上下文边界,对应用程序的业务逻辑进行建模,并确定需要组织每个有界上下文内部组件交互的架构模式。最后,我们稍微偏离了主题,探讨了另一个常常引起激烈争论的主题——哪种测试更重要——并使用相同的框架根据业务领域来优先安排不同的测试。
Making design decisions is important, but even more so is to verify the decisions’ validity over time. In the next chapter, we will shift our discussion to the next phase of the software design lifecycle: the evolution of design decisions.
制定设计决策很重要,但更重要的是随着时间的推移验证这些决策的有效性。在下一章中,我们将把讨论转向软件设计生命周期的下一个阶段:设计决策的演变。
Exercises
练习
1. Assume you are implementing WolfDesk’s (see Preface) ticket lifecycle management system. It’s a core subdomain that requires deep analysis of its behavior so that the algorithm can be further optimized over time. What would be your initial strategy implementing the business logic and the component’s architecture? What would be your testing strategy? 假设您正在实现 WolfDesk 的(参见前言)工单生命周期管理系统。它是一个核心子域,需要对其行为进行深入分析,以便随着时间的推移进一步优化算法。你实现业务逻辑和组件架构的初步策略是什么?你的测试策略又是什么?
答案:Event-sourced domain model, CQRS architecture, and testing strategy that focuses on unit tests. 基于事件源的领域模型、CQRS架构以及侧重于单元测试的测试策略(金字塔测试)。
2. What would be your design decisions for WolfDesk’s support agents’ shift management module? 对于WolfDesk的客服值班管理模块,你会做出哪些设计决策?
答案:The shifts can be modeled as active records, working in the layered architectural pattern. The testing strategy should primarily focus on integration tests. 班次可以建模为活动记录,在分层架构模式下工作。测试策略应主要侧重于集成测试。
3. To ease the process of managing agents’ shifts, you want to use an external provider of public holidays for different geographical regions. The process works by periodically calling the external provider and fetching the dates and names of forthcoming public holidays. What business logic and architectural patterns would you use to implement the integration? How would you test it? 为了简化管理客服值班的过程,你希望使用不同地理区域的公共假日的外部提供商。这个过程通过定期调用外部提供商并获取即将到来的公共假日的日期和名称来工作。你会使用什么业务逻辑和架构模式来实现这种集成?你将如何测试它?
答案:The business logic can be implemented as a transaction script, organized in a layered architecture. From a testing perspective, it’s worth concentrating on end-to-end tests, verifying the full integration flow. 业务逻辑可以作为事务脚本实现,组织在分层架构中。从测试的角度来看,值得集中精力进行端到端测试,验证完整的集成流程。
4. Based on your experience, what other aspects of the software development process can be included in the heuristics-based decision tree presented in this chapter? 根据您的经验,在本章中介绍的基于启发的决策树中还可以包括软件开发过程的哪些其他方面?
1 Gigerenzer, G., Todd, P. M., & ABC Research Group (Research Group, Max Planck Institute, Germany). (1999). Simple Heuristics That Make Us Smart. New York: Oxford University Press. (1999年)。让我们变聪明的简单启示。纽约: 牛津大学出版社。
2 Chapter 11 is dedicated to the interplay between bounded contexts and microservices. 第11章专门讨论有界上下文和微服务之间的相互作用。
