0

I'm looking for ways how to accept arguments in the domain model when you want to add items in it.

Let's take a look at this example:

public class Order {
    public ICollection<OrderItem> Items { get; set; }
    
    // Way 1
    public void AddItem(OrderItem item) {
       // Add
    }

    // Way 2
    public void AddItem(decimal amount, Address address, string cardNumber) {
       // Add
    }

    // Way 3
    public void AddItem(ItemArgs args) {
       // Add
    }
}

As you can see there I have been using 3 ways but which one is right and why?

Is that okay to build up domain model entities from outside? Like create OrderItem from outside of the domain model and pass it through AddItem method

In the second case I'm accepting many arguments if there will be more properties then it could grow up.

and last one where I have argument class where are properties aggregated.

For example if I need same properties as I have in OrderItem should I accept that type directly ? But that case not happens everytime. So I don't want to have different approaches in my domain model. I would like to implement the one and follow that.

I would like to hear it from you how you manage such things in your domain model?

1
  • 1
    Not 1. Probably 2. 3 is a different way of representing 2. In either case call sites will break if the requirements change - though if 3 is chosen the error may show up in a completely different context than where the method is called. Which brings us to another downside of 3: It also adds an additional vector of change (ItemArgs can be changed independently of the method requirements). I'm not so sure that needing to update the method signature to accept more arguments if/when they are needed is a downside! That is necessary complexity. Adding indirection doesn't really make it simpler. – king-side-slide Nov 5 '20 at 17:59
2

As you can see there I have been using 3 ways but which one is right and why?

The first thing that I would call your attention to is that this is purely a design question; what you are asking, really, is whether the OrderItem constructor should be called from inside the domain model or from the outside.

Design is what we do to get more of what we want than we would get by just doing it -- Ruth Malan

So the answer is going to be "it depends"; it depends on "what you want" -- once you understand that, then guessing which design gives you more of it is usually a straight forward process.

One of the ideas hidden with domain driven design is that the domain dynamics (my term for the parts of the domain that govern how information changes) should be described by an abstraction in the language of the domain itself. Imagine, if you will, an expert in your domain who is not a programmer, looking at your code and correctly identifying a bug because she can see the inconsistency between what you've written and how the domain should work.

In your example, that would normally mean we want

void AddItem(OrderItem item)

because that's the way we think about it when we are thinking in the language of the domain - we add Items, not "things that can be frankensteined together to create an Item", to orders. We describe the dynamics at that level of abstraction, and the other "computer stuff" happens somewhere else.

If you like, you can think on it through the lens of SRP - the responsibility of the domain model is the domain dynamics; the responsibility to assemble the information into abstractions that the domain understands belongs out in the plumbing.

(The line isn't normally quite so clean as that - the OrderItem constructor, and the Address constructor, and so on - are often deployed along side the domain dynamics, and if you look at the flow of control it will frequently pinball between modules until the data structures and abstractions are prepared.)

10
  • Letting the client instantiate and keep a reference to mutable internal state is a violation of encapsulation. Would you still allow addItem(orderItem) if OrderItem is an entity and not a value? Is the violation less important than reflecting the UL? I guess the addItem implementation could always re-create a new entity from the original one, but that will certainly add some complexity to the domain. – plalx Nov 5 '20 at 2:53
  • There's room to challenge the "violation" claim in the first sentence. Excusing that point for the moment, "it depends" -- what's the more of what you want? – VoiceOfUnreason Nov 5 '20 at 3:07
  • That said, multiple mutable references to state challenge a lot of simplifying assumptions, and the patterns described by Evans rather gloss over the whole thing. It's the sort of topic that calls for long discussions, and dissection of underlying assumptions, and so on. – VoiceOfUnreason Nov 5 '20 at 3:10
  • Well, with "it depends" it usually helps to understand what should be considered to make an enlightened decision? In which situation would you find that acceptable and in which cases you wouldn't? In order to preserve encapsulation I never hold onto injected mutable state and always opt for additional value classes (e.g. OrderItemData). This does sacrifice UL alignment for technical concerns though. I like the integrity guarantees this gives to the model, but I never liked the purely technical concepts introduced (immutable DTOs). I guess that's a non-issue in pure FP languages too. – plalx Nov 5 '20 at 3:14
  • @plalx "Letting the client instantiate and keep a reference to mutable internal state is a violation of encapsulation" - only if the thing that the client instantiates is actually internal state, and not a data structure (or even a proper object) that's part of the interface of an internal component (what you described as an additional value class - <Something>Data - but note that other people may not use the same naming convention, and in general it doesn't have to have value semantics, although it often does). – Filip Milovanović Nov 5 '20 at 5:33

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.