Eleven. Applying Analysis Patterns
Deep models and supple designs don’t come easily. Progress comes from lots of learning about the domain, lots of talking, and lots of trial and error. Sometimes, though, we can get a leg up.
深层模型和柔性设计并非唾手可得。要想取得进展,必须学习大量领域知识并进行充分的讨论,还需要经历大量的尝试和失败。但有时我们也能从中获得一些优势。
When an experienced developer looking at a domain problem sees a familiar sort of responsibility or a familiar web of relationships, he or she can draw on the memory of how the problem was solved before. What models were tried and which worked? What difficulties arose in implementation and how were they resolved? The trial and error of that earlier experience is suddenly relevant to the new situation. Some of these patterns have been documented and shared, allowing the rest of us to draw on the accumulated experience.
一位经验丰富的开发人员在研究领域问题时,如果发现了他所熟悉的某种职责或某个关系网,他会想起以前这个问题是如何解决的。以前尝试过哪些模型?哪些是有效的?在实现中有哪些难题?它们是如何解决的?先前经历过的尝试和失败会突然间与新的情况联系起来。这些模式当中有一些已经记载到文献中供大家分享,这样我们就可以借鉴这些积累的经验。
In contrast to the fundamental building block patterns presented in Part II, and the supple design principles of Chapter 10, these patterns are higher level and more specialized, involving the use of a few objects to represent some concept. They let us cut through expensive trial and error to start with a model that is already expressive and implementable and addresses subtleties that might be costly to learn. From that starting point, we refactor and experiment. These are not out-of-the-box solutions.
与第二部分提出的基本构造块模式和第 10 章介绍的柔性设计原则相比,这些模式属于更高级和专用的模式,其中还涉及使用少量对象来表示某种概念。利用这些模式,可以避免一些代价高昂的尝试和失败过程,而直接从一个已经具有良好表达力和易实现的模型开始工作,并解决了一些可能难于学习的微妙的问题。我们可以从这样一个起点来重构和试验。然而,它们并不是现成的解决方案。
In Analysis Patterns: Reusable Object Models, Martin Fowler defined his patterns this way:
在《分析模式》一书中,Martin Fowler 这样定义分析模式[Fowler 1997, p. 8]:
Analysis patterns are groups of concepts that represent a common construction in business modeling. It may be relevant to only one domain or it may span many domains. [Fowler 1997, p. 8]
分析模式是一种概念集合,用来表示业务建模中的常见结构。它可能只与一个领域有关,也可能跨越多个领域。
The analysis patterns Fowler presents arose from experience in the field, and so they are practical, in the right situation. Such patterns provide someone facing a challenging domain with very valuable starting points for their iterative development process. The name emphasizes their conceptual nature. Analysis patterns are not technological solutions; they are guides to help you work out a model in a particular domain.
Fowler 所提出的分析模式来自于实践经验,因此只要用在合适的情形下,它们会非常实用。对于那些面对着具有挑战性领域的人们,这些模式为他们的迭代开发过程提供了一个非常有价值的起点。“分析模式”这个名字本身就强调了其概念本质。分析模式并不是技术解决方案,他们只是些参考,用来指导人们设计特定领域中的模型。
What the name unfortunately does not convey is that there is significant discussion of implementation in these patterns, including some code. Fowler understands the pitfalls of analysis without thought for practical design. Here is an interesting example where he is looking even beyond deployment, to the implications of specific model choices on the long-term maintenance of the system in the field:
但从这个名字中我们看不出分析模式也讨论了大量实现问题,包括一些代码。Fowler 知道,在不考虑实际设计的情况下进行单纯的分析是有缺陷的。下面举一个很有趣的例子,在这个例子中,Fowler 用更长远的眼光审视了模型选择的意义——考虑在部署之后,模型选择对系统长期维护的影响[Fowler 1997, p. 151]。
When we build a new [accounting] practice, we create a network of new instances of the posting rule. We can do this without any recompilation or rebuilding of the system, while it is still up and running. There will be unavoidable occasions when we need a new subtype of posting rule, but these will be rare. [p. 151]
当构建一个新的[会计]实务时,我们会创建一个新的过账规则(posting rule)的实例网。我们可以在完全不需要重新编译或构建系统的情况下实现它,因而不影响系统的运行。有时我们将不可避免地需要过账规则的某个新的子类型,但这种情况并不多见。
On a mature project, model choices are often informed by experience with the application. Multiple implementations of various components will have been tried. Some of these will have been carried into production and even will have faced the maintenance phase. Many problems can be avoided when such experience is available. Analysis patterns at their best can carry that kind of experience from other projects, combining model insights with extensive discussions of design directions and implementation consequences. To discuss model ideas out of that context makes them harder to apply and risks opening the deadly divide between analysis and design, which is antithetical to MODEL-DRIVEN DESIGN.
在一个成熟的项目上,模型选择往往是根据实用经验做出的。人们已经尝试了各种组件的多种实现方法。其中的一些实现已经被采用,有些甚至已经到了维护阶段。这些经验可以帮助人们避免很多问题。分析模式的最大作用是借鉴其他项目的经验,把那些项目中有关设计方向和实现结果的广泛讨论与当前模型的理解结合起来。脱离具体的上下文来讨论模型思想不但难以落地,而且还会造成分析与设计严重脱节的风险,而这一点正是 MODEL-DRIVEN DESIGN 坚决反对的。
The principle and application of analysis patterns can be explained better by example than through abstract description. In this chapter, I will give two examples of developers making use of a small, representative sample of models from the chapter “Inventory and Accounting” in Fowler 1997. The analysis patterns will be summarized just enough to support the examples. This is obviously not an attempt to catalog patterns of this kind or even to fully explain the sample patterns. The point is to illustrate their integration into the domain-driven design process.
用实际的例子比用单纯的抽象描述能够更好地解释分析模式的原则和应用。本章将给出两个示例,在这两个例子中,开发人员借鉴了[Fowler 1997]一书中提供的一个具有代表性的小范例(来自“Inventory and Accounting”一章)。本章只是为了讲解这两个例子而概述分析模式。显然,本章的目的不是对这种模式进行归纳分类,甚至对示例所使用的模式也没有做全面的解释。本章的重点是说明如何将它们集成到领域驱动的设计过程中。
Example: Earning Interest with Accounts
示例账户的利息计算
Chapter 10 showed various possible ways that a developer might search for a deeper model for a particular specialty accounting application. Here is yet another scenario. This time, the developers will mine Fowler’s Analysis Patterns book for useful ideas.
第 10 章展示了开发人员为某种专用会计应用程序去寻找更深层模型的各种可能途径。本示例是另外一个场景,这里开发人员将深入挖掘 Fowler 的《分析模式》一书,从中寻找有用的思想。
To review, an application for tracking loans and other interest-bearing assets calculates the interest and fees generated and tracks payments from the borrower. A nightly batch process takes those figures and passes them to the legacy accounting system, indicating the specific ledger each amount should be posted to. The design works, but it is awkward to use, tricky to change, and does not communicate well.
来复习一下。用于跟踪贷款和其他有息资产的应用程序将计算所产生的利息和手续费,并跟踪借方的付款情况。夜间会有一个批处理操作提取这些数字,并传递给原来的会计系统,并标明每个账目应该过账到哪个分类账中。这种设计虽然能工作,但使用起来却很麻烦,修改起来也很复杂,而且不易于交流沟通。
The initial class diagram
The developer decides to read Chapter 6 in Analysis Patterns, “Inventory and Accounting.” Here is a summary of the part she found most relevant.
开发人员决定读一下《分析模式》的第 6 章“Inventory and Accounting”。下面摘录了一些与之最为相关的内容。
Accounting Models in Analysis Patterns
《分析模式》中的账户模型
Business applications of all sorts track accounts, which hold things of value, typically money. In a lot of applications, it isn’t enough to keep track of the amount in an account. It is essential to account for and control each change to that amount. That is the motivation for the most basic of the accounting models.
所有种类的业务应用程序都需要对账户进行跟踪,因为账户中保存了与数值有关的信息(通常是钱)。在很多应用程序中,仅跟踪账户总额是不够的,记录和控制账户总额的每次修改也很重要。这也是会计模型最基本的动机。
A basic accounting model
Value can be added by inserting an Entry. Value can be removed by inserting a negative Entry. Entries are never removed, so the whole history is retained. The balance is the combined effect of all Entries. This balance could be computed on demand or cached, an implementation decision that is encapsulated by the Account interface.
通过插入一个 Entry(项)可以向账户中增加数值,而插入一个负的 Entry 则可以从账户中减少数值。Entry 永远不会被删除,因此整个历史就被保留下来。余额就是把所有 Entry 汇总得到的结果。这个余额可以实时计算,也可以被缓存,这是由 Account 接口封装的一个实现决策。
A basic principle of accounting is conservation. Money doesn’t appear out of nowhere, nor does it disappear without a trace. It is only moved from one Account to another.
会计的一条基本原则就是账目的平恒。钱即不会无中生有,也不会凭空消失。它只能从一个账户转移到另一个账户。
A transaction model
This is the well-established concept of double-entry book-keeping: Every credit has a matching debit. Of course, like other conservation principles, it applies only to a closed system, one that includes all sources and sinks. Many simple applications do not require this rigor.
这就是众所周知的“复式记账”(double-entry book-keeping)概念。每个贷方都有与之相应的借方。当然,像其他守恒定律一样,它只适用于封闭的系统,这个系统包含了入账和出账的所有明细。但很多简单的应用程序并不需要这么严格。
In his book, Fowler includes more elaborate forms of these models and considerable discussion of the trade-offs.
Fowler 在他的书中介绍了这些模型的较全面的形式,并对各种折中选择做了大量讨论。
This reading gives the developer (Developer 1) several new ideas. She shows the chapter to a colleague (Developer 2) who has been working on some of the interest calculation logic with her and who wrote the nightly batch program. Together, they rough out a change to their model, incorporating some of the model elements they’ve read about.
开发人员(开发人员 1)通过阅读这些内容获得了一些新的思路。她把这章内容介绍给她的同事(开发人员 2)看,这位同事正在与她一起编写利息计算逻辑,而且他还编写了夜间批处理程序。他们一起对模型作了粗略的修改——在模型中加进了一些在阅读该章时看到的模型元素。
The new model proposal
Then they pull in their domain expert (Expert) for a discussion of their new model ideas.
然后他们找来领域专家(以下称专家)一起讨论新模型的思路。
- Developer 1: With this new model, we make an Entry into the Interest Account for the interest earned, rather than just adjusting the interestDueAmount. Then, another Entry for the payment balances it out.
- Expert: So now we’d be able to see a history of all the interest accruals as well as the payment history? That’s something we’ve been wanting.
- Developer 2: I’m not sure we’ve used “Transaction” quite right. The definition talks about moving money from one Account to another, not two entries that balance each other in the same Account.
- Developer 1: That’s a good point. I was also worried that the book seems to make quite a point about the transaction being created all at once. The interest payments can be several days late.
- Expert: Those payments aren’t necessarily late. There is a lot of flexibility in when they pay.
- Developer 1: So this may be a blind alley. I was thinking we might have identified some implicit concepts. Having the Interest Calculator create Entry objects does seem to communicate better. And Transaction seemed to neatly tie together the calculated interest with the payment.
- Expert: Why do we need to tie together the accrual to the payment? They are separate postings in the accounting system. The balance on the Account is the main thing. Along with the individual Entries, we really have what we need.
- Developer 2: You mean you don’t track whether they’ve made the interest payment?
- Expert: Well, of course we do. But it isn’t as simple as this one-accrual/one-payment scheme of yours.
- Developer 2: It could actually simplify a lot of things to stop worrying about that connection.
- Developer 1: OK, how about this? [Takes copy of old class diagram and starts sketching modifications] By the way, you used the word accruals a few times. Could you clarify what it means?
- Expert: Sure. An accrual is just when you account for an expense or income at the time it is incurred, never mind when money actually changes hands. So, we accrue interest every day, but at the end of the month (for example) we receive a payment against it.
- Developer 1: Yes, we really needed a word like that. OK, how does this look?
- 开发人员 1:利用这个新模型,我们可以在 Interest Account 中为每笔利息收入增加一个 Entry,而不是只调整 interestDueAmount。然后,另一个付款的 Entry 会使其平账。
- 专家:这样是不是就可以看到所有的应计(accrual)利息和付款历史了?这正是需要的功能。
- 开发人员 2:我不确定这里使用“Transaction”(交易)是否完全正确。定义讲的是把钱从一个 Account 转移到另一个 Account,而不是两个 Entry 在同一个 Account 中互相平衡。
- 开发人员 1:这个问题很好,我也有些担心,因为书上似乎强调交易是瞬间建立的,而利息的付款可以过几天再进行。
- 专家:那些付款不一定要推迟几天,在支付时间上可以灵活处理。
- 开发人员 1:那么这种担心就没有必要了。我想我们或许已经发现了一些隐含的概念。让 Interest Calculator 来创建 Entry 对象似乎确实更易理解。而且 Transaction 似乎把计算出的利息和付款巧妙地联系在一起了。
- 专家:为什么要把应计项目和付款联系在一起呢?它们在会计系统中是分开过账的。Account 的平账才是主要的。沿着一个一个的 Entry,我们就可以查出所有的账目。
- 开发人员 2:你的意思是说不用跟踪利息是否已经支付这一点吗?专家:当然需要跟踪。但它并不是你们所说的“一次应计项目/一笔付款”这种简单的模式。
- 开发人员 2:实际上,如果不用考虑那种关联,很多事情都可以简化。
- 开发人员 1:好的,这样如何?[拿来旧类图的复印件开始把修改的地方画出来]。顺便问一下,你好几次提到“应计项目”这个词,能确切地讲一下它的意思吗?
- 专家:当然可以。应计项目(accrual)是指在一笔支出或收入发生的时候把它记录到账目中,而永远不管现金实际是何时过账的。因此,利息每天都会计算,但只有在(举例来说)月末才会支付。
- 开发人员 1:是的,我们确实需要这个词。好,现在这个图怎么样?
Original class diagram, accruals separated from payment
- Developer 1: Now we can get rid of all the complications that were in the calculator from relating payments, and we’ve introduced the term accruals, which reveals the intent better.
- Expert: So we’re not going to have the Account object? I was looking forward to being able to see everything together there, with the accruals and the payments and a balance.
- Developer 1: Really?! Well in that case, maybe this would work. [Takes other diagram and sketches]
- 开发人员 1:现在,我们可以删掉与付款有关的所有复杂计算了,而且我们引入了“accrual”这个术语,它更好地表明了我们的意图。
- 专家:那么我们就不会有 Account 对象了吧?我本来还希望能够把应计项目、付款和余额等项都放到这个对象中呢。
- 开发人员 1:是吗?!如果是那样的话,或许这么画就可以[拿起另一张图开始画起来]。
The account-based diagram, without Transaction
- Expert: That actually looks pretty good!
- Developer 2: The batch script will be easy to change to use these new objects.
- Developer 1: It will take a few days to get the new Interest Calculator working. There are quite a few tests to change. But the test will read clearer afterward.
- 专家:这看起来确实好极了!
- 开发人员 2:批处理脚本也只需要简单的修改就能使用这些新对象。
- 开发人员 1:新的 Interest Calculator 过几天才能使用。有好些个测试需要修改。但修改之后测试会更清楚。
The two developers went off and started refactoring based on the new model. As they got their hands on the code, tightening up the design, they had insights that refined the model.
两位开发人员开始基于新模型进行重构。他们在着手编写代码并加强设计时,又有了一些对模型进行精化的新想法。
Entries were subclassed into Payment and Accrual because closer inspection revealed slightly different responsibilities in the application for these, and because they were both important domain concepts. On the other hand, there was no conceptual or behavioral distinction between Entries based on whether they resulted from fees or interest. They simply appeared in the appropriate Account.
通过更仔细的研究,他们决定为 Entry 创建两个子类——Payment 和 Accrual,因为他们发现这两个子类在应用程序中的职责稍有不同,而且都是非常重要的领域概念。但另一方面,无论 Entry 是因为手续费而产生的,还是因为利息产生的,其在概念和行为上都没有任何区别。它们只是出现在适当的 Account 中。
Yet, unfortunately, the developers found they had to give up this last abstraction for the implementation. Data was stored in relational tables, and the project standard was to make those tables interpretable without running the program. This meant keeping fee entries and interest entries in separate tables. The only way for developers to do this, using their particular object-relational mapping framework, was to make concrete subclasses (Fee Payments, Interest Payments, and so on). With different infrastructure, they might have avoided this clumsy expansion.
但遗憾的是,开发人员们发现,出于实现方面的考虑,他们不得不放弃最后这一次抽象。数据存储在关系表中,而且项目标准要求在不运行程序的情况下也能解释清楚这些表。这意味着要把手续费项和利息项分开保存到不同的表中。根据他们所使用的对象—关系映射框架,将手续费项和利息项保存到不同表中的唯一方法就是创建具体的子类(Fee Payment、Interest Payment 等)。如果换成别的基础设施,他们或许可以避免使用这些笨拙的子类。
I threw this twist into this largely fictitious story to represent the rub of reality that we encounter all the time. We have to make calculated compromises and then move on without letting it throw us off our MODEL-DRIVEN DESIGN.
我在这个大部分是虚构的故事中讲述这段小插曲的原因是想说明我们在现实中总是会遇到这类小的障碍。我们必须做出一些适当的折中选择然后继续前进,而不能因为这些小问题而改变 MODEL-DRIVEN DESIGN 的方向。
The class diagram after the implementation
The new design was much easier to analyze and test because the most complex functionality is in SIDE-EFFECT-FREE FUNCTIONS. The remaining command has simple code (because it calls various FUNCTIONS) and is characterized by ASSERTIONS.
新的设计更易于分析和测试,因为最复杂的功能已被封装到了 SIDE-EFFECT-FREE FUNCTION 中。剩下的命令代码很简单(因为它只需调用各种 FUNCTION),并使用了 ASSERTION。
Sometimes there are parts of our programs that we don’t even suspect have the potential to benefit from a domain model. They may have started very simply and evolved mechanistically. They seem like complicated application code, rather than domain logic. Analysis patterns can be particularly helpful in showing us these blind spots.
有时,我们甚至想象不到,程序的一些部分也能从领域模型获益。它们可能一开始很简单,并一步步机械地演变。它们看上去就像是复杂的应用程序代码,而不是领域逻辑。分析模式在找到这些盲点方面特别有用。
In the following example, a developer has a new insight into the black box of the nightly batch, which had not been considered domain oriented.
在下一个例子中,一位开发人员对夜间批处理程序的内部机制产生了新的想法,以前他并没有从领域的角度来考虑这一问题。
Example: Insight into the Nightly Batch
示例对夜间批处理程序的深入理解
After a few weeks, the improved Account-based model had started to settle in. As often happens, the clarity of the new design made other problems more visible. The developer (Developer 2) who was adapting the nightly batch to interact with the new design began to see connections between the behavior of the batch and some of the concepts in Analysis Patterns. Here is a summary of some of the concepts he found most relevant.
几星期后,改进后的基于 Account 的模型基本完成了。如时常发生的那样,当新设计更加清晰之后,它就暴露出其他一些问题。开发人员(开发人员 2)在修改夜间批处理程序以使之与新设计交互的时候,发现批处理程序的行为与《分析模式》一书中所讲的一些概念有联系。下面就是他发现的一些最相关的概念:
Posting Rules
过账规则
Accounting systems often provide multiple views of the same basic financial information. One account might track income while another might track an estimated tax on that income. If the system is expected to automatically update the estimated tax account, the implementation of those two accounts becomes very intertwined. There are systems in which the majority of account entries result from such rules; in such a system, the dependency logic gets to be a mess. Even in more modest systems, such cross-posting can be tricky. The first step toward taming the tangle of dependencies is to make these rules explicit by introducing a new object.
会计系统经常提供同一个基本财务信息的多种视图。一个账户可能用于跟踪收入,而另一个账户可能用于跟踪该收入的估税。如果我们希望系统自动更新估税总额,那么这两个账户的实现将会彼此紧密关联。在有些系统中,大部分账目都是由这些规则产生的,在这样的系统中,依赖逻辑会变得一团糟。即使是在规模不大的系统中,这样的交叉过账也会很复杂。减少这种缠杂不清的依赖的第一步是通过引入一个新对象来使这些规则明朗化。
The class diagram of the basic posting rule
A posting rule is triggered by a new Entry in its “input” account. It then derives a new Entry (based on its own calculation Method) and inserts the new Entry into its “output” Account. In a payroll system, an Entry in a salary Account might trigger a Posting Rule that would calculate a 30 percent estimated income tax and insert it as an Entry in the tax with-holding Account.
当过账规则的 input 账目收到一个新的 Entry 时,这个 Entry 就会触发过账规则。然后过账规则会生成一个新的 Entry(基于它自己的计算方法),并将这个 Entry 插入它的“output”账目中。在工资系统中,工资 Account 中的 Entry 可能会触发一个过账规则,此规则计算 30%的估计收入所得税,并将其作为一个 Entry 插入扣税 Account 中。
Executing Posting Rules
执行过账规则
The Posting Rule has established the conceptual dependency between Accounts, but if the pattern stopped there, it could be difficult to follow. One of the trickiest parts of dependency designs is the timing and control of updates. Fowler discusses three options.
过账规则建立了各个 Account 之间概念上的依赖性,但如果对这个模式的使用仅限于此,那么它仍然很难使用。在依赖性设计中,最复杂的部分是更新的时机和控制措施。Fowler 讨论了 3 种选择:
- “Eager firing” is the most obvious, but typically the least practical. Whenever an Entry is inserted into an Account, it immediately triggers the Posting Rules and all updates are made immediately.
- “Account-based firing” allows processing to be deferred. At some point, a message is sent to an Account and it triggers its Posting Rules to process all Entries inserted since its last firing.
- Finally, “Posting-Rule-based firing” is initiated by an external agent, which tells the Posting Rule to fire. The Posting Rule is responsible for looking up all Entries made to its input Accounts since the last time it fired.
- “主动触发”(Eager firing)是最直接的方式,但通常也最不实用。每当一个 Entry 被插入到 Account 中时,它立即就触发过账规则,并立即进行所有更新。
- “基于 Account 的触发”允许推迟处理。在过后的某个时刻,向 Account 发送一条消息,令其触发过账规则,来处理自从上一次触发以来所插入的所有 Entry。
- 最后,“基于过账规则的触发”由外部代理来启动,它通知过账规则触发。过账规则负责查找自从上次触发以来插入到其输入 Account 中的所有 Entry。
Although firing modes can be mixed in a system, each particular set of rules needs to have one clearly defined point of initiation and responsibility for identifying input Account Entries. The addition of the three firing modes to the UBIQUITOUS LANGUAGE is as important to the success of the pattern as the model object definitions themselves. It eliminates ambiguity and guides decision making directly to a clearly defined set of choices. These modes identify an easily overlooked challenge and provide vocabulary to support clear discussion.
尽管在一个系统中可以混合使用各种触发模式,但每组特定的规则都需要有一个明确定义的“启动点”(应该在何时启动),还要定义由谁负责查找插入到输入的 Account 中的 Entry。将这三种触发模式添加到 UBIQUITOUS LANGUAGE 中对于成功使用这种模式具有至关重要的意义,这与模型对象定义本身同等重要。这样,触发的概念就不再模糊了,而且还能直接指导决策,从而获得一组明确定义的可选方案。这些触发模式揭示出了一个很容易被忽略的重点,并且丰富了我们的词汇,从而使讨论更清晰。
Developer 2 needed a sounding board to discuss his new ideas. He met up his colleague (Developer 1), the developer who had been primarily responsible for modeling the accruals.
开发人员 2 需要找个人来讨论他的新思路。他找到了同事(开发人员 1),开发人员 1 原来主要负责建立应计项目(accrual)的模型。
Developer 2: At some point, the nightly batch started being a place where we swept stuff under the rug. There is domain logic implicit in what the script does, and it’s been getting more and more complicated. For a long time I’ve wanted to do a model-driven design for the batch, separate out a domain layer, and make the script itself a simple layer on top of the domain. But I could never figure out what that domain model would be like. It seemed like maybe it was just some procedures that didn’t really make sense as objects. As I’ve been reading the section in Analysis Patterns on Posting Rules, I’ve been getting some ideas. Here’s what I had in mind. [Hands over a sketch]
开发人员 2:有的时候,夜间批处理程序成为一个隐藏问题的地方。脚本的行为中隐含了领域逻辑,而且正在变得越来越复杂。很长时间以来,我一直想用 MODEL-DRIVEN DESIGN 的方法来修改一下批处理,将领域层分离出来,并使脚本本身成为领域层之上的一个简单的层。但我一直没有想出这个领域模型应该是什么样的。看上去它似乎只是一些操作步骤,而把它们实现为对象没什么实际意义。但当我读完《分析模式》一书中有关 Posting Rule 的内容后,获得了一些思路。这个图就是我所想到的[递过来一张草图]。
A shot at using Posting Rules in the batch
- Developer 1: What is this “Posting Service”?
- Developer 2: That is a FACADE that exposes the accounting application’s API and presents it as a SERVICE. I actually made that a while back to simplify the batch code, and it also gave me an INTENTION-REVEALING INTERFACE for posting to the legacy system.
- Developer 1: Interesting. So, which firing style do you plan to use for these Posting Rules?
- Developer 2: I hadn’t really gotten that far.
- Developer 1: Eager Firing would work for Accruals, since the batch actually tells the Asset to insert them, but it wouldn’t work for Payments, which get entered during the day.
- Developer 2: I don’t think we would want to couple the calculation method that tightly to the batch anyway. If we ever decided to trigger interest calculations at a different time, it would mess things up. And it just doesn’t seem right, conceptually.
- Developer 1: It sounds like Posting-Rule-based firing. The batch tells each Posting Rule to execute, and the rule goes and looks for appropriate new Entries and then does its thing. That’s pretty much the way you’ve drawn it.
- Developer 2: So then we avoid creating a lot of dependencies on the batch design, and the batch keeps control. That sounds right.
- Developer 1: I’m still a little vague on the interaction of these objects with the Accounts and Entries.
- Developer 2: You and me both. The examples in the book create a direct link between the Accounts and the Posting Rules. That is kind of logical, but I don’t think it will work very well for us. We have to instantiate these objects from data each time, so we would have to figure out which rule applies in order to associate it. Meanwhile, the Asset object is the one that knows the content of each Account, and therefore which rule to apply. Anyway, what about the rest of this?
- Developer 1: I hate to nitpick, but I don’t think that we’re using “Method” right. I think the concept is that the Method computes the amount to be posted—like, say, a 20 percent tax with-holding on income. But in our case, that’s simple: it’s always the full amount being posted. I think the Posting Rule itself is supposed to know which Account to post to, which corresponds to our “ledger name.”
- Developer 2: Oh. So if the Posting Rule is responsible for knowing the correct ledger name, we probably don’t need Method at all.
- 开发人员 1:Posting Service 是指什么?
- 开发人员 2:这是个 FACADE,它提供了会计应用程序的 API,并且将其呈现为一个 SERVICE。实际上我使用它已经有一段时间了,主要用来简化批处理代码,而且它也为我提供了一个 INTENTION-REVEALING INTERFACE,可用于向老系统过账。
- 开发人员 1:很有趣,那么你打算为这些 Posting Rule(过账规则)使用哪种触发模式?
- 开发人员 2:我还没有想那么多。
- 开发人员 1:“主动触发”可能适用于 Accrual,因为批处理程序实际上通知 Asset 插入 Accrual,但“主动触发”可能不适用于 Payment,因为 Payment 是在白天输入的。
- 开发人员 2:不管怎样,我认为我们都不希望把计算方法与批处理程序特别紧密地联系到一起。如果我们决定在一个不同的时间来触发利息计算,那么情况将会是一团糟。而且从概念上看,这也是不正确的。
- 开发人员 1:这听上去有点像“基于 Posting Rule 的触发”。由批处理程序通知每个 PostingRule 去执行,然后规则找出相应的新 Entry,并完成其工作。这种思路基本上就与你画的图中表现出来的思路差不多吧。
- 开发人员 2:这样在批处理设计中就不会产生很多依赖,而且它也易于控制了,看样子不错。
- 开发人员 1:我没有完全明白这些对象是如何与 Account 和 Entry 交互的。
- 开发人员 2:我也没完全明白。那本书中的示例在 Account 和 Posting Rule 之间建立了直接联系。在某种程度上这是合乎逻辑的,但我认为它并不完全适用于我们的情况。我们每次都需要用数据来实例化这些对象,因此要使用这种方法,必须知道应用哪条规则。同时,Asset 对象知道每个 Account 的内容,因此也知道应用哪条规则。不管怎么说,这个模型的其他方面呢?
- 开发人员 1:虽然我讨厌过分挑剔,但我确实认为 Method 的使用不正确。我认为在概念上 Method 是用于计算要过账的总额的,比方说,在收入上计算 20%的扣税。但我们的情形很简单:它始终是全额过账。我想 Posting Rule 本身应该是知道要过账给哪个 Account 的,这个 Account 对应于我们的 ledger name(分类账名称)。
- 开发人员 2:哦,那么如果让 Posting Rule 负责查知正确的 ledger name,我们可能就完全不需要 Method 了。
Actually, this whole business of choosing the right ledger name is getting more and more complicated. It is already a combination of the type of income (fee or interest) with the “asset class” (a category the business applies to each Asset). That is one place I’m hoping this new model will help.
实际上,选择正确的 ledger name 这件事情变得越来越复杂了。它已经是收入类型(手续费或利息)与“Asset 类别”(公司对每种 Asset 所使用的分类)的组合了。我希望新模型能够在解决这个复杂性上有所帮助。
- Developer 1: OK, let’s focus there. The Posting Rule is responsible for choosing the ledger based on attributes of the Account. For now, we can make it a straightforward way to handle asset class and the distinction between interest and fees. In the future, you’ll have an OBJECT MODEL you can enhance to handle more complex cases.
- Developer 2: I need to think about this some more. Let me mull it over, and reread the patterns, and then I’ll take another stab at it. Could I talk with you about this again tomorrow afternoon?
- 开发人员 1:好的,我们就把重点集中在这里。Posting Rule 负责根据 Account 的属性来选择 Ledger。现在,我们可以先用一种简单直接的方式来处理资产类型以及利息与收入之间的区分。将来,我们会有一个 OBJECT MODEL,可以通过改进这个模型来处理更复杂的情形。
- 开发人员 2:在这方面我还要多考虑一下。我会再仔细研究一番,再把模式读一遍,然后再来尝试解决这个问题。明天下午我能再次和你讨论这个问题吗?
Over the next few days, the two developers worked out a model and refactored the code so that the batch simply iterated through the Assets, sending a few self-explanatory messages to each and then committing the database transactions. The complexity was shifted into the domain layer, where an object model made it both more explicit and more abstract.
在接下来的几天时间里,这两位开发人员设计出了一个模型,并对代码进行了重构,使得批处理程序只是简单地依次访问各个 Asset,并向每个 Asset 发送几条非常浅显易懂的消息,然后提交数据库事务。复杂性被转移到领域层中,领域层中的对象模型使问题变得更加明确,也更抽象。
The class diagram with Posting Rules
Sequence diagram showing rule firing
The developers departed considerably from the details of the models presented in Analysis Patterns, yet they felt they had preserved the essence of the concepts. They were a little uncomfortable about involving the Asset in the selection of the Posting Rule. They went that way because the Asset had the knowledge of the nature of each Account (fee or interest) and was also the natural access point for the script. To have associated the rule object directly with the Account would have required a collaboration with the Asset object on each instantiation of the objects (each time the batch was run). Instead, they let the Asset object look up the two relevant rules through their SINGLETON access and pass them the appropriate Account. It seemed to make the code much more direct and so they made a pragmatic decision.
在一些细节问题上,这两位开发人员开发的模型与《分析模式》中给出的相差甚远,但他们认为二者在概念本质上还是相同的。有一个问题令他们稍感不安,那就是在 Posting Rule 的选择中把 Asset 牵扯进来了。之所以这样做,是因为 Asset 知道每个 Account 的性质(手续费或利息),而且它也是脚本的自然访问点。如果让规则对象直接与 Account 发生关联,这些对象在每次实例化时(每次运行批处理程序时)都需要与 Asset 对象进行协作。可他们没有这样做,而是让 Asset 对象通过 SINGLETON 访问来查找这两个相关规则,并把对应的 Account 传递给它们。这样一来代码就变得更直接了,因此这是一个正确的决定。
They both felt that conceptually it would have been better to associate Posting Rules only with Accounts, while keeping the Asset focused on its job of generating Accruals. They hoped that subsequent refactorings and deeper insight would bring them back to this and show them a way to make this clean division without losing the obviousness of the code.
从概念上看,他们都感到更好的做法是让 Posting Rule 只与 Account 发生关联,而令 Asset 只负责生成 Accrual。他们希望等到有了后续的重构和更深入的理解之后再回头看这个问题,并找到一种将职责分离得更清楚而又不影响代码明确性的方法。
Analysis Patterns Are Knowledge to Draw On
分析模式是很有价值的知识
When you are lucky enough to have an analysis pattern, it hardly ever is the answer to your particular needs. Yet it offers valuable leads in your investigation, and it provides cleanly abstracted vocabulary. It should also give you guidance about implementation consequences that will save you pain down the road.
当你可以幸运地使用一种分析模式时,它一般并不会直接满足你的需求。但它为你的研究提供了有价值的线索,而且提供了明确抽象的词汇。它还可以指导我们的实现,从而省去很多麻烦。
All this feeds into the dynamo of knowledge crunching and refactoring toward deeper insight and stimulates development. The result often resembles the form documented in the analysis pattern, but adapted to circumstances. Sometimes the result doesn’t even obviously relate to the analysis pattern itself, yet was stimulated by the insights from the pattern.
我们应该把所有分析模式的知识融入到知识消化和重构的过程中,从而形成更深刻的理解,并促进开发。当我们应用一种分析模式时,所得到的结果通常与该模式的文献中记载的形式非常相像,只是因具体情况不同而略有差异。但有时完全看不出这个结果与分析模式本身有关,然而这个结果仍然是受该模式思想的启发而得到的。
There is one kind of change you should avoid. When you use a term from a well-known analysis pattern, take care to keep the basic concept it designates intact, however much the superficial form might change. There are two reasons for this. First, the pattern may embed understanding that will help you avoid problems. Second, and more important, your UBIQUITOUS LANGUAGE is enhanced when it includes terms that are widely understood or at least well explained. If your model definitions change through the natural evolution of the model, take the trouble to change the names too.
但有一个误区是应该避免的。当使用众所周知的分析模式中的术语时,一定要注意,不管其表面形式的变化有多大,都不要改变它所表示的基本概念。这样做有两个原因,一是模式中蕴含的基本概念将帮助我们避免问题,二是(也是更重要的原因)使用被广泛理解或至少是被明确解释的术语可以增强 UBIQUITOUS LANGUAGE。如果在模型的自然演变过程中模型的定义也发生改变,那么就要修改模型名称了。
Quite a lot of object models have been written about, some specialized for one kind of application in one industry and some quite general. Most of them provide the seed of an idea, but only a few have captured the reasoning behind the choices and the consequences that follow, which are the most useful parts of an analysis pattern. More of these refined analysis patterns would be valuable, to help save us from reinventing the wheel again and again. I’d be surprised ever to see a comprehensive catalog, but industry-specific catalogs might arise. And patterns for some domains that cross many applications could be widely shared.
很多对象模型都有文献资料可查,其中有些对象模型专门用于某个行业中的某种应用,而有些则是通用模型。大部分对象模型都有助于开阔思路,但只有为数不多的一些模型精辟地阐述了选择这些模式的原理和使用的结果,而这些才是分析模式的精华所在。这些精化后的分析模式大部分都很有价值,有了它们,可以免去一次次的重复开发工作。尽管我们不大可能归纳出一个包罗万象的分析模式类目,但针对具体行业的类目还是能够开发出来的。而且在一些跨越多个应用的领域中适用的模式可以被广泛共享。
This kind of reapplication of organized knowledge is completely different from attempts to reuse code through frameworks or components, except that either could provide the seed of an idea that is not obvious. A model, even a generalized framework, is a complete working whole, while an analysis is a kit of model fragments. Analysis patterns focus on the most critical and difficult decisions and illuminate alternatives and choices. They anticipate downstream consequences that are expensive if you have to discover them for yourself.
这种对已组织好的知识的重复利用完全不同于通过框架或组件进行的代码重用,但是二者唯一的共同点是它们都提供了一种新思路的萌芽,而这种新思路先前可能并不十分明晰。一个模型,甚至一个通用框架,都是一个完整的整体,而分析则相当于一个工具包,它被应用于模型的一些部分。分析模式专注于一些最关键和最艰难的决策,并阐明了各种替代和选择方案。它们提前预测了一些后期结果,而如果单靠我们自己去发现这些结果,可能会付出高昂的代价。