I have a neat domain model that makes it easy to communicate with an external web service. New requirements have made the external web service's interface messy and now I have to gather data from multiple places in my model. Is a DTO appropriate to hold all the necessary data?

I have an interface that deals with searching for some domain models. The concrete implementation behind it goes to an external web service for results.

interface IConsentSearch    // only one method
 +Search(Customer) : IList<Consent>

class Customer
 +various properties

My domain model is, of course, much more complex, but this is the gist of it.

Now, I got some new requirements that would allow us to search by order id in addition to the Customer's properties. In my internal domain model, orderId has its own little place somewhere (not part of Customer). The problem is that the external web service interface has a completely different idea of where orderId is.

Note: it is not an either/or situation: everything Customer has to offer is mandatory for the search to take place - order id is just an additional criteria.

I am going to have to change Search() signature and thought of creating a new DTO in my application service layer called ConsentSearchDTO. New DTO would serve one purpose: to combine data and models that are now scattered throughout my domain model and necessary for querying the external web service:

interface IConsentSearch    // still only one method
 +Search(ConsentSearchDTO) : IList<Consent>

class Customer      // still the same
 +various properties

class ConsentSearchDTO
 +Customer : Customer
 +OrderId : int

My questions:

  1. Does the above ConsentSearchDTO make sense?
  2. Is it ok to create a DTO that references domain model classes (Customer) or should I create a whole new structure that mimics the original domain model? This seems like a lot of work and doesn't bring me any additional value.
  • 1
    That's not really the point of DTOs. They're used to have a coarser granularity when transferring data usually over different processes, or over a network protocol. They're not used to transfer data between layers of a same application. Moreover, the DTO should never appear in your domain layer, it should be broken down into domain objects in your UI/API layer. – Vincent Savard Feb 12 '18 at 18:19
  • @VincentSavard: Should I treat CustomerSearch as a domain model? – robotron Feb 12 '18 at 18:29
  • 1
    Maybe, maybe not, only you can tell, if you do DDD, you're supposed to be the expert of your domain after all! (Or at least have access to domain experts which will help you model the problem.) – Vincent Savard Feb 12 '18 at 18:42
  • Well, both orderId and Customer are part of a larger structure. Up until now, it was really easy to call the IConsentSearch since all the relevant query data was in Customer class. But now, I have to either a) create a new, ad-hoc class simply to bundle up Customer and orderId or b) create a new method on the interface that has two parameters: Customer and orderId? Hm... Didn't think of option b) up until this second. Such an approach would go well with the Interface segregation (support your clients!) and wouldn't break the existing domain model with an ad-hoc class. – robotron Feb 12 '18 at 18:47

It seems you're facing two different problems:

  • You have a new requirement: search a Consent by an OrderId.

  • Somehow the API of your search component changed (or needs to change for this requirement).

A change to the web API should not affect the IConsentSearch interface. That's the whole point of this abstraction. Anything you have to do to make your application work with the new API must happen in the module that implements IConsentSearch (let's call it WebConsentSearch).

Now it appears that the new requirement does require a change to IConsentSearch. But the interface must serve the domain model, and be expressed in terms of the domain. So your interface should have a new method such as SearchByOrderId(Customer, OrderId) and it's again the job of WebConsentSearch to deal with this.

To answer your questions:

  1. In the background, WebConsentSearch may use all kinds of value objects to pass data between the sub-components that are involved.

    But the term DTO or ValueObject has no place in your domain layer - and you shouldn't introduce new domain objects just for the purposes of making life easier for your infrastructure. Your domain doesn't "know" what the infrastructure's needs are.

  2. In principle, those value objects may have a reference to a domain class. However, in most cases that's not the best idea. Instead, you should probably have an adapter that translates the domain object into a more basic data types that provides that information in a format convenient for your web API client.

    The 'added value' here is that only the adapter will depend on your domain model, making the API easier to change and easier to test.

[A DTO is, strictly speaking, for transferring data between processes. I've changed my answer to use the more appropriate term 'value object' above, though I think it's not uncommon to use the terms interchangeably. In any case, an actual DTO should generally not contain domain objects, because DTOs need to be easily serializable. Also see here]

  • Vincent Savard and you have convinced me that DTO is an unacceptable solution. Plus, I have wondered about DTOs referencing domain classes for some time and this was a great opportunity to ask more about it. – robotron Feb 12 '18 at 19:38
  • For sake of completeness, I wanted to clarify what the changes on the external web service API are: absolutely none. They simply instructed us to cram orderId in another existing field customerId, which obviously has nothing in common with the nature of orderId. In my domain model orderId and customerId are miles apart. Up until that moment our internal model and their interface were rather compatible, but them reusing an existing field for something completely different caught me by surprise. – robotron Feb 12 '18 at 19:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.