第 11 章 应用分析模式

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.

In Analysis Patterns: Reusable Object Models, Martin Fowler defined his patterns this way:

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.

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:

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]

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.

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.

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.

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.

Image Figure 11.1. 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.

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.

Image Figure 11.2. 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.

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.

Image Figure 11.3. 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.

In his book, Fowler includes more elaborate forms of these models and considerable discussion of the trade-offs.

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.

Image Figure 11.4. 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?

Image Figure 11.5. 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]

Image Figure 11.6. 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.

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.

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.

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.

Image Figure 11.7. 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.

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.

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.

Image Figure 11.8. 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.

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.

  1. “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.

  2. “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.

  3. 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.

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.

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.

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]

Image Figure 11.9. 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.

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.

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?

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.

Image Figure 11.10. The class diagram with Posting Rules

Image Figure 11.11. 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.

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.

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.

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.