烂翻译系列之学习领域驱动设计——第十三章:现实世界中的领域驱动设计

We have covered domain-driven design tools for analyzing business domains, sharing knowledge, and making strategic and tactical design decisions. Just imagine how fun it will be to apply this knowledge in practice. Let’s consider a scenario in which you are working on a greenfield project. All of your coworkers have a strong grasp of domain-driven design, and right from the get-go all are doing their best to design effective models and, of course, are devotedly using the ubiquitous language. As the project advances, the bounded contexts’ boundaries are explicit and effective in protecting the business domain models. Finally, since all tactical design decisions are aligned with the business strategy, the codebase is always in great shape: it speaks the ubiquitous language and implements the design patterns that accommodate the model’s complexity. Now wake up.

我们已经介绍了用于分析业务领域、共享知识以及制定战略和战术设计决策的领域驱动设计工具。想象一下将这些知识应用到实践中会有多有趣。让我们考虑一个场景,你正在参与一个绿地项目(未开发项目)。你的所有同事都熟练掌握了领域驱动设计,从一开始就全力以赴地设计有效的模型,当然,他们都在忠实地使用通用语言。随着项目的推进,有界上下文的边界明确且有效地保护了业务领域模型。最后,由于所有的战术设计决策都与业务战略保持一致,代码库始终保持良好的状态:它使用通用语言并实施了适应模型复杂性的设计模式。现在醒来吧。

Your chances of experiencing the laboratory conditions I just described are about as good as winning the lottery. Of course, it’s possible, but not likely. Unfortunately, many people mistakenly believe that domain-driven design can only be applied in greenfield projects and in ideal conditions in which everybody on the team is a DDD black belt. Ironically, the projects that can benefit from DDD the most are the brownfield projects: those that already proved their business viability and need a shake-up to fight accumulated technical debt and design entropy. Coincidentally, working on such brownfield, legacy, big-balls-of-mud codebases is where we spend most of our software engineering careers.

你遇到我刚才描述的实验室条件的几率几乎和中彩票一样。当然,这是可能的,但不太可能。不幸的是,许多人错误地认为领域驱动设计只能应用于未开发项目,并且在理想的条件下,团队中的每个人都是DDD高手。具有讽刺意味的是,最能从DDD中受益的项目是待重新开发项目:那些已经证明其业务可行性并需要整顿以应对累积的技术债务和设计混乱的项目。巧合的是,我们大部分的软件工程职业生涯都花在了处理这样的待重新开发、遗留下来的、一团糟的代码库上。

Another common misconception about DDD is that it’s an all-or-nothing proposition—either you apply every tool the methodology has to offer, or it’s not domain-driven design. That’s not true. It might seem overwhelming to come to grips with all of these concepts, let alone implement them in practice. Luckily, you don’t have to apply all of the patterns and practices to gain value from domain-driven design. This is especially true for brownfield projects, where it’s practically impossible to introduce all the patterns and practices in a reasonable time frame.

关于DDD的另一个常见误解是,它要么全部应用,要么一点也不应用——要么你使用该方法提供的所有工具,要么它就不是领域驱动设计。这是不正确的。要掌握所有这些概念可能已经让人感到不知所措了,更不用说在实践中实现它们了。幸运的是,你不需要应用所有的模式和实践就能从领域驱动设计中获得价值。这对于待重新开发项目尤其如此,因为在合理的时间范围内引入所有的模式和实践几乎是不可能的。

In this chapter, you will learn strategies for applying domain-driven design tools and patterns in the real world, including on brownfield projects and in less-than-ideal environments.

在本章中,你将学习在现实世界(包括在待重新开发项目和不理想的环)中应用领域驱动设计工具和模式的策略。

Strategic Analysis

战略分析

Following the order of our exploration of domain-driven design patterns and practices, the best starting point for introducing DDD in an organization is to invest time in understanding the organization’s business strategy and the current state of its systems’ architecture.

按照我们探索领域驱动设计模式和实践的顺序,在组织中引入DDD的最佳起点是投入时间了解组织的业务战略及其系统架构的当前状态。

Understand the Business Domain

理解业务领域

First, identify the company’s business domain:

首先,确定公司的业务领域:

  • What is the organization’s business domain(s)?    组织的业务领域是什么?
  • Who are its customers?    公司的客户是谁?
  • What service, or value, does the organization provide to customers?    组织向客户提供什么样的服务或价值?
  • What companies or products is the organization competing with?    组织与哪些公司或产品竞争?

Answering these questions will give you a bird’s-eye view of the company’s high-level goals. Next, “zoom in” to the domain and look for the business building blocks the organization employs to achieve its high-level goals: the subdomains.

回答这些问题将让你从整体上了解公司的高级目标。接下来,“放大”到业务领域,并寻找组织为实现其高级目标所使用的业务构建块:子域。

A good initial heuristic is the company’s org chart: its departments and other organizational units. Examine how these units cooperate to allow the company to compete in its business domain.

一个好的初步启发是查看公司的组织结构图:其部门和其他组织单位。检查这些单位如何合作以使公司能够在其业务领域中竞争。

Furthermore, look for the signs of specific types of subdomains.

此外,寻找特定类型子域的迹象。

Core subdomains

核心子域

To identify the company’s core subdomains, look for what differentiates it from its competitors:

要识别公司的核心子域,请寻找它与其他竞争对手的不同之处:

  • Does the company have a “secret sauce” that its competitors lack? For example, intellectual property, such as patents and algorithms designed in-house?    该公司是否拥有竞争对手所缺乏的“独门秘籍”?例如,知识产权,如专利和内部设计的算法?
  • Keep in mind that the competitive advantage, and thus the core subdomains, are not necessarily technical. Does the company possess a nontechnical competitive advantage? For example, the ability to hire top-level personnel, produce a unique artistic design, and so on?    请记住,竞争优势(因此也包括核心子域)并不一定是技术性的。该公司是否拥有非技术性的竞争优势?比如,能否聘请顶级人才,制作独特的艺术设计等等?

Another powerful yet unfortunate heuristic for core subdomains is identifying the worst-designed software components—those big balls of mud that all engineers hate but the business is unwilling to rewrite from scratch because of the accompanying business risk. The key here is that the legacy system cannot be replaced with a ready-made system—it would be a generic subdomain—and any modification to it entails business risks.

识别核心子域的另一个强大却不幸的启发是找出设计最糟糕的软件组件——那些所有工程师都讨厌但业务方面由于伴随的商业风险而不愿意从头开始重写的大泥球。这里的关键是,遗留系统无法用现成的系统来替代——如果能替代,那将是一个通用子域——并且任何对它的修改都会带来商业风险。

Generic subdomains

通用子域

To identify generic subdomains, look for off-the-shelf solutions, subscription services, or integration of open source software. As you learned in Chapter 1, the same ready-made solutions should be available to the competing companies, and those companies leveraging the same solution should have no business impact on your company.

要识别通用子域,请寻找现成的解决方案、订阅服务或开源软件的集成。正如你在第1章中学到的,相同的现成解决方案应该对竞争对手公司可用,并且利用相同解决方案的公司不会对你的公司产生业务影响。

Supporting subdomains

支撑子域

For supporting subdomains, look for the remaining software components that cannot be replaced with ready-made solutions yet do not directly provide a competitive advantage. If the code is in rough shape, it triggers less emotional response from software engineers since it changes infrequently. Thus, the effects of the suboptimal software design are not as severe as for the core subdomains.

对于支撑子域,请寻找那些无法用现成解决方案替换又不直接提供竞争优势的剩余软件组件。如果代码状况不佳,由于它很少更改,因此软件工程师对此的反应不会那么强烈。因此,次优软件设计的影响不会像核心子域那样严重。

You don’t have to identify all of the core subdomains. It won’t be practical or even possible to do so, even for a medium-sized company. Instead, identify the overall structure, but pay closer attention to the subdomains that are most relevant to the software systems you are working on.

您不需要识别所有的核心子域。对于一家中等规模的公司来说,这样做既不实际也不可能。相反,您应该识别总体结构,但更关注与您正在开发的软件系统最相关的子域。

Explore the Current Design

探索当前设计

Once you are familiar with the problem domain, you can continue to investigate the solution and its design decisions. First, start with the high-level components. These are not necessarily bounded contexts in the DDD sense, but rather boundaries used to decompose the business domain into subsystems.

一旦您熟悉了问题域,您就可以继续研究解决方案及其设计决策。首先,从高级组件开始。这些组件不一定是 DDD 意义上的有界上下文,而是用于将业务领域分解为子系统的边界。

The characteristic property to look for is the components’ decoupled lifecycles. Even if the subsystems are managed in the same source control repository (mono-repo) or if all the components reside in a single monolithic codebase, check which can be evolved, tested, and deployed independently from the others.

要寻找的特征属性是组件的解耦生命周期。即使子系统在同一个源代码控制存储库(单体仓库)中管理,或者所有组件都驻留在单个单体代码库中,也要检查哪些组件可以独立于其他组件进行演进、测试和部署。

Evaluate the tactical design

评估战术设计

For each high-level component, check which business subdomains it contains and what technical design decisions were taken: what patterns are used to implement the business logic and define the component’s architecture?

对于每个高级组件,检查它包含哪些业务子域以及采取了哪些技术设计决策:使用了哪些模式来实现业务逻辑并定义组件的架构?

Does the solution fit the complexity of the problem? Are there areas where more elaborate design patterns are needed? Conversely, are there any subdomains where it’s possible to cut corners or use existing, off-the-shelf solutions? Use this information to make smarter strategic and tactical decisions.

该解决方案是否适合问题的复杂性?是否有需要更精细设计模式的领域?相反,是否有任何子域可以简化或使用现有的现成解决方案?利用这些信息来做出更明智的战略和战术决策。

Evaluate the strategic design

评估战略设计

Use the knowledge of the high-level components to chart the current design’s context map, as though these high-level components were bounded contexts. Identify and track the relationships between the components in terms of bounded context integration patterns.

利用对高级组件的了解,绘制当前设计的上下文映射图,就好像这些高级组件是有界上下文一样。识别并跟踪组件之间在有界上下文集成模式方面的关系。

Finally, analyze the resultant context map and evaluate the architecture from a domain-driven design perspective. Are there suboptimal strategic design decisions? For example:

最后,分析得到的上下文映射,从领域驱动设计的角度评估架构。是否存在次优的战略设计决策? 例如:

  • Multiple teams working on the same high-level component    多个团队在同一个高级组件上工作
  • Duplicate implementations of core subdomains    核心子域的重复实现
  • Implementation of a core subdomain by an outsourced company    由外包公司实现核心子域
  • Friction because of frequently failing integration    由于集成经常失败而产生的摩擦
  • Awkward models spreading from external services and legacy systems   从外部服务和遗留系统蔓延的笨拙模型

These insights are a good starting point for planning the design modernization strategy. But first, given this more in-depth knowledge of both the problem (business domain) and the solution (current design) spaces, look for lost domain knowledge. As we discussed in Chapter 11, knowledge of the business domain can get lost for various reasons. The problem is widespread and acute in core subdomains, where the business logic is both complex and business critical. If you encounter such cases, facilitate EventStorming sessions to try to recover the knowledge. Also, use the EventStorming session as the foundation for cultivating a ubiquitous language.

这些见解是规划设计现代化策略的良好起点。但是首先,鉴于对问题(业务领域)和解决方案(当前设计)空间有了更深入的了解,请寻找丢失的领域知识。正如我们在第11章中讨论的,由于各种原因,业务领域的知识可能会丢失。这个问题在核心子域中尤为普遍和严重,因为这里的业务逻辑既复杂又关键。如果您遇到这样的情况,请组织事件风暴研讨会来尝试恢复这些知识。同时,将事件风暴研讨会作为培养通用语言的基础。

Modernization Strategy

现代化策略

The “big rewrite” endeavors, in which the engineers are trying to rewrite the system from scratch, this time designing and implementing the whole system correctly, are rarely successful. Even more rarely does management support such architectural makeovers.

“彻底重写”的努力,即工程师们试图从头开始重写系统,这次正确地设计和实现整个系统,这种努力很少会成功。管理层也很少支持这样的架构改造。

A safer approach to improving the design of existing systems is to think big but start small. As Eric Evans says, not all of a large system will be well designed. That’s a fact we have to accept, and therefore we must strategically decide where to invest in terms of modernization efforts. A prerequisite for making this decision is to have boundaries dividing the system’s subdomains. The boundaries don’t have to be physical, making each subdomain a full-fledged bounded context. Instead, start by ensuring that at least the logical boundaries (namespace, modules, and packages, depending on the technology stack) are aligned with the subdomains’ boundaries, as shown in Figure 13-1.

改善现有系统设计的一种更安全的方法是“放眼全局,从小处着手”。正如埃里克·埃文斯(Eric Evans)所说,大型系统并非所有部分都会设计得很好。这是我们必须接受的事实,因此我们必须从战略上决定在现代化努力方面投资哪些部分。做出这一决定的前提是要有划分系统子域的边界。这些边界不必是物理的,物理边界要求每个子域都成为一个完整的有界上下文。相反,首先要确保至少逻辑边界(包括命名空间、模块和包,取决于技术栈)与子域的边界保持一致,如图13-1所示。

Figure 13-1. Reorganizing the bounded context’s modules to reflect the business subdomains’ boundaries rather than technical implementation patterns

图13-1.  重新组织有界上下文的模块,以反映业务子域的边界,而不是技术实现模式

Adjusting the system’s modules is a relatively safe form of refactoring. You are not modifying the business logic, just repositioning the types in a more well-organized structure. That said, ensure that references by full type names, such as the dynamic loading of libraries, reflection, and so on, are not breaking.

调整系统的模块是一种相对安全的重构形式。您并没有修改业务逻辑,只是将类型重新放置到一个更有组织的结构中。也就是说,请确保通过完整类型名称(如动态加载库、反射等)进行的引用没有中断。

In addition, keep track of the subdomains’ business logic implemented in different codebases; stored procedures in a database, serverless functions, and so on. Make sure to introduce the new boundaries in those platforms as well. For instance, if some of the logic is handled in the database’s stored procedures, either rename the procedures to reflect the module they belong to or introduce a dedicated database schema and relocate the stored procedures.

此外,跟踪在不同代码库中实现的子域的业务逻辑; 数据库中的存储过程、无服务器函数等等。确保在这些平台中也引入新的边界。例如,如果在数据库的存储过程中处理了某些逻辑,则要么重命名这些过程以反映它们所属的模块,要么引入专用的数据库模式(架构)并将存储过程放置到新的数据库模式中。

Strategic Modernization

战略现代化

As we discussed in Chapter 10, it can be risky to prematurely decompose the system into the smallest bounded contexts possible. We will discuss bounded contexts and microservices in more detail in the next chapter. For now, look for where the most value can be gained by turning the logical boundaries into physical boundaries. The process of extracting a bounded context(s) by turning a logical boundary into a physical one is shown in Figure 13-2.

正如我们在第10章中讨论的,过早地将系统分解为尽可能小的有界上下文是存在风险的。我们将在下一章中更详细地讨论有界上下文和微服务。现在,请寻找将逻辑边界转变为物理边界能带来最大价值的地方。将逻辑边界转变为物理边界以提取有界上下文的过程如图13-2所示。

Questions to ask yourself:

要问自己的问题:

  • Are multiple teams working on the same codebase? If so, decouple the development lifecycles by defining bounded contexts for each team.    是否有多个团队在同一个代码库上工作?如果是,请为每个团队定义有界上下文,以解耦开发周期。
  • Are conflicting models being used by the different components? If so, relocate the conflicting models into separate bounded contexts.    不同的组件是否使用了冲突的模型?如果是,请将冲突的模型重新定位到不同的有界上下文中。

Figure 13-2. Extracting a bounded context by turning a logical boundary into a physical boundary

图13-2.  通过将逻辑边界转换为物理边界来提取有界上下文

When the minimum required bounded contexts are in place, examine the relationships and integration patterns between them. See how the teams working on different bounded contexts communicate and collaborate. Especially when they are communicating through ad hoc or shared-kernel– like integration, do the teams have shared goals and adequate collaboration levels?

当所需的最小有界上下文到位时,检查它们之间的关系和集成模式。了解在不同的有界上下文中工作的团队是如何沟通和协作的。特别是当他们通过临时的或类似于共享内核的集成方式进行沟通时,团队是否有共同目标和充分的合作水平?

Pay attention to problems that the context integration patterns can address:

注意上下文集成模式可以解决的问题:

Customer–supplier relationships    客户-供应商关系

       As we discussed in Chapter 11, organizational growth can invalidate prior communication and collaboration patterns. Look for components designed for a partnership relationship of multiple engineering teams, but where the partnership is no longer sustainable. Refactor to the appropriate type of customer–supplier relationship (conformist, anticorruption layer, or open-host service).

       正如我们在第11章中讨论的,组织的增长可能会使先前的沟通和协作模式失效。寻找为多个工程团队之间的伙伴关系而设计的组件,但如果这种伙伴关系不再可持续,则将其重构为适当类型的客户-供应商关系(遵俸者、防腐层或开放主机服务)。

Anticorruption layer    防腐层

       Anticorruption layers can be useful for protecting bounded contexts from legacy systems, especially, when legacy systems are using inefficient models that tend to spread into downstream components.

       防腐层可以用于保护有界上下文免受遗留系统的侵害,特别是当遗留系统使用倾向于蔓延到下游组件的低效模型时。

       Another common use case for implementing an anticorruption layer is to protect a bounded context from frequent changes in the public interfaces of an upstream service it uses.

       实现防腐层的另一个常见用例是保护有界上下文免受其所使用的上游服务的公共接口频繁更改的影响。

Open-host service    开放主机服务

       If changes in the implementation details of one component often ripple through the system and affect its consumers, consider making it an open-host service: decouple its implementation model from the public API it exposes.

       如果一个组件的实现细节的变化经常在整个系统中产生连锁反应并影响其消费者,请考虑将其变为一个开放主机服务:将其实现模型与其公开的API解耦。

Separate ways    各行其道

       Especially in large organizations, you may encounter friction among engineering teams resulting from having to collaborate and co-evolve a shared functionality. If the “apple of discord” functionality is not business critical—that is, it’s not a core subdomain—the teams can go their separate ways and implement their own solutions, eliminating the source of friction.

       特别是在大型组织中,您可能会遇到工程团队之间因需要协作和共同开发共享功能而产生的摩擦。如果“不和之苹”功能不是业务关键功能(即不是核心子域),那么团队可以各行其道,实现自己的解决方案,从而消除摩擦的根源。

Tactical Modernization

战术现代化

First and foremost, from a tactical standpoint, look for the most “painful” mismatches in business value and implementation strategies, such as core subdomains implementing patterns that don’t match the complexity of the model—transaction script or active record. These system components that directly impact the success of the business have to change the most often, yet are painful to maintain and evolve due to poor design.

首先,从战术角度出发,寻找业务价值和实现策略之间最“痛苦”的不匹配,例如与模型复杂性不匹配的核心子域实现模式——事务脚本或活动记录。这些直接影响业务成功的系统组件必须经常进行更改,但由于设计不佳,它们很难维护和进化。

Cultivate a Ubiquitous Language

培养一种通用语言

A prerequisite to the successful modernization of a design is the domain knowledge and effective model of the business domain. As I have mentioned several times throughout this book, domain-driven design’s ubiquitous language is essential for achieving knowledge and building an effective solution model.

设计成功现代化的前提是具备业务领域的知识和有效的模型。正如我在本书中多次提到的,领域驱动设计的通用语言对于获得知识和构建有效的解决方案模型至关重要。

Don’t forget domain-driven design’s shortcut for gathering domain knowledge: EventStorming. Use EventStorming to build a ubiquitous language with the domain experts and explore the legacy codebase, especially if the codebase is an undocumented mess that no one truly understands. Gather everyone related to its functionality and explore the business domain. EventStorming is a fantastic tool for recovering domain knowledge.

不要忘记领域驱动设计收集领域知识的捷径:事件风暴(EventStorming)。使用事件风暴与领域专家一起构建通用语言,并探索遗留代码库,特别是如果代码库是一个无人真正理解的、没有文档记录的混乱局面。召集与该功能相关的所有人员,探索业务领域。事件风暴是恢复领域知识的绝佳工具。

Once you are equipped with the domain knowledge and its model(s), decide which business logic implementation patterns best suit the business functionality in question. As a starting point, use the design heuristics described in Chapter 10. The next decision you have to make concerns the modernization strategy: gradually replacing whole components of the system (the strangler pattern), or gradually refactoring the existing solution.

一旦您具备了领域知识及其模型,就可以决定哪种业务逻辑实现模式最适合所讨论的业务功能。首先,使用第10章中描述的设计启示。接下来你要做出的决定涉及现代化策略:是逐渐替换系统的整个组件(扼杀者模式),还是逐渐重构现有解决方案。

Strangler pattern

扼杀者模式

Strangler fig, shown in Figure 13-3, is a family of tropical trees that share a peculiar growth pattern: stranglers grow over other trees—host trees. A strangler begins its life as a seed in the upper branches of the host tree. As the strangler grows, it makes its way down until it roots in the soil. Eventually, the strangler grows foliage that overshadows the host tree, leading to the host tree’s death.

如图13-3所示,榕树(Strangler fig)是一种热带树木,具有一种特殊的生长模式:榕树生长在其他树木——宿主树上。榕树的生命始于宿主树上部枝条中的一颗种子。随着榕树的生长,它向下延伸,直到在土壤中扎根。最终,榕树长出茂盛的枝叶,遮蔽了宿主树,导致宿主树死亡。

Figure 13-3. A strangler fig growing on top of its host tree (source: https://unsplash.com/photos/y_l5tep9wxI)

图13-3.  生长在宿主树顶部的榕树

The strangler migration pattern is based on the same growth dynamic as the tree the pattern is named after. The idea is to create a new bounded context —the strangler—use it to implement new requirements, and gradually migrate the legacy context’s functionality into it. At the same time, except for hotfixes and other emergencies, the evolution and development of the legacy bounded context stops. Eventually, all functionality is migrated to the new bounded context—the strangler— and following the analogy, leading to the death of the host—the legacy codebase.

扼杀者迁移模式(Strangler Migration Pattern)是基于与这种树木相同的生长动力而命名的。其思路是创建一个新的有界上下文——即扼杀者(strangler)——使用它来实现新的需求,并逐渐将遗留上下文的功能迁移到这个新上下文中。同时,除了紧急修复和其他紧急情况外,遗留有界上下文的演进和开发将停止。最终,所有功能都将迁移到新的有界上下文——即扼杀者(strangler)——中,按照这种类比,导致宿主——即遗留代码库——的“死亡”。

Usually, the strangler pattern is used in tandem with the façade pattern: a thin abstraction layer that acts as the public interface and is in charge of forwarding the requests to processing either by the legacy or the modernized bounded context. When migration completes—that is, when the host dies—the façade is removed as it is no longer necessary (see Figure 13-4).

通常,扼杀者模式是与门面模式一起使用的:一个作为公共接口的薄抽象层,负责将请求转发到通过遗留或现代化的有界上下文进行处理。当迁移完成时——也就是宿主完成使命时——门面被移除,因为它不再需要(参见图13-4)。

Figure 13-4. The façade layer forwarding the request based on the status of migrating the functionality from the legacy to the modernized system; once the migration is complete, both the façade and the legacy system are removed

图13-4.  门面层根据从遗留系统迁移到现代化系统的功能状态来转发请求;一旦迁移完成,门面层和遗留系统都将被移除

Contrary to the principle that each bounded context is a separate subsystem, and thus cannot share its database with other bounded contexts, the rule can be relaxed when implementing the strangler pattern. Both the modernized and the legacy contexts can use the same database for the sake of avoiding complex integration between the contexts, which in many cases can entail distributed transactions—both contexts have to work with the same data, as shown in Figure 13-5.

与每个有界上下文都是独立的子系统,因此不能与其他有界上下文共享其数据库的原则相反,在实现扼杀者模式时,这一规则可以放宽。为了避免上下文之间的复杂集成(这在很多情况下可能涉及分布式事务),现代化和遗留上下文都可以使用相同的数据库,因为这两个上下文都需要处理相同的数据,如图13-5所示。

The condition for bending the one-database-per-bounded-context rule is that eventually, and better sooner than later, the legacy context will be retired, and the database will be used exclusively by the new implementation.

放宽“每个有界上下文一个数据库”规则的条件是,遗留上下文最终将被淘汰,并且越快越好,并且数据库将由新实现专门使用。

Figure 13-5. Both the legacy and the modernized systems temporarily working with the same database

图13-5.  遗留系统和现代化系统暂时使用相同的数据库

An alternative to strangler-based migration is modernizing the legacy codebase in place, also called refactoring.

基于扼杀者模式的迁移的一个替代方案是在原地现代化改造遗留代码库,这也被称为重构(refactoring)。

Refactoring tactical design decisions

重构战术设计决策

In Chapter 11, you learned the various aspects of migrating tactical design decisions. However, there are two nuances to be aware of when modernizing a legacy codebase.

在第11章中,您学习了迁移战术设计决策的各个方面。然而,在对遗留代码库进行现代化改造时,需要注意两个细微差别。

First, small incremental steps are safer than a big rewrite. Therefore, don’t refactor a transaction script or active record straight to an event-sourced domain model. Instead, take the intermediate step of designing state-based aggregates. Invest the effort in finding effective aggregate boundaries. Ensure that all related business logic resides within those boundaries. Going from state-based to event-sourced aggregates will be orders of magnitude safer than discovering wrong transactional boundaries in an event-sourced aggregate.

首先,小步骤的增量改进比大规模重写更安全。因此,不要直接将事务脚本或活动记录重构为基于事件的领域模型。相反,采取基于状态的聚合设计的中间步骤。投入精力寻找有效的聚合边界。确保所有相关的业务逻辑都位于这些边界内。从基于状态的聚合到基于事件的聚合的转变,将比在基于事件的聚合中发现错误的事务边界要安全得多。

Second, following the same reasoning of taking small incremental steps, refactoring to a domain model doesn’t have to be an atomic change. Instead, you can gradually introduce the elements of the domain model pattern.

其次,同样基于采取小步骤的增量改进这一逻辑,重构领域模型不必一蹴而就。相反,你可以逐渐引入领域模型模式的元素。

Start by looking for possible value objects. Immutable objects can significantly reduce the solution’s complexity, even if you are not using a full-blown domain model.

首先,寻找可能的值对象。即使不使用完整的领域模型,不可变对象也可以显著降低解决方案的复杂性。

As we discussed in Chapter 11, refactoring active records into aggregates doesn’t have to be done overnight. It can be done in gradual steps. Start by gathering the related business logic. Next, analyze the transactional boundaries. Are there decisions that require strong consistency but operate on eventually consistent data? Or conversely, does the solution enforce strong consistency where eventual consistency would suffice? When analyzing the codebase, don’t forget that these decisions are driven by business, not technology, concerns. Only after a thorough analysis of the transactional requirements should you design the aggregate’s boundaries.

正如我们在第11章中讨论的那样,将活动记录重构为聚合不必一蹴而就。这可以逐步进行。首先,收集相关的业务逻辑。接下来,分析事务边界。是否存在需要强一致性但操作在最终一致数据上的决策?或者相反,解决方案是否在最终一致性足以满足要求的情况下强制执行了强一致性?在分析代码库时,不要忘记这些决策是由业务需求驱动的,而不是技术。只有在彻底分析了事务需求之后,才应该设计聚合的边界。

Finally, when necessary as you’re refactoring legacy systems, protect the new codebase from old models using an anticorruption layer, and protect the consumers from changes in the legacy codebase by implementing an open-host service and exposing a published language.

最后,在重构遗留系统时,如有必要,请使用防腐层(anticorruption layer)保护新代码库免受旧模型的影响,并通过实现开放主机服务并公开发布语言来保护消费者免受遗留代码库更改的影响。

Pragmatic Domain-Driven Design

务实的领域驱动设计

As we discussed in this chapter’s introduction, applying domain-driven design is not an all-or-nothing endeavor. You don’t have to apply every tool DDD has to offer. For example, for some reason, the tactical patterns might not work for you. Maybe you prefer to use other design patterns because they work better in your specific domain, or just because you find other patterns more effective. That’s totally fine!

正如我们在本章引言中讨论的那样,应用领域驱动设计(DDD)并不是一项要么全有要么全无的工作。你不需要应用DDD提供的所有工具。例如,出于某种原因,战术模式可能并不适合你。也许你更喜欢使用其他设计模式,因为它们在你的特定领域中效果更好,或者仅仅是因为你觉得其他模式更有效。这完全没问题!

As long as you analyze your business domain and its strategy, look for effective models to solve particular problems, and most importantly, make design decisions based on the business domain’s needs: that’s domain-driven design!

只要你分析你的业务领域及其策略,寻找有效的模型来解决特定问题,最重要的是,基于业务领域的需求做出设计决策:这就是领域驱动设计!

It’s worth reiterating that domain-driven design is not about aggregates or value objects. Domain-driven design is about letting your business domain drive software design decisions.

值得重申的是,领域驱动设计不仅仅是关于聚合或值对象的。领域驱动设计是关于让你的业务领域驱动软件设计决策的。

Selling Domain-Driven Design

推销领域驱动设计

When I present on this topic at technology conferences, there is one question that I’m asked almost every time: “That all sounds great, but how do I ‘sell’ domain-driven design to my team and management?” That’s an extremely important question.

当我在技术会议上就这个主题进行演讲时,几乎每次都会有人问我一个问题:“这些听起来都很棒,但我该如何向我的团队和管理层‘推销’领域驱动设计呢?”这是一个非常重要的问题。

Selling is hard, and personally, I hate selling. That said, if you think about it, designing software is selling. We are selling our ideas to the team, to management, or to customers. However, a methodology that covers such a wide range of design decision aspects, and even reaches outside the engineering zone to involve other stakeholders, can be extremely hard to sell.

推销很难,我个人也很讨厌推销。不过,如果仔细想想,设计软件其实就是一种推销。我们在向团队、管理层或客户推销我们的想法。然而,一种涉及如此广泛的设计决策方面,甚至超出工程领域涉及其他利益相关者的方法论,可能会非常难以推销。

Management support is essential for making any considerable changes in an organization. However, unless the top-level managers are already familiar with domain-driven design or are willing to invest time to learn the business value of the methodology, it’s not top of mind for them, especially because of a seemingly large shift in the engineering process that DDD entails. Fortunately, however, it doesn’t mean you can’t use domain-driven design.

管理层的支持对于组织中的任何重大变革都是至关重要的。然而,除非高层管理者已经熟悉领域驱动设计,或者愿意投入时间学习该方法论的业务价值,否则他们不会优先考虑这一点,尤其是因为领域驱动设计似乎需要工程流程发生很大的转变。然而,幸运的是,这并不意味着你不能使用领域驱动设计。

Undercover Domain-Driven Design

揭秘领域驱动设计

Make domain-driven design a part of your professional toolbox, not an organizational strategy. DDD’s patterns and practices are engineering techniques, and since software engineering is your job, use them!

将领域驱动设计作为你专业工具箱的一部分,而不是组织策略。DDD的模式和实践是工程技术,既然软件工程是你的工作,那就使用它们吧!

Let’s see how to incorporate DDD into your day-to-day job without making much ado about it.

让我们来看看如何将 DDD 融入到你的日常工作中,而不必为此大费周章。

Ubiquitous language

通用语言

The use of a ubiquitous language is the cornerstone practice of domain-driven design. It is essential for domain knowledge discovery, communication, and effective solution modeling.

使用通用语言是领域驱动设计的基础实践。它对领域知识发现、沟通和有效的解决方案建模至关重要。

Luckily, this practice is so trivial that it’s borderline common sense. Listen carefully to the language the stakeholders use when they speak about the business domain. Gently steer the terminology away from technical jargon and toward its business meaning.

幸运的是,这种做法非常简单,几乎可以说是常识。仔细倾听利益相关者谈论业务领域时所使用的语言。平滑地将术语从技术术语引导到其业务含义上。

Look for inconsistent terms and ask for clarifications. For example, if there are multiple names for the same thing, look for the reason. Are those different models intertwined in the same solution? Look for contexts and make them explicit. If the meaning is the same, follow common sense and ask for one term to be used.

寻找不一致的术语并要求澄清。例如,如果同一事物有多个名称,请寻找原因。这些不同的模型是否在同一解决方案中相互交织?寻找上下文并使其明确。如果含义相同,请遵循常识并要求使用一个术语。

Also, communicate with domain experts as much as possible. These efforts shouldn’t necessarily require formal meetings. Watercoolers and coffee breaks are great communication facilitators. Speak with the domain experts about the business domain. Try using their language. Look for difficulties in understanding and ask for clarifications. Don’t worry—domain experts are usually happy to collaborate with engineers who are sincerely interested in learning about the problem domain!

此外,尽可能多与领域专家沟通。这些努力不一定需要正式的会议。饮水机旁和咖啡休息时间都是很好的沟通促进者。与领域专家谈论业务领域。试着使用他们的语言。寻找理解上的困难并要求澄清。不用担心——领域专家通常很高兴与真正对问题领域感兴趣的工程师合作!

Most importantly, use the ubiquitous language in your code and all project-related communication. Be patient. Changing the terminology that has been used in an organization for a while will take time, but eventually, it will catch on.

最重要的是,在你的代码和所有与项目相关的沟通中使用通用语言。要有耐心。改变一个组织已经使用了一段时间的术语需要时间,但最终它会被接受。

Bounded contexts

有界上下文

When exploring possible decomposition options, resolve to the principles behind what the bounded context pattern is based on:

在探索可能的分解选项时,要遵循有界上下文模式所基于的原则:

  • Why is it better to design problem-oriented models instead of a single model for all use cases? Because “all-in-one” solutions are rarely effective for anything.    为什么设计面向问题的模型比设计适用于所有用例的单一模型更好?因为“一刀切”的解决方案几乎对任何事情都不有效。
  • Why can’t a bounded context host conflicting models? Because of the increased cognitive load and solution complexity.    为什么限界上下文不能包含相互冲突的模型?因为这会增加认知负荷和解决方案的复杂性。
  • Why is it a bad idea for multiple teams to work on the same codebase? Because of friction and hindered collaboration between the teams.    为什么多个团队在同一个代码库上工作是个坏主意?因为团队之间会产生摩擦和阻碍协作。

Use the same reasoning for bounded context integration patterns: make sure you understand the problem each pattern is supposed to solve.

对于有界上下文集成模式,也使用相同的推理:确保你理解每个模式应该解决的问题。

Tactical design decisions

战术设计决策

When discussing tactical design patterns, don’t appeal to authority: “Let’s use an aggregate here because the DDD book says so!” Instead, appeal to logic. For example:

在讨论战术设计模式时,不要诉诸权威:“我们在这里使用聚合,因为DDD的书上是这么说的!”相反,要诉诸逻辑。例如:

  • Why are explicit transactional boundaries important? To protect the consistency of the data.    为什么明确的事务边界很重要?为了保护数据的一致性。
  • Why can’t a database transaction modify more than one instance of an aggregate? To ensure that the consistency boundaries are correct.    为什么数据库事务不能修改多个聚合的实例?为了确保一致性边界的正确性。
  • Why can’t an aggregate’s state be modified directly by an external component? To ensure that all the related business logic is colocated and not duplicated.    为什么聚合的状态不能直接由外部组件修改?为了确保所有相关的业务逻辑都位于同一位置且不会重复。
  • Why can’t we offload some of the aggregate’s functionality to a stored procedure? To make sure that no logic is duplicated. Duplicated logic, especially in logically and physically distant components of a system, tends to go out of sync and lead to data corruption.    为什么我们不能将聚合的一些功能放到存储过程中呢?为了确保没有重复的逻辑。重复的逻辑,尤其是在逻辑上和物理上相距甚远的系统组件中,往往会失去同步,导致数据损坏。
  • Why should we strive for small aggregate boundaries? Because wide transactional scope will both increase the complexity of the aggregate and negatively impact the performance.    为什么我们应该努力保持小的聚合边界?因为宽泛的事务范围既会增加聚合的复杂性,又会对性能产生负面影响。
  • Why, instead of event sourcing, can’t we just write events to a logfile? Because there are no long-term data consistency guarantees.    为什么我们不能直接将事件写入日志文件来代替事件溯源?因为没有长期的数据一致性保证。

Speaking of event sourcing, when the solution calls for an event-sourced domain model, implementation of this pattern might be hard to sell. Let’s take a look at a Jedi mind trick that can help with this.

说到事件溯源,当解决方案需要事件溯源领域模型时,这种模式的实现可能很难推销。让我们来看看一个绝地武士的心灵操控,可以帮助这一点。

翻译:说到事件溯源,当解决方案需要一个基于事件源的领域模型时,实现这个模式可能很难被接受。让我们来看看一个可以帮助我们的“绝地武士思维技巧”。

Event-sourced domain model

事件溯源领域模型

Despite its many advantages, event sourcing sounds too radical for many people. As with everything we’ve discussed in this book, the solution is to let the business domain drive this decision.

尽管事件溯源有很多优点,但对许多人来说,它听起来太激进了。与本书中我们讨论的所有内容一样,解决方法是让业务领域驱动这个决策。

Talk to domain experts. Show them the state- and event-based models. Explain the differences and the advantages offered by event sourcing, especially with regard to the dimension of time. More often than not, they will be ecstatic with the level of insight it provides and will advocate event sourcing themselves.

与领域专家交谈,向他们展示基于状态和事件的模型。解释事件溯源提供的差异和优势,特别是在时间维度方面。通常情况下,他们会对它提供的洞察力水平感到欣喜若狂,并且会拥护事件溯源。

And while interacting with the domain experts, don’t forget to work on the ubiquitous language!

在与领域专家交流时,不要忘记使用通用语言!

Conclusion

总结

In this chapter, you learned various techniques for leveraging domaindriven design tools in real-life scenarios: when working on brownfield projects and legacy codebases, and not necessarily with a team of DDD experts.

在本章中,您学习了在实际场景中利用领域驱动设计工具的各种技术:在处理待重新开发项目和遗留代码库时,不一定要与 DDD 专家团队一起工作。

As in greenfield projects, always start by analyzing the business domain. What are the company’s goals and its strategy for achieving them? Use the organizational structure and existing software design decisions to identify the organization’s subdomains and their types. With this knowledge, plan the modernization strategy. Look for pain points. Look to gain the most business value. Modernize legacy code either by refactoring or by replacing the relevant components. Either way, do it gradually. Big rewrites entail more risk than business value!

与全新项目一样,始终从分析业务领域开始。公司的目标是什么?实现这些目标的策略是什么?利用组织结构和现有的软件设计决策来确定组织的子域及其类型。有了这些知识,就可以规划现代化战略。寻找痛点。寻求获得最大的商业价值。通过重构或替换相关组件来现代化遗留代码。无论哪种方式,都要逐步进行。大规模的重写会带来比商业价值更多的风险!

Finally, you can use domain-driven design tools even if DDD is not widely adopted in your organization. Use the right tools, and when discussing them with colleagues, always use the logic and principles behind each pattern.

最后,即使您的组织中没有广泛采用领域驱动设计,您仍然可以使用领域驱动设计工具。使用正确的工具,并在与同事讨论这些工具时,始终使用每个模式背后的逻辑和原则。

This chapter concludes our discussion of domain-driven design on its own. In Part IV, you will learn about the interplay of DDD with other methodologies and patterns.

本章结束了我们关于领域驱动设计本身的讨论。在第四部分,您将学习DDD与其他方法和模式之间的相互作用。

Exercises

练习

1. Assume you want to introduce domain-driven design tools and practices to a brownfield project. What is going to be your first step?    假设您想将领域驱动设计的工具和实践引入到一个待重新开发项目中。您的第一步会是什么?

a. Refactor all business logic to the event-sourced domain model.    将所有业务逻辑重构为基于事件源的领域模型。

b. Analyze the organization’s business domain and its strategy.    分析组织的业务领域及其战略。

c. Improve the system’s components by ensuring that they follow the principles of proper bounded contexts.    通过确保系统组件遵循适当的有界上下文原则来改进它们。

d. It’s impossible to use domain-driven design in a brownfield project.    在待重新开发项目中使用领域驱动设计是不可能的。

答案:b。

2. In what ways does the strangler pattern contradict some of the core principles of domain-driven design during the migration process?    在迁移过程中,哪些方面使得“扼杀者模式”(Strangler Pattern)与领域驱动设计(DDD)的一些核心原则相矛盾?

a. Multiple bounded contexts are using a shared database.    多个有界上下文使用共享数据库。

b. If the modernized bounded context is a core subdomain, its implementation gets duplicated in the old and the new implementations.    如果现代化的有界上下文是一个核心子域,那么它的实现将在旧的和新的实现中得到重复。

c. Multiple teams are working on the same bounded context.    多个团队在相同的有界上下文中工作。

d. A and B.    A和B。

答案:d。

3. Why is it generally not a good idea to refactor active-record-based business logic straight into the event-sourced domain model?    为什么通常不建议直接将基于活动记录(Active Record)的业务逻辑重构到基于事件源的领域模型中?

a. A state-based model makes it easier to refactor aggregates’ boundaries during the learning process.    基于状态的模型使得在学习过程中重构聚合的边界变得更加容易。

b. It’s safer to introduce big changes gradually.    逐步引入大的变化会更安全。

c. A and B.    A和B。

d. None of the above. It’s reasonable to refactor even a transaction script straight into an event-sourced domain model.    以上都不是。即使是事务脚本直接重构为基于事件源的领域模型也是合理的。

答案:c。

4. When you’re introducing the aggregate pattern, your team asks why the aggregate can’t just reference all the possible entities and thus make it possible to traverse the whole business domain from one place. How do you answer them?    当你引入聚合模式时,你的团队问为什么聚合不能直接引用所有可能的实体,从而使得能够从一个地方遍历整个业务领域。你如何回答他们?

答案:An aggregate with a bounded context-wide boundary may make all of the bounded context’s data a part of one big transaction. It’s also likely that performance issues with this approach will be evident from the get go. Once that happens, the transactional boundary will be removed. As a result, it will no longer be possible to assume that the information residing in the aggregate is strongly consistent.    一个具有有界上下文范围内边界的聚合可能会使限界上下文的所有数据成为一个大事务的一部分。此外,这种方法从一开始就可能出现性能问题。一旦出现这种情况,事务边界将被移除。因此,将不再可能假设聚合中的信息是强一致性的。

posted @ 2023-03-18 10:48  菜鸟吊思  阅读(71)  评论(0)    收藏  举报