Fourteen. Maintaining Model Integrity
I once worked on a project where several teams were working in parallel on a major new system. One day, the team working on the customer-invoicing module was ready to implement an object they called Charge, when they discovered that another team had already built one. Diligently, they set out to reuse the existing object. They discovered it didn’t have an “expense code,” so they added one. It already had the “posted amount” attribute they needed. They had been planning to call it “amount due,” but—what’s in a name?—they changed it. Adding a few more methods and associations, they got something that looked like what they wanted, without disturbing what was there. They had to ignore many associations they didn’t need, but their application module ran.
我曾经参加过一个项目,在这个项目中几个团队同时开发一个重要的新系统。有一天,当负责“客户发票”模块的团队正准备实现一个他们称之为 Charge(收费)的对象时,他们发现另一个团队已经构建了这个对象,于是决定重复使用这个现有对象。他们发现它没有 expense code(费用代码)属性,因此添加了一个。对象中有一个 posted amount(过账金额)属性是他们所需要的。他们本来计划把这个属性叫做 amount due(到期金额),但名称不同有什么关系呢?于是他们把名称改成了“posted amount”。又添加了几个方法和关联后,他们得到了所需的对象,而且没有扰乱任何事情。虽然他们必须忽略掉一些不需要的关联,但他们的模块运行很正常。
A few days later, mysterious problems surfaced in the bill-payment application module for which the Charge had originally been written. Strange Charges appeared that no one remembered entering and that didn’t make any sense. The program began to crash when some functions were used, particularly the month-to-date tax report. Investigation revealed that the crash resulted when a function was used that summed up the amount deductible for all the current month’s payments. The mystery records had no value in the “percent deductible” field, although the validation of the data-entry application required it and even put in a default value.
几天之后,“账单支付”模块出现了一些奇怪的问题(Charge 对象最初就是为这个模块编写的)。系统中出现了一些奇怪的 Charge,没有人记得曾经输入过它们,而且它们也没有任何意义。当使用某些函数时,特别是使用当月月初至今(month-to-date)的税务报表时,程序就会崩溃。调查发现,当用于计算所有当月付款的可扣除总额的函数被调用时,程序就会崩溃。那些来历不明的记录在 percent deductible(可扣除百分比)字段中没有值,尽管数据录入应用程序的验证需要这个值,甚至为它设置了一个默认值。
The problem was that these two groups had different models, but they did not realize it, and there were no processes in place to detect it. Each made assumptions about the nature of a charge that were useful in their context (billing customers versus paying vendors). When their code was combined without resolving these contradictions, the result was unreliable software.
问题在于这两个团队使用了不同的模型,而他们并没有认识到这一点,也没有用于检测这一问题的过程。每个团队都对 Charge 对象的特性做了一些假设,使之能够在自己的上下文中使用(一个是向客户收费,另一个是向供应商付款)。当他们的代码被组合到一起而没有消除这些矛盾时,结果就产生了不可靠的软件。
If only they had been more aware of this reality, they could have consciously decided how to deal with it. That might have meant working together to hammer out a common model and then writing an automated test suite to prevent future surprises. Or it might simply have meant an agreement to develop separate models and keep hands off each other’s code. Either way, it starts with an explicit agreement on the boundaries within which each model applies.
如果他们一开始就意识到这一点,就能决定如何来解决它。他们可以共同开发出一个公共的模型,然后编写自动测试套件来防止以后出现意外。也可以双方商定开发各自的模型,而互相不干扰对方的代码。无论采用哪种方法,首先都要明确边界,各模型只在各自的边界内使用。
What did they do once they knew about the problem? They created separate Customer Charge and Supplier Charge classes and defined each according to the needs of the corresponding team. The immediate problem having been solved, they went back to doing things just as before. Oh well.
他们在知道了问题所在之后采取了什么措施呢?他们创建了两个不同的类:Customer Charge(客户收费)类和 Supplier Charge(供应商收费)类。并根据各自的需求定义了每个类。解决了眼前这个问题之后,他们又按以前的方式开始工作了。
Although we seldom think about it explicitly, the most fundamental requirement of a model is that it be internally consistent; that its terms always have the same meaning, and that it contain no contradictory rules. The internal consistency of a model, such that each term is unambiguous and no rules contradict, is called unification. A model is meaningless unless it is logically consistent. In an ideal world, we would have a single model spanning the whole domain of the enterprise. This model would be unified, without any contradictory or overlapping definitions of terms. Every logical statement about the domain would be consistent.
模型最基本的要求是它应该保持内部一致,术语总具有相同的意义,并且不包含互相矛盾的规则:虽然我们很少明确地考虑这些要求。模型的内部一致性又叫做统一(unification),这种情况下,每个术语都不会有模棱两可的意义,也不会有规则冲突。除非模型在逻辑上是一致的,否则它就没有意义。在理想世界中,我们可以得到涵盖整个企业领域的单一模型。这个模型将是统一的,没有任何相互矛盾或相互重叠的术语定义。每个有关领域的逻辑声明都是一致的。
But the world of large systems development is not the ideal world. To maintain that level of unification in an entire enterprise system is more trouble than it is worth. It is necessary to allow multiple models to develop in different parts of the system, but we need to make careful choices about which parts of the system will be allowed to diverge and what their relationship to each other will be. We need ways of keeping crucial parts of the model tightly unified. None of this happens by itself or through good intentions. It happens only through conscious design decisions and institution of specific processes. Total unification of the domain model for a large system will not be feasible or cost-effective.
但大型系统开发并非如此理想。在整个企业系统中保持这种水平的统一是一件得不偿失的事情。在系统的各个不同部分中开发多个模型是很有必要的,但我们必须慎重地选择系统的哪些部分可以分开,以及它们之间是什么关系。我们需要用一些方法来保持模型关键部分的高度统一。所有这些都不会自行发生,而且光有良好的意愿也是没用的。它只有通过有意识的设计决策和建立特定过程才能实现。大型系统领域模型的完全统一即不可行,也不划算。
Sometimes people fight this fact. Most people see the price that multiple models exact by limiting integration and making communication cumbersome. On top of that, having more than one model somehow seems inelegant. This resistance to multiple models sometimes leads to very ambitious attempts to unify all the software in a large project under a single model. I know I’ve been guilty of this kind of overreaching. But consider the risks.
有时人们会反对这一点。大多数人都看到了多个模型的代价:它们限制了集成,并且使沟通变得很麻烦。更重要的是,多个模型看上去似乎不够雅致。有时,对多个模型的抵触会导致“极富雄心”的尝试——将一个大型项目中的所有软件统一到单一模型中。我自己就很后悔曾经这么做过了头。但请一定要考虑下面的风险。
- Too many legacy replacements may be attempted at once.
- Large projects may bog down because the coordination overhead exceeds their abilities.
- Applications with specialized requirements may have to use models that don’t fully satisfy their needs, forcing them to put behavior elsewhere.
- Conversely, attempting to satisfy everyone with a single model may lead to complex options that make the model difficult to use.
- 一次尝试对遗留系统做过多的替换。
- 大项目可能会陷入困境,因为协调的开销太大,超出了这些项目的能力范围。
- 具有特殊需求的应用程序可能不得不使用无法充分满足需求的模型,而只能将这些无法满足的行为放到其他地方。
- 另一方面,试图用一个模型来满足所有人的需求可能会导致模型中包含过于复杂的选择,因而很难使用。
What’s more, model divergences are as likely to come from political fragmentation and differing management priorities as from technical concerns. And the emergence of different models can be a result of team organization and development process. So even when no technical factor prevents full integration, the project may still face multiple models.
此外,除了技术上的因素以外,权力上的划分和管理级别的不同也可能要求把模型分开。而且不同模型的出现也可能是团队组织和开发过程导致的结果。因此,即使完全的集成没有来自技术方面的阻力,项目也可能会面临多个模型。
Given that it isn’t feasible to maintain a unified model for an entire enterprise, we don’t have to leave ourselves at the mercy of events. Through a combination of proactive decisions about what should be unified and pragmatic recognition of what is not unified, we can create a clear, shared picture of the situation. With that in hand, we can set about making sure that the parts we want to unify stay that way, and the parts that are not unified don’t cause confusion or corruption.
既然无法维护一个涵盖整个企业的统一模型,那就不要再受到这种思路的限制。通过预先决定什么应该统一,并实际认识到什么不能统一,我们就能够创建一个清晰的、共同的视图。确定了这些之后,就可以着手开始工作,以保证那些需要统一的部分保持一致,不需要统一的部分不会引起混乱或破坏模型。
We need a way to mark the boundaries and relationships between different models. We need to choose our strategy consciously and then follow our strategy consistently.
我们需要用一种方式来标记出不同模型之间的边界和关系。我们需要有意识地选择一种策略,并一致地遵守它。
This chapter lays out techniques for recognizing, communicating, and choosing the limits of a model and its relationships to others. It all starts with mapping the current terrain of the project. A BOUNDED CONTEXT defines the range of applicability of each model, while a CONTEXT MAP gives a global overview of the project’s contexts and the relationships between them. This reduction of ambiguity will, in and of itself, change the way things happen on the project, but it isn’t necessarily enough. Once we have a CONTEXT BOUNDED, a process of CONTINUOUS INTEGRATION will keep the model unified.
本章将介绍一些用于识别、沟通和选择模型边界及关系的技术。讨论首先从描绘项目当前的范围开始。BOUNDED CONTEXT(限界上下文)定义了每个模型的应用范围,而 CONTEXT MAP(上下文图)则给出了项目上下文以及它们之间关系的总体视图。这些降低模糊性的技术能够使项目更好地进行,但仅仅有它们还是不够的。一旦确立了 CONTEXT 的边界之后,仍需要持续集成这种过程,它能够使模型保持统一。
Then, starting from this stable situation, we can start to migrate toward more effective strategies for BOUNDING CONTEXTS and relating them, ranging from closely allied contexts with SHARED KERNELS to loosely coupled models that go their SEPARATE WAYS.
其后,在这个稳定的基础之上,我们就可以开始实施那些在界定和关联 CONTEXT 方面更有效的策略了——从通过共享内核(SHARED KERNEL)来紧密关联上下文,到那些各行其道(SEPARATE WAYS)地进行松散耦合的模型。
A navigation map for model integrity patterns
Cells can exist because their membranes define what is in and out and determine what can pass.
细胞之所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜
Multiple models coexist on big projects, and this works fine in many cases. Different models apply in different contexts. For example, you may have to integrate your new software with an external system over which your team has no control. A situation like this is probably clear to everyone as a distinct context where the model under development doesn’t apply, but other situations can be more vague and confusing. In the story that opened this chapter, two teams were working on different functionality for the same new system. Were they working on the same model? Their intention was to share at least part of what they did, but there was no demarcation to tell them what they did or did not share. And they had no process in place to hold a shared model together or quickly detect divergences. They realized they had diverged only after their system’s behavior suddenly became unpredictable.
大型项目上会有多个模型共存,在很多情况下这没什么问题。不同的模型应用于不同的上下文中。例如,你可能必须将你的新软件与一个外部系统集成,而你的团队对这个外部系统没有控制权。在这种情况下,任何人都明白这个外部系统是一种完全不同的上下文,不适用他们正在开发的模型,但还有很多情况是比较含糊和混乱的。在本章开篇所讲的那个故事中,两个团队为同一个新系统开发不同的功能。那么他们使用的是同一个模型吗?他们的意图是至少共享其所做的一部分工作,但却没有界限告诉他们共享了什么、没有共享什么。而且他们也没有一个过程来维持共享模型,或快速检测模型是否有分歧。他们只是在系统行为突然变得不可预测时才意识到他们之间产生了分歧。
Even a single team can end up with multiple models. Communication can lapse, leading to subtly conflicting interpretations of the model. Older code often reflects an earlier conception of the model that is subtly different from the current model.
即使在同一个团队中,也可能会出现多个模型。团队的沟通可能会不畅,导致对模型的理解产生难以捉摸的冲突。原先的代码往往反映的是早先的模型概念,而这些概念与当前模型有着微妙的差别。
Everyone is aware that the data format of another system is different and calls for a data conversion, but this is only the mechanical dimension of the problem. More fundamental is the difference in the models implicit in the two systems. When the discrepancy is not with an external system, but within the same code base, it is even less likely to be recognized. Yet this happens on all large team projects.
每个人都知道两个系统的数据格式是不同的,因此需要进行数据转换,但这只是问题的表面。问题的根本在于两个系统所使用的模型不同。当这种差异不是来自外部系统,而是发生在同一个系统中时,它将更难发现。然而,所有大型团队项目都会发生这种情况。
Multiple models are in play on any large project. Yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand. Communication among team members becomes confused. It is often unclear in what context a model should not be applied.
任何大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现 bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。
Failure to keep things straight is ultimately revealed when the running code doesn’t work right, but the problem starts in the way teams are organized and the way people interact. Therefore, to clarify the context of a model, we have to look at both the project and its end products (code, database schemas, and so on).
模型混乱的问题最终会在代码不能正常运行时暴露出来,但问题的根源却在于团队的组织方式和成员的交流方法。因此,为了澄清模型的上下文,我们既要注意项目,也要注意它的最终产品(代码、数据库模式等)。
A model applies in a context. The context may be a certain part of the code, or the work of a particular team. For a model invented in a brainstorming session, the context could be limited to that particular conversation. The context of a model used in an example in this book is that particular example section and any later discussion of it. The model context is whatever set of conditions must apply in order to be able to say that the terms in a model have a specific meaning.
一个模型只在一个上下文中使用。这个上下文可以是代码的一个特定部分,也可以是某个特定团队的工作。如果模型是在一次头脑风暴会议中得到的,那么这个模型的上下文可能仅限于那次讨论。就拿本书中的例子来说,示例中所使用的模型的上下文就是那个示例所在的小节以及任何相关的后续讨论。模型上下文是为了保证该模型中的术语具有特定意义而必须要应用的一组条件。
To begin to solve the problems of multiple models, we need to define explicitly the scope of a particular model as a bounded part of a software system within which a single model will apply and will be kept as unified as possible. This definition has to be reconciled with the team organization.
为了解决多个模型的问题,我们需要明确地定义模型的范围——模型的范围是软件系统中一个有界的部分,这部分只应用一个模型,并尽可能使其保持统一。团队组织中必须一致遵守这个定义。
Therefore:
因此:
Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas. Keep the model strictly consistent within these bounds, but don’t be distracted or confused by issues outside.
明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
A BOUNDED CONTEXT delimits the applicability of a particular model so that team members have a clear and shared understanding of what has to be consistent and how it relates to other CONTEXTS. Within that CONTEXT, work to keep the model logically unified, but do not worry about applicability outside those bounds. In other CONTEXTS, other models apply, with differences in terminology, in concepts and rules, and in dialects of the UBIQUITOUS LANGUAGE. By drawing an explicit boundary, you can keep the model pure, and therefore potent, where it is applicable. At the same time, you avoid confusion when shifting your attention to other CONTEXTS. Integration across the boundaries necessarily will involve some translation, which you can analyze explicitly.
BOUNDED CONTEXT 明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。在 CONTEXT 中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他 CONTEXT 中,会使用其他模型,这些模型具有不同的术语、概念、规则和 UBIQUITOUS LANGUAGE 的技术行话。通过划定明确的边界,可以使模型保持纯粹,因而在它所适用的 CONTEXT 中更有效。同时,也避免了将注意力切换到其他 CONTEXT 时引起的混淆。跨边界的集成必然需要进行一些转换,但我们可以清楚地分析这些转换。
BOUNDED CONTEXTS Are Not MODULES
The issues are confused sometimes, but these are different patterns with different motivations. True, when two sets of objects are recognized as making up different models, they are almost always placed in separate MODULES. Doing so does provide different name spaces (essential for different CONTEXTS) and some demarcation.
有时这两个概念易引起混淆,但它们是具有不同动机的不同模式。确实,当两组对象组成两个不同模型时,人们几乎总是把它们放在不同的 MODULE 中。这样做的确提供了不同的命名空间(对不同的 CONTEXT 很重要)和一些划分方法。
But MODULES also organize the elements within one model; they don’t necessarily communicate an intention to separate CONTEXTS. The separate name spaces that MODULES create within a BOUNDED CONTEXT actually make it harder to spot accidental model fragmentation.
但人们也会在一个模型中用 MODULE 来组织元素,它们不一定要表达划分 CONTEXT 的意图。MODULE 在 BOUNDED CONTEXT 内部创建的独立命名空间实际上使人们很难发现意外产生的模型分裂。
Example: Booking Context
示例预订系统的上下文
A shipping company has an internal project to develop a new application for booking cargo. This application is to be driven by an object model. What is the BOUNDED CONTEXT within which this model applies? To answer this question, we have to look at what is happening on the project. Keep in mind, this is a look at the project as it is, not as it ideally should be.
一家运输公司的内部项目——为货物预订开发一个新的应用程序。这个应用由一个对象模型驱动。那么这个模型所应用的 BOUNDED CONTEXT 是什么呢?为了回答这个问题,我们必须看一下项目正在发生的事情。记住,这里是观察项目的现状,而不是它的理想状态。
One project team is working on the booking application itself. They are not expected to modify the model objects, but the application they are building has to display and manipulate those objects. This team is a consumer of the model. The model is valid within the application (its primary consumer), and therefore the booking application is in bounds.
预订应用程序的开发工作由一个项目团队负责。他们不能修改模型对象,但他们所构建的应用程序还必须要显示和操作这些对象。这个团队是模型的使用者。模型在应用程序(模型的主要使用者)中是有效的,因此预订应用程序在 BOUNDED CONTEXT 的边界之内。
The completed bookings have to be passed to the legacy cargotracking system. A decision was made up front that the new model would depart from that of the legacy, so the legacy cargotracking system is outside the boundary. Necessary translation between the new model and the legacy is to be the responsibility of the legacy maintenance team. The translation mechanism is not driven by the model. It is not in the BOUNDED CONTEXT. (It is part of the boundary itself, which will be discussed in CONTEXT MAP.) It is good that translation is out of CONTEXT (not based on the model). It would be unrealistic to ask the legacy team to make any real use of the model because their primary work is out of CONTEXT.
已完成的预订必须传递给用于货物跟踪的遗留系统来处理。项目一开始就已决定新模型将与原有系统的模型不同,因此原来的货物跟踪系统位于 BOUNDED CONTEXT 的边界之外。新旧模型之间的必要转换由原有系统的维护团队来负责处理。转换机制不是由新模型驱动的。因此它不在 BOUNDED CONTEXT 中(转换其实是边界本身的一部分,这一点将在 CONTEXT MAP 中讨论)。将转换机制置于 CONTEXT 之外(不基于模型),这一点很好。要求遗留系统的团队使用这个模型是不切实际的,因为他们的主要工作都发生在 CONTEXT 之外。
The team responsible for the model deals with the whole life cycle of each object, including persistence. Because this team has control of the database schema, they’ve been deliberately keeping the object-relational mapping straightforward. In other words, the schema is being driven by the model and therefore is in bounds.
每个对象的整个生命周期都由负责模型的团队来处理,包括对象的持久化。由于这个团队也控制着数据库模式,因此他们特意把对象—关系映射设计得简单直接。换言之,数据库模式是由新模型驱动的,因此在 BOUNDED CONTEXT 的边界之内。
Yet another team is working on a model and application for scheduling the voyages of the cargo ships. The scheduling and booking teams were initiated together, and both teams had intended to produce a single, unified system. The two teams have casually coordinated with each other, and they occasionally share objects, but they are not systematic about it. They are not working in the same BOUNDED CONTEXT. This is a risk, because they do not think of themselves as working on separate models. To the extent they integrate, there will be problems unless they put in place processes to manage the situation. (The SHARED KERNEL, discussed later in this chapter, might be a good choice.) The first step, though, is to recognize the situation as it is. They are not in the same CONTEXT and should stop trying to share code until some changes are made.
另有一个团队正在开发安排货轮航次的模型和应用。从项目一开始,这个团队与负责货物预订的团队就在一起工作,他们都打算开发一个单独的、统一的系统。这两支团队偶尔互相协调,也偶尔共享对象,但没有系统性地去做。他们不在同一个 BOUNDED CONTEXT 中工作。这会带来风险,因为他们并没有意识到各自正在使用不同的模型。到了集成的时候,就会出现问题,除非他们采取特定的过程来管理这种情况(共享内核可能就是一个很好的选择,本章后面会介绍)。但是,第一步是认清现状。他们不在同一个 CONTEXT 中,因此应该停止共享代码,直到做出一些改变之后再去共享。
This BOUNDED CONTEXT is made up of all those aspects of the system that are driven by this particular model: the model objects, the database schema that persists the model objects, and the booking application. Two teams work primarily in this CONTEXT: the modeling team and the application team. Information has to be exchanged with the legacy tracking system, and the legacy team has primary responsibility for the translation at this boundary, with cooperation from the modeling team. There is no clearly defined relationship between the booking model and the voyage schedule model, and defining that relationship should be one of those teams’ first actions. In the meantime, they should be very careful about sharing code or data.
在这个系统中,由该具体模型驱动的所有方方面面构成了其对应的 BOUNDED CONTEXT,这包括模型对象、用于模型对象持久化的数据库模式以及预订应用程序。在这个 CONTEXT 中主要有两支团队在工作,一个是建模团队,另一个是应用程序团队。这个系统需要与遗留的货物跟踪系统交换信息,遗留系统的维护团队主要负责在这个边界上的转换,并且与建模团队进行合作。预订模型和航次安排模型之间没有明确定义的关系,定义这种关系应该是这两个团队的首要任务之一。同时,他们应该在共享代码或数据方面格外谨慎。
So, what has been gained by defining this BOUNDED CONTEXT? For the teams working in CONTEXT: clarity. Those two teams know they must stay consistent with one model. They make design decisions in that knowledge and watch for fractures. For the teams outside: freedom. They don’t have to walk in the gray zone, not using the same model, yet somehow feeling they should. But the most concrete gain in this particular case is probably realizing the risk of the informal sharing between the booking model team and the voyage schedule team. To avoid problems, they really need to decide on the cost/benefit trade-offs of sharing and put in processes to make it work. This won’t happen unless everyone understands where the bounds of the model contexts are.
因此,通过定义这个 BOUNDED CONTEXT,最终得到了什么?对 CONTEXT 内的团队而言:清晰!。这两支团队知道他们必须与这个模型保持一致。他们根据这一点制定设计决策,并注意防范出现不一致的情况。而 CONTEXT 之外的团队获得了:自由。他们不必行走在灰色地带,不必使用同一个模型,虽然他们还是总觉得应该使用同一个模型。但在这个具体例子中,最实际的收获是认识到了在预订模型团队和航次安排团队之间进行信息共享存在着风险。为了避免问题产生,他们实际上需要在共享的代价和收益之间作出权衡,并制定流程来确保其发挥作用。只有每个人都理解模型上下文的边界在哪里,这一切才会发生。
Of course, boundaries are special places. The relationships between a BOUNDED CONTEXT and its neighbors require care and attention. The CONTEXT MAP charts the territory, giving the big picture of the CONTEXTS and their connections, while several patterns define the nature of the various relationships between CONTEXTS. And a process of CONTINUOUS INTEGRATION preserves unity of the model within a BOUNDED CONTEXT.
当然,边界只不过是一些特殊的位置。各个 BOUNDED CONTEXT 之间的关系需要我们仔细地处理。CONTEXT MAP 画出了上下文的范围,并给出了 CONTEXT 以及它们之间联系的总体视图,而几种模式定义了 CONTEXT 之间的各种关系的性质。CONTINUOUS INTEGRATION 的过程可以使模型在 BOUNDED CONTEXT 中保持统一。
But before proceeding to all that, what does it look like when unification of a model is breaking down? How do you recognize conceptual splinters?
但在讨论所有这些模式之前,想一想当模型的统一性被破坏时,模型会是什么样子呢?我们又该如何识别概念上的不一致呢?
Recognizing Splinters Within a BOUNDED CONTEXT
识别 BOUNDED CONTEXT 中的不一致
Many symptoms may indicate unrecognized model differences. Some of the most obvious are when coded interfaces don’t match up. More subtly, unexpected behavior is a likely sign. The CONTINUOUS INTEGRATION process with automated tests can help catch these kinds of problems. But the early warning is usually a confusion of language.
很多征兆都可能表明模型中出现了差异。最明显的是已编码的接口不匹配。对于更微妙的情况,一些意外行为也可能是一种信号。采用了自动测试的 CONTINUOUS INTEGRATION 可以帮助捕捉到这类问题。但语言上的混乱往往是一种早期的警告信号。
Combining elements of distinct models causes two categories of problems: duplicate concepts and false cognates. Duplication of concepts means that there are two model elements (and attendant implementations) that actually represent the same concept. Every time this information changes, it has to be updated in two places with conversions. Every time new knowledge leads to a change in one of the objects, the other has to be reanalyzed and changed too. Except the reanalysis doesn’t happen in reality, so the result is two versions of the same concept that follow different rules and even have different data. On top of that, the team members must learn not one but two ways of doing the same thing, along with all the ways they are being synchronized.
将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生变化时,都必须更新两个地方。每次由于新知识导致一个对象被修改时,必须重新分析和修改另一个对象。如果不进行实际的重新分析,结果就会出现同一概念的两个版本,它们遵守不同的规则,甚至有不同的数据。更严重的是,团队成员必须学习做同一件事情的两种方法,以及保持这两种方法同步的各种方式。
False cognates may be slightly less common, but more insidiously harmful. This is the case when two people who are using the same term (or implemented object) think they are talking about the same thing, but really are not. The example in the beginning of this chapter (two different business activities both called Charge) is typical, but conflicts can be even subtler when the two definitions are actually related to the same aspect in the domain, but have been conceptualized in slightly different ways. False cognates lead to development teams that step on each other’s code, databases that have weird contradictions, and confusion in communication within the team. The term false cognate is ordinarily applied to natural languages. For example, English speakers learning Spanish often misuse the word embarazada. This word does not mean “embarrassed”; it means “pregnant.” Oops.
假同源可能稍微少见一点,但它潜在的危害更大。它是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。本章开头的示例就是一个典型的例子(两个不同的业务活动都叫做 Charge)。但是,当两个定义都与同一个领域方面相关,而只是在概念上稍有区别时,这种冲突更难以发现。假同源会导致开发团队互相干扰对方的代码,也可能导致数据库中含有奇怪的矛盾,还会引起团队沟通的混淆。假同源这个术语在自然语言中也经常使用。例如,说英语的人在学习西班牙语时常常会误用 embarazada 这个词。这个词的意思并不是 embarrassed(难堪的),而是 pregnant(怀孕的)。很惊讶吧!
When you detect these problems, your team will have to make a decision. You may want to pull the model back together and refine the processes to prevent fragmentation. Or the fragmentation may be a result of groups who want to pull the model in different directions for good reasons, and you may decide to let them develop independently. Dealing with these issues is the subject of the remaining patterns in this chapter.
当发现这些问题时,团队必须要做出相应的决定。可能需要将模型重新整合为一体,并加强用来预防模型分裂的过程。分裂也有可能是由分组造成的,一些小组出于合理的原因,需要以一些不同的方式来开发模型,而且你可能也决定让他们独立开发。本章接下来要讨论的模式的主题就是如何解决这些问题。
Having defined a BOUNDED CONTEXT, we must keep it sound.
定义完一个 BOUNDED CONTEXT 后,必须让它保持合理。
When a number of people are working in the same BOUNDED CONTEXT, there is a strong tendency for the model to fragment. The bigger the team, the bigger the problem, but as few as three or four people can encounter serious problems. Yet breaking down the system into ever-smaller CONTEXTS eventually loses a valuable level of integration and coherency.
当很多人在同一个 BOUNDED CONTEXT 中工作时,模型很容易发生分裂。团队越大,问题就越大,但即使是 3、4 个人的团队也有可能会遇到严重的问题。然而,如果将系统分解为更小的 CONTEXT,最终又难以保持集成度和一致性。
Sometimes developers do not fully understand the intent of some object or interaction modeled by someone else, and they change it in a way that makes it unusable for its original purpose. Sometimes they don’t realize that the concepts they are working on are already embodied in another part of the model and they duplicate (inexactly) those concepts and behavior. Sometimes they are aware of those other expressions but are afraid to tamper with them, for fear of corrupting the existing functionality, and so they proceed to duplicate concepts and functionality.
有时开发人员没有完全理解其他人所创建的对象或交互的意图,就对它进行了修改,使其失去了原来的作用。有时他们没有意识到他们正在开发的概念已经在模型的另一个部分中实现了,从而导致了这些概念和行为(不正确的)重复。有时他们意识到了这些概念有其他的表示,但却因为担心破坏现有功能而不敢去改动它们,于是他们继续重复开发这些概念和功能。
It is very hard to maintain the level of communication needed to develop a unified system of any size. We need ways of increasing communication and reducing complexity. We also need safety nets that prevent overcautious behavior, such as developers duplicating functionality because they are afraid they will break existing code.
开发统一的系统(无论规模大小)需要维持很高的沟通水平,而这一点常常很难做到。我们需要运用各种方法来增进沟通并减小复杂性。还需要一些安全防护措施,以避免过于谨慎的行为(例如,开发人员由于担心破坏现有代码而重复开发一些功能)。
It is in this environment that Extreme Programming (XP) really comes into its own. Many XP practices are aimed at this specific problem of maintaining a coherent design that is being constantly changed by many people. XP in its purest form is a nice fit for maintaining model integrity within a single BOUNDED CONTEXT. However, whether or not XP is being used, it is essential to have some process of CONTINUOUS INTEGRATION.
极限编程(XP)在这样的环境中真正显示出自己的强大威力。很多 XP 实践都是针对在很多人频繁更改设计的情况下如何维护设计的一致性这个特定问题而出现的。最纯粹的 XP 非常适合维护单一 BOUNDED CONTEXT 中的模型完整性。但是,无论是否使用 XP,都很有必要采取 CONTINUOUS INTEGRATION 过程。
CONTINUOUS INTEGRATION means that all work within the context is being merged and made consistent frequently enough that when splinters happen they are caught and corrected quickly. CONTINUOUS INTEGRATION, like everything else in domain-driven design, operates at two levels: (1) the integration of model concepts and (2) the integration of the implementation.
CONTINUOUS INTEGRATION 是指把一个上下文中的所有工作足够频繁地合并到一起,并使它们保持一致,以便当模型发生分裂时,可以迅速发现并纠正问题。像领域驱动设计中的其他方法一样,CONTINUOUS INTEGRATION 也有两个级别的操作:(1)模型概念的集成;(2)实现的集成。
Concepts are integrated by constant communication among team members. The team must cultivate a shared understanding of the ever-changing model. Many practices help, but the most fundamental is constantly hammering out the UBIQUITOUS LANGUAGE. Meanwhile, the implementation artifacts are being integrated by a systematic merge/build/test process that exposes model splinters early. Many processes for integration are used, but most of the effective ones share these characteristics:
团队成员之间通过经常沟通来保证概念的集成。团队必须对不断变化的模型形成一个共同的理解。有很多方法可以帮助做到这一点,但最基本的方法是对 UBIQUITOUS LANGUAGE 多加锤炼。同时,实际工件通过系统性的合并/构建/测试过程来集成,这样能够尽早暴露出模型的分裂问题。用来集成的过程有很多,大部分有效的过程都具备以下这些特征:
- A step-by-step, reproducible merge/build technique;
- Automated test suites; and
- Rules that set some reasonably small upper limit on the lifetime of unintegrated changes.
- 分步集成,采用可重现的合并/构建技术;
- 自动测试套件;
- 有一些规则,用来为那些尚未集成的改动设置一个相当小的生命期上限。
The other side of the coin in effective processes, although it is seldom formally included, is conceptual integration.
有效过程的另一面是概念集成,虽然它很少被正式地纳入进来。
- Constant exercise of the UBIQUITOUS LANGUAGE in discussions of the model and application
- 在讨论模型和应用程序时要坚持使用 UBIQUITOUS LANGUAGE。
Most Agile projects have at least daily merges of each developer’s code changes. The frequency can be adjusted to the pace of change, as long as any unintegrated change would be merged before a significant amount of incompatible work could be done by other team members.
大多数敏捷项目至少每天会把每位开发人员所做的修改合并进来。这个频率可以根据更改的步伐来调整,只要确保该间隔不会导致大量不兼容的工作产生即可。
In a MODEL-DRIVEN DESIGN, the integration of concepts smooths the way for the integration of the implementation, while the integration of the implementation proves the validity and consistency of the model and exposes splinters.
在 MODEL-DRIVEN DESIGN 中,概念集成为实现集成铺平了道路,而实现集成验证了模型的有效性和一致性,并暴露出模型的分裂问题。
Therefore:
因此:
Institute a process of merging all code and other implementation artifacts frequently, with automated tests to flag fragmentation quickly. Relentlessly exercise the UBIQUITOUS LANGUAGE to hammer out a shared view of the model as the concepts evolve in different people’s heads.
建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查明模型的分裂问题。严格坚持使用 UBIQUITOUS LANGUAGE,以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能达成一个共识。
Finally, do not make the job any bigger than it has to be. CONTINUOUS INTEGRATION is essential only within a BOUNDED CONTEXT. Design issues involving neighboring CONTEXTS, including translation, don’t have to be dealt with at the same pace.
最后,不要在持续集成中做一些不必要的工作。CONTINUOUS INTEGRATION 只有在 BOUNDED CONTEXT 中才是重要的。相邻 CONTEXT 中的设计问题(包括转换)不必以同一个步调来处理。
CONTINUOUS INTEGRATION would be applied within any individual BOUNDED CONTEXT that is larger than a two-person task. It maintains the integrity of that single model. When multiple BOUNDED CONTEXTS coexist, you have to decide on their relationships and design any necessary interfaces. . . .
CONTINUOUS INTEGRATION 可以在任何单独的 BOUNDED CONTEXT 中使用,只要它的工作规模大到需要两个以上的人去完成就可以。它可以维护单一模型的完整性。当多个 BOUNDED CONTEXT 共存时,我们必须要确定它们的关系,并设计任何必需的接口。
An individual BOUNDED CONTEXT still does not provide a global view. The context of other models may still be vague and in flux.
只有一个 BOUNDED CONTEXT 并不能提供全局视图。其他模型的上下文可能仍不清楚而且还在不断变化。
People on other teams won’t be very aware of the CONTEXT bounds and will unknowingly make changes that blur the edges or complicate the interconnections. When connections must be made between different contexts, they tend to bleed into each other.
其他团队中的人员并不是十分清楚 CONTEXT 的边界,他们会不知不觉地做出一些更改,从而使边界变得模糊或者使互连变得复杂。当不同的上下文必须互相连接时,它们可能会互相重叠。
Code reuse between BOUNDED CONTEXTS is a hazard to be avoided. Integration of functionality and data must go through a translation. You can reduce confusion by defining the relationship between the different contexts and creating a global view of all the model contexts on the project.
BOUNDED CONTEXT 之间的代码重用是很危险的,应该避免。功能和数据的集成必须要通过转换去实现。通过定义不同上下文之间的关系,并在项目中创建一个所有模型上下文的全局视图,可以减少混乱。
A CONTEXT MAP is in the overlap between project management and software design. The natural course of events is for the boundaries to follow the contours of team organization. People who work closely will naturally share a model context. People on different teams, or those that don’t talk, even if they are on the same team, will split off into different contexts. Physical office space can have an impact too, as team members on opposite ends of a building—not to mention different cities—will probably diverge without extra integration effort. Most project managers intuitively recognize these factors and broadly organize teams around subsystems. But the interrelationship between team organization and software model and design is still not prominent enough. Both managers and team members need a clear view into the ongoing conceptual subdivision of the software model and design.
CONTEXT MAP 位于项目管理和软件设计的重叠部分。按照常规,人们往往按团队组织的轮廓来划定边界。紧密协作的人会很自然地共享一个模型上下文。不同团队的人员(或者在同一个团队中但从不交流的人)将使用不同的上下文。办公室的物理位置也有影响,例如,分别位于大楼两端的团队成员(更不用说在不同城市工作的人了)如果没有为整合做额外的工作,很有可能会使用不同的上下文。大多数项目经理会本能地意识到这些因素,并围绕子系统大致把各个团队组织起来。但团队组织与软件模型及设计之间的相互关系仍然不够明显。对于软件模型与设计的持续概念细分,项目经理和团队成员需要一个清晰的视图。
Therefore:
因此:
Identify each model in play on the project and define its BOUNDED CONTEXT. This includes the implicit models of non-object-oriented subsystems. Name each BOUNDED CONTEXT, and make the names part of the UBIQUITOUS LANGUAGE.
识别在项目中起作用的每个模型,并定义其 BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。为每个 BOUNDED CONTEXT 命名,并把名称添加到 UBIQUITOUS LANGUAGE 中。
Describe the points of contact between the models, outlining explicit translation for any communication and highlighting any sharing.
描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。
Map the existing terrain. Take up transformations later.
先将当前的情况描绘出来。以后再做改变。
Within each BOUNDED CONTEXT, you will have a coherent dialect of the UBIQUITOUS LANGUAGE. The names of the BOUNDED CONTEXTS will themselves enter that LANGUAGE so that you can speak unambiguously about the model of any part of the design by making your CONTEXT clear.
在每个 BOUNDED CONTEXT 中,都将有一种一致的 UBIQUITOUS LANGUAGE 的“方言”。我们需要把 BOUNDED CONTEXT 的名称添加到该方言中,这样只要通过明确 CONTEXT 就可以清楚地讨论任意设计部分的模型。
The MAP does not have to be documented in any particular form. I find diagrams like the ones in this chapter to be helpful in visualizing and communicating the map. Others may prefer a more textual description or a different graphical representation. In some situations, discussion among teammates may be sufficient. The level of detail can vary according to need. Whatever form the MAP takes, it must be shared and understood by everyone on the project. It must provide a clear name for each BOUNDED CONTEXT, and it must make the points of contact and their natures clear.
CONTEXT MAP 无需拘泥于任何特定的文档格式。我发现类似本章的简图在可视化和沟通上下文图方面很有帮助。有些人可能喜欢使用较多的文本描述或别的图形表示。在某些情况下,团队成员之间的讨论就足够了。需求不同,细节层次也不同。不管 CONTEXT MAP 采用什么形式,它必须在所有项目人员之间共享,并被他们理解。它必须为每个 BOUNDED CONTEXT 提供一个明确的名称,而且必须阐明联系点和它们的本质。
The relationships between BOUNDED CONTEXTS take many forms depending on both design issues and project organizational issues. Later, this chapter will lay out various patterns of relationships between CONTEXTS that are effective in different situations, and that can provide terms to describe the relationships you find in your own MAP. Keeping in mind that the CONTEXT MAP always represents the situation as it stands, the relationships you find may not fit these patterns initially. If they fall close, you may wish to use the pattern name, but don’t force it. Just describe the relationships you find. Later you can begin to migrate toward more standardized relationships.
根据设计问题和项目组织问题的不同,BOUNDED CONTEXT 之间的关系有很多种形式。本章稍后将介绍 CONTEXT 之间的各种关系模式,这些模式分别适用于不同的情况,并且提供了一些术语,这些术语可以用来描述你在自己的上下文图中发现的关系。记住,CONTEXT MAP 始终表示它当前所处的情况,你所发现的关系一开始可能并不适合这些模式。如果它们与某种模式非常接近,你可能想用这个模式名来描述它们,但不要生搬硬套。只需描述你所发现的关系即可。过后,你可以向更加标准化的关系过渡。
So, what do you do if you’ve discovered a splinter—a model that is completely entangled but contains inconsistencies? Put a dragon on the map and finish describing everything. Then, with an accurate global view, address the points of confusion. A minor splinter can be repaired, and processes can be put in place to shore it up. If a relationship is vague, you can choose the nearest pattern and move toward it. Your first order of business is to arrive at a clear CONTEXT MAP, and this may mean fixing real problems you have found. But don’t let this necessary repair lead to wholesale reorganization. Until you have an unambiguous CONTEXT MAP that places all your work into some BOUNDED CONTEXT, with explicit relationships between all connected models, change only the outright contradictions.
那么,如果你发现模型产生了分裂——模型完全混乱且包含不一致时,你该怎么办呢?这时一定要十分注意,先把描述工作停下来。然后,从精确的全局角度来解决这些混乱点。小的分裂可以修复,并且可以通过实施一些过程来为修复提供支持。如果关系很模糊,可以选择一种最接近的模式,然后向此模式靠拢。最重要的任务是画出一个清晰的 CONTEXT MAP,而这可能意味着修复实际发现的问题。但不要因为修复必要的问题而重组整个结构。我们只需修改那些明显的矛盾即可,直到得出一个明确的 CONTEXT MAP,在这个图中,你的所有工作都被放到某个 BOUNDED CONTEXT 中,而且所有互连的模型都有明确的关系。
Once you have a coherent CONTEXT MAP, you’ll see things you want to change. You can make considered changes to the organization of teams or to the design. Remember, don’t change the map until the change in reality is done.
一旦获得了一致的 CONTEXT MAP,就会看到需要修改的那些地方。在经过深思熟虑后,你可以调整团队的组织或设计。记住,在更改实际上完成以前,不要先修改 CONTEXT MAP。
Example: Two CONTEXTS in a Shipping Application
示例运输应用程序中的两个 CONTEXT
We return again to the shipping system. One of the application’s major features was to be the automatic routing of cargos at booking time. The model was something like this:
我们再次回到运输系统。应用程序的主要特性之一是在客户预订的时候自动为货物安排路线。模型类似于图 14-2。
The Routing Service is a SERVICE that encapsulates a mechanism behind an INTENTION-REVEALING INTERFACE made up of SIDEEFFECT-FREE FUNCTIONS. The results of those functions are characterized with ASSERTIONS.
Routing Service 是一个 SERVICE,它把服务的机制封装在一个 INTENTION-REVEALING INTERFACE 后面,这个接口是由一些 SIDE-EFFECT-FREE FUNCTION 构成的。这些函数的结果是用 ASSERTION 刻画的。
- The interface declares that when a Route Specification is passed in, an Itinerary will be returned.
- The ASSERTION states that the returned Itinerary will satisfy the Route Specification that was passed in.
- 接口声明了当传入一个 Route Specification 时,将返回一个 Itinerary。
- ASSERTION 规定返回的 Itinerary 将满足所传入的 Route Specification。
Nothing is stated about how this very difficult task is performed. Now let’s go behind the curtain to see the mechanism.
从上面这些并不能看出这项困难任务是如何执行的。现在,让我们来看一下幕后的机制。
Initially on the project on which this example is based, I was too dogmatic about the internals of the Routing Service. I wanted the actual routing operation to be done with an extended domain model that would represent vessel voyages and directly relate them to the Legs in the Itinerary. But the team working on the routing problem pointed out that, to make it perform well and to draw on well-established algorithms, the solution needed to be implemented as an optimized network, with each leg of a voyage represented as an element in a matrix. They insisted on a distinct model of shipping operations for this purpose.
最初在这个示例所在的项目中,我在 Routing Service 的内部机制上太过教条了。我希望把领域模型扩展一下,以便把实际的路线安排操作包括进来,由模型来表示航名航次,并直接把这些航名航次与 Itinerary 中的 Leg(航段)关联起来。但负责处理路线问题的团队指出,为了更好地执行路线安排,并充分利用那些成熟的算法,应该把这个解决方案实现为一个优化网络,并把航次的每个航段表示为矩阵中的一个元素。他们坚持要用一个完全不同的运输作业模型来实现此目的。
They were clearly right about the computational demands of the routing process as then designed, and so, lacking any better idea, I yielded. In effect, we created two separate BOUNDED CONTEXTS, each of which had its own conceptual organization of shipping operations. (See Figure 14.3.)
就当时的设计而言,他们在路线安排过程的计算要求上无疑是正确的,而且我也没有更好的思路,因此我只好同意了。实际上,我们创建了两个独立的 BOUNDED CONTEXT,每个上下文都有各自运输作业的概念组织(参见图 14-3)。
Two BOUNDED CONTEXTS formed to allow efficient routing algorithms to be applied
Our requirement was to take a Routing Service request, translate it into terms the Network Traversal Service could understand, then take the result and translate it into the form a Routing Service is expected to give.
我们需要接受一个 Routing Service 请求,并将它转换为 Network Traversal Service 可以理解的术语,然后获取结果,并将其转换为 Routing Service 所期望得到的格式。
This means it was not necessary to map everything in these two models, but only to be able to make two specific translations:
这意味着并不需要映射这两个模型中的所有事物,而只要能够进行这两个特定的转换即可:
Route Specification → List of location codes
Route Specification→ 地点代码的列表
List of Node IDs → Itinerary
Node 标识的列表 →Itinerary
To do this, we have to look at the meaning of an element of one model and figure out how to express it in terms of the other.
为了进行这两个转换,我们必须研究元素在一个模型中的含义,并弄清楚如何在另一个模型中把它表示出来。
Starting with the first translation (Route Specification → List of location codes), we have to think about the meaning of the sequence of locations in the list. The first in the list will be the beginning of the path, which will then be forced to pass through each location in turn until it reaches the last location in the list. So the origin and destination are the first and last in the list, with the customs clearance location (if there is one) in the middle.
我们从第一个转换开始(Route Specification→ 地点代码的列表),我们必须考虑列表中的地点序列的含义。列表中的第一项是路线的开始,然后必须依次通过每个地点,直到到达列表中的最后一个地点。因此,起点和目的地分别是列表中的第一项和最后一项,中间(如果有的话)则是清关地点(参见图 4-14)。
Translation of a query to the Network Traversal Service
(Mercifully, the two teams used the same location codes, so we don’t have to deal with that level of translation.)
(幸运的是,两个团队使用相同的地点代码,因此我们不必处理地点代码之间的转换。)
Notice that the reverse translation would be ambiguous, because the network traversal input allows any number of intermediate points, not just one specifically designated as customs clearance point. Fortunately, this is no problem because we don’t need to translate in that direction, but it gives a glimpse of why some translations are impossible.
注意,反向转换是不明确的,因为网络遍历的输入允许任意数目的中间点,而不是只有特别指定的清关点。幸运的是,由于我们并不需要反向转换,因此不会产生这个问题,但由此我们也了解到为什么有些转换是不可能的。
Now, let’s translate the result (List of Node IDs → Itinerary). We’ll assume that we can use a REPOSITORY to look up the Node and Shipping Operation objects based on the Node IDs we receive. So, how do those Nodes map to Legs? Based on the operationType-Code, we can break the list of Nodes into departure/arrival pairs. Each pair then relates to one Leg.
现在,我们开始对结果进行转换(Node 标识的列表 →Itinerary)。假设我们可以根据所得到的 Node ID 来使用 Repository 查询 Node 和 Shipping Operation 对象。那么,这些 Node 是如何映射到 Leg 上的呢?根据 operationType-Code,我们可以把 Node 列表分解为“出发/到达”对。每一对组成一个 Leg。
Translation of a route found by the Network Traversal Service
The attributes for each Node pair would be mapped as follows:
每个 Node 对的属性按下面这样进行映射:
departureNode.shippingOperation.vesselVoyageId → leg.vesselVoyageId
departureNode.shippingOperation.date → leg.loadDate
departureNode.locationCode → leg.loadLocationCode
arrivalNode.shippingOperation.date → leg.unloadDate
arrivalNode.locationCode → leg.unloadLocationCode
This is the conceptual translation map between these two models. Now we have to implement something that can do the translation for us. In a simple case like this, I typically create an object for the purpose, and then find or create another object to provide the service to the rest of our subsystem.
这是两个模型之间的概念转换映射。现在,我们必须通过某种方法来实现这些转换。在像这样的简单例子中,我通常先创建一个用于转换的对象,然后找到或创建另一个对象来为子系统的其余部分提供服务。
A two-way translator
This is the one object that both teams have to work together to maintain. The design should make it very easy to unit-test, and it would be a particularly good idea for the teams to collaborate on a test suite for it. Other than that, they can go their separate ways.
这是两个团队必须一起维护的对象。设计应该使其易于单元测试,因此最好让两个团队协作开发一个测试套件。除此之外,他们可以采用不同的方式各自开发。
The Routing Service implementation now becomes a matter of delegating to the Translator and the Network Traversal Service. Its single operation would look something like this:
Routing Service 的实现现在变成了把任务委托给 Translator 和 Network Traversal Service。其唯一的操作可能如下面代码所示:
public Itinerary route(RouteSpecification spec) {
Booking_TransportNetwork_Translator translator =
new Booking_TransportNetwork_Translator();
List constraintLocations =
translator.convertConstraints(spec);
// Get access to the NetworkTraversalService
List pathNodes =
traversalService.findPath(constraintLocations);
Itinerary result = translator.convert(pathNodes);
return result;
}
Not bad. The BOUNDED CONTEXTS served to keep each of the models relatively clean, let the teams work largely independently, and if initial assumptions had been correct, would probably have served well. (We’ll return to that later in this chapter.)
这种处理方法还不错。BOUNDED CONTEXT 使每个模型都保持相对整洁,使团队很大程度上彼此独立工作,而且,如果最初的假设是正确的,它们可能会发挥很好的作用(本章后面还会回头讨论这个问题)。
The interface between the two contexts is fairly small. The interface of the Routing Service insulates the rest of the Booking CONTEXT’s design from events in the route-finding world. The interface is easy to test because it is made up of SIDE-EFFECT-FREE FUNCTIONS. One of the secrets to comfortable coexistence with other CONTEXTS is to have effective sets of tests for the interfaces. “Trust, but verify,” said President Reagan when negotiating arms reductions.1
两个上下文之间的接口非常小。Routing Service 的接口把预订上下文中的其余部分与路线查找事件隔离开。这个接口完全由 SIDE-EFFECT-FREE FUNCTION 构成,因此很容易测试。与其他 CONTEXT 和谐共存的一个秘诀是拥有有效的接口测试集。正如里根总统在裁减核武器谈判时所说的名言“信任,但要确认”。
It should be easy to devise a set of automated tests that would feed Route Specifications into the Routing Service and check the returned Itinerary.
我们很容易设计一组自动测试集来把 Route Specification 输入到 Routing Service 中并检查返回的 Itinerary。
Model contexts always exist, but without conscious attention they may overlap and fluctuate. By explicitly defining BOUNDED CONTEXTS and a CONTEXT MAP, your team can begin to direct the process of unifying models and connecting distinct ones.
模型上下文总是存在的,但如果我们不注意的话,它们可能会发生重叠和变化。通过明确地定义 BOUNDED CONTEXT 和 CONTEXT MAP,团队就可以掌控模型的统一过程,并把不同的模型连接起来。
Contact points with other BOUNDED CONTEXTS are particularly important to test. Tests help compensate for the subtleties of translation and the lower level of communication that typically exist at boundaries. They can act as a valuable early warning system, especially reassuring in cases where you depend on the details of a model you don’t control.
对各个 BOUNDED CONTEXT 的联系点的测试特别重要。这些测试有助于解决转换时所存在的一些细微问题以及弥补边界沟通上存在的不足。测试充当了有用的早期报警系统,特别是在我们必须信赖那些模型细节却又无法控制它们时,它能让我们感到放心。
There are only two important points here:
这里只有以下两个重点。
- The BOUNDED CONTEXTS should have names so that you can talk about them. Those names should enter the UBIQUITOUS LANGUAGE of the team.
- Everyone has to know where the boundaries lie, and be able to recognize the CONTEXT of any piece of code or any situation.
- BOUNDED CONTEXT 应该有名称,以便可以讨论它们。这些名称应该被添加到团队的 UBIQUITOUS LANGUAGE 中。
- 每个人都应该知道边界在哪里,而且应该能够分辨出任何代码段的 CONTEXT,或任何情况的 CONTEXT。
The second requirement could be met in many ways depending on the culture of the team. Once the BOUNDED CONTEXTS have been defined, it comes naturally to segregate the code of different CONTEXTS into different MODULES, which leaves the question of how to keep track of which MODULE belongs in which CONTEXT. A naming convention might be used to indicate this, or any other mechanism that is easy and avoids confusion.
有很多种方式可以满足第二项需求,这取决于团队的文化。一旦定义了 BOUNDED CONTEXT,那么把不同上下文的代码隔离到不同的 MODULE 中就再自然不过了,但这样就产生了一个问题——如何跟踪哪个 MODULE 属于哪个 CONTEXT。我们可以用命名规范来表明这一点,或者使用其他简单且不会产生混淆的机制。
Equally important is communicating the conceptual boundaries in such a way that everyone on the team understands them the same way. For this communication purpose, I like informal diagrams like the ones in the example. More rigorous diagrams or textual lists could be made, showing all packages in each CONTEXT, along with the points of contact and the mechanisms responsible for connecting and translating. Some teams will be more comfortable with this approach, while others will get by fine based on spoken agreement and lots of discussion.
同样重要的是以一种适当的形式来传达概念边界,以使团队中的每个人都能以相同的方式来理解它们。就沟通而言,我喜欢用非正式的图,就像示例中所显示的那些图一样。也可以使用更严格的图或文本列表来显示每个 CONTEXT 中的所有包,同时显示出联系点以及负责连接和转换的机制。有些团队更愿意使用这种方法,而另一些团队通过口头协定和大量的讨论也能很好地实现这一目的。
In any case, working the CONTEXT MAP into discussions is essential if the names are to enter the UBIQUITOUS LANGUAGE. Don’t say, “George’s team’s stuff is changing, so we’re going to have to change our stuff that talks to it.” Say instead, “The Transport Network model is changing, so we’re going to have to change the translator for the Booking context.”
无论是哪种情况,将 CONTEXT MAP 融入到讨论中都是至关重要的,前提是 CONTEXT 的名称要添加到 UBIQUITOUS LANGUAGE 中。不要说“George 团队的内容改变了,因此我们也需要改变那些与其进行交互的内容”,而应该说:“Transport Network 模型发生了改变,因此我们也需要修改 Booking 上下文的转换器。”
The following patterns cover a range of strategies for relating two models that can be composed to encompass an entire enterprise. These patterns serve the dual purpose of providing targets for successfully organizing development work, and supplying vocabulary for describing the existing organization.
下面介绍的这些模式涵盖了将两个模型关联起来的众多策略。把模型连接到一起之后,就能够把整个企业笼括在内。这些模式有着双重目的,一是为成功地组织开发工作设定目标,二是为描述现有组织提供术语。
An existing relationship may, by chance or by design, fall near one of these patterns, in which case you can describe it using that term, variations duly noted. Then, with each small design change, the relationship can be drawn closer to the chosen pattern.
现有关系可能与这些模式中的某一种很接近——这可能是由于巧合,也可能是有意设计的——在这种情况下可以使用这个模式的术语来描述关系,但差异之处应该引起重视。然后,随着每次小的设计修改,关系会与所选定的模式越来越接近。
On the other hand, you may find that an existing relationship is muddled or overcomplicated. Some reorganization might be necessary just to make an unambiguous CONTEXT MAP possible. In this situation, or any situation in which you are considering reorganization, these patterns present a range of choices that are favored in different circumstances. Prominent variables include the level of control you have over the other model, the level and type of cooperation between teams, and the degree of integration of features and data.
另一方面,你可能会发现现有关系很混乱或过于复杂。要想得到一个明确的 CONTEXT MAP,需要重新组织一些关系。在这种情况或任何需要考虑重组的情况下,这些模式提供了应对各种不同情况的选择。这些模式的主要区别包括你对另一个模型的控制程度、两个团队之间合作水平和合作类型,以及特性和数据的集成程度。
The following set of patterns covers some of the most common and important cases, which should give you a good idea of how to approach other cases. A crack team working closely on a tightly integrated product can deploy a large unified model. The need to serve different user communities or a limitation on the coordination abilities of the team might lead to a SHARED KERNEL or CUSTOMER/SUPPLIER relationships. Sometimes a good hard look at the requirements reveals that integration is not essential and it is best for systems to go their SEPARATE WAYS. And, of course, most projects have to integrate to some degree with legacy and external systems, which can lead to OPEN HOST SERVICES or ANTICORRUPTION LAYERS.
下面这些模式涵盖了一些最常见和最重要的情况,它们提供了一些很好的思路,沿着这些思路,我们就可以知道如何处理其他情况。开发一个紧密集成产品的优秀团队可以部署一个大的、统一的模型。如果团队需要为不同的用户群提供服务,或者团队的协调能力有限,可能就需要采用 SHARED KERNEL(共享内核)或 CUSTOMER/SUPPLIER(客户/供应商)关系。有时仔细研究需求之后可能发现集成并不重要,而系统最好采用 SEPARATE WAY(各行其道)模式。当然,大多数项目都需要与遗留系统或外部系统进行一定程度的集成,这就需要使用 OPEN HOSTSERVICE(开放主机服务)或 ANTICORRUPTION LAYER(防护层)。
When functional integration is limited, the overhead of CONTINUOUS INTEGRATION may be deemed too high. This may especially be true when the teams do not have the skill or the political organization to maintain continuous integration, or when a single team is simply too big and unwieldy. So separate BOUNDED CONTEXTS might be defined and multiple teams formed.
当功能集成受到局限,CONTINUOUS INTEGRATION 的开销可能会变得非常高。尤其是当团队的技能水平或行政组织不能保持持续集成,或者只有一个庞大的、笨拙的团队时,更容易发生这种情况。在这种情况下就要定义单独的 BOUNDED CONTEXT,并组织多个团队。
Uncoordinated teams working on closely related applications can go racing forward for a while, but what they produce may not fit together. They can end up spending more on translation layers and retrofitting than they would have on CONTINUOUS INTEGRATION in the first place, meanwhile duplicating effort and losing the benefits of a common UBIQUITOUS LANGUAGE.
当不同团队开发一些紧密相关的应用程序时,如果团队之间不进行协调,即使短时间内能够取得快速进展,但他们开发出的产品可能无法结合到一起。最后可能不得不耗费大量精力在转换层上,并且频繁地进行改动,不如一开始就使用 CONTINUOUS INTEGRATION 那么省心省力,同时这也造成重复工作,并且无法实现公共的 UBIQUITOUS LANGUAGE 所带来的好处。
On many projects I’ve seen the infrastructure layer shared among teams that worked largely independently. An analogy to this can work well within the domain as well. It may be too much overhead to fully synchronize the entire model and code base, but a carefully selected subset can provide much of the benefit for less cost.
在很多项目中,我看到一些基本上独立工作的团队共享基础设施层。领域工作采用类似的方法也可以得到很好的效果。保持整个模型和代码完全同步的开销可能太高了,但从系统中仔细挑选出一部分并保持同步,就能以较小的代价获得较大的收益。
Therefore:
因此:
Designate some subset of the domain model that the two teams agree to share. Of course this includes, along with this subset of the model, the subset of code or of the database design associated with that part of the model. This explicitly shared stuff has special status, and shouldn’t be changed without consultation with the other team.
从领域模型中选出两个团队都同意共享的一个子集。当然,除了这个模型子集以外,还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的地位,一个团队在没与另一个团队商量的情况下不应擅自更改它。
Integrate a functional system frequently, but somewhat less often than the pace of CONTINUOUS INTEGRATION within the teams. At these integrations, run the tests of both teams.
功能系统要经常进行集成,但集成的频率应该比团队中 CONTINUOUS INTEGRATION 的频率低一些。在进行这些集成的时候,两个团队都要运行测试。
It is a careful balance. The SHARED KERNEL cannot be changed as freely as other parts of the design. Decisions involve consultation with another team. Automated test suites must be integrated because all tests of both teams must pass when changes are made. Usually, teams make changes on separate copies of the KERNEL, integrating with the other team at intervals. (For example, on a team that CONTINUOUSLY INTEGRATES daily or better, the KERNEL merger might be weekly.) Regardless of when code integration is scheduled, the sooner both teams talk about the changes, the better.
这是一个仔细的平衡。SHARED KERNEL(共享内核)不能像其他设计部分那样自由更改。在做决定时需要与另一个团队协商。共享内核中必须集成自动测试套件,因为修改共享内核时,必须要通过两个团队的所有测试。通常,团队先修改各自的共享内核副本,然后每隔一段时间与另一个团队的修改进行集成。例如,在每天(或更短的时间周期)进行 CONTINUOUS INTEGRATION 的团队中,可以每周进行一次内核的合并。不管代码集成是怎样安排的,两个团队越早讨论修改,效果就会越好。
The SHARED KERNEL is often the CORE DOMAIN, some set of GENERIC SUBDOMAINS, or both (see Chapter 15), but it can be any part of the model that is needed by both teams. The goal is to reduce duplication (but not to eliminate it, as would be the case if there were just one BOUNDED CONTEXT) and make integration between the two subsystems relatively easy.
SHARED KERNEL 通常是 CORE DOMAIN,或是一组 GENERIC SUBDOMAIN(通用子领域),也可能二者兼有(参见第 15 章),它可以是两个团队都需要的任何一部分模型。使用 SHARED KERNEL 的目的是减少重复(并不是消除重复,因为只有在一个 BOUNDED CONTEXT 中才能消除重复),并使两个子系统之间的集成变得相对容易一些。
Often one subsystem essentially feeds another; the “downstream” component performs analysis or other functions that feed back very little into the “upstream” component, and all dependencies go one way. The two subsystems commonly serve very different user communities, who do different jobs, where different models may be useful. The tool set may also be different, so that program code cannot be shared.
我们常常会碰到这样的情况:一个子系统主要服务于另一个子系统;“下游”组件执行分析或其他功能,这些功能向“上游”组件反馈的信息非常少,所有依赖都是单向的。两个子系统通常服务于完全不同的用户群,其执行的任务也不同,在这种情况下使用不同的模型会很有帮助。工具集可能也不相同,因此无法共享程序代码。
Upstream and downstream subsystems separate naturally into two BOUNDED CONTEXTS. This is especially true when the two components require different skills or employ a different tool set for implementation. Translation is easier for having to operate in one direction only. But problems can emerge, depending on the political relationship of the two teams.
上游和下游子系统很自然地分隔到两个 BOUNDED CONTEXT 中。如果两个组件需要不同的技能或者不同的工具集来实现时,更需要把它们隔离到不同的上下文中。转换很容易,因为只需要进行单向转换。但两个团队的行政组织关系可能会引起问题。
The freewheeling development of the upstream team can be cramped if the downstream team has veto power over changes, or if procedures for requesting changes are too cumbersome. The upstream team may even be inhibited, worried about breaking the downstream system. Meanwhile, the downstream team can be helpless, at the mercy of upstream priorities.
如果下游团队对变更具有否决权,或请求变更的程序太复杂,那么上游团队的开发自由度就会受到限制。由于担心破坏下游系统,上游团队甚至会受到抑制。同时,由于上游团队掌握优先权,下游团队有时也会无能为力。
Downstream needs things from upstream, but upstream is not responsible for downstream deliverables. It takes a lot of extra effort to anticipate what will affect the other team, and human nature being what it is, and time pressures being what they are, well . . . . It makes everyone’s life easier to formalize the relationship between the teams. The process can be organized to balance the needs of the two user communities and schedule work on features needed downstream.
下游团队依赖于上游团队,但上游团队却不负责下游团队的产品交付。要琢磨拿什么来影响对方团队,是人性呢,还是时间压力,亦或其他诸如此类的,这需要耗费大量额外的精力。因此,正式规定团队之间的关系会使所有人工作起来更容易。这样,就可以对开发过程进行组织,均衡地处理两个用户群的需求,并根据下游所需的特性来安排工作。
On an Extreme Programming project, there already is a mechanism in place for doing just that: the iteration planning process. All we have to do is define the relationship between the two teams in terms of the planning process. Representatives of the downstream team can function much like the user representatives, joining them in planning sessions, discussing directly with their fellow “customers” the trade-offs for the tasks they want. The result is an iteration plan for the supplier team that includes tasks the downstream team needs most or defers tasks by agreement, so there is no expectation of delivery.
在极限编程项目中,已经有了实现此目的的机制——迭代计划过程。我们只需根据计划过程来定义两个团队之间的关系。下游团队的代表类似于用户代表,参加上游团队的计划会议,上游团队直接与他们的“客户”同仁讨论和权衡其所需的任务。结果是供应商团队得到一个包含下游团队最需要的任务的迭代计划,或是通过双方商定推迟一些任务,这样下游团队也就知道这些被推迟的功能不会交付给他们。
If a process other than XP is used, whatever analogous method serves to balance the concerns of different users can be expanded to include the downstream application’s needs.
如果使用的不是 XP 过程,那么无论使用什么类似的方法来平衡不同用户的关注点,都可以对这种方法加以扩充,使之把下游应用程序的需求包括进来。
Therefore:
因此:
Establish a clear customer/supplier relationship between the two teams. In planning sessions, make the downstream team play the customer role to the upstream team. Negotiate and budget tasks for downstream requirements so that everyone understands the commitment and schedule.
在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。
Jointly develop automated acceptance tests that will validate the interface expected. Add these tests to the upstream team’s test suite, to be run as part of its continuous integration. This testing will free the upstream team to make changes without fear of side effects downstream.
两个团队共同开发自动化验收测试,用来验证预期的接口。把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。这些测试使上游团队在做出修改时不必担心对下游团队产生副作用。
During the iteration, the downstream team members need to be available to the upstream developers just as conventional customers are, to answer questions and help resolve problems.
在迭代期间,下游团队成员应该像传统的客户一样随时回答上游团队的提问,并帮助解决问题。
Automating the acceptance tests is a vital part of this customer relationship. Even on the most cooperative project, although the customer can identify and communicate its dependencies, and the supplier can diligently try to communicate changes, without tests, surprises will happen. They will disrupt the downstream team’s work and force the upstream team to take on unscheduled, emergency fixes. Instead, have the customer team, in collaboration with the supplier team, develop automated acceptance tests that will validate the interface it expects. The upstream team will run these tests as part of its standard test suite. Any change to these tests calls for communication with the other team, because changing the tests implies changing the interface.
自动化验收测试是这种客户关系的一个重要部分。即使在合作得非常好的项目中,虽然客户很明确他们所依赖的功能并告诉上游团队,而且供应商也能很认真地把所做的修改传递给下游团队,但如果没有测试,仍然会发生一些很意外的事情。这些事情将破坏下游团队的工作,并使上游团队不得不采取计划外的紧急修复措施。因此,客户团队在与供应商团队合作的过程中,应该开发自动验收测试来验证所期望的接口。上游团队将把这些测试作为标准测试套件的一部分来运行。任何一个团队在修改这些测试时都需要与另一个团队沟通,因为修改测试就意味着修改接口。
Customer/supplier relationships also emerge between projects in separate companies, in situations where a single customer is very important to the business of the supplier. The tail can wag the dog: an influential customer can make demands that are important to the upstream project’s success, but those demands can also be disruptive to the upstream project’s development. Both parties benefit from the formalization of the process of responding to requirements, because the cost/benefit trade-offs are even harder to see in external relationships than they are in the internal IT situation.
当某个客户对供应商的业务至关重要时,不同公司的项目之间也会出现客户/供应商关系。下游团队也能制约上游团队,一个有影响力的客户所提出的要求对上游项目的成功非常重要,但这些要求也能破坏上游项目的开发。建立正式的需求响应过程对双方都有利,因为与内部 IT 关系相比,在这种外部关系中更难做出“成本/效益”的权衡。
There are two crucial elements to this pattern.
这种模式有两个关键要素。
- The relationship must be that of customer and supplier, with the implication that the customer’s needs are paramount. Because the downstream team is not the only customer, the different customers’ demands have to be balanced in negotiation—but they remain priorities. This situation is in contrast to the poor-cousin relationship that often emerges, in which the downstream team has to come begging to the upstream team for its needs.
- There must be an automated test suite that allows the upstream team to change its code without fear of breaking the downstream, and lets the downstream team concentrate on its own work without constantly monitoring the upstream team.
- 关系必须是客户与供应商的关系,其中客户的需求是至关重要的。由于下游团队并不是唯一的客户,因此不同客户的要求必须通过协商来平衡,但这些要求都是非常重要的。这种关系与那种经常出现的“穷亲威”关系相反,在后者的关系中,下游团队不得不乞求上游团队满足其需求。
- 必须有自动测试套件,使上游团队在修改代码时不必担心破坏下游团队的工作,并使下游团队能够专注于自己的工作,而不用总是密切关注上游团队的行动。
In a relay race, the forward runner can’t be looking backward all the time, checking. He or she has to be able to trust the baton carrier to make the handoff precisely, or else the team will be hopelessly slowed down.
在接力赛中,前面的选手在接棒的时候不能一直回头看,这位选手必须相信队友能够把接力棒准确地交到他手中,否则整个团队的速度无疑会慢下来。
Example: Yield Analysis Versus Booking
示例收益分析与预订
Back to our trusty shipping example. A highly specialized team has been set up to analyze all the bookings that flow through the firm, to see how to maximize income. Team members might find that ships have empty space and might recommend more overbooking. They might find that the ships are filling up with bulk freight early, forcing the company to turn away more lucrative specialty cargoes. In that case they might recommend reserving space for these types of cargo or raising prices on the bulk freight.
我们再次回到运输示例中。公司组建了一支专门的团队,负责分析公司收到的所有预订,看看如何实现收益的最大化。团队成员可能发现货轮上还有空位置,并建议接受更多超订。他们可能发现货轮过早地装满了散装货物,从而使公司不得不拒绝利润更大的特殊货物。在这种情况下,他们可能会建议为这类货物预留空间,或是提高散货的运输价格。
To do this analysis, they use their own complex models. For implementation, they use a data warehouse with tools for building analytical models. And they need lots of information from the Booking application.
为了进行这种分析,他们使用自己的复杂模型。在实现过程中,他们使用了一个带有构建分析模型工具的数据仓库。而且他们需要从预订应用程序中获取大量信息。
From the start, it is clear that these are two BOUNDED CONTEXTS, because they use different implementation tools and, most important, different domain models. What should the relationship between them be?
从一开始就知道,这显然是两个 BOUNDED CONTEXT,因为它们使用不同的实现工具,而且最重要的是,它们使用不同的领域模型。那么它们之间应该具有什么样的关系呢?
A SHARED KERNEL might seem logical, because yield analysis is interested in a subset of the Booking’s model, and their own model has some overlapping concepts of cargos, prices, and so on. But SHARED KERNEL is difficult in a case where different implementation technologies are being used. Besides, the modeling needs of the yield analysis team are quite specialized, and they continuously play with their models and try alternative ones. They may well be better off translating what they need from the Booking CONTEXT into their own. (On the other hand, if they can use a SHARED KERNEL, their translation burden will be much lighter. They will still have to reimplement the model and translate the data to the new implementation, but if the model is the same, the transfer should be simple.)
在这种情况下使用 SHARED KERNEL 看起来很合乎逻辑,因为收益分析只对预订模型的一个子集感兴趣,而且它们自己的模型也有一些诸如货物、价格等的重叠概念。但是,当使用了不同的实现技术时,SHARED KERNEL 是很难做到的。此外,收益分析团队需要建立非常专门的模型,他们要不断修改模型,并且尝试其他的模型。他们最好从预订 CONTEXT 中找到所需的东西,并把它们转换到自己的上下文中。(另一方面,如果他们使用 SHARED KERNEL,他们的翻译负担将会轻得多。他们仍然必须重新实现模型,并把数据转换到新的实现中,但如果模型相同的话,转换就简单多了。)
The Booking application has no dependency on the yield analysis, because there is no intention of automatically adjusting policies. Human specialists will make the decisions and convey them to the needed people and systems. So we have an upstream/downstream relationship. What downstream needs is this:
预订应用程序并不依赖收益分析,因为并没有打算做自动调整策略。调整决策将由专家来制定,并传递给相关的人员和系统。这样我们就有了一个上游/下游关系。下游的需求如下:
- Some data not needed by any booking operation
- Some stability in database schema (or at least reliable notification of change) or an export utility
- 一些数据(任何预订操作都不需要这些数据);
- 数据库模式具有一定稳定性(或至少具有可靠的变更通知机制),或者一个用于导出的实用程序。
Fortunately, the project manager of the Booking application development team is motivated to help the yield analysis team. This could have been a problem, because the operations department that actually does day-to-day booking reports to a different vice president than the people who actually do yield analysis. But the upper management cares deeply about yield management and, having seen past cooperation problems between the two departments, structured the software development project so that the project managers of both teams report to the same person.
幸运的是,预订应用程序开发团队的项目经理非常积极主动地帮助收益分析团队。原本以为两个团队的合作会是个问题,因为实际负责处理日常预订业务的运营部门和实际执行收益分析的团队并非向同一个副总裁报告工作。但高管层非常关心收益管理,而且过去曾看到过两个部门之间的合作问题,因此调整了一下软件开发项目的结构,让两个团队的项目经理向同一个人汇报工作。
Therefore, all the requirements are in place to apply CUSTOMER/SUPPLIER DEVELOPMENT TEAMS.
这样,应用 CUSTOMER/SUPPLIER DEVELOPMENT TEAM(客户/供应商开发团队)的所有要求都满足了。
I’ve seen this scenario evolve in multiple places, where analysis software developers and operations software developers had a customer/supplier relationship. When the upstream team members thought of their role as serving a customer, things worked out pretty well. It was almost always organized informally, and in each case it worked out about as well as the personal relationship of the two project managers.
我曾经看到过这种场景出现在很多地方,其中分析软件开发人员和操作软件开发人员具有客户/供应商关系。当上游团队成员认为他们的角色是服务于客户时,工作会进展得相当顺利。这种关系几乎总是非正式地组织起来的,因此工作顺利与否有赖于两个项目经理的私人关系。
On one XP project, I saw this relationship formalized in the sense that, for each iteration, representatives of the downstream team played the “planning game” in the role of customers, huddling with the more conventional customer representatives (of application functionality) to negotiate which tasks made it into the iteration plan. This project was at a small company, and so the nearest shared boss was not far up the chain. It worked very well.
在一个 XP 项目中,我曾经看到过正式的客户/供应商关系,在每次迭代中,下游团队的代表以客户的身份参与到计划过程中,他们与更为传统的(应用程序功能的)客户代表聚到一起,共同协商哪些任务应该被添加到迭代计划中。这是一家小公司的项目,因此最近一级的共同主管不会处在关系链的很远位置。项目进展得非常顺利。
CUSTOMER/SUPPLIER TEAMS are more likely to succeed if the two teams work under the same management, so that ultimately they do share goals, or where they are in different companies that actually have those roles. When there is nothing to motivate the upstream team, the situation is very different. . . .
CUSTOMER/SUPPLIER TEAM 涉及的团队如果能在同一个部门中工作,最后会形成共同的目标,这样成功机会将更大一些,如果两个团队分属不同的公司,但实际上也具有这些角色,同样也容易成功。但是,当上游团队不愿意为下游团队提供服务时,情况就会完全不同……
When two teams with an upstream/downstream relationship are not effectively being directed from the same source, a cooperative pattern such as CUSTOMER/SUPPLIER TEAMS is not going to work. Naively trying to apply it will get the downstream team into trouble. This can be the case in a large company in which the two teams are far apart in the management hierarchy or where the shared supervisor is indifferent to the relationship of the two teams. It also arises between teams in different companies when the customer’s business is not individually important to the supplier. Perhaps the supplier has many small customers, or perhaps the supplier is changing market direction and no longer values the old customers. The supplier may just be poorly run. It may have gone out of business. Whatever the reason, the reality is that the downstream is on its own.
当两个具有上游/下游关系的团队不归同一个管理者指挥时,CUSTOMER/SUPPLIER TEAM 这样的合作模式就不会奏效。勉强应用这种模式会给下游团队带来麻烦。大公司可能会发生这种情况,其中两个团队在管理层次中相隔很远,或者两个团队的共同主管不关心它们之间的关系。当两个团队属于不同公司时,如果客户的业务对供应商不是非常重要,那么也会出现这种情况。或许供应商有很多小客户,或者供应商正在改变市场方向,而不再重视老客户。也可能是供应商的运营状况较差,或者已经倒闭。不管是什么原因,现实情况是下游团队只能靠自己了。
When two development teams have an upstream/downstream relationship in which the upstream has no motivation to provide for the downstream team’s needs, the downstream team is helpless. Altruism may motivate upstream developers to make promises, but they are unlikely to be fulfilled. Belief in those good intentions leads the downstream team to make plans based on features that will never be available. The downstream project will be delayed until the team ultimately learns to live with what it is given. An interface tailored to the needs of the downstream team is not in the cards.
团队将无能为力。出于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。下游团队出于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。下游项目只能被搁置,直到团队最终学会利用现有条件自力更生为止。下游团队不会得到根据他们的需求而量身定做的接口。
In this situation, there are three possible paths. One is to abandon use of the upstream altogether. This option should be evaluated realistically, making no assumptions that the upstream will accommodate downstream needs. Sometimes we overestimate the value or underestimate the cost of such a dependency. If the downstream team decides to cut the strings, they are going their SEPARATE WAYS (see the pattern description later in this chapter).
在这种情况下,有 3 种可能的解决途径。一种是完全放弃对上游的使用。做出这种选择时,应进行切实地评估,绝不要假定上游会满足下游的需求。有时我们会高估这种依赖性的价值,或是低估它的成本。如果下游团队决定切断这条链,他们将走上 SEPARATE WAY(各行其道)的道路(参见本章后面介绍的模式)。
Sometimes the value of using the upstream software is so great that the dependency has to be maintained (or a political decision has been made that the team cannot change). In this case, two paths remain open; the choice depends on the quality and style of the upstream design. If the design is very difficult to work with, perhaps for lack of encapsulation, awkward abstractions, or modeling in a paradigm the team cannot use, then the downstream team will still need to develop its own model. They will have to take full responsibility for a translation layer that is likely to be complex. (See ANTICORRUPTION LAYER, later in this chapter.).
有时,使用上游软件具有非常大的价值,因此必须保持这种依赖性(或者是行政决策规定团队不能改变这种依赖性)。在这种情况下,还有两种途径可供选择,选择哪一种取决于上游设计的质量和风格。如果上游的设计很难使用(可能是由于缺乏封装、使用了不恰当的抽象或者建模时使用了下游团队无法使用的范式),那么下游团队仍然需要开发自己的模型。他们将担负起开发转换层的全部责任,这个层可能会非常复杂(参见本章后面要介绍的 ANTICORRUPTION LAYER)。
Following Isn’t Always Bad
跟随并不总是坏事
When using an off-the-shelf component that has a large interface, you should typically CONFORM to the model implicit in that component. Because the component and the application are clearly different BOUNDED CONTEXTS, based on team organization and control, adapters may be needed for minor format changes, but the model should be equivalent. Otherwise, you should question the value of having the component. If it is good enough to give you value, there is probably knowledge crunched into its design. Within its narrow sphere, it may well be much more advanced than your own understanding. Your model presumably extends beyond the scope of this component, and your own concepts will evolve for those other parts. But where they connect, your model is a CONFORMIST, following the lead of the component’s model. In effect, you could be dragged into a better design.
当使用一个具有很大接口的现成组件时,一般应该遵循(CONFORM)该组件中隐含的模型。组件和你自己的应用程序显然是不同的 BOUNDED CONTEXT,因此根据团队组织和控制的不同,可能需要使用适配器来进行一点点格式转换,但模型一定要保持相同。否则,就应该质疑使用该组件的价值。如果它确实能够提供价值,那说明它的设计中已经消化吸收了一些知识。在该组件的应用范围内,它可能比你的理解要深入。你的模型大概会超出该组件的范围,而且这些超出部分将演化出你自己的概念。但在两者连接的地方,你的模型将是一个 CONFORMIT,遵从组件模型的领导。实际上,你将被带到一个更好的设计中。
When your interface with a component is small, sharing a unified model is less essential, and translation is a viable option. But when the interface is large and integration is more significant, it usually makes sense to follow the leader.
当你与组件的接口很小时,那么共享一个统一模型就不那么重要了,而且转换也是个可行的选项。但是,当接口很大而且集成更加重要时,跟随通常是有意义的。
On the other hand, if the quality is not so bad, and the style is reasonably compatible, then it may be best to give up on an independent model altogether. This is the circumstance that calls for a CONFORMIST.
另一方面,如果上游设计的质量不是很差,而且风格也能兼容的话,那么最好不要再开发一个独立的模型。这种情况下可以使用 CONFORMIST(跟随者)模式。
Therefore:
因此:
Eliminate the complexity of translation between BOUNDED CONTEXTS by slavishly adhering to the model of the upstream team. Although this cramps the style of the downstream designers and probably does not yield the ideal model for the application, choosing CONFORMITY enormously simplifies integration. Also, you will share a UBIQUITOUS LANGUAGE with your supplier team. The supplier is in the driver’s seat, so it is good to make communication easy for them. Altruism may be sufficient to get them to share information with you.
通过严格遵从上游团队的模型,可以消除在 BOUNDED CONTEXT 之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择 CONFORMITY 模式可以极大地简化集成。此外,这样还可以与供应商团队共享 UBIQUITOUS LANGUAGE。供应商处于统治地位,因此最好使沟通变容易。他们从利他主义的角度出发,会与你分享信息。
This decision deepens your dependency on the upstream and limits your application to the capabilities of the upstream model—plus purely additive enhancements. It is very unappealing emotionally, which is why we choose it less often than we probably should.
这个决策会加深你对上游团队的依赖,同时你的应用也受限于上游模型的功能,充其量也只能做一些简单的增强而已。人们在主观上不愿意这样做,因此有时本应该这样选择时,却没有这样选择。
If these trade-offs are not acceptable, but the upstream dependency is indispensable, the second option still remains: Insulate yourself as much as possible by creating an ANTICORRUPTION LAYER, an aggressive approach to implementing a translation map that will be discussed later.
如果这些折中不可接受,而上游的依赖又必不可少,那么还可以选择第二种方法。通过创建一个 ANTICORRUPTION LAYER 来尽可能把自己隔离开,这是一种实现转换映射的积极方法,后面将会讨论它。
CONFORMIST resembles SHARED KERNEL in that both have an overlapping area where the model is the same, areas where your model has been extended by addition, and areas where the other model does not affect you. The difference between the patterns is in the decision-making and development processes. Where the SHARED KERNEL is a collaboration between two teams that coordinate tightly, CONFORMIST deals with integration with a team that is not interested in collaboration.
CONFORMIST 模式类似于 SHARED KERNEL 模式。在这两种模式中,都有一个重叠的区域——在这个重叠区域内模型是相同的,此外还有你的模型所扩展的部分,以及另一个模型对你没有影响的部分。这两种模式之间的区别在于决策制定和开发过程不同。SHARED KERNEL 是两个高度协调的团队之间的合作模式,而 CONFORMIST 模式则是应对与一个对合作不感兴趣的团队进行集成。
We’ve been proceeding down a spectrum of cooperation in the integration between BOUNDED CONTEXTS, from highly cooperative SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS to the one-sidedness of the CONFORMIST. Now we’ll take the final step to an even more pessimistic view of the relationship, assuming neither cooperation nor a usable design on the other side. . . .
前面介绍了在两个 BOUNDED CONTEXT 之间集成时可以进行的各种合作,从高度合作的 SHARED KERNEL 模式或 CUSTOMER/SUPPLIER DEVELOPER TEAM 到单方面的 CONFORMIST 模式。现在,我们最后来看一种更悲观的关系,假设另一个团队既不合作,而且其设计也无法使用时,该如何应对。
New systems almost always have to be integrated with legacy or other systems, which have their own models. Translation layers can be simple, even elegant, when bridging well-designed BOUNDED CONTEXTS with cooperative teams. But when the other side of the boundary starts to leak through, the translation layer may take on a more defensive tone.
新系统几乎总是需要与遗留系统或其他系统进行集成,这些系统具有其自己的模型。当把参与集成的 BOUNDED CONTEXT 设计完善并且团队相互合作时,转换层可能很简单,甚至很优雅。但是,当边界那侧发生渗透时,转换层就要承担起更多的防护职责。
When a new system is being built that must have a large interface with another, the difficulty of relating the two models can eventually overwhelm the intent of the new model altogether, causing it to be modified to resemble the other system’s model, in an ad hoc fashion. The models of legacy systems are usually weak, and even the exception that is well developed may not fit the needs of the current project. Yet there may be a lot of value in the integration, and sometimes it is an absolute requirement.
当正在构建的新系统与另一个系统的接口很大时,为了克服连接两个模型而带来的困难,新模型所表达的意图可能会被完全改变,最终导致它被修改得像是另一个系统的模型了(以一种特定的风格)。遗留系统的模型通常很弱。即使对于那些模型开发得很好的例外情况,它们可能也不符合当前项目的需要。然而,集成遗留系统仍然具有很大的价值,而且有时还是绝对必要的。
The answer is not to avoid all integration with other systems. I’ve been on projects where people enthusiastically set out to replace all the legacy, but this is just too much to take on at once. Besides, integrating with existing systems is a valuable form of reuse. On a large project, one subsystem will often have to interface with several other, independently developed subsystems. These will reflect the problem domain differently. When systems based on different models are combined, the need for the new system to adapt to the semantics of the other system can lead to a corruption of the new system’s own model. Even when the other system is well designed, it is not based on the same model as the client. And often the other system is not well designed.
正确答案是不要全盘封杀与其他系统的集成。在我经历过的一些项目中,人们非常热衷于替换所有遗留系统,但由于工作量太大,这不可能立即完成。此外,与现有系统集成是一种有价值的重用形式。在大型项目中,一个子系统通常必须与其他独立开发的子系统连接。这些子系统将从不同角度反映问题领域。当基于不同模型的系统被组合到一起时,为了使新系统符合另一个系统的语义,新系统自己的模型可能会被破坏。即使另一个系统被设计得很好,它也不会与客户基于同一个模型。而且其他系统往往并不是设计得很好。
There are many hurdles in interfacing with an external system. For example, the infrastructure layer must provide the means to communicate with another system that might be on a different platform or use different protocols. The data types of the other system must be translated into those of your system. But often overlooked is the certainty that the other system does not use the same conceptual domain model.
当通过接口与外部系统连接时,存在很多障碍。例如,基础设施层必须提供与另一个系统进行通信的方法,那个系统可能处于不同的平台上,或是使用了不同的协议。你必须把那个系统的数据类型转换为你自己系统的数据类型。但通常被忽视的一个事实是那个系统肯定不会使用相同的概念领域模型。
It seems clear enough that errors will result if you take some data from one system and misinterpret it in another. You may even corrupt the database. But even so, this problem tends to sneak up on us because we think that what we are transporting between systems is primitive data, whose meaning is unambiguous and must be the same on both sides. This assumption is usually wrong. Subtle yet important differences in meaning arise from the way the data are associated in each system. And even if primitive data elements do have exactly the same meaning, it is usually a mistake to make the interface to the other system operate at such a low level. A low-level interface takes away the power of the other system’s model to explain the data and constrain its values and relationships, while saddling the new system with the burden of interpreting primitive data that is not in terms of its own model.
如果从一个系统中取出一些数据,然后在另一个系统中错误地解释了它,那么显然会发生错误,甚至会破坏数据库。尽管我们已经认识到这一点,这个问题仍然会“偷袭”我们,因为我们认为在系统之间转移的是原始数据,其含义是明确的,并且认为这些数据在两个系统中的含义肯定是相同的。这种假设常常是错误的。数据与每个系统的关联方式会使数据的含义出现细微但重要的差别。而且,即使原始数据元素确实具有完全相同的含义,但在原始数据这样低的层次上进行接口操作通常是错误的。这样的底层接口使另一个系统的模型丧失了解释数据以及约束其值和关系的能力,同时使新系统背负了解释原始数据的负担(而且并未使用这些数据自己的模型)。
We need to provide a translation between the parts that adhere to different models, so that the models are not corrupted with undigested elements of foreign models.
我们需要在不同模型的关联部分之间建立转换机制,这样模型就不会被未经消化的外来模型元素所破坏。
Therefore:
因此:
Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models.
创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。这个层通过另一个系统现有接口与其进行对话,而只需对那个系统作出很少的修改,甚至无需修改。在内部,这个层在两个模型之间进行必要的双向转换。
This discussion of a mechanism to link two systems might bring to mind issues of transporting the data from one program to another or from one server to another. I’ll discuss the incorporation of the technical communications mechanism shortly. But such details shouldn’t be confused with an ANTICORRUPTION LAYER, which is not a mechanism for sending messages to another system. Rather, it is a mechanism that translates conceptual objects and actions from one model and protocol to another.
这种连接两个系统的机制可能会使我们想到把数据从一个程序传输到另一个程序,或者从一个服务器传输到另一个服务器。我们很快就会讨论技术通信机制的使用。但这些细节问题不应与 ANTICORRUPTION LAYER 混淆,因为 ANTICORRUPTION LAYER 并不是向另一个系统发送消息的机制。相反,它是在不同的模型和协议之间转换概念对象和操作的机制。
An ANTICORRUPTION LAYER can become a complex piece of software in its own right. Next I’ll outline some of the design considerations for creating one.
ANTICORRUPTION LAYER 本身就可能是一个复杂的软件。接下来将概要描述在创建 ANTICORRUPTION LAYER 时需要考虑的一些事项。
The public interface of the ANTICORRUPTION LAYER usually appears as a set of SERVICES, although occasionally it can take the form of an ENTITY. Building a whole new layer responsible for the translation between the semantics of the two systems gives us an opportunity to reabstract the other system’s behavior and offer its services and information to our system consistently with our model. It may not even make sense, in our model, to represent the external system as a single component. It may be best to use multiple SERVICES (or occasionally ENTITIES), each of which has a coherent responsibility in terms of our model.
ANTICORRUPTION LAYER 的公共接口通常以一组 SERVICE 的形式出现,但偶尔也会采用 ENTITY 的形式。构建一个全新的层来负责两个系统之间的语义转换为我们提供了一个机会,它使我们能够重新对另一个系统的行为进行抽象,并按照与我们的模型一致的方式把服务和信息提供给我们的系统。在我们的模型中,把外部系统表示为一个单独的组件可能是没有意义的。最好是使用多个 SERVICE(或偶尔使用 ENTITY),其中每个 SERVICE 都使用我们的模型来履行一致的职责。
One way of organizing the design of the ANTICORRUPTION LAYER is as a combination of FACADES, ADAPTERS (both from Gamma et al. 1995), and translators, along with the communication and transport mechanisms usually needed to talk between systems.
对 ANTICORRUPTION LAYER 设计进行组织的一种方法是把它实现为 FACADE、ADAPTER(这两种模式来自[Gamma et al. 1995])和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制。
We often have to integrate with systems that have large, complicated, messy interfaces. This is an implementation issue, not an issue of conceptual model differences that motivated the use of ANTICORRUPTION LAYERS, but it is a problem you’ll encounter trying to create them. Translating from one model to another (especially if one model is fuzzy) is a hard enough job without simultaneously dealing with a subsystem interface that is hard to talk to. Fortunately, that is what FACADES are for.
我们常常需要与那些具有大而复杂、混乱的接口的系统进行集成。这不是概念模型差别的问题(概念模型差别是我们使用 ANTICORRUPTION LAYER 的动机),而是一个实现问题。当我们尝试创建 ANTICORRUPTION LAYER 时,会遇到这个实现问题。当从一个模型转换到另一个模型的时候(特别是当一个模型很混乱时),如果不能同时处理那些难于沟通的子系统接口,那么将很难完成。好在 FACADE 可以解决这个问题。
A FACADE is an alternative interface for a subsystem that simplifies access for the client and makes the subsystem easier to use. Because we know exactly what functionality of the other system we want to use, we can create a FACADE that facilitates and streamlines access to those features and hides the rest. The FACADE does not change the model of the underlying system. It should be written strictly in accordance with the other system’s model. Otherwise, you will at best diffuse responsibility for translation into multiple objects and overload the FACADE and at worst end up creating yet another model, one that doesn’t belong to the other system or your own BOUNDED CONTEXT. The FACADE belongs in the BOUNDED CONTEXT of the other system. It just presents a friendlier face specialized for your needs.
FACADE 是子系统的一个可供替换的接口,它简化了客户访问,并使子系统更易于使用。由于我们非常清楚要使用另一个系统的哪些功能,因此可以创建 FACADE 来促进和简化对这些特性的访问,并把其他特性隐藏起来。FACADE 并不改变底层系统的模型。它应该严格按照另一个系统的模型来编写。否则会产生严重的后果:轻则导致转换职责蔓延到多个对象中,并加重 FACADE 的负担;重则创建出另一个模型,这个模型既不属于另一个系统,也不属于你自己的 BOUNDED CONTEXT。FACADE 应该属于另一个系统的 BOUNDED CONTEXT,它只是为了满足你的专门需要而呈现出的一个更友好的外观。
An ADAPTER is a wrapper that allows a client to use a different protocol than that understood by the implementer of the behavior. When a client sends a message to an ADAPTER, it is converted to a semantically equivalent message and sent on to the “adaptee.” The response is converted and passed back. I’m using the term adapter a little loosely, because the emphasis in Gamma et al. 1995 is on making a wrapped object conform to a standard interface that clients expect, whereas we get to choose the adapted interface, and the adaptee is probably not even an object. Our emphasis is on translation between two models, but I think this is consistent with the intent of ADAPTER.
ADAPTER 是一个包装器,它允许客户使用另外一种协议,这种协议可以是行为实现者不理解的协议。当客户向适配器发送一条消息时,ADAPTER 把消息转换为一条在语义上等同的消息,并将其发送给“被适配者”(adaptee)。之后 ADAPTER 对响应消息进行转换,并将其发回。我在这里使用适配器(adapter)这个术语略微有点儿不严谨,因为[Gamma et al. 1995]一书中强调的是使包装后的对象符合客户所期望的标准接口,而我们选择的是被适配的接口,而且被适配者甚至可能不是一个对象。我们强调的是两个模型之间的转换,但我认为这与 ADAPTER 的意图是一致的。
For each SERVICE we define, we need an ADAPTER that supports the SERVICE’S interface and knows how to make equivalent requests of the other system or its FACADE.
我们所定义的每种 SERVICE 都需要一个支持其接口的 ADAPTER,这个适配器还需要知道怎样才能向其他系统及其 FACADE 发出相应的请求)。
The remaining element is the translator. The ADAPTER’S job is to know how to make a request. The actual conversion of conceptual objects or data is a distinct, complex task that can be placed in its own object, making them both much easier to understand. A translator can be a lightweight object that is instantiated when needed. It needs no state and does not need to be distributed, because it belongs with the ADAPTER(S) it serves.
剩下的要素就是转换器了。ADAPTER 的工作是知道如何生成请求。概念对象或数据的实际转换是一种完全不同的复杂任务,我们可以让一个单独的对象来承担这项任务,这样可以使负责转换的对象和 ADAPTER 都更易于理解。转换器可以是一个轻量级的对象,它可以在需要的时候被实例化。由于它只属于它所服务的 ADAPTER,因此不需要有状态,也不需要是分布式的。
Those are the basic elements I use to create an ANTICORRUPTION LAYER. There are a few other considerations.
这些都是我用来创建 ANTICORRUPTION LAYER 的基本元素。此外,还有其他一些需要考虑的因素。
- Typically, the system under design (your subsystem) will be initiating action, as implied by Figure 14.8. There are cases, however, when the other subsystem may need to request something of your subsystem or notify it of some event. An ANTICORRUPTION LAYER can be bidirectional, defining SERVICES on both interfaces with their own ADAPTERS, potentially using the same translators with symmetrical translations. Although implementing the ANTICORRUPTION LAYER doesn’t usually require any change to the other subsystem, it might be necessary in order to make the other system call on SERVICES of the ANTICORRUPTION LAYER.
- 如图 14-8 所示,一般是由正在设计的系统(你的子系统)来发起一个动作。但在有些情况下,其他子系统可能需要向你的子系统提交某种请求,或是把某个事件通知给你的子系统。ANTICORRUPTION LAYER 可以是双向的,它可能使用具有对称转换的相同转换器来定义两个接口上的 SERVICE(并使用各自的 ADAPTER)。尽管实现 ANTICORRUPTION LAYER 通常不需要对另一个子系统做任何修改,但为了使它能够调用 ANTICORRUPTION LAYER 的 SERVICE,有时还是有必要修改的。
The structure of an ANTICORRUPTION LAYER
- You’ll usually need some communications mechanism to connect the two subsystems, and they could well be on separate servers. In this case, you have to decide where to place these communication links. If you have no access to the other subsystem, you may have to put the links between the FACADE and the other subsystem. However, if the FACADE can be integrated directly with the other subsystem, then a good option is to put the communication link between the ADAPTER and FACADE, because the protocol of the FACADE is presumably simpler than what it covers. There also will be cases where the entire ANTICORRUPTION LAYER can live with the other subsystem, placing communication links or distribution mechanisms between your subsystem and the SERVICES that make up the ANTICORRUPTION LAYER’s interface. These are implementation and deployment decisions to be made pragmatically. They have no bearing on the conceptual role of the ANTICORRUPTION LAYER.
- If you do have access to the other subsystem, you may find that a little refactoring over there can make your job easier. In particular, try to write more explicit interfaces for the functionality you’ll be using, starting with automated tests, if possible.
- Where integration requirements are extensive, the cost of translation goes way up. It may be necessary to make choices in the model of the system under design that keep it closer to the external system, in order to make translation easier. Do this very carefully, without compromising the integrity of the model. It is only something to do selectively when translation difficulty gets out of hand. If this approach seems the most natural solution for much of the important part of the problem, consider making your subsystem a CONFORMIST pattern, eliminating translation.
- If the other subsystem is simple or has a clean interface, you may not need the FACADE.
- Functionality can be added to the ANTICORRUPTION LAYER if it is specific to the relationship of the two subsystems. An audit trail for use of the external system or trace logic for debugging the calls to the other interface are two useful features that come to mind.
- 我们通常需要一些通信机制来连接两个子系统,而且它们可能位于不同的服务器上。在这种情况下,必须决定在哪里放置通信链接。如果无法访问另一个子系统,那么可能必须在 FACADE 和另一个子系统之间设置通信链接。但是,如果 FACADE 可以直接与另一个子系统集成到一起,那么在适配器和 FACADE 之间设置通信链接也不失为一种好的选择,这是因为 FACADE 的协议比它所封装的内容要简单。在有些情况下,整个 ANTICORRUPTION LAYER 可以与另一个子系统放在一起,这时可以在你的系统和构成 ANTICORRUPTION LAYER 接口的 SERVICE 之间设置通信链接或分发机制。这些都是需要根据实际情况做出的实现和部署决策。它们与 ANTICORRUPTION LAYER 的概念角色无关。
- 如果有权访问另一个子系统,你可能会发现对它进行少许的重构会使你的工作变得更容易。特别是应该为那些需要使用的功能编写更显式的接口,如果可能的话,首先从编写自动测试开始。
- 当需要进行广泛的集成时,转换的成本会直线上升。这时需要对正在设计的系统的模型做出一些选择,使之尽量接近外部系统,以便使转换更加容易。做这些工作时要非常小心,不要破坏模型的完整性。这是只有当转换的难度无法掌控时才选择进行的事情。如果这种方法看起来是大部分重要问题的最自然的解决方案,那么可以考虑让你的子系统采用 CONFORMIST 模式,从而消除转换。
- 如果另一个子系统很简单或有一个很整洁的接口,可能就不需要 FACADE 了。
- 如果一个功能是两个系统的关系所需的,就可以把这个功能添加到 ANTICORRUPTION LAYER 中。此外我们还很容易想到两个特性,一是外部系统使用情况的审计跟踪,二是追踪逻辑,其用于调试对另一个接口的调用。
Remember, an ANTICORRUPTION LAYER is a means of linking two BOUNDED CONTEXTS. Ordinarily, we are thinking of a system created by someone else; we have incomplete understanding of the system and little control over it. But that is not the only situation where you need a little padding between subsystems. There are even situations in which it makes sense to connect two subsystems of your own design with an ANTICORRUPTION LAYER, if they are based on different models. Presumably, in such a case, you will have full control over both sides and typically can use a simple translation layer. However, if two BOUNDED CONTEXTS have gone SEPARATE WAYS yet still have some need of functional integration, an ANTICORRUPTION LAYER can reduce the friction between them.
记住,ANTICORRUPTION LAYER 是连接两个 BOUNDED CONTEXT 的一种方式。我们常常需要使用别人创建的系统,然而我们并未完全理解这些系统,并且也无法控制它们。但这并不是我们需要在两个子系统之间使用防护层的唯一情况。如果你自己开发的两个子系统基于不同的模型,那么使用 ANTICORRUPTION LAYER 把它们连接起来也是有意义的。在这种情况下,你应该可以完全控制这两个子系统,而且通常可以使用一个简单的转换层。但是,如果这两个 BOUNDED CONTEXT 采用了 SEPARATE WAY 模式,而仍然需要进行一定的功能集成,那么可以使用 ANTICORRUPTION LAYER 来减少它们之间的矛盾。
Example: The Legacy Booking Application
示例遗留预订应用程序
In order to have a small, quick first release, we will write a minimal application that can set up a shipment and then pass that to the legacy system through a translation layer for booking and support operations. Because we built the translation layer specifically to protect our developing model from the influence of the legacy design, this translation is an ANTICORRUPTION LAYER.
为了有一个小的、可以快速开始的最初版本,我们将编写一个最小化的应用程序,它可以建立一次装运(shipment)并通过一个转换层传递给遗留系统进行预订和支持操作。由于我们是专门为了保护正在开发的模型不受遗留设计的影响才构建的转换层,因此这个转换就是一个 ANTICORRUPTION LAYER。
Initially, the ANTICORRUPTION LAYER will accept the objects representing a shipment, convert them, pass them to the legacy system and request a booking, and then capture the confirmation and translate it back into the confirmation object of the new design. This isolation will allow us to develop our new application mostly independently of the old one, though we’ll have to invest quite a bit in translation.
最初,ANTICORRUPTION LAYER 将接收表示装载的对象,对它们进行转换并传递给遗留系统,请求一个预订,然后捕获确认消息并将其转换成新设计的确认对象。这种隔离使我们基本上能够独立于遗留系统来开发新的应用程序,尽管这也必须投入相当多的转换工作。
With each successive release, the new system can either take over more functions of the legacy or simply add new value without replacing existing capabilities, depending on later decisions. This flexibility, and the ability to continually operate the combined system while making a gradual transition, probably makes it worth the expense to build the ANTICORRUPTION LAYER.
在后续的每个版本中,根据后面的决策,新系统要么可以接管遗留系统的更多功能,要么可以在不替换现有功能的情况下增加一些新的功能。这种灵活性,以及能够持续地操作合并的系统并同时进行新老系统的逐步过渡,会使我们在构建 ANTICORRUPTION LAYER 上投入的工作变得有价值。
To protect their frontiers from raids by neighboring nomadic warrior tribes, the early Chinese built the Great Wall. It was not an impenetrable barrier, but it allowed a regulated commerce with neighbors while providing an impediment to invasion and other unwanted influence. For two thousand years it defined a boundary that helped the Chinese agricultural civilization to define itself with less disruption from the chaos outside.
为了保护边境不受周边好战的游牧部落的侵犯,古代中国修建了长城。虽然它并不是一道不可逾越的屏障,但它却使得与邻近地区的通商变得规范有序,同时也可以抵御侵略和其他不良影响。两千多年来,它定义了一个边界,保护中国的农业文明较少受到外界混乱局面的干扰。
Although China might not have become so distinct a culture without the Great Wall, the Wall’s construction was immensely expensive and bankrupted at least one dynasty, probably contributing to its fall. The benefits of isolation strategies must be balanced against their costs. There is a time to be pragmatic and make measured revisions to the model, so that it can fit more smoothly with foreign ones.
如果没有长城,中国可能不会形成如此独特的文明,但尽管如此,长城的修建耗资巨大,它至少使一个朝代“破产”,而且也可能导致了它最终灭亡。隔离策略的益处必须平衡它产生的代价。我们应该从实际出发,对模型做出适度的修改,使之能够更好地适应外部模型。
There is overhead involved in any integration, from full-on CONTINUOUS INTEGRATION inside a single BOUNDED CONTEXT, through the lesser commitments of SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS, to the one-sidedness of the CONFORMIST and the defensive posture of the ANTICORRUPTION LAYER. Integration can be very valuable, but it is always expensive. We should be sure it is really needed. . . .
任何集成都是有开销的,无论这种集成是单一 BOUNDED CONTEXT 中的完全 CONTINUOUS INTEGRATION,还是集成度较轻的 SHARED KERNEL 或 CUSTOMER/SUPPLIER DEVELOPERTEAM,或是单方面的 CONFORMIST 模式和防御型的 ANTICORRUPTION LAYER 模式。集成可能非常有价值,但它的代价也总是十分高昂的。我们应该确保在真正需要的地方进行集成。
We must ruthlessly scope requirements. Two sets of functionality with no indispensable relationship can be cut loose from each other.
我们必须严格划定需求的范围。如果两组功能之间的关系并非必不可少,那么二者完全可以彼此独立。
Integration is always expensive. Sometimes the benefit is small.
集成总是代价高昂,而有时获益却很小。
In addition to the usual expense of coordinating teams, integration forces compromises. The simple specialized model that can satisfy a particular need must give way to the more abstract model that can handle all situations. Perhaps some completely different technology could provide certain features very easily, but it is difficult to integrate. Maybe some team is just so hard to get along with that nothing works very well when other teams try to collaborate with them.
除了在团队之间进行协调所需的常见开销以外,集成还迫使我们做出一些折中。可以满足某一特定需求的简单专用模型要为能够处理所有情况的更加抽象的模型让路。或许有些完全不同的技术能够轻而易举地提供某些特性,但它却难以集成。或许某个团队很难合作,使得其他团队在尝试与之合作时找不到行之有效的方法。
In many circumstances, integration provides no significant benefit. If two functional parts do not call upon each other’s functionality, or require interactions between objects that are touched by both, or share data during their operations, then integration, even through a translation layer, may not be necessary. Just because features are related in a use case does not mean they must be integrated.
在很多情况下,集成不会提供明显的收益。如果两个功能部分并不需要互相调用对方的功能,或者这两个部分所使用的对象并不需要进行交互,或者在它们操作期间不共享数据,那么集成可能就是没有必要的(尽管可以通过一个转换层进行集成)。仅仅因为特性在用例中相关,并不一定意味着它们必须集成到一起。
Therefore:
因此:
Declare a BOUNDED CONTEXT to have no connection to the others at all, allowing developers to find simple, specialized solutions within this small scope.
声明一个与其他上下文毫无关联的 BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案。
The features can still be organized in middleware or the UI layer, but there will be no sharing of logic, and an absolute minimum of data transfer through translation layers—preferably none.
特性仍然可以被组织到中间件或 UI 层中,但它们将没有共享的逻辑,而且应该把通过转换层进行的数据传输减至最小,最好是没有数据传输。
Example: An Insurance Project Slims Down
示例一个保险项目的简化
One project team had set out to develop new software for insurance claims that would integrate into one system everything a customer service agent or a claims adjuster needed. After a year of effort, team members were stuck. A combination of analysis paralysis and a major up-front investment in infrastructure had found them with nothing to show an increasingly impatient management. More seriously, the scope of what they were trying to do was overwhelming them.
一个项目团队着手开发一个新的保险理赔软件,他们打算把客户服务代理或理赔人所需的一切功能都集成到一个系统中。经过一年的工作后,团队成员陷入僵局。分析瘫痪再加上巨大的基础设施前期投资使他们在渐渐失去耐心的管理层面前没有任何可以展示的成果。更严重的是,他们尝试完成的工作规模将他们彻底压垮了。
A new project manager forced everyone into a room for a week to form a new plan. First they made lists of requirements and tried to estimate their difficulty and assign importance. They ruthlessly chopped the difficult and unimportant ones. Then they started to bring order to the remaining list. Many smart decisions were made in that room that week, but in the end, only one turned out to be important. At some point it was recognized that there were some features for which integration provided little added value. For example, adjusters needed access to some existing databases, and their current access was very inconvenient. But, although the users needed to have this data, none of the other features of the proposed software system would use it.
新任项目经理把所有人员集中到一个房间中,让他们一周内制定一个新的计划。他们首先整理出需求列表,然后尝试估计它们的难度和重要性。他们坚决地删减掉那些困难并且不重要的需求。然后,开始为剩下的需求列表排列顺序。这个星期他们在这个房间里制定了很多明智的决策,但最后只有一个被证明是真正重要的。某个时候他们终于认识到有些特性几乎没有从集成得到任何好处。例如,理赔人需要访问一些现有数据库,而且他们目前的访问非常不方便。但是,尽管用户需要得到这些数据,但软件系统的其他特性却没有一个用到它们。
Team members proposed various ways of providing easy access. In one case, a key report could be exported as HTML and placed on the intranet. In another case, adjusters could be provided with a specialized query written using a standard software package. All these functions could be integrated by organizing links on an intranet page or by placing buttons on the user’s desktop.
团队成员提出了各种简单的访问方式。一个提议是,可以把关键报告导出为 HTML 并放到内部网(intranet)上。另一个提议是,可以为理赔人提供一种专用查询,这种查询是用一个标准软件包编写的。通过在内部网的页面上放置链接,或者在用户桌面上放置按钮,就可以把所有这些功能集成进来。
The team launched a set of small projects that attempted no more integration than launching from the same menu. Several valuable capabilities were delivered almost overnight. Dropping the baggage of these extraneous features left a distilled set of requirements that seemed for a while to give hope for delivery of the main application.
团队启动了一组小项目,这些项目除了从同一个菜单启动之外,不再尝试任何集成。几个很有价值的功能几乎在一夜之间就完成了。卸去了这些过多特性的包袱之后,只剩下了一组精炼的需求,这使得主应用程序的交付又有了希望。
It could have gone that way, but unfortunately the team slipped back into old habits. They paralyzed themselves again. In the end, their only legacy turned out to be those small applications that had gone their SEPARATE WAYS.
团队本来可以这样进行下去,但遗憾的是,他们又回到了老路,再次陷入困境。最后,只有那些采用 SEPARATE WAY 模式开发的小应用程序被证明是有用的。
Taking SEPARATE WAYS forecloses some options. Although continuous refactoring can eventually undo any decision, it is hard to merge models that have developed in complete isolation. If integration turns out to be needed after all, translation layers will be necessary and may be complex. Of course, this is something you will face anyway.
采用 SEPARATE WAY(各行其道)模式需要预先决定一些选项。尽管持续重构最后可以撤销任何决策,但完全隔离开发的模型是很难合并的。如果最终仍然需要集成,那么转换层将是必要的,而且可能很复杂。当然,不管怎样,这都是我们将要面对的问题。
Now, turning back to more cooperative relationships, let’s look at ways to scale up integration. . . .
现在,让我们回到更为合作的关系上,来看一下几种提高集成度的模式。
Typically for each BOUNDED CONTEXT, you will define a translation layer for each component outside the CONTEXT with which you have to integrate. Where integration is one-off, this approach of inserting a translation layer for each external system avoids corruption of the models with a minimum of cost. But when you find your subsystem in high demand, you may need a more flexible approach.
一般来说,在 BOUNDED CONTEXT 中工作时,我们会为 CONTEXT 外部的每个需要集成的组件定义一个转换层。当集成是一次性的,这种为每个外部系统插入转换层的方法可以以最小的代价避免破坏模型。但当子系统要与很多系统集成时,可能就需要更灵活的方法了。
When a subsystem has to be integrated with many others, customizing a translator for each can bog down the team. There is more and more to maintain, and more and more to worry about when changes are made.
当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。需要维护的东西会越来越多,而且进行修改的时候担心的事情也会越来越多。
The team may be doing the same thing again and again. If there is any coherence to the subsystem, it is probably possible to describe it as a set of SERVICES that cover the common needs of other subsystems.
团队可能正在反复做着同样的事情。如果一个子系统有某种内聚性,那么或许可以把它描述为一组 SERVICE,这组 SERVICE 满足了其他子系统的公共需求。
It is a lot harder to design a protocol clean enough to be understood and used by multiple teams, so it pays off only when the subsystem’s resources can be described as a cohesive set of SERVICES and when there are a significant number of integrations. Under those circumstances, it can make the difference between maintenance mode and continuing development.
要想设计出一个足够干净的协议,使之能够被多个团队理解和使用,是一件十分困难的事情,因此只有当子系统的资源可以被描述为一组内聚的 SERVICE 并且必须进行很多集成的时候,才值得这样做。在这些情况下,它能够把维护模式和持续开发区别开。
Therefore:
因此:
Define a protocol that gives access to your subsystem as a set of SERVICES. Open the protocol so that all who need to integrate with you can use it. Enhance and expand the protocol to handle new integration requirements, except when a single team has idiosyncratic needs. Then, use a one-off translator to augment the protocol for that special case so that the shared protocol can stay simple and coherent.
定义一个协议,把你的子系统作为一组 SERVICE 供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚。
This formalization of communication implies some shared model vocabulary—the basis of the SERVICE interfaces. As a result, the other subsystems become coupled to the model of the OPEN HOST, and other teams are forced to learn the particular dialect used by the HOST team. In some situations, using a well-known PUBLISHED LANGUAGE as the interchange model can reduce coupling and ease understanding. . . .
这种通信形式暗含一些共享的模型词汇,它们是 SERVICE 接口的基础。这样,其他子系统就变成了与 OPEN HOST(开放主机)的模型相连接,而其他团队则必须学习 HOST 团队所使用的专用术语。在一些情况下,使用一个众所周知的 PUBLISHED LANGUAGE(公开发布的语言)作为交换模型可以减少耦合并简化理解。
The translation between the models of two BOUNDED CONTEXTS requires a common language.
两个 BOUNDED CONTEXT 之间的模型转换需要一种公共的语言。
When two domain models must coexist and information must pass between them, the translation process itself can become complex and hard to document and understand. If we are building a new system, we will typically believe that our new model is the best available, and so we will think in terms of translating directly into it. But sometimes we are enhancing a set of older systems and trying to integrate them. Choosing one messy model over the other may be choosing the lesser of two evils.
当两个领域模型必须共存而且必须交换信息时,转换过程本身就可能很复杂,而且很难文档化和理解。如果正在构建一个新系统,我们一般会认为新模型是最好的,因此只考虑把其他模型转换成新模型就可以了。但有时我们的工作是增强一系列旧系统并尝试集成它们。这时要在众多模型中选择一个比较不烂的模型,也就是说“两害取其轻”。
Another situation: When businesses want to exchange information with one another, how do they do it? Not only is it unrealistic to expect one to adopt the domain model of the other, it may be undesirable for both parties. A domain model is developed to solve problems for its users; such a model may contain features that needlessly complicate communication with another system. Also, if the model underlying one of the applications is used as the communications medium, it cannot be changed freely to meet new needs, but must be very stable to support the ongoing communication role.
另一种情况是,当不同业务之间需要互相交换信息时,应该如何做?想让一个业务采用另一个业务的领域模型不仅是不现实的,而且可能也不符合双方的需要。领域模型是为了解决其用户的需求而开发的,这样的模型所包含的一些特性可能使得与另一个系统的通信变得复杂,而实际上没有必要这么复杂。此外,如果把一个应用程序的模型用作通信媒介,那么它可能就无法为满足新需求而自由地修改了,它必须非常稳定,以便支持当前的通信职责。
Direct translation to and from the existing domain models may not be a good solution. Those models may be overly complex or poorly factored. They are probably undocumented. If one is used as a data interchange language, it essentially becomes frozen and cannot respond to new development needs.
与现有领域模型进行直接的转换可能不是一种好的解决方案。这些模型可能过于复杂或设计得较差。它们可能没有被很好地文档化。如果把其中一个模型作为数据交换语言,它实质上就被固定住了,而无法满足新的开发需求。
The OPEN HOST SERVICE uses a standardized protocol for multiparty integration. It employs a model of the domain for interchange between systems, even though that model may not be used internally by those systems. Here we go a step further and publish that language, or find one that is already published. By publish I simply mean that the language is readily available to the community that might be interested in using it, and is sufficiently documented to allow independent interpretations to be compatible.
OPEN HOST SERVICE 使用一个标准化的协议来支持多方集成。它使用一个领域模型来在各系统间进行交换,尽管这些系统的内部可能并不使用该模型。这里我们可以更进一步——发布这种语言,或找到一种已经公开发布的语言。我这里所说的发布仅仅是指该语言已经可以供那些对它感兴趣的群体使用,而且已经被充分文档化,兼容一些独立的解释。
Recently, the world of e-commerce has become very excited about a new technology: Extensible Markup Language (XML) promises to make interchange of data much easier. A very valuable feature of XML is that, through the document type definition (DTD) or through XML schemas, XML allows the formal definition of a specialized domain language into which data can be translated. Industry groups have begun to form for the purpose of defining a single standard DTD for their industry so that, say, chemical formula information or genetic coding can be communicated between many parties. Essentially these groups are creating a shared domain model in the form of a language definition.
最近,电子商务界出现了一种激动人心的新技术:XML(可扩展标记语言)。这种技术有望使数据交换变得更加容易。XML 的一个非常有价值的特性是通过 DTD(文档类型定义)或 XML 模式来正式定义一个专用的领域语言,从而使得数据可以被转换为这种语言。一些行业组织已经成立,准备为各自的行业定义一种标准的 DTD,这样,业内多方就可以交换信息了,如交换化学公式信息或遗传代码信息。实际上这些组织正在以语言定义的形式创建一种共享的领域模型。
Therefore:
因此:
Use a well-documented shared language that can express the necessary domain information as a common medium of communication, translating as necessary into and out of that language.
把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在其他信息与该语言之间进行转换。
The language doesn’t have to be created from scratch. Many years ago, I was contracted by a company that had a software product written in Smalltalk that used DB2 to store its data. The company wanted the flexibility to distribute the software to users without a DB2 license and contracted me to build an interface to Btrieve, a lighter-weight database engine that had a free runtime distribution license. Btrieve is not fully relational, but my client was using only a small part of DB2’s power and was within the lowest common denominator of the two databases. The company’s developers had built on top of DB2 some abstractions that were in terms of the storage of objects. I decided to use this work as the interface for my Btrieve component.
这种语言不必从头创建。很多年以前,我曾经受聘于一家公司,这家公司有一个用 Smalltalk 编写的软件产品,它使用 DB2 存储数据。公司希望灵活地把软件分发给那些没有 DB2 许可的用户,于是请我为 Btrieve 创建一个接口,Btrieve 是一个轻量级的数据库引擎,它有一个免费的运行时分发许可。Btrieve 并不完全是关系型的,但我的客户只用到 DB2 的很小的一部分功能,而且两个数据库都能提供这种能力。公司的开发人员已经在 DB2 之上建立了某种存储对象的抽象,于是我决定把这些工作作为我的 Btrieve 组件的接口。
This approach did work. The software smoothly integrated with my client’s system. However, the lack of a formal specification or documentation of the abstractions of persistent objects in the client’s design meant a lot of work for me to figure out the requirements of the new component. Also, there wasn’t much opportunity to reuse the component to migrate some other application from DB2 to Btrieve. And the new software more deeply entrenched the company’s model of persistence, so that refactoring that model of persistent objects would have been even more difficult.
这种方法确实很有效。软件顺利地与我的客户系统集成到一起。但是,客户设计中缺少有关持久化对象的抽象的正式规格说明或文档,这意味着我必须做很多工作来确定新组件的需求。此外,不太可能重用该组件把其他应用程序从 DB2 迁移到 Btrieve。而且新软件稳固了公司的持久化模型,使得持久化对象模型的重构变得更困难。
A better way might have been to identify the subset of the DB2 interface that the company was using and then support that. The interface of DB2 is made up of SQL and a number of proprietary protocols. Although it is very complex, the interface is tightly specified and thoroughly documented. The complexity would have been mitigated because only a small subset of the interface was being used. If a component had been developed that emulated the necessary subset of the DB2 interface, it could have been very effectively documented for developers simply by identifying the subset. The application it was integrated into already knew how to talk to DB2, so little additional work would have been needed. Future redesign of the persistence layer would have been constrained only to the use of the DB2 subset, just as before the enhancement.
更好的方法可能是标识出公司所使用的那一小部分 DB2 接口,然后为其提供支持就可以了。DB2 的接口由 SQL 和大量专有协议构成。尽管接口很复杂,但它已经被严格地规定并充分文档化。由于公司只使用接口的一个很小的子集,因此复杂性有所降低。如果已开发出一个模拟必要的 DB2 接口子集的组件,那么开发人员所需做的文档化工作只是标识出该子集即可。与之集成的应用程序已经知道如何与 DB2 对话,因此额外要做的工作很少。将来重新设计持久层的工作仅限于 DB2 子集的使用,就像前面做的改进一样。
The DB2 interface is an example of a PUBLISHED LANGUAGE. In this case, the two models are not in the business domain, but all the principles apply just the same. Because one of the models in the collaboration is already a PUBLISHED LANGUAGE, there is no need to introduce a third language.
DB2 接口是 PUBLISHED LANGUAGE 的一个例子。在这个例子中,两个模型都不属于业务领域,但它们所应用的原则是一致的。由于协作中的一个模型已经是一种 PUBLISHED LANGUAGE,因此就不需要引入第三方语言了。
Example: A PUBLISHED LANGUAGE for Chemistry
示例一种化学的 PUBLISHED Language
Innumerable programs are used to catalog, analyze, and manipulate chemical formulas in industry and academia. Exchanging data has always been difficult, because almost every program uses a different domain model to represent chemical structures. And of course, most of them are written in languages, such as FORTRAN, that do not express the domain model very fully anyway. Whenever anyone wanted to share data, they had to unravel the details of the other system’s database and work out some sort of translation scheme.
在工业界和学术界,有无数的程序用于分类、分析和处理化学公式。几乎每个程序都使用不同的领域模型来表示化学结构,因此数据的交换总是很难。当然,大部分程序都是用一些无法充分表达领域模型的语言编写的(如 FORTRAN)。当有人想要共享数据时,他们不得不先了解其他系统的数据库的细节,然后再研究出某种转换方案。
Enter the Chemical Markup Language (CML), a dialect of XML intended as a common interchange language for this domain, developed and managed by a group representing academics and industry (Murray-Rust et al. 1995).
CML(化学标记语言)正是在这种背景下诞生的,它是作为化学领域的公共交流语言被开发出来的专用 XML,由一个代表学术界和工业界的组织负责开发和管理[Murray-Rust et al. 1995]。
Chemical information is very complex and diverse, and it changes all the time with new discoveries. So they developed a language that could describe the basics, such as the chemical formulas of organic and inorganic molecules, protein sequences, spectra, or physical quantities.
化学信息非常复杂和多样化,而且会随着新发现而不断变化。因此,该组织开发了一种用于描述基础知识的语言,如有机和无机分子的化学公式、蛋白质序列、光谱或物理量。
Now that the language has been published, tools can be developed that would never have been worth the trouble to write before, when they would have only been usable for one database. For example, a Java application, called the JUMBO Browser, was developed that creates graphical views of chemical structures stored in CML. So if you put your data in the CML format, you’ll have access to such visualization tools.
既然这种语言已经公开发布,人们就可以开发相应的工具了(以前,要开发这样的工具是不值得的,因为它们只能用于一种数据库)。例如,人们开发一种名为 JUMBO Browser 的 Java 应用程序,它的功能是为那些以 CML 格式存储的化学结构创建图形视图。因此,如果你的数据采用了 CML 格式,就可以使用这样的可视化工具。
In fact, CML gained a double advantage by using XML, a sort of “published meta-language.” The learning curve of CML is flattened by people’s familiarity with XML; the implementation is eased by various off-the-shelf tools, such as parsers; and documentation is helped by the many books written on all aspects of handling XML.
事实上,CML 通过使用 XML(一种已发布的元语言)获得了双重优势。一个优势是人们对 XML 很熟悉,因此很容易学习 CML,另一个优势是由于有大量现成的工具(如解析器),因此 CML 的实现很容易,而且有大量书籍介绍了 XML 的各个方面,这对 CML 的文档化有很大帮助。
Here is a tiny sample of CML. It is only vaguely intelligible to nonspecialists like myself, but the principle is clear.
下面是一个 CML 的小例子。虽然像我这样的外行并不能清楚地理解它是什么意思,但它的原则还是很清晰的。
<CML.ARR ID="array3" EL.TYPE=FLOAT NAME="ATOMIC ORBITAL ELECTRON POPULATIONS" SIZE=30 GLO.ENT=CML.THE.AOEPOPS>
1.17947 0.95091 0.97175 1.00000 1.17947 0.95090 0.97174 1.00000
1.17946 0.98215 0.94049 1.00000 1.17946 0.95091 0.97174 1.00000
1.17946 0.95091 0.97174 1.00000 1.17946 0.98215 0.94049 1.00000
0.89789 0.89790 0.89789 0.89789 0.89790 0.89788
</CML.ARR>
It was six men of Indostan
To learning much inclined,
Who went to see the Elephant
(Though all of them were blind),
That each by observation
Might satisfy his mind.
The First approached the Elephant,
And happening to fall
Against his broad and sturdy side,
At once began to bawl:
"God bless me! but the Elephant
Is very like a wall!"
. . .
The Third approached the animal,
And happening to take
The squirming trunk within his hands,
Thus boldly up and spake:
"I see," quoth he, "the Elephant
Is very like a snake."
The Fourth reached out his eager hand,
And felt about the knee.
"What most this wondrous beast is like
Is mighty plain," quoth he;
"’Tis clear enough the Elephant
Is very like a tree!"
. . .
The Sixth no sooner had begun
About the beast to grope,
Than, seizing on the swinging tail
That fell within his scope,
"I see," quoth he, "the Elephant
Is very like a rope!"
And so these men of Indostan
Disputed loud and long,
Each in his own opinion
Exceeding stiff and strong,
Though each was partly in the right,
And all were in the wrong!
. . .
—From “The Blind Men and the Elephant,” by John Godfrey Saxe (1816–1887), based on a story in the Udana, a Hindu text
六个好学的古印度人,一起去看大象,(他们都是盲人),都通过触摸,来满足了解事物的心愿。第一个接近大象的盲人,恰巧了撞上了大象宽阔结实的身躯,马上叫到:“上帝保佑,原来大象就像一堵墙。”……第三个盲人,碰巧把扭动着的象鼻抓在手中,因此就大胆地说道:“依我看,大象就像一条蛇!”第四个盲人急切地伸出双手,摸到了大象的膝盖,“这头奇异的怪兽最像什么已经很明显了”,他说,“很明显,大象就像一棵树”……第六个盲人一开始摸这头大象,就抓住了它摆动着的尾巴,他说,“我认为大象就像一根绳子!”这六个印度人,大声地争论个不停,他们每个人的观点,都过于僵化和固执,尽管他们每人都有正确的地方,但从整体上都是错误的!……
——摘自 John Godfrey Saxe(1816—1887)创作的《盲人与象》,来源于印度自说经 Udana 中的故事
Depending on their goals in interacting with the elephant, the various blind men may still be able to make progress, even if they don’t fully agree on the nature of the elephant. If no integration is required, then it doesn’t matter that the models are not unified. If they require some integration, they may not actually have to agree on what an elephant is, but they will get a lot of value from merely recognizing that they don’t agree. This way, at least they don’t unknowingly talk at cross-purposes.
即便他们对大象的本质不能达成完全的一致,这些盲人仍然可以根据他们所触摸到的大象身体的部位来扩展各自的认识。如果并不需要集成,那么模型统不统一就无关紧要。如果他们需要进行一些集成,那么实际上并不需要对大象是什么达成一致,而只要接受各种不同意见就会获得很多价值。这样,他们就不会在不知不觉中各执己见。
The diagrams in Figure 14.9 are UML representations of the models the blind men have formed of the elephant. Having established separate BOUNDED CONTEXTS, the situation is clear enough for them to work out a way to communicate with each other about the few aspects they care about in common: the location of the elephant, perhaps.
上图用 UML 图表示了 6 个盲人所认识到的大象模型。这张图建立了 4 个独立的 BOUNDED CONTEXT,情况很明显,他们必须找到一种方式来交流他们共同关心的少数几个方面,或许他们共同关心的就是大象所在的位置。
Four contexts: no integration
Four contexts: minimal integration
As the blind men want to share more information about the elephant, the value of sharing a single BOUNDED CONTEXT goes up. But unifying the disparate models is a challenge. None of them is likely to give up his model and adopt one of the others. After all, the man who touched the tail knows the elephant is not like a tree, and that model would be meaningless and useless to him. Unifying multiple models almost always means creating a new model.
当盲人想要分享更多有关大象的信息时,他们会从共享单个 BOUNDED CONTEXT 得到更大的价值。但统一不同的模型却很难做到。可能没有人愿意放弃自己的模型而采用别人的模型。毕竟,摸到尾巴的那个人知道大象并不像一颗树,而且那个模型对他来说没有意义,也没有用处。统一多个模型几乎总是意味着创建一个新模型。
With some imagination and continued discussion (probably heated), the blind men could eventually recognize that they have been describing and modeling different parts of a larger whole. For many purposes, a part-whole unification may not require much additional work. At least the first stage of integration only requires figuring out how the parts are related. It may be adequate for some needs to view an elephant as a wall, held up by tree trunks, with a rope at one end and a snake at the other.
经过一些想象和讨论(也许是激烈的讨论)之后,盲人们最终可能会认识到他们正在对一个更大整体的不同部分进行描述和建模。从很多方面来讲,部分—整体的统一可能不需要花费很多工作。至少集成的第一步只需弄清楚各个部分是如何相连的就够了。可以把大象看成一堵墙,下面通过树干支撑着,一头儿是一根绳子,另一头儿是一条蛇,这样看就可以适当地满足一些需求了。
One context: crude integration
The unification of the various elephant models is easier than most such mergers. Unfortunately, it is the exception when two models purely describe different parts of the whole, although this is often one aspect of the difference. Matters are more difficult when two models are looking at the same part in a different way. If two men had touched the trunk and one described it as a snake and the other described it as a fire hose, they would have had more difficulty. Neither can accept the other’s model, because it contradicts his own experience. In fact, they need a new abstraction that incorporates the “aliveness” of a snake with the water-shooting functionality of a fire hose, but one that leaves out the inapt implications of the first models, such as the expectation of possibly venomous fangs, or the ability to be detached from the body and rolled up into a compartment in a fire truck.
大象模型的统一要比大多数这样的合并相对简单一些。遗憾的是,大象模型的统一只是一个特例——不同模型纯粹是在描述整体的不同部分,然而,这通常是模型之间差别的一个方面而已。当两个模型以不同方式描述同一部分时,问题会变得更加困难。如果两个盲人都摸到了象鼻子,一个人认为它像蛇,而另一个人认为它像消防水管,那么他们将更难集成。双方都无法接受对方的模型,因为那不符合自己的体验。事实上,他们需要一个新的抽象,这个抽象需要把蛇的“活着的特性”与消防水管的喷水功能合并到一起,而这个抽象还应该排除先前两个模型中的一些不确切的含义,如人们可能会想到的毒牙,或者可以从身体上拆下并卷起来放到救火车中的这种性质。
Even though we have combined the parts into a whole, the resulting model is crude. It is incoherent, lacking any sense of following contours of an underlying domain. New insights could lead to a deeper model in a process of continuous refinement. New application requirements can also force the move to a deeper model. If the elephant starts moving, the “tree” theory is out, and our blind modelers may break through to the concept of “legs.”
尽管我们已经把部分合并成一个整体,但得到的模型还是很简陋的。它缺乏内聚性,也没有形成任何潜在领域的轮廓。在持续精化的过程中,新的理解可能会产生更深层的模型。新的应用程序需求也可能会促成更深层的模型。如果大象开始移动了,那么“树”理论就站不住脚了,而盲人建模者们也可能会有所突破,形成“腿”的概念。
One context: deeper model
This second pass of model integration tends to slough off incidental or incorrect aspects of the individual models and creates new concepts—in this case, “animal” with parts “trunk,” “leg,” “body,” and “tail”—each of which has its own properties and clear relationships to other parts. Successful model unification, to a large extent, hinges on minimalism. An elephant trunk is both more and less than a snake, but the “less” is probably more important than the “more.” Better to lack the water-spewing ability than to have an incorrect poison-fang feature.
模型集成的第二步是去掉各个模型中那些偶然或不正确的方面,并创建新的概念,在本例中,这个概念就是一种“动物”,它长着“鼻子”、“腿”、“身体”和“尾巴”,每个部分都有其自己的属性以及与其他部分的明确关系。在很大程度上,成功的模型应该尽可能做到精简。象鼻与蛇相比,其特性和功能可能比蛇多,也可能比蛇少,但宁“少”勿“多”。宁可缺少喷水功能,也不要包含不正确的毒牙特性。
If the goal is simply to find the elephant, then translating between each model’s expression of location will do. When more integration is needed, the unified model doesn’t have to reach full maturity in the first version. It may be adequate for some needs to view an elephant as a wall, held up by tree trunks, with a rope at one end and a snake at the other. Later, driven by new requirements and by improved understanding and communication, the model can be deepened and refined.
如果目标只是找到大象,那么只要对每个模型中所表示的位置进行转换就可以了。当需要更多集成时,第一个版本的统一模型不一定达到完全的成熟。把大象看成一堵墙,下面用树干支撑着,一头儿是一根绳子,另一头儿是一条蛇,就可以适当地满足一些需求了。紧接着,通过新需求和进一步的理解及沟通的推动,模型可以得到加深和精化。
Recognizing multiple, clashing domain models is really just facing reality. By explicitly defining a context within which each model applies, you can maintain the integrity of each and clearly see the implications of any particular interface you want to create between the two. There is no way for the blind men to see the whole elephant, but their problem would be manageable if only they recognized the incompleteness of their perception.
承认多个互相冲突的领域模型实际上正是面对现实的做法。通过明确定义每个模型都适用的上下文,可以维护每个模型的完整性,并清楚地看到要在两个模型之间创建的任何特殊接口的含义。盲人没办法看到整个大象,但只要他们承认各自的理解是不完整的,他们的问题就能得到解决。
It is important always to draw the CONTEXT MAP to reflect the current situation at any given time. Once that’s done, though, you may very well want to change that reality. Now you can begin to consciously choose CONTEXT boundaries and relationships. Here are some guidelines.
在任何时候,绘制出 CONTEXT MAP 来反映当前状况都是很重要的。但是,一旦绘制好 CONTEXT MAP 之后,你很可能想要改变现状。现在,你可以开始有意识地选择 CONTEXT 的边界和关系。以下是一些指导原则。
First, teams have to make decisions about where to define BOUNDED CONTEXTS and what sort of relationships to have between them. Teams have to make these decisions, or at least the decisions have to be propagated to the entire team and understood by everyone. In fact, such decisions often involve agreements beyond your own team. On the merits, decisions about whether to expand or to partition BOUNDED CONTEXTS should be based on the cost-benefit trade-off between the value of independent team action and the value of direct and rich integration. In practice, political relationships between teams often determine how systems are integrated. A technically advantageous unification may be impossible because of reporting structure. Management may dictate an unwieldy merger. You won’t always get what you want, but at least you may be able to assess and communicate something of the cost incurred, and take steps to mitigate it. Start with a realistic CONTEXT MAP and be pragmatic in choosing transformations.
首先,团队必须决定在哪里定义 BOUNDED CONTEXT,以及它们之间有什么样的关系。这些决策必须由团队做出,或者至少传达给整个团队,并且被团队里的每个人理解。事实上,这样的决策通常需要与外部团队达成一致。按照本身价值来说,在决定是否扩展或分割 BOUNDED CONTEXT 时,应该权衡团队独立工作的价值以及能产生直接且丰富集成的价值,以这两种价值的成本—效益作为决策的依据。在实践中,团队之间的行政关系往往决定了系统的集成方式。由于汇报结构,有技术优势的统一可能无法实现。管理层所要求的合并可能并不实用。你不会总能得到你想要的东西,但你至少可以评估出这些决策的代价,并反映给管理层,以便采取相应的措施来减小代价。从一个现实的 CONTEXT MAP 开始,并根据实际情况来选择改变。
When we are working on a software project, we are interested primarily in the parts of the system our team is changing (the “system under design”) and secondarily in the systems it will communicate with. In a typical case, the system under design is going to get carved into one or two BOUNDED CONTEXTS that the main development teams will be working on, perhaps with another CONTEXT or two in a supporting role. In addition to that are the relationships between these CONTEXTS and the external systems. This is a simple, typical view, to give some rough expectation for what you are likely to encounter.
开发软件项目时,我们首先是对自己团队正在开发的那些部分感兴趣(“设计中的系统”),其次是对那些与我们交互的系统感兴趣。典型情况下,设计中的系统将被划分为一到两个 BOUNDED CONTEXT,开发团队的主力将在这些上下文中工作,或许还会有另外一到两个起支持作用的 CONTEXT。除此之外,就是这些 CONTEXT 与外部系统之间的关系。这是一种简单、典型的情况,能让你对可能会遇到的情形有一些粗略的了解。
We really are part of that primary CONTEXT we are working in, and that is bound to be reflected in our CONTEXT MAP. This isn’t a problem if we are aware of the bias and are mindful of when we step outside the limits of that MAP’s applicability.
实际上,我们正是自己所处理的主要 CONTEXT 的一部分,这会在我们的 CONTEXT MAP 中反映出来。只要我们知道自己存在偏好,并且在超出该 CONTEXT MAP 的应用边界时能够意识到已越界,那么就不会有什么问题。
There are an unlimited variety of situations and an unlimited number of options for drawing the boundaries of BOUNDED CONTEXTS. But typically the struggle is to balance some subset of the following forces:
在画出 BOUNDED CONTEXT 的边界时,有无数种情况,也有无数种选择。但权衡时所要考虑的通常是下面所列出的某些因素。
Favoring Larger BOUNDED CONTEXTS
首选较大的 BOUNDED CONTEXT
- Flow between user tasks is smoother when more is handled with a unified model.
- It is easier to understand one coherent model than two distinct ones plus mappings.
- Translation between two models can be difficult (sometimes impossible).
- Shared language fosters clear team communication.
- 当用一个统一模型来处理更多任务时,用户任务之间的流动更顺畅。
- 一个内聚模型比两个不同模型再加它们之间的映射更容易理解。
- 两个模型之间的转换可能会很难(有时甚至是不可能的)。
- 共享语言可以使团队沟通起来更清楚。
Favoring Smaller BOUNDED CONTEXTS
首选较小的 BOUNDED CONTEXT
- Communication overhead between developers is reduced.
- CONTINUOUS INTEGRATION is easier with smaller teams and code bases.
- Larger contexts may call for more versatile abstract models, requiring skills that are in short supply.
- Different models can cater to special needs or encompass the jargon of specialized groups of users, along with specialized dialects of the UBIQUITOUS LANGUAGE.
- 开发人员之间的沟通开销减少了。
- 由于团队和代码规模较小,CONTINUOUS INTEGRATION 更容易了。
- 较大的上下文要求更加通用的抽象模型,而掌握所需技巧的人员会出现短缺。
- 不同的模型可以满足一些特殊需求,或者是能够把一些特殊用户群的专门术语和 UBIQUITOUS LANGUAGE 的专门术语包括进来。
Deep integration of functionality between different BOUNDED CONTEXTS is impractical. Integration is limited to those parts of one model that can be rigorously stated in terms of the other model, and even this level of integration may take considerable effort. This makes sense when there will be a small interface between two systems.
在不同 BOUNDED CONTEXT 之间进行深度功能集成是不切实际的。在一个模型中,只有那些能够严格按照另一个模型来表述的部分才能够进行集成,而且,即便是这种级别的集成可能也需要付出相当大的工作量。当两个系统之间有一个很小的接口时,集成是有意义的。
14.13.4 Accepting That Which We Cannot Change: Delineating the External Systems 接受那些我们无法更改的事物:描述外部系统
It is best to start with the easiest decisions. Some subsystems will clearly not be in any BOUNDED CONTEXT of the system under development. Examples would be major legacy systems that you are not immediately replacing and external systems that provide services you’ll need. You can identify these immediately and prepare to segregate them from your design.
最好从一些最简单的决策开始。一些子系统显然不在开发中的系统的任何 BOUNDED CONTEXT 中。一些无法立即淘汰的大型遗留系统和那些提供所需服务的外部系统就是这样的例子。我们很容易就能识别出这些系统,并把它们与你的设计隔离开。
Here we must be careful about our assumptions. It is convenient to think of each of these systems as constituting its own BOUNDED CONTEXT, but most external systems only weakly meet the definition. First, a BOUNDED CONTEXT is defined by an intention to unify the model within certain boundaries. You may have control of maintenance of the legacy system, in which case you can declare the intention, or the legacy team may be well coordinated and be carrying out an informal form of CONTINUOUS INTEGRATION, but don’t take it for granted. Check into it, and if the development is not well integrated, be particularly cautious. It is not unusual to find semantic contradictions in different parts of such systems.
在做出假设时必须要保持谨慎。我们会很轻易地认为这些系统构成了其自己的 BOUNDED CONTEXT,但大多数外部系统只是勉强满足定义。首先,定义 BOUNDED CONTEXT 的目的是把模型统一在特定边界之内。你可能负责遗留系统的维护,在这种情况下,可以明确地声明这一目的,或者也可以很好地协调遗留团队来执行非正式的 CONTINUOUS INTEGRATION,但不要认为遗留团队的配合是理所当然的事情。仔细检查,如果开发工作集成得不好,一定要特别小心。在这样的系统中,不同部分之间出现语义矛盾是很平常的事情。
There are three patterns that can apply here. First, to consider SEPARATE WAYS. Yes, you wouldn’t have included them if you didn’t need integration. But be really sure. Would it be sufficient to give the user easy access to both systems? Integration is expensive and distracting, so unburden your project as much as you can.
这里可以应用 3 种模式。首先,可以考虑 SEPARATE WAY 模式。当然,如果你不需要集成,就不用把它们包括进来。但一定要真正确定不需要集成。只为用户提供对两个系统的简单访问确实够用吗?集成要花费很大代价而且还会分散精力,因此要尽可能为你的项目减轻负担。
If the integration is really essential, you can choose between two extremes: CONFORMIST or ANTICORRUPTION LAYER. It is not fun to be a CONFORMIST. Your creativity and your options for new functionality will be limited. In building a major new system, it is unlikely to be practical to adhere to the model of a legacy or external system (after all, why are you building a new system?). However, sticking with the legacy model may be appropriate in the case of peripheral extensions to a large system that will continue to be the dominant system. Examples of this choice include the lightweight decision-support tools that are often written in Excel or other simple tools. If your application is really an extension to the existing system and your interface with that system is going to be large, the translation between CONTEXTS can easily be a bigger job than the application functionality itself. And there is still some room for good design work, even though you have placed yourself in the BOUNDED CONTEXT of the other system. If there is a discernable domain model behind the other system, you can improve your implementation by making that model more explicit than it was in the old system, just as long as you strictly conform to the old model. If you decide on a CONFORMIST design, you must do it wholeheartedly. You restrict yourself to extension only, with no modification of the existing model.
如果集成确实非常重要,可以在两种极端的模式之中进行选择:CONFORMIST 模式或 ANTICORRUPTION LAYER 模式。作为 CONFORMIST 并不那么有趣,你的创造力和你对新功能的选择都会受到限制。当构建一个大型的新系统时,遵循遗留系统或外部系统的模型可能是不现实的(毕竟,为什么要构建新系统呢?)。但是,当对一个大的系统进行外围扩展时,而且这个系统仍然是主要系统,在这种情况下,继续使用遗留模型可能就很合适。这种选择的例子包括轻量级的决策支持工具,这些工具通常是用 Excel 或其他简单工具编写的。如果你的应用程序确实是现有系统的一个扩展,而且与该系统的接口很大,那么 CONTEXT 之间转换所需的工作量可能比应用程序功能本身需要的工作量还大。尽管你已经处于另一个系统的 BOUNDED CONTEXT 中,但你自己的一些好的设计仍然有用武之地。如果另一个系统有着可以识别的领域模型,那么只要使这个模型比在原来的系统中更清晰,你就可以改进你的实现,唯一需要注意的是要严格地遵照那个老模型。如果你决定采用 CONFORMIST 设计,就必须全心全意地去做。你应该约束自己只可以去扩展现有模型,而不能去修改它。
When the functionality of the system under design is going to be more involved than an extension to an existing system, where your interface to the other system is small, or where the other system is very badly designed, you’ll really want your own BOUNDED CONTEXT, which means building a translation layer, or even an ANTICORRUPTION LAYER.
当正在设计的系统功能并不仅仅是扩展现有系统时,而且你与另一个系统的接口很小,或者另一个系统的设计非常糟糕,那么实际上你会希望使用自己的 BOUNDED CONTEXT,这意味着需要构建一个转换层,甚至是一个 ANTICORRUPTION LAYER。
The software your project team is actually building is the system under design. You can declare BOUNDED CONTEXTS within this zone and apply CONTINUOUS INTEGRATION within each to keep them unified. But how many should you have? What relationships should they have to each other? The answers are less cut and dried than with the external systems because we have more freedom and control.
你的项目团队正在构建的软件就是设计中的系统。你可以在这个区域内声明 BOUNDED CONTEXT,并在每个 BOUNDED CONTEXT 中应用 CONTINUOUS INTEGRATION,以便保持它们的统一。但应该有几个上下文呢?各个上下文之间又应该是什么关系呢?与外部系统的情况相比,这些问题的答案会变得更加不确定,因为我们拥有更多的主动权。
It could be quite simple: a single BOUNDED CONTEXT for the entire system under design. For example, this would be a likely choice for a team of fewer than ten people working on highly interrelated functionality.
情况可能非常简单:设计中的整个系统使用一个 BOUNDED CONTEXT。例如,当一个少于 10 人的团队正在开发高度相关的功能时,这可能就是一种很好的选择。
As the team grows larger, CONTINUOUS INTEGRATION may become difficult (although I have seen it maintained for somewhat larger teams). You may look for a SHARED KERNEL and break off relatively independent sets of functionality into separate BOUNDED CONTEXTS, each with fewer than ten people. If all of the dependencies between two of these go in one direction, you could set up CUSTOMER/SUPPLIER DEVELOPMENT TEAMS.
随着团队规模的增大,CONTINUOUS INTEGRATION 可能会变得困难起来(尽管我也曾看到过一些较大的团队仍能保持持续集成)。你可能希望采用 SHARED KERNEL 模式,并把几组相对独立的功能划分到不同的 BOUNDED CONTEXT 中,使得在每个 BOUNDED CONTEXT 中工作的人员少于 10 人。在这些 BOUNDED CONTEXT 中,如果有两个上下文之间的所有依赖都是单向的,就可以建成 CUSTOMER/SUPPLIER DEVELOPMENT TEAM。
You may recognize that the mind-sets of two groups are so different that their modeling efforts constantly clash. It may be that they actually need quite different things from the model, it may be just a difference in background knowledge, or it may be a result of the management structure the project is embedded in. If the cause of the clash is something you can’t change, or don’t want to change, you may choose to allow the models to go SEPARATE WAYS. Where integration is needed, a translation layer can be developed and maintained jointly by the two teams as the single point of CONTINUOUS INTEGRATION. This is in contrast with integration with external systems, where the ANTICORRUPTION LAYER typically has to accommodate the other system as is and without much support from the other side.
你可能认识到两个团队的思想截然不同,以致他们的建模工作总是发生矛盾。可能他们需要从模型得到完全不同的东西,或者只是背景知识有某种不同,又或者是由于项目所采用的管理结构而引起的。如果这种矛盾的原因是你无法改变或不想改变的,那么可以让他们的模型采用 SEPARATE WAY 模式。在需要集成的地方,两个团队可以共同开发并维护一个转换层,把它作为唯一的 CONTINUOUS INTEGRATION 点。这与同外部系统的集成正好相反,在外部集成中,一般由 ANTICORRUPTION LAYER 来起调节作用,而且从另一端得不到太多的支持。
Generally speaking, there is a correspondence of one team per BOUNDED CONTEXT. One team can maintain multiple BOUNDED CONTEXTS, but it is hard (though not impossible) for multiple teams to work on one together.
一般来说,每个 BOUNDED CONTEXT 对应一个团队。一个团队也可以维护多个 BOUNDED CONTEXT,但多个团队在一个上下文中工作却是比较难的(虽然并非不可能)。
Different groups within the same business have often developed their own specialized terminologies, which may have diverged from one another. These local jargons may be very precise and tailored to their needs. Changing them (for example, by imposing a standardized, enterprise-wide terminology) requires extensive training and analysis to resolve the differences. Even then, the new terminology may not serve as well as the finely tuned version they already had.
同一业务的不同小组常常有各自的专用术语,而且可能各不相同。这些本地术语可能是非常精确的,并且是根据他们的需要定制的。要想改变它们(例如,施行标准化的企业级术语),需要大量的培训和分析,以便解决差异问题。即使如此,新术语仍然可能没有原来那个已经经过精心调整的术语好用。
You may decide to cater to these special needs in separate BOUNDED CONTEXTS, allowing the models to go SEPARATE WAYS, except for CONTINUOUS INTEGRATION of translation layers. Different dialects of the UBIQUITOUS LANGUAGE will evolve around these models and the specialized jargon they are based on. If the two dialects have a lot of overlap, a SHARED KERNEL may provide the needed specialization while minimizing the translation cost.
你可能决定通过不同的 BOUNDED CONTEXT 来满足这些特殊需要,除了转换层的 CONTINUOUS INTEGRATION 以外,让模型采用 SEPARATE WAY 模式。UBIQUITOUS LANGUAGE 的不同专用术语将围绕这些模型以及它们所基于的行话来发展。如果两种专用术语有很多重叠之处,那么 SHARED KERNEL 模式就可以满足特殊化要求,同时又能把转换成本减至最小。
Where integration is not needed, or is relatively limited, this allows continued use of customary terminology and avoids corruption of the models. It also has its costs and risks.
当不需要集成或者集成相对有限时,就可以继续使用已经习惯的术语,以免破坏模型。但这也有其自己的代价和风险。如下所示。
- The loss of shared language will reduce communication.
- There is extra overhead in integration.
- There will be some duplication of effort, as different models of the same business activities and entities evolve.
- 没有共同的语言,交流将会减少。
- 集成开销更高。
- 随着相同业务活动和实体的不同模型的发展,工作会有一定的重复。
But perhaps the biggest risk is that it can become an argument against change and a justification for any quirky, parochial model. How much do you need to tailor this individual part of the system to meet specialized needs? Most important, how valuable is the particular jargon of this user group? You have to weigh the value of more independent action of teams against the risks of translation, keeping an eye out for rationalizing terminology variations that have no value.
但是,最大的风险或许是,它会成为拒绝改变的理由,或为古怪、狭隘的模型辩护。为了满足特殊的需要,需要对系统的这一部分进行多大的定制?最重要的是,这个用户群的专门术语有多大的价值?你必须在团队独立操作的价值与转换的风险之间做出权衡,并且留心合理地处理一些没有价值的术语变化。
Sometimes a deep model emerges that can unify these distinct languages and satisfy both groups. The catch is that deep models emerge later in the life cycle, after a lot of development and knowledge crunching, if at all. You can’t plan on a deep model; you just have to accept the opportunity when it arises, change your strategy, and refactor.
有时会出现一个深层次的模型,它把这些不同语言统一起来,并能够满足双方的要求。只有经过大量开发工作和知识消化之后,深层次模型才会在生命周期的后期出现。深层次模型不是计划出来的,我们只能在它出现的时候抓住机遇,修改自己的策略并进行重构。
Keep in mind that, where integration requirements are extensive, the cost of translation goes way up. Some coordination of the teams, from the pinpoint modifications of one object that has a complicated translation ranging up to a SHARED KERNEL, can make translation easier while still not requiring full unification.
记住,在需要大量集成的地方,转换成本会大大增加。在团队之间进行一些协调工作(从精确地修改一个具有复杂转换的对象到采用 SHARED KERNEL 模式)可以使转换变得更加容易,同时又不需要完全的统一。
Coordinating the packaging and deployment of complex systems is one of those boring tasks that are almost always a lot harder than they look. The choice of BOUNDED CONTEXT strategy has an impact on the deployment. For example, when CUSTOMER/SUPPLIER TEAMS deploy new versions, they have to coordinate with each other to release versions that have been tested together. Both code and data migrations have to work in these combinations. In a distributed system, it may help to keep the translation layers between CONTEXTS together within a single process, so that you don’t have multiple versions coexisting.
在复杂系统中,对打包和部署进行协调是一项繁琐的任务,这类任务总是要比看上去难得多。BOUNDED CONTEXT 策略的选择将影响部署。例如,当 CUSTOMER/SUPPLIER TEAM 部署新版本时,他们必须相互协调来发布经过共同测试的版本。在这些版本中,必须要进行代码和数据迁移。在分布式系统中,一种好的做法是把 CONTEXT 之间的所有转换层放在同一个进程中,这样就不会出现多个版本共存的情况。
Even deployment of the components of a single BOUNDED CONTEXT can be challenging when data migration takes time or when distributed systems can’t be updated instantaneously, resulting in two versions of the code and data coexisting.
当数据迁移可能很花时间或者分布式系统无法同步更新时,即使是单一 BOUNDED CONTEXT 中的组件部署也是很困难的,这会导致代码和数据有两个版本共存。
Many technical considerations come into play depending on the deployment environment and technology. But the BOUNDED CONTEXT relationships can point you toward the hot spots. The translation interfaces have been marked out.
由于部署环境和技术存在不同,有很多技术因素需要考虑。但 BOUNDED CONTEXT 关系可以为我们指出重点问题。转换接口已经被标出。
The feasibility of a deployment plan should feed back into the drawing of the CONTEXT boundaries. When two CONTEXTS are bridged by a translation layer, one CONTEXT can be updated just so a new translation layer provides the same interface to the other CONTEXT. A SHARED KERNEL imposes a much greater burden of coordination, not just in development but also in deployment. SEPARATE WAYS can make life much simpler.
绘制 CONTEXT 边界时应该反映出部署计划的可行性。当两个 CONTEXT 通过一个转换层连接时,要想更新其中的一个 CONTEXT,新的转换层需要为另一个 CONTEXT 提供相同的接口。SHARED KERNEL 需要进行更多的协调工作,不仅在开发中如此,而且在部署中也同样应该如此。SEPARATE WAY 模式可以使工作简单很多。
To sum up these guidelines, there is a range of strategies for unifying or integrating models. In general terms, you will trade off the benefits of seamless integration of functionality against the additional effort of coordination and communication. You trade more independent action against smoother communication. More ambitious unification requires control over the design of the subsystems involved.
通过总结这些指导原则可知有很多统一或集成模型的策略。一般来说,我们需要在无缝功能集成的益处和额外的协调和沟通工作之间做出权衡。还要在更独立的操作与更顺畅的沟通之间做出权衡。更积极的统一需要对有关子系统的设计有更多控制。
The relative demands of CONTEXT relationship patterns
Most likely, you are not starting a project but are looking to improve a project that is already under way. In this case, the first step is to define BOUNDED CONTEXTS according to the way things are now. This is crucial. To be effective, the CONTEXT MAP must reflect the true practice of the teams, not the ideal organization you might decide on by following the guidelines just described.
很多情况下,我们不是从头开发一个项目,而是会改进一个正在开发的项目。在这种情况下,第一步是根据当前的状况来定义 BOUNDED CONTEXT。这很关键。为了有效地定义上下文,CONTEXT MAP 必须反映出团队的实际工作,而不是反映那个通过遵守以上描述的指导原则而得出的理想组织。
Once you have delineated your true current BOUNDED CONTEXTS and described the relationships they currently have, the next step is to tighten up the team’s practices around that current organization. Improve your CONTINUOUS INTEGRATION within the CONTEXTS. Refactor any stray translation code into your ANTICORRUPTION LAYERS. Name the existing BOUNDED CONTEXTS and make sure they are in the UBIQUITOUS LANGUAGE of the project.
描述了当前真实的 BOUNDED CONTEXT 以及它们的关系以后,下一步就是围绕当前组织结构来加强团队的工作。在 CONTEXT 中加强 CONTINUOUS INTEGRATION。把所有分散的转换代码重构到 ANTICORRUPTION LAYER 中。命名现有的 BOUNDED CONTEXT,并确保它们处于项目的 UBIQUITOUS LANGUAGE 中。
Now you are ready to consider changes to the boundaries and relationships themselves. These changes will naturally be driven by the same principles I’ve already described for a new project, but they will have to be bitten off in small pieces, chosen pragmatically to give the most value for the least effort and disruption.
现在可以开始考虑修改边界和它们的关系了。这些修改很自然地由相同的原则来驱动——之前已经描述了在新项目上使用这些原则,但我们应该把这些修改分成较小的部分,以便根据实际情况做出选择,从而在只花费最少的工作和对模型产生最小破坏的前提下创造最大的价值。
The next section discusses how to go about actually making changes to your CONTEXT boundaries once you have decided to.
下一节将讨论如何修改 CONTEXT 的边界。
Like any other aspect of modeling and design, decisions about BOUNDED CONTEXTS are not irrevocable. Inevitably, there will be many cases in which you have to change your initial decision about the boundaries and relationships between BOUNDED CONTEXTS. Generally speaking, breaking up CONTEXTS is pretty easy, but merging them or changing the relationships between them is challenging. I’ll describe a few representative changes that are difficult yet important. These transformations are usually much too big to be taken in a single refactoring or possibly even in a single project iteration. For that reason, I’ve outlined game plans for making these transformations as a series of manageable steps. These are, of course, guidelines that you will have to adapt to your particular circumstances and events.
像建模和设计的其他方面一样,有关 BOUNDED CONTEXT 的决策并非不可改变的。在很多情况下,我们必须改变最初有关边界以及 BOUNDED CONTEXT 之间关系的决策,这是不可避免的。一般而言,分割 CONTEXT 是很容易的,但合并它们或改变它们之间的关系却很难。下面将介绍几种有代表性的修改,它们很难,但也很重要。这些转换往往很大,无法在一次重构中完成,甚至无法在一次项目迭代中完成。因为这个原因,我将把这些转换划分为一系列简单的步骤。当然,这些只是一些指导原则,你必须根据你的特殊情况和事件对它们进行调整。
Translation overhead is too high. Duplication is too obvious. There are many motivations for merging BOUNDED CONTEXTS. This is hard to do. It’s not too late, but it takes some patience.
合并 BOUNDED CONTEXT 的动机很多:翻译开销过高、重复现象很明显。合并很难,但什么时候做都不晚,只是需要一些耐心。
Even if your eventual goal is to merge completely to a single CONTEXT with CONTINUOUS INTEGRATION, start by moving to a SHARED KERNEL.
即使你的最终目标是完全合并成一个采用 CONTINUOUS INTEGRATION 的 CONTEXT,也应该先过渡到 SHARED KERNEL。
- Evaluate the initial situation. Be sure that the two CONTEXTS are indeed internally unified before beginning to unify them with each other.
- Set up the process. You’ll need to decide how the code will be shared and what the module naming conventions will be. There must be at least weekly integration of the SHARED KERNEL code. And it must have a test suite. Set this up before developing any shared code. (The test suite will be empty, so it should be easy to pass!)
- Choose some small subdomain to start with—something duplicated in both CONTEXTS, but not part of the CORE DOMAIN. This first merger is going to establish the process, so it is best to use something simple and relatively generic or noncritical. Examine the integrations and translations that already exist. Choosing something that is being translated has the advantage of starting out with a proven translation, plus you’ll be thinning your translation layer.
- 1)评估初始状况。在开始统一两个 CONTEXT 之前,一定要确信它们确实需要统一。
- 2)建立合并过程。你需要决定代码的共享方式以及模块应该采用哪种命名约定。SHARED KERNEL 的代码至少每周要集成一次,而且它必须有一个测试套件。在开发任何共享代码之前,先把它设置好。(测试套件将是空的,因此很容易通过!)
- 3)选择某个小的子领域作为开始,它应该是两个 CONTEXT 中重复出现的子领域,但不是 CORE DOMAIN 的一部分。最初的合并主要是为了建立合并过程,因此最好选择一些简单且相对通用或不重要的部分。检查已存在的集成和转换。选择那些经过转换的部分,其优势在于一开始就有用于验证的转换机制,此外还可以简化转换层。
At this point, you have two models that address the same subdomain. There are basically three approaches to merging. You can choose one model and refactor the other CONTEXT to be compatible. This decision can be made wholesale, setting the intention of systematically replacing one CONTEXT’S model and retaining the coherence of a model that was developed as a unit. Or you can choose one piece at a time, presumably ending up with the best of both (but taking care not to end up with a jumble).
此时,我们有两个应对相同子领域的模型。基本上有 3 种合并方法。我们可以选择一个模型,并重构另一个 CONTEXT,使之与第一个模型兼容。我们可以从整体上做出这个决策,把目标设置为系统性地替换一个 CONTEXT 的模型,并保持被开发模型的内聚性。也可以一次选择一部分,到最后两个模型可能会“两全其美”(但注意最后不要弄得一团糟)。
The third option is to find a new model, presumably deeper than either of the originals, capable of assuming the responsibilities of both.
第三种选择是找到一个新模型,这个模型可能比最初的两个都深刻,能够承担二者的职责。
- Form a group of two to four developers, drawn from both teams, to work out a shared model for the subdomain. Regardless of how the model is derived, it must be ironed out in detail. This includes the hard work of identifying synonyms and mapping any terms that are not already being translated. This joint team outlines a basic set of tests for the model.
- Developers from either team take on the task of implementing the model (or adapting existing code to be shared), working out details and making it function. If these developers run into problems with the model, they reconvene the team from step 3 and participate in any necessary revisions of the concepts.
- Developers of each team take on the task of integrating with the new SHARED KERNEL.
- Remove translations that are no longer needed.
- 从两个团队中共选出 2 ~ 4 位开发人员组成一个小组,由他们来为子领域开发一个共享的模型。不管模型是如何得出的,它的内容必须详细。这包括一些困难的工作:识别同义词和映射那些尚未被翻译的术语。这个联合团队需要为模型开发一个基本的测试集。
- 来自两个团队的开发人员一起负责实现模型(或修改要共享的现有代码)、确定各种细节并使模型开始工作。如果这些开发人员在模型中遇到了问题,就从第(3)步开始重新组织团队,并进行必要的概念修订工作。
- 每个团队的开发人员都承担与新的 SHARED KERNEL 集成的任务。
- 清除那些不再需要的翻译。
At this point, you will have a very small SHARED KERNEL, with a process in place to maintain it. In subsequent project iterations, repeat steps 3 through 7 to share more. As the processes firm up and the teams gain confidence, you can take on more complicated subdomains, multiple ones at the same time, or subdomains that are in the CORE DOMAIN.
这时你会得到一个非常小的 SHARED KERNEL,并且有一个过程来维护它。在后续的项目迭代中,重复第(3)~(7)步来共享更多内容。随着过程的不断巩固和团队信心的树立,就可以选择更复杂的子领域了,同时处理多个子领域,或者处理 CORE DOMAIN 中的子领域。
A note: As you take on more domain-specific parts of the models, you may encounter cases where the two models have conformed to the specialized jargon of different user communities. It is wise to defer merging these into the SHARED KERNEL unless a breakthrough to a deep model has occurred, providing you with a language capable of superseding both specialized ones. An advantage of a SHARED KERNEL is that you can have some of the advantages of CONTINUOUS INTEGRATION while retaining some of the advantages of SEPARATE WAYS.
注意:当从模型中选取更多与领域有关的部分时,可能会遇到这样的情况,即两个模型各自采用了不同用户群的专用术语。聪明的做法是先不要把它们合并到 SHARED KERNEL 中,除非工作中出现了突破,得到了一个深层模型,这个模型为你提供了一种能够替代那两种专用术语的语言。SHARED KERNEL 的优点是它具有 CONTINUOUS INTEGRATION 的部分优势,同时又保留了 SEPARATE WAY 模式的一些优点。
Those are some guidelines for merging into a SHARED KERNEL. Before going ahead, consider one alternative that satisfies some of the needs addressed by this transformation. If one of the two models is definitely preferred, consider shifting toward it without integrating. Instead of sharing common subdomains, just systematically transfer full responsibility for those subdomains from one BOUNDED CONTEXT to the other by refactoring the applications to call on the model of the more favored CONTEXT, and making any enhancements that model needs. Without any ongoing integration overhead, you have eliminated redundancy. Potentially (but not necessarily), the more favored BOUNDED CONTEXT could eventually take over completely, and you’ll have created the same effect as a merger. In the transition (which can be quite long or indefinite), this will have the usual advantages and disadvantages of going SEPARATE WAYS, and you have to weigh them against the pros and cons of a SHARED KERNEL.
以上这些是把模型的一些部分合并到 SHARED KERNEL 中的指导原则。在继续讨论之前,我们来看一下另外一种方法,它能够部分解决上述转换所面对的问题。如果两个模型中有一个毫无疑问是符合首选条件的,那么就考虑向它过渡,而不用进行集成。不共享公共的子领域,而只是系统性地通过重构应用程序把这些子领域的所有职责从一个 BOUNDED CONTEXT 转移到另一个 BOUNDED CONTEXT,从而使用那个更受青睐的 CONTEXT 的模型,并对该模型进行需要的增强。在没有集成开销的情况下,消除了冗余。很有可能(但也不是必然的)那个更受青睐的 BOUNDED CONTEXT 最终会完全取代另一个 BOUNDED CONTEXT,这样就实现了与合并完全一样的效果。在转换过程中(这个过程可能相当长或无法确定),这种方法具有 SEPARATE WAY 模式常见的优点和缺点,而且我们必须拿这些优缺点与 SHARED KERNEL 的利弊进行权衡。
14.14.2 Merging CONTEXTS: SHARED KERNEL → CONTINUOUS INTEGRATION 合并 CONTEXT:SHARED KERNEL→CONTINUOUS INTEGRATION
If your SHARED KERNEL is expanding, you may be lured by the advantages of full unification of the two BOUNDED CONTEXTS. This is not just a matter of resolving the model differences. You are going to be changing team structures and ultimately the language people speak.
如果你的 SHARED KERNEL 正在扩大,你可能会被完全统一两个 BOUNDED CONTEXT 的优点所吸引。但这并不只是一个解决模型差异的问题。你将改变团队的结构,而且最终会改变人们所使用的语言。
Start by preparing the people and the teams.
这个过程从人员和团队的准备开始。
- Be sure that all the processes needed for CONTINUOUS INTEGRATION (shared code ownership, frequent integration, and so on) are in place on each team, separately. Harmonize integration procedures on the two teams so that everyone is doing things in the same way.
- Start circulating team members between teams. This will create a pool of people who understand both models, and will begin to connect the people of the two teams.
- Clarify the distillation of each model individually. (See Chapter 15.)
- At this point, confidence should be high enough to begin merging the core domain into the SHARED KERNEL. This can take several iterations, and sometimes temporary translation layers are needed between the newly shared parts and the not-yet-shared parts. Once into merging the CORE DOMAIN, it is best to go pretty fast. It is a high-overhead phase, fraught with errors, and should be shortened as much as possible, taking priority over most new development. But don’t take on more than you can handle.
- 确保每个团队都已经建立了 CONTINUOUS INTEGRATION 所需的所有过程(共享代码所有权、频繁集成等)。两个团队协商集成步骤,以便所有人都以同一步调工作。
- 团队成员在团队之间流动。这样可以形成一大批同时理解两个模型的人员,并且可以把两个团队的人员联系起来。
- 澄清每个模型的精髓(参见第 15 章)。
- 现在,团队应该有了足够的信心把核心领域合并到 SHARED KERNEL 中。这可能需要多次迭代,有时需要在新共享的部分与尚未共享的部分之间使用临时的转换层。一旦进入到合并 CORE DOMAIN 的过程中,最好能快速完成。这是一个开销高且易出错的阶段,因此应该尽可能缩短时间,要优先于新的开发任务。但注意量力而行,不要超过你的处理能力。
To merge the CORE models, you have a few choices. You can stick with one model and modify the other to be compatible with it, or you can create a new model of the subdomain and adapt both contexts to use it. Watch out if the two models have been tailored to address distinct user needs. You may need the specialized power of both original models. This calls for developing a deeper model that can supersede both original models. Developing a deeper unifying model is very difficult, but if you are committed to the full merger of the two CONTEXTS, you no longer have the option of multiple dialects. There will be a reward in terms of the clarity of integration of the resulting model and code. Be careful that it doesn’t come at the cost of your ability to address the specialized needs of your users.
有几种方式用于合并 CORE 模型。可以保持一个模型,然后修改另一个,使之与第一个兼容,或者可以为子领域创建一个新模型,并通过修改两个上下文来使用这个模型。如果两个模型已经被修改以满足不同用户的需要,你就要注意了。你需要保留两个初始模型中的这些专业能力。这就要求开发一个能够替代两个原始模型的更深层的模型。开发这样一个更深入的统一模型是很难的,但如果你已经决定完全合并两个 CONTEXT,就没有选择多种专门术语的空间了。这样做的好处是最终模型和代码的集成变得更清晰了。注意不要影响到你满足用户特殊需要的能力。
- As the SHARED KERNEL grows, increase the integration frequency to daily and finally to CONTINUOUS INTEGRATION.
- As the SHARED KERNEL approaches the point of encompassing all of the two former BOUNDED CONTEXTS, you will find yourself with either one large team or two smaller teams that have a shared code base that they INTEGRATE CONTINUOUSLY, and that trade members back and forth frequently.
- 随着 SHARED KERNEL 的增长,把集成频率提高到每天一次,最后实现 CONTINUOUS INTEGRATION。
- 当 SHARED KERNEL 逐渐把先前两个 BOUNDED CONTEXT 的所有内容都包括进来的时候,你会发现要么形成了一个大的团队,要么形成了两个较小的团队,这两个较小的团队共享一个 CONTINUOUS INTEGRATION 的代码库,而且团队成员可以经常在两个团队之间来回流动。
All good things must come to an end, even legacy computer software. But it doesn’t happen on its own. These old systems can be so woven into the business and other systems that extricating them can take many years. Fortunately, it doesn’t have to be done all at once.
好花美丽不常开,好景怡人不常在,就算遗留计算机软件也一样会走向终结。但这可不会自动自发地出现。这些老的系统可能与业务及其他系统紧密交织在一起,因此淘汰它们可能需要很多年。好在我们并不需要一次就把所有东西都淘汰掉。
The possibilities are too various for me to do more than scratch the surface here. But I’ll discuss a common case: An old system that is used daily in the business has been supplemented recently by a handful of more modern systems that communicate with the legacy system through an ANTICORRUPTION LAYER.
这一话题的涉及面太广了,这里的讨论也只能浅尝辄止。我们将讨论一种常见的情况:用一系列更现代的系统来补充业务中每天都在使用的老系统,新系统通过一个 ANTICORRUPTION LAYER 与老系统进行通信。
One of the first steps should be to decide on a testing strategy. Automated unit tests should be written for new functionality in the new systems, but phasing out legacy introduces special testing needs. Some organizations run new and old in parallel for some period of time.
首先要执行的步骤是确定测试策略。应该为新系统中的新功能编写自动的单元测试,但逐步淘汰遗留系统还有一些特殊的测试需求。一些组织在某段时间内会同时运行新旧两个系统。
In any given iteration:
在任何一次迭代中:
- Identify specific functionality of the legacy that could be added to one of the favored systems within a single iteration.
- Identify additions that will be required in the ANTICORRUPTION LAYER.
- Implement.
- Deploy.
- 确定遗留系统的哪个功能可以在一个迭代中被添加到某个新系统中;
- 确定需要在 ANTICORRUPTION LAYER 中添加的功能;
- 实现;
- 部署;
Sometimes it will be necessary to spend more than one iteration writing equivalent functionality to a unit that can be phased out of the legacy, but still plan the new functions in small, iteration-sized units, only waiting multiple iterations for deployment.
有时,需要进行多次迭代才能编写一个与遗留系统的某个功能等价的功能单元,这时在计划新的替代功能时仍以小规模的迭代为单元,最后一次性部署多次迭代。
Deployment is another point at which too much variation exists to cover all the bases. It would be nice for development if these small, incremental changes could be rolled out to production, but usually it is necessary to organize bigger releases. The users must be trained to use the new software. A parallel period sometimes must be completed successfully. Many logistical problems will have to be worked out.
部署涉及的变数太多,以至于我不可能涵盖所有的基本情况。就开发而言,如果这些小规模、增量的改动能够推到生产环境,那真是再好不过了。但通常情况,还是需要将他们组织成更大的发布。在新软件的使用方面,用户培训是必不可少的。有时在成功部署的同时还必须进行开发工作。还有很多后勤问题需要解决。
Once it is finally running in the field:
一旦最终进入运行阶段后,应该遵循如下步骤。
- Identify any unnecessary parts of the ANTICORRUPTION LAYER and remove them.
- Consider excising the now-unused modules of the legacy system, though this may not turn out to be practical. Ironically, the better designed the legacy system is, the easier it will be to phase it out. But badly designed software is hard to dismantle a little at a time. It may be possible to just ignore the unused parts until a later time when the remainder has been phased out and the whole thing can be switched off.
- 找出 ANTICORRUPTION LAYER 中那些不必要的部分,并去掉它们;
- 考虑删除遗留系统中目前未被使用的模块,虽然这种做法未必实际。有趣的是,遗留系统设计得越好,它就越容易被淘汰。而设计得不好的软件却很难一点儿一点儿地去除。这时,我们可以暂时忽略那些未使用的部分,直到将来剩余部分已经被淘汰,这时整个遗留系统就可以停止使用了。
Repeat this over and over. The legacy system should become less involved in the business, and eventually it will be possible to see the light at the end of the tunnel and finally switch off the old system. Meanwhile, the ANTICORRUPTION LAYER will alternately shrink and swell as various combinations increase or decrease the interdependence between the systems. All else being equal, of course, you should migrate first those functions that lead to smaller ANTICORRUPTION LAYERS. But other factors are likely to dominate, and you may have to live with some hairy translations during some transitions.
不断重复这几个步骤。遗留系统应该越来越少地参与业务,最终,替换工作会看到希望的曙光并完全停止遗留系统。同时,随着各种组合增加或减小系统之间的依赖,ANTICORRUPTION LAYER 将相应地收缩或扩张。当然,在其他条件都相同的情况下,应该首先迁移那些只产生较小 ANTICORRUPTION LAYER 的功能。但其他因素也可能会起主导作用,有时候在过渡期间可能必须经历一些麻烦的转换。
You have been integrating with other systems with a series of ad hoc protocols, but the maintenance burden is mounting as more systems want access, or perhaps the interaction is becoming very difficult to understand. You need to formalize the relationship between the systems with a PUBLISHED LANGUAGE.
我们已经通过一系列特定的协议与其他系统进行了集成,但随着需要访问的系统逐渐增多,维护负担也不断增加,或者交互变得很难理解。我们需要通过 PUBLISHED LANGUAGE 来规范系统之间的关系。
- If an industry-standard language is available, evaluate it and use it if at all possible.
- If no standard or prepublished language is available, then begin by sharpening up the CORE DOMAIN of the system that will serve as the host. (See Chapter 15.)
- Use the CORE DOMAIN as the basis of an interchange language, using a standard interchange paradigm such as XML, if at all possible.
- Publish the new language to all involved in the collaboration (at least).
- If a new system architecture is involved, publish that too.
- Build translation layers for each collaborating system.
- Switch over.
- 如果有一种行业标准语言可用,则尽可能评估并使用它。
- 如果没有标准语言或预先公开发布的语言,则完善作为 HOST 的系统的 CORE DOMAIN(参见第 15 章)。
- 使用 CORE DOMAIN 作为交换语言的基础,尽可能使用像 XML 这样的标准交互范式。
- (至少)向所有参与协作的各方发布新语言。
- 如果涉及新的系统架构,那么也要发布它。
- 为每个协作系统构建转换层。
- 切换。
At this point, additional collaborators should be able to enter with minimal disruption.
现在,当加入更多协作系统时,对整个系统的破坏已经减至最小了。
Remember, the PUBLISHED LANGUAGE must be stable, yet you’ll still need the freedom to change the host’s model as you continue your relentless refactoring. Therefore, do not equate the interchange language and the model of the host. Keeping them close together will reduce translation overhead, and you may choose to make your host a CONFORMIST. But reserve the right to beef up the translation layer and diverge if the cost-benefit trade-off favors that.
记住,PUBLISHED LANGUAGE 必须是稳定的,但是当继续进行重构时,仍然需要能够自由地更改 HOST 的模型。因此,不要把交换语言和 HOST 的模型等同起来。保持它们的密切关系可以减小转换开销,而你的 HOST 可以采用 CONFORMIST 模式。但是应该保留对转换层进行补充的权力,在成本—效益的折中需要时,可以把这个权利分离出去。
Project leaders should define BOUNDED CONTEXTS based on functional integration requirements and relationships of development teams. Once BOUNDED CONTEXTS and a CONTEXT MAP are explicitly defined and respected, then logical consistency should be protected. Related communication problems will at least be exposed so they can be dealt with.
项目领导者应该根据功能集成需求和开发团队之间的关系来定义 BOUNDED CONTEXT。一旦 BOUNDED CONTEXT 和 CONTEXT MAP 被明确地定义下来并获得认可,就应该保持它们的逻辑一致性。最起码要把相关的通信问题提出来,以便解决它们。
However, sometimes model contexts, whether consciously bounded or naturally occurring, are misapplied to solve problems other than logical inconsistency within a system. The team may find that the model of a large CONTEXT seems too complex to comprehend as a whole, or to analyze completely. By choice or by chance, this often leads to breaking down the CONTEXTS into more manageable pieces. This fragmentation leads to lost opportunities. Now, it is worth scrutinizing a decision to establish a large model in a broad CONTEXT, and if it is not organizationally or politically possible to keep together, if it is in reality fragmenting, then redraw the map and define boundaries you can keep. But if a large BOUNDED CONTEXT addresses compelling integration needs, and if it seems feasible apart from the complexity of the model itself, then breaking up the CONTEXT may not be the best answer.
但是,有时模型上下文(无论是我们有意识地划定边界的还是自然出现的上下文)被错误地用来解决系统中的一些其他问题,而不是逻辑不一致问题。团队可能会发现一个很大的 CONTEXT 的模型由于过于复杂而无法作为一个整体来理解或透彻地分析。出于有意或无意的考虑,团队往往会把 CONTEXT 分割为更易管理的部分。这种分割会导致失去很多机会。现在,值得花费一些功夫仔细考查在一个大的 CONTEXT 中建立一个大模型的决策了。如果从组织结构或行政角度来看保持一个大模型并不现实,如果实际上模型就是分裂的,那么就重新绘制上下文图,并定义能够保持的边界。但是,如果保持一个大的 BOUNDED CONTEXT 能够解决迫切的集成需要,而且除了模型本身的复杂性以外,这看上去是行得通的,那么分割 CONTEXT 可能就不是最佳的选择了。
There are other means of making large models tractable that should be considered before making this sacrifice. The next two chapters focus on managing complexity within a big model by applying two more broad principles: distillation and large-scale structure.
在做出这种牺牲之前,还应该考虑其他一些能够使大模型变得易于管理的方法。下两章将着重讨论通过应用两种更广泛的原则(精炼和大型结构)来管理大模型的复杂性。