The Action/Executor Pattern
Solution Manager/Architect, Avanade
Summary: The Action/Executor pattern identifies a strategy for mapping use cases to code, allowing better visibility and agility. Also, it addresses the issues of contaminating entities and skipping over proper use of transactions. (12 printed pages)
Have you ever been handed an application where you looked at the design and implementation, and felt that you were seeing plenty of square pegs smashed into round holes or just straight into the boards? Or have you ever had that “this just feels wrong” feeling when you found yourself adding methods to entities that did not really have a lot to do with the entities themselves, but there was no other reasonable place to put them? Have you ever skipped using transactions properly, because it just was not really evident where they were supposed to go?
The frustration often comes from having a design model that is incomplete, without knowing it. The standard model for separating presentation, business, and data layers only divides responsibilities one way, and very coarsely—leaving patterns of implementation to bring about these frustrations.
The pattern that is presented in this article takes a step into the business and data layers, and further subdivides them—addressing these common issues, and describing where complementing technologies and patterns fit in. It is called the Action/Executor pattern.
Too often, the business layer ends up contaminated with business logic strewn everywhere; the relationship between the use cases and implemented logic lost; entities with methods responsible for persisting themselves, but without context; and some “I didn’t have anywhere better to put it, but I must be able to call this stored procedure”–type methods. The data layer often ends up being a pass-through to the stored-procedure set of classes. In these situations, the business layer has become more of a non-UI layer and a not-that-much-data layer, or a whatever-worked-at-the-time layer.
The Action/Executor pattern divides up the responsibilities for the business layer, which consists of concepts (entities) and atomic or composite actions that are directly related to use cases (actions), and it splits the data layer into mapping entities to their persistence store (persisters) and the action complement and transaction owner (executors). It is the Executor concept—explained further in the article—that makes the difference in this approach.
Figure 1 is a high-level diagram that shows the responsibilities and their layers:
Figure 1. High-level diagram showing concepts and data, steps and logic
NOTE Usually, it takes two—sometimes three—meetings with development teams, before it clicks what it is about this approach that is just different enough that it becomes a “holy cow, how did we not happen upon this before?”
As one might expect, most reasonably well-made applications have the standard layered structure that Figure 2 shows:
Figure 2. Standard layer structure of applications
Each layer does what you might expect:
· The interface layer is responsible for managing how the application is going to interact with the outside world—be it through Web pages, a smart client, Web services, or another messaging approach.
· The business layer starts out with entities representing a mix of real data entities and conceptual entities.
· The data layer ends up being basically a mapper of the entities to the database. The arrows in Figure 2 show the dependency between the layers.
It would seem that all is good, because everything is separated. But is it, really? The entities end up with methods such as Delete, Select, or Get, and either a Save or an Insert and Update to start with. Already, you see the storage mechanism showing its particularities in the entities, because the parameters of these methods almost always are identical to the stored procedures at the end of the line. Then, the entities start having methods that do not make any sense, but which are seen as a way around creating other entities that have a single purpose; and they usually need their own CRUD, but in a way that is related to the entity onto which they have been grafted, and so other methods or optional parameters are created. Then, the business logic starts to get blended into different versions of the Save method or different cases (ifs) of it, and you find yourself with business rules spread randomly throughout your code—with no clear way for the developer who comes after you to know how it all ties back to the original use cases, and thus how to map changes to the right place in the code. Over a couple of months or years, this very often leads to instant legacy code (“I don't know if that code really does anything anymore, but leave it, just in case”).
Let’s look at an example. Say that you have an invoice that you want to submit; so, the Web page creates an invoice object, populates it with your new info, and calls Invoice.Save(). Yay! We’re done. Ah, not quite. The requirements just got updated; it seems that now we also have to create a dummy customer, if this is the first invoice for a customer, but we do not have the customer info yet. Okay, modify the Save method to have a switch statement. Another change; now, if the credit limit is over a certain amount and we're doing an invoice submission, instead of posting an invoice, then... Oh, another new requirement; now, we have another situation in which we must add a Purchase Order (PO), but no dummy customer is allowed into the mix. When it saves, we must save also the invoice and update the customer (in some cases, depending on the circumstances)... Now, we have Save1, Save2, Save3 (effectively), and a number of other clumsy methods from the point of view of “clear and easily mapped to the requirements.”
By the way, where's the database transaction in all of this? A significant number of applications that are written today do not really use one, because it is not clear where it goes, or it is clumsy and complex, and in large part because of the constraints of the model that is used. Invariably, it gets declared by the entity in these cases, which starts it all off, and everything is contained within their method; and, when you step back, it just does not make sense. Whether or not this is done semi-abstractly by telling your autogenerated data-access object to start a transaction, it still means that your business layer fully understands that your data layer is talking to a database.
The result is polluted entities, at the very least. It's not uncommon to see a couple of different types of Save methods, and then to hear how, when the project was transitioned to someone else to support, things went from bad to worse, because new entities and methods were wired to the “wrong” versions of Save and other methods—some of which performed cascading saves when not expected or created dummy values where not expected (or other random sets of changes) or had pieces that were outside of the transaction, and so on. The reason is simple: It is that the entity that represents a concept was given business-activity responsibilities, and that is not its intent—which is why things start to break down from the beginning. Sometimes, senior developers refer to this as the uncomfortable feeling that they have, because they know that it should not be this way, but it is; and they accept it, though they hope to find a better way one day.
People who have had sufficient pain with this approach usually turn to some form of the following, as Figure 3 shows:
Figure 3. Modified layer structure
Now, the business logic is grouped into classes that are distinct from the entities and use the entities to get a job done; but, other than that, their purpose is not clear. This is better, because now we can have a set of process classes that help us avoid the issue of having a Save1, Save2, and so on. It also helps us address the other classical situation of having business activities to perform that don't really map simply to a specific entity—such as, say, determining credit-worthiness.
The challenge here becomes two key things, the first of which is transactional responsibility. Who is responsible for creating the transaction that ensures that all of the steps that must be taken to complete the process are taken; and can we do it without revealing (in the business layer) that we are dealing with a database to allow us later flexibility for Web services or other approaches? The usual (75+ percent) answer is the process class (leaving aside TransactionScope until later), which means that the business layer knows some concrete stuff about the data layer because it ends up creating a database transaction. Twenty-four percent of the time, it is something in the entities, because the entities still have CRUD methods on them. One percent is left to the truly creative approaches.
This brings us to the second challenge. The problem with entities having CRUD methods and process classes is that the entity methods can be used outside of the confines of the process classes—potentially resulting in the storage of invalid results. Is it valid to save an invoice outside of the SubmitInvoice process? Maybe; but then, how can you validate when? The premise is: Do not make something available to be misused and misunderstood, because, if you do, it will (at some point).
Another of the problems with this approach is: What defines a process? The idea is not anchored to anything, in most cases, and usually ends up being any method that cannot be attached clearly to an entity or is complex enough to involve a number of entities relatively equally. Yet another issue is: Where do you put those pesky little extras, such as calling special stored procedures that are not related to a particular entity, but are necessary as part of the “process.” They are part of the data-level activities, such as recalculating credit scores or archiving a previous “set of data”; they must be included as part of the transaction.
This is where the Action/Executor pattern starts to provide immediate value—by breaking up the business layer and the data layer into four areas, as Figure 4 shows:
Figure 4. Modified layer structure, showing broken-up business and data layers
There are two key changes here from the previous model. The first seems syntactic, going from process to actions, but there is some method to the madness. The idea is that an action is a logical atomic block of work, as defined in a use case; thus, a use case has one or more actions that map to it. Actions use entities to accomplish tasks. Therefore, all of the logic within an action is business logic for both validation and execution of that action. An action has only two methods—validation and execution (surprised?)—and all entities that are required are provided to the constructor, so that changes "don't occur" (this is an in-principal point, not a technically accurate one) or are retrieved by the action itself.
The action’s responsibility in terms of validation is to ensure that everything that has been provided meets the conditions to allow the action to be executed. If validation fails, usually two pieces of information are returned: an indicator of success or failure (usually a Boolean) and a user-readable error string (as an out string parameter). The objective is to provide the “use-case–oriented reasons for failure that can be presented back to the user” and not an “Error 1571 - Null Reference Exception” type of error. Validation is also where complicated security rules can be checked to determine whether or not this user, at this time, is allowed to do what is being requested.
The action’s Execute method is responsible for preparing everything to happen, then invoking the atomic permanence step (that is, calling the executor), and then doing whatever else must be done before control is returned. Preparing can consist of looking up other information, creating or calculating whatever is needed, creating audit messages that will be persisted along with the other data updates, and so on. When everything is ready, it calls the executor.
The executor, which is named after the executor of a last will and testament, knows concretely what type of transaction is needed—be it a database transaction, Web service transaction, and so on—because it is a member of the data layer. It then orchestrates the proper usage of the persisters—weaving the transaction through them, including writing whatever audit messages (I'm big on audit messages)—and lastly is able to run whatever additional stored procedures or other activities must be performed as part of the permanence step and, logically speaking, for completing that atomic business action. That ability to have a nice, clean place for those “extras”—for that “recalculating credit score stored procedure” that I mentioned earlier—is key, because it avoids contaminating so many different objects. (Note that later there is a brief section that talks about TransactionScope.)
A persister is responsible for a single entity, mapping it to the appropriate persistence layer—be that to a single table or multiple tables on multiple databases, or calling Web services or whatever else. A persister’s job is to understand how to map the entity to that table, and nothing else, without any cascading.
As a side note, consider not having Insert and Update methods; but, instead, push this all the way into the database by having only a Save method and a Save stored procedure. Inside the stored procedure, always pass in the unique ID and check to see if the ID that is provided is equal to a not set value for the particular type of data entity (for example, use –1); if that is the case, insert otherwise update. Return the ID value, and set it to the ID property in your code, always. If it is an update, the ID will be unchanged; if it is an insert, it will now be set properly. This helps keep code completely ignorant of potentially complex performance-optimized schemas.
An advantage already from the perspective of design and communication is that the design model now can be illustrated as Figure 5 shows:
Figure 5. Diagram of design model, showing responsibilities and their layers
This means that it is easier to map between requirements and implementation, and easier to track as well as discuss among developers, project managers, and analysts what logic is where and why.
From the perspective of a standard layer stack, it means the following, as Figure 6 shows:
Figure 6. Modified diagram of standard layer structure
Instead of the layers being just horizontal, there is now also conceptually a vertical layering. Actions and executors are centered on the idea of executing a set of steps first and foremost, whereas the entities and persisters are centered on data. With this in mind, let us look at how the elements of the different layers use each other, as the Dependency Inversion pattern now steps in.
The Dependency Inversion pattern is the idea of reversing a dependency, which concretely here is the idea that the business entities do not call the persisters, but instead the persisters will take the entities as parameters for their methods. This allows the data layer to be the only area in which there is any knowledge whatsoever of how things are stored—which really is the point of the data layer, is it not?
Figure 7. Layer structure, showing reversed relationships
NOTE There are a couple of key questions that always come up; they are addressed soon (wait, it’s coming).
So, Figure 7 shows that the entities never talk to the persisters; instead, the relationship is the other way around. The interface layer only manipulates entities and actions to accomplish what it needs.
At this point, the approach can be used within a single DLL in Microsoft Visual Studio 2005 (or later) by using solution folders to separate the various elements, if so desired. If you keep yourself honest, there is no issue; and, for really small projects, it is well worth it for you to do this.
For more sizable solutions, the chain of references resembles Figure 8:
Figure 8. Chain of references for more sizable solutions
The persisters need a reference to the entities as do the executors. At first pass, you might ask why, only then to realize that the actions have provided the entities to the executor for the action to be finalized, and the executor then needs to hand them over to the persisters.
The question that of course arises is: How do you take the next step and introduce the flexibility of having either various or replaceable (flexible) data layers working with the same business layer and above—allowing for a database to be used today, but a Web service to be used tomorrow or, more importantly, to allow for a mock data layer? Enter the Service Locator pattern.
There are a lot of patterns that have bad names; Dependency Injection is one (it really could be called “Passing Objects into Constructors! Now with Polymorphism!”), and Service Locator is another (it could be called “VB6-Like Late Binding” or “COM-Like Interfaces Are Back!” or, even better, “Lazy Class Loading with Flair!”).
The idea behind Service Locator is that your classes reference either a base class type or an interface, and you pass a singleton—usually called the Service Locator—the type that you are looking for, and you ask it to give you the real type that you are supposed to use. The singleton looks up—either in a configuration file or in a database, usually—what the type that you provided maps to, which is a DLL name and a specific class name. The singleton then usually instantiates this “real class” for you and hands it back to the caller. The caller is abstracted from the real type, and the classes that are used this way are dynamically configurable (XML file, database table, and so on), instead of requiring recompilation to change. This is great for being able to use mock persisters and executors, or for changing from one type of database to another or to Web services.
Figure 9 shows the relationship to the entities and so forth:
Figure 9. Layer structure, showing relationships using Service Locator pattern
Now, the actions are “bound” to the IExecutor or IExecutors, depending on whether you decide to use either a super class pattern for all of your executors or multiple distinct executors. Given that usually there is only one method needed per action class, either grouping into executors or using a super class should fit the bill. A benefit of this strategy is that you can provide a proxy executor (if needed) at any time by configuration with the Service Locator pattern, which allows you to push the real transactional work elsewhere without the action ever knowing. Actions could also be looked up dynamically by using interfaces for your actions and calling the Service Locator to "get them." This is particularly helpful when you have evolving tax rules or similar such "volatile" types.
NOTE The new Microsoft Enterprise Library 4.0, which is for Microsoft .NET Framework 3.5 only, has a Unity Application Block, which is a Service Locator that has dependency injection and inversion.
The library references now have become a bit more complex, as Figure 10 shows:
Figure 10. Modified library references
Creating, retrieving, updating, and deleting—CRUD. We have to deal with it; and, in the next section, we will address the retrieval part. It is recommended not to have actions named after what is essentially database activity. You do not CreateInvoice, in the minds of most business people; you GenerateAnInvoice. You do not UpdateAnInvoice, unless you are ModifyingExistingInvoice; otherwise, you are SubmittingInvoice or ResubmittingInvoice. You do not DeleteAnInvoice; you either VoidAnInvoice or credit against it (or some other activity).
The intent with the action naming is to give it the business-related names that you likely have in your use-case or requirements document, and forcing this almost always results in the development team thinking about these objects differently. Usually, a day or two after this discussion, a question arises like, “About SubmittingInvoice... Would it make sense for us to check X before sending it to the executor? It’s part of our business process that’s done manually, but we were kind of ignoring it from a technical perspective, although I see where it could go now.”
Some developers immediately ask, in the hopes of avoiding adoption of the model, “Aren’t we just going to be creating a lot of unnecessary code to deal with what essentially will be unnecessary classes to manage trivial activity?”—to which the answer to the real question is: “Use code snippets and generics to speed things up.”
Now, does it make sense to have an action for every single piece of data retrieval, every single drop-down list and everything? No, of course not. The idea is not to have a hammer and hit everything, yelling, “NAIL!”
There are circumstances in which having an action for data retrieval makes sense—for example, if there are security rules that you must implement. However, in general, the simple recommended approach is to treat data retrieval like a crosscutting concern (that is, an aspect), using a RetrievalFactory, for example.
Figure 11 shows the same diagram as previously, now including the RetrievalFactory and other standard aspects:
Figure 11. Layer structure, showing RetrievalFactory and other aspects
The RetrievalFactory is highlighted here simply to draw attention to it. The Service Locator pattern is not required, although it could be involved if you were to use it to abstract away from your persisters.
The RetrievalFactory returns a factory (surprise!) for retrieving the particular type of entities that you are looking for, usually with something like RetrievalFactory.GetFactory(typeof(Student)) or RetrievalFactory<Student>.GetFactory() or however else you might address it. The RetrievalFactory is allowed to call persisters directly, instead of passing-through the two steps of an action and executor itself, because there are no business rules that usually are associated with retrieval at all, or any transactions. If there are, you need both an action and an executor.
The other thing that having the RetrievalFactory does is truly separate the reading use of the information from the writing use of the information, which sometimes is helpful, as in situations in which different user accounts should be used for security purposes or in which you want to have some centralized caching.
The TransactionScope class offers the ability in .NET Framework 2.0 and later to declare a transaction to which other objects can attach themselves, without having to describe right upfront what type of transaction it is. In .NET Framework 2.0, it required Microsoft SQL Server 2005 to be the database, for it to be a database transaction; otherwise, it was promoted to a DTC transaction, which can cause security concerns (a Swiss-cheese firewall).
From a model perspective, there are two places in which the TransactionScope class could fit. One place is the action class’s Execute method, before invoking the executor. The benefit of this approach is that it would allow for composite actions—for example, Action X = Action Y + Action Z, where the Execute method of Action X would create a transaction and then call the Execute methods of Action Y and Action Z, and where each Execute method would check to see if there was a transaction to subscribe (otherwise, it would create its own).
The other logical place to think about using the TransactionScope class is within the executor, which would declare a transaction (and the persisters would join in), without having to have the transaction information passed as a parameter to key methods.
A Quick Note on Web Services
A benefit of using this approach is that, when you must change the presentation layer from, say, a Web site to Web services, having pushed all of the logic into well-organized actions and entities, the business layer is ready for it.
A Quick Note on DAL Generators
On occasion, upon discussing this model, someone will bring up that a DAL generator—be it CodeSmith with specific templates, or MyGeneration or SubSonic—addresses the entire need, which is incorrect. It produces persisters; but, again, it does not address the responsibilities that are taken on by the executor—instead, pushing this into the action class. Remember that the executor is the logical place in which to put those extra stored-procedure calls or other activities that must be made—the logic that is in alignment with the action being performed, but not warranting an entity of its own.
The Action/Executor pattern identifies a strategy for mapping use cases to code, allowing better visibility and agility, and addresses the sticky issues of contaminating entities and skipping over proper use of transactions (as it is unclear where they go).
This pattern does not fit all types of projects; however, it does address a significant number of them. More importantly, for the ones that it does not fit, it brings up the notion of identifying areas of responsibility within each layer.
The Action/Executor pattern has been adopted by several companies as their default .NET application-design pattern. Their support in concretizing the pattern over the past two years has been greatly appreciated.