Dealing with Complexity with DTOs and Command Objects
Peter starts off with a perfectly good solution to a problem but then complicates the problem . . . and ends up moving to a different design pattern. While on that journey he has some best practices around designing Data Transfer Objects.
Sometimes the design pattern you start with isn't the design pattern you end up with. For example, in a previous column, I showed how to implement the Chain of Responsibility (CoR) design pattern so that, as problems get more complicated, you can insert new solutions into a chain of objects. Each object in the chain will either handle the problem or pass the problem off to another item that can handle the problem. In a follow-up column, I looked at some enhancements that I might have to make to that pattern to handle increasingly more complex problems.
In this column, I'm going to show how, when the problem becomes sufficiently complex, you might need to switch patterns altogether. That will lead me to showing how to use a simple-minded version of the Command Object pattern to support applications that aren't completely predictable. Along the way, I'll also discuss a best practice in creating Data Transfer Objects (DTOs). The most important thing I want to show, however, is that you can move from one pattern to another by refactoring existing code, rather than throwing out your existing solution and starting over. In fact, most complex problems are handled not by using a single design pattern, but by combining patterns to create a well-designed solution.
Passing Parameters in DTOs
For example, in those earlier columns, I made a simplifying assumption: I assumed that all of the objects in the chain would require the same parameters to retrieve a Customer object. In reality, some objects in the chain might require different information than other objects in the chain. For example, the company has two divisions that store information about customers very differently. Odds are that the company has dedicated applications for each of those divisions and, as a result, an application will need to flag what kind of customer it's working with.
To support that, I could pass a two parameters to the first object in the chain and keep passing those parameters to each item in the chain -- a CustomerId property (a string) and a CustomerType property (probably an enumerated value). It would be simpler to use the DTO pattern and wrap those parameters into a class with a CustomerId (a string) and a CustomerType property. That DTO would look like this:
Public Class CustomerRetrievalDTO
Public Property CustomerId As String
Public Property CustomerType As CustomerType
Any client would set the properties on this DTO that it knew about and any object in the chain would read the properties it cared about.
However, as I add objects to the chain, I'd probably end up adding more properties to my DTO. Adding properties to the DTO could force me to recompile every client that uses the chain, along with every chain object. This is a common issue with any DTO that's used by multiple clients.
Best Practices for DTOs
To head off that issue, I recommend following the interface segregation principle and always define an interface for any DTO. For this case study, that interface would look like this:
Public Interface ICustomerRetrievalParms
Property CustomerId As String
Property CustomerType As CustomerType
Leveraging that interface, my DTO would now look like this:
Public Class CustomerRetrievalParms
Public Property ICustomerRetrievalParms_CustomerId As String Implements ICustomerRetrievalParms.CustomerId
Public Property ICustomerRetrievalParms_CustomerType As CustomerType Implements ICustomerRetrievalParms.CustomerType
Unfortunately, in my earlier columns, I was only passing a single parameter to my chain of objects. Moving now to a DTO will require some extensive rewriting because all of the methods in my chain objects will need to accept and work with my parameter object … beginning with the base object that all of those objects derive from.
That leads to my second recommendation: Always pass a DTO as your parameter object, even if you're only passing a single value.
After moving to a DTO, my base object now looks like this:
Public MustInherit Class CustomerRetrievalBase
Public Property NextItemInChain As CustomerRetrievalBase
Public MustOverride Function GetById(custParms As ICustomerRetrievalParms) As Customer
A typical method in a chain object will begin like this:
Public Overrides Function GetById(custParms As ICustomerRetrievalParms) As Customer
Dim custId As String
custId = custParms.CustomerId
With this design in place, my client code to call the first object in the chain would look like this:
Dim parms As ICustomerRetrievalParms
parms = New CustomerRetrievalDTO
parms.CustomerId = "A123"
Dim rep As CustomerRetrievalBase
rep = New CustomerRetrievalHandler
Dim cust As Customer
cust = rep.GetById(parms)
At some future date, I might create a new object for the chain that would use the existing customer Id parameter and a new customer credit limit parameter to find the right Customer object. In that case, I would define a new interface that inherits from my original interface and adds CreditLimit:
Public Interface ICustomerRetrievalParmsCredit
Property CreditLimit As Decimal
My DTO would now implement this new interface. Existing clients and chain objects would find that the old interface still works and wouldn't need to be recompiled. New client/chain objects would have the flexibility of picking and choosing among the parameters in the new interface.
Handling a Complex Client
Up until now, I've been assuming the back-end processing was where the complexity existed. However, the company is now rolling out a Customer Complaint Resolution application that is, in itself, quite complex. In this new system, bringing together information about a complaint is often an incremental process with different information gathered at different points in the process -- it's hard to predict how the application will be used to resolve any particular complaint. In this application, there's no one point where the Customer object will be retrieved and no one point where all the necessary information about a customer can definitively be said to be present. For this application, I'd probably leverage the Command Object pattern.
In the Command Object pattern, an object accepts parameters and also provides a way to trigger processing (typically, a method called "Execute"). A client creates a Command object and passes the object around, setting the object's properties as it goes. Finally, some part of the application calls the method on the object that triggers processing without having to know much about what's behind that method call.
With my DTO in place, I've already got an object that holds parameters. To convert this to a simple Command object, I just need to add an Execute method that calls my head-of-chain object. The head-of-chain method needs access to the parameters, of course, and the simplest solution is to just pass a reference to the Command object itself. To flag that I expect the GetById method to use only the parameters on my Command object (and not the Execute method) I pass the object using the ICustomerRetrievalParmsCredit interface.
If I was a decent human being, I'd rename my DTO to reflect its new purpose in life and end up with code like what you see in Listing 1. I should also define the Execute method in a separate interface but I've probably beaten that horse to death.
Listing 1: A Command Object Created from a DTO
Public Class CustomerRetrievalCommand
'...properties defined earlier
Public Function Execute() As Customer
Dim rep As CustomerRetrievalBase
rep = New CustomerRetrievalHandler
Dim parms As ICustomerRetrievalParmsCredit
parms = Me
My client code might look like this:
Dim cmd As CustomerRetrievalCommand
cmd = New CustomerRetrievalCommand
cmd.CustomerId = "A123"
'...later and elsewhere in the application
cmdCreditLimit.CreditLimit = 10000
'...still later and elsewhere in the application
cust = cmd.Execute
I'd be the first person to agree that this is almost certainly more solution than you probably need for any application you're currently building. What I want to point out, though, is that -- at any point in this process -- I only had as much "solution" as I needed. Each refactoring/enhancement was in response to a genuine need. Converting existing code to the new solution might be a pain and might be regarded as "rework" … but building a more complex solution than is currently required is also a waste. I can live with the rework (though I should point out that, as a consultant, I am paid by the hour).
I also want to point out that each of these "enhancements" are built upon existing code while either minimizing changes to existing applications or allowing existing applications to continue to function. If I was willing to give up renaming my DTO, for example, I could probably move from the CoR pattern to my simple implementation of the Command Object pattern without upsetting any existing clients -- my Execute method isn't part of any interface that existing clients are using.
I'm trying to suggest that the critical success factor for developers in using design patterns is not designing these patterns from the beginning (though that would certainly be a good thing to do). The key to success is knowing about all the possible relevant design patterns so that you can recognize when one should be applied…and then moving to the pattern in a way that does the least damage to existing code. That's an achievable goal.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.