In DDD how to pass Value Objects via DTO?
Asked Answered
I

2

6

In my domain each Domain Entity may have many Value Objects. I have created value objects to represent money, weight, count, length, volume, percentage, etc.

Each of these value objects contains both a numeric value and a unit of measure. E.g. money contains the monetary value and the currency ($, euro,...) , weight contains the numeric value and the unit of weight (kilo, pound, ...)

In the user interface these are displayed side-by-side as well: field name, its value followed by its accompanying unit, typically in a properties panel. The domain entities have equivalent DTOs that are exposed to the UI.

I have been searching for the best way to transfer the value objects inside the DTOs to the UI.

  1. Do I simply expose the specific value object as a part of the DTO?
  2. Do I expose a generic "value object"-equivalent that provides name/value/unit in a DTO?
  3. Do I split it into separate name/value/unit members inside the DTO, just to reassemble them in the UI?
  4. Do I transfer them as a KeyValuePair or Tuple inside the DTO?
  5. Something else?

I have searched intensively but no other question seems to quite address this issue. Greatly appreciate any suggestions!

EDIT: In the UI both values and units could get changed and sent back to the domain to update.

Integrator answered 22/2, 2015 at 0:9 Comment(10)
1) seems like the simplest approach, why make it more complicated?Grimaldo
@dbugger, many discussions on value objects. Most seem to indicate to not expose domain objects as an anti-pattern. I am open to exposing them, but would like to understand the pros and cons.Integrator
Yes, there can be coupling issues in the case of core domain entities. However, in this instance, adding a set of duplicate objects and the extra code for conversions seems to be adding undo complexity.Grimaldo
What happens to the amount field for a "MoneyDTO" when the user edits the "Currency"? Does this have a ripple effect to other fields in the "Order" (e.g. a totals, VAT, ...).that now need to change? Is so: (1) who is responsible for keeping these all consistent and (2) do you marshal this modified MoneyDTO instance back in an incorrect state (changed currency, unchanged amount)?Leoni
@Alex, this domain does not care about VAT etc. This could be considered master data. For example (a simple example) a supplier has quoted their purchase price in US$ even though they may be located in Canada. For some reason a new price going forward is quoted in CA$. The domain has the time-variable exchange rates already and just converts all monetary values to the base currency (which could be US$) if needed before calculating overall optimal plans in a single currency. Naturally a change in currency does not happen very often, the value will.Integrator
Ok, so do I interpret this as the following answers (1) the domain is responsible for changing the amount and keeping the consistency and (2) the DTO is sent in an incorrect state (new currency, unchanged amount)?Leoni
@Alex, honestly neither domain nor DTO would ever have any way of logically knowing if the value/currency combination is incorrect. It is input data. Usually sourced from an external system/db/file, sometimes via manual entry in the UI. If the actual value increased by 50% by switching say euros to swiss francs without changing the value who is to say that is an error or maybe the supplier did really increase their price with a currency change, or maybe the planner is doing what-if analysis. The context would determine which, but all are options with the same data.Integrator
Stefan, but this is exactly what is problematic with a UI or exposed API where effectively "domain object instances" are edited. This does not reveal intention. The examples you mention do and - so I suspect - are part of the language of your domain that you are now not capturing. I would imagine it is relevant for your organization to know that there was an error (currency changed, amount unchanged) or that the supplier increased its price, or to have explicit support for a "what-if" analysis that does not modify actual "live" data.Leoni
@Alex, the context would show which case it concerns. And yes live data and what-if data are different members. Both could still be a MoneyDTO, one editable by the user, the other not. Are you suggesting take one approach with those Money objects (and other value objects) that are editable and another one with ones that are not, even when they are part of the same domain entity? In the UI for best UX I intend to show these very similar, albeit one field is editable and another is not. This may even be role specific.Integrator
Let us continue this discussion in chat.Leoni
H
2

I would be inclined to agree with debuggr's comment above if these are one-way transfers; Value Objects aren't really Domain objects - they have no behaviour that can change their state and therefore in many ways they are only specialised "bit-buckets" in that you can serialise them without losing context.

However; if you have followed DDD practices (or if your back-end is using multi-threading, etc) then your Value Objects are immutable i.e they perhaps look something like this:

public class Money
{
    readonly decimal _amount;
    readonly string _currency;
    public decimal Amount {get{return _amount;}}
    public decimal Currency {get{return _currency;}}

    public Money(decimal amount, string currency)
    {
        //validity checks here and then
        _amount=amount;
        _currency=currency;
    }
}

Now if you need to send these back from the client, you can't easily re-use them directly in DTO objects unless whatever DTO mapping system you have (custom WebAPI Model binder, Automapper, etc) can easily let you bind the DTO to a Value Object using constructors...which may or may not be a problem for you, it could get messy :)

  • I would tend to stay away from "generic" DTO objects for things like this though, bear in mind that on the UI you still want some semblance of the "Domain" for the client-side code to work with (regardless of if that's Javascript on a Web Page or C# on a Form/Console, or whatever). Plus, it tends to be only a matter of time before you find an exceptional Value Object that has Name/Value/Unit/Plus One Weird Property specific to that Value concept

  • The only "fool-proof"*** way of handling this is one DTO per Value Object; although this is extra work you can't really go wrong - if you have lots and lots of these Value Objects, you can always write a simple DTO generation tool or use a T4 template to generate them for you, based on the public properties of your Value Objects.

***not a guarantee

Hiddenite answered 22/2, 2015 at 2:0 Comment(4)
"they have no behaviour" That's totally wrong. You should strive to put behavior on VOs when it's the behavior's natural home.Trappings
@Trappings yep you are right, I should have clarified that they have no mutating behaviour, which is what I meant to say. Answer is updated.Hiddenite
@StephenByrne, yes the client could update these and send them back. So if I understand correctly, if in the domain I have an Order root entity with Money value object, in your "fool-proof"*** way I would also have an OrderDTO containing MoneyDTO.Integrator
@StefandeKok - yes, pretty much - and then you have some kind of mapping factory or helper to turn a MoneyDTO into Money, Money into a MoneyDTO, etc. The likes of Automapper can be very useful for this, but you can roll your own as well - the main thing is to keep the mapping centralised so you have only one place to configure it.Hiddenite
L
2

DDD is all about behavior and explicitly expressing intent, next to clearly identifying the bounded contexts (transactional and organizational boundaries) for the problem you are trying to solve. This is far more important than the type of "structural" questions for which you are requesting answers.

I.e. starting from the "Domain Entities" that may have "Value Objects", where "Domain Entities" are mapped as a "DTO" to show/be edited in a UI is a statement about how you have structured things, that says nothing about what a user is trying to achieve in this UI, nor what the organization is required to do in response to this (i.e. the real business rules, such as awarding discounts, changing a shipping address, recommending other products a user might be interested in, changing a billing currency, etc).

It appears from your description, that you have a domain model that is mirroring what needs to be viewed/edited on a UI. That is kinda "putting the horse behind the carriage". Now you have a lot of "tiers" that provide no added value, and add a lot of complexity.

Let me try to explain what I mean, using the (simplified) example that was mentioned on having an "Order" with "Money". Using the approach that was mentioned, trying to show this on screen would likely involve the following steps:

  1. Read the "Order Entity" for a given OrderId and its related "Money" values (likely in Order Lines for specific Product Types with a given Quantity and Unit Price). This would require a SQL statement with several joins (if using a SQL DB).
  2. Map each of these somehow to a mirroring "domain objects" structure.
  3. Map these again to mirroring a "DTO" object hierarchy.
  4. Map these "DTO" objects to "View" or "ViewModel" objects in the UI.

That is a lot of work that in this example has not yielded any benefit of having a model which is supposed to capture and execute business logic.

Now as the next step, the user is editing fields in a UI. And you somehow have to marshal this back to your domain entity using the reverse route and try to infer the user's intent from the fields that were changed and subsequently apply business rules to that.

So say for instance that the user changes the currency on the "MoneyDTO" of a line item. What could be the user's intent? Make this the new Billing Currency and change it for all other line items as well? And how does this relate to the business rules? Do you need to look up the exchange rate and change the "Moneys" for all line items? Is there different business logic for more volatile currencies? Do you need to switch to new rules regarding VAT?

Those are the types of questions that seem to be more relevant for your domain, and would likely lead to a structure of domain entities and services that is different from the model which is viewed/modified on a UI.

Why not simply store the viewmodel in your database (e.g. as Json so it can be retrieved with a single query and rendered directly), so that you do not need additional translation layers to show it to a user. Also, why not structure your UI to reveal intent, and map this to commands to be sent to your domain service. E.g. a "change shipping address" command is likely relevant in the "shipping" bounded context of your organisation, "change billing currency" is relevant in the "billing" bounded context.

Also, if you complement this with domain events that are generated from your domain, denoting something that "has happened" you get additional benefits. For example the "order line added" event could be picked up by the "Additional Products A User Might Be Interested In" service, that in response updates the "Suggested Products" viewmodel in the UI for the user.

I would recommend you to have a look at concepts from CQRS as one possible means for dealing with these types of problems. As a very basic introduction with some more detailed references you could check out Martin Fowler's take on this: http://martinfowler.com/bliki/CQRS.html

Leoni answered 22/2, 2015 at 18:9 Comment(3)
Hi @Alex, thank you for the detailed answer. I purposely stated my question as simple and generic as possible. In reality my domain is very complex, but the value objects do have very close similarity between UI and domain (as it should be I believe). Most DTOs I have seen used contain lots of primitives. It just seemed the wrong way in DDD to me, but could not find any specific guidelines in this area.Integrator
@StefandeKok Stephan, I agree that using types that reflect the language of the domain and immutable value types (such as a Money type that includes the currency) is a good practice in general. However mirroring the UI as a domain model (or the other way around), generally is not. Then you are basically doing CRUD + Forms with a lot of unnecessary (non-value added) layers in between.Leoni
I think we are in full agreement. I +1-ed your answer since it does contain quite a few helpful tips, but will likely mark Stephen Byrne's answer as the official answer since it addresses the specific issue I was struggling with. Waiting for a few days to give others option to provide other answers.Integrator

© 2022 - 2024 — McMap. All rights reserved.