DDD Factory Entity Value Object
Asked Answered
S

2

6

I have been increasingly adopting DDD / Onion Architecture in my current project. One of the many things I am still unclear about is how much encapsulation should there be. Easier explained with a concrete example.

Example

namespace MyProject.Model
{
    public class ComplexEntity
    {
        private int _id;
        public int Id { get {return _id;} }
        public ValueObjectA ValueA {get; set;}
        public bool IsBool {get; set;}
        public ComplexEntity(ValueObjectA a, bool isBool)
        {
            // Do some validation first
            ValueA = a;
            ValueB = b;
            IsBool = isBool;
        }
    }

    public class ValueObjectA
    {
        public bool IsBoolA {get; private set;}
        public bool IsBoolB {get; private set;}
        public ValueObjectA(bool a, bool b)
        {
            IsBoolA = a;
            IsBoolB = b;
        }
    }

    public Interface IComplextEntityFactory
    {
        // Option 1
        ComplexEntity Create(
            ValueObjectA a,
            bool IsBool);

        // Option 2
        ComplexEntity Create(
            bool valueABool a,
            bool valueBBool b,
            bool isBool);
    }
}

Question

For the factory of an entity, do you,

  1. Expect the caller to construct the value objects for you and use it to initialize the ComplexEntity?
  2. Have essentially CLR basic types being passed to the factory and you construct every ValueObject that makes up the Entity?

I am leaning towards option 2 but I can't seem to find supporting literature for it.

Edit 1

To be honest I am still no clearer. What about aggregate roots?

When my entity refers to other entities, e.g. below.

  1. Should I have an IComplexEntityFactory, ILessComplexEntityFactory? Or just an IComplexEntityAggregateFactory that creates the LessComplexEntity and instantiates ComplexEntity ?
  2. In the case of the AggregateFactory solution, what do I do if the LessComplexEntity attribtues that have been passed to the factory correspond to an existing LessComplexEntity? Do I retrieve and reuse it from a Repository? Or do I return an error to the caller?
  3. What would the method signature be for the AggregateFactory? Would it be (ValueObject a, ValueObject b), or (ValueObject value, LessCompelxEntity entity)

    public class ComplexEntity { private readonly int _id; public int Id { get { return _id;} }

    public ValueObject Value {get; set;}
    public LessComplexEntity Entity {get; set;}
    
    public ComplexEntity(int id, ValueObject value, LessComplexEntity entity)
    {
    }
    

    }

    public class LessComplexEntity { private readonly int _id; public int Id { get {return _id;} } public ValueObject Value {get; set;} public LessComplexEntity(int id, ValuObject value) { } }

Sinusitis answered 9/7, 2012 at 12:29 Comment(3)
I would go with #1, it would be really tedious if you have to pass e.g. multiple Address vo's to an purchaseorder otherwise. CreatePurchaseOrder( otherArgs , ShippingAddress, BillingAddress ...)Interpol
@RogerAlsing but then I don't understand the benefit of the factory. All it would be doing then is just calling the constructor of the ComplexEntity rather than creating all the subcomponents that make it up.Sinusitis
Its about semantics IMO, OrderFactory.CreateVIPCustomerOrder , OrderFactory.CreateAnonymousCustomerOrder etc. initializing the aggregate roots differently depending on context.. it should not be just another layer of indirection. the factory methods could have alot less args than the AR constructor, or vice versaInterpol
D
5

I'd choose option 1.

  • Makes explicit to everyone that you need a ValueObjectA to build a ComplexEntity. More readable, less head scratching when you see the method used somewhere.

  • If ValueObjectA changes, you'll have to modify the code in only one place (the factory's caller) as opposed to changing the signature of Create() + adjusting the value object creation inside the factory.

  • Less parameters to the factory's Create() method is less verbose and more readable.

  • In unit tests, it gives you a lot more options to be able to inject the ValueObjectA you want. There's not a lot you can do in terms of testing if you keep ValueObjectA's creation hidden inside the factory entirely.

[Edit]

It's not clear what your real problem is with Aggregate Roots and Factories, but you shouldn't mix up the responsibility to retrieve/rehydrate an existing object with the responsibility to create an object.

I'd take as a general rule that a Factory's job is to assemble a new object from smaller parts (whether they be primitive types, value objects, entities...)

The Factory should be provided with all these parts, it is not its duty to retrieve or rehydrate them from somewhere. You can leave that to the Factory's caller. It will make the Factory more cohesive and coupled to less classes (Repositories, etc.)

Dullard answered 9/7, 2012 at 14:26 Comment(1)
thanks, that's a little clearer with good reasons. I am not clear though about AggregateRoots then?Sinusitis
P
2

I prefer Option #1 as it would reduce the number of parameters required on the factory method. But my advice would be to only use a factory when the aggregate in question requires a creation strategy.

In one of your comments you say:

"..but then I don't understand the benefit of the factory. All it would be doing then is just calling the constructor of the ComplexEntity rather than creating all the subcomponents that make it up."

The factory's job is to make decisions on how to instantiate the aggregate by looking at the data passed to it. In my experience a common scenario where this is required is where inheritance/polymorphism has been used. For example, imagine you have an abstract Account class, with two sub classes: SavingsAccount & CurrentAccount. The factory's job is to decide on which one to instantiate, given some data.

Another potential benefit of a factory is how expressive you can be. If an aggregate can be instantiated in more than one way (i.e. different arguments) then this can be expressed better with a method name on a factory than an overloaded constructor.

As I say, my advice is to not create a factory for every aggregate unless for one of the above reasons. Otherwise, as you have pointed out, it will just be one line calling the constructor with the same arguments passed to it.

Paleogeography answered 9/7, 2012 at 14:1 Comment(1)
thanks. What about the case when an aggregate root exists and people pass in parameters that match a corresponding entity that's part of the aggregate. Do you retrieve it from a repository? or return an error?Sinusitis

© 2022 - 2024 — McMap. All rights reserved.