Practical .NET

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
End Class

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
End Interface

Leveraging that interface, my DTO would now look like this:

Public Class CustomerRetrievalParms
  Implements ICustomerRetrievalParms

  Public Property ICustomerRetrievalParms_CustomerId As String Implements ICustomerRetrievalParms.CustomerId
  Public Property ICustomerRetrievalParms_CustomerType As CustomerType Implements ICustomerRetrievalParms.CustomerType
End Class

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
  Inherits ICustomerRetrievalParms
  Property CreditLimit As Decimal
End Interface

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
  Implements ICustomerRetrievalParmsCredit

  '...properties defined earlier

  Public Function Execute() As Customer
    Dim rep As CustomerRetrievalBase
    rep = New CustomerRetrievalHandler
    Dim parms As ICustomerRetrievalParmsCredit
    parms = Me
    Return rep.GetById(parms)
  End Function
End Class

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.

About the Author

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

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube