0

Currently, my aggregates and value objects have protected constructors and some of them are being created by static factory methods inside the aggregate with descriptive names. It creates a nice DSL and pretty encapsulated model but makes the unit testing painful (if the domain model creation fails, the service/command handler will be marked as failed as well). Introducing factory methods on the other hand for each entity, and value object forces me to inject a factory interface per entity/value object into the service.

Is it ok to create a factory service per aggregate root with a factory method per entity/value object? Here's an example of a factory service which creates a company aggregate root, and it's internal entities/VOs:

public class CompanyFactory : ICompanyAggregateRootFactory
{
    public Company CreateCompany(...){}
    public Employee CreateEmployee(...){}
    public CEO CreateCEO(...){}
    ....

}

Is there any other way which enforces the same level of encapsulation and DSL clarity without making the unit tests depending on one another?

  • 1
    still not entirely clear on what you are asking. Why are factories mandatory for every object? Splitting the interface by responsibility would be standard, but what are you attempting to achieve? where are your statics? – Ewan Jul 30 '18 at 10:48
  • also, how are you using protected constructors with this factory? – Ewan Jul 30 '18 at 10:49
  • A related question is here : stackoverflow.com/questions/9890469/… – Mohsen Jul 30 '18 at 11:33
  • @Mohsen it seems you're mixing up a lot of questions and concepts here. It's unclear why you find aggregate factory methods painful to unit test in the first place, and the line of thought that follows is strange: " Introducing factory methods on the other hand for each entity forces me to inject a factory interface" - what are you contrasting it with, why and why would it force you to do such a thing? – guillaume31 Jul 30 '18 at 11:36
  • @guillaume31: > why you find aggregate factory methods painful to unit test? Because some of them are static (because of the protected constructors). – Mohsen Jul 30 '18 at 11:43
1

IMO doing outside-in TDD isn't a valid enough reason to introduce the kind of "factory service" you mentioned.

  • If constructing a domain object involves complicated logic (such as composing it from multiple different sources), then it might be worth it to put that logic in a separate Factory class. You can mock that class in tests if you want to, but no need for an additional creation façade.

  • If it is simple value-to-field assignment, the logic is probably within the reasonable responsibility of the object itself. Don't try to mock it. Just because you don't mock it doesn't mean it breaks outside-in TDD. Newing up an object you need, at any step of an outside-in TDD process, is legit.

all of your service layer tests are being failed as long as you are not implemented the static factory method

But adding an extra layer of "Factory Service" doesn't add any value and isn't the solution. It just shows that your Application Service is able to talk to a layer that didn't exist before.

Implementing a minimal Entity and its constructor is small enough work to allow you to pass your service layer tests with roughly the same, or even less, effort than writing a creation interface and mocking it.

Besides, the typical assertion in outside-in tests when you're creating something is to spy on a Repository or data access object to know if the object received the new thing to store. This defuses the "inner construction method vs dedicated factory interface" debate with regard to testing, because that's not where you exercise your tests.

As a side note, some people recommend to avoid mocking domain objects altogether (a.k.a. mock only at architecturally significant boundaries).

3

makes the unit testing painful

Part of the motivation for "test first" development is the heuristic that awkward tests imply awkward interfaces -- if writing the test clients is painful, that's a hint (but not necessarily a promise) that writing the production clients will also be painful.

Is it ok to create a factory service per aggregate root with a factory method per aggregate/value object?

You probably mean "with a factory method per entity/value object" -- we normally have one or more entities per aggregate, but nesting multiple aggregates isn't a common pattern.

And yes, if you think that interface is easier to work with, then fine, go right ahead.

Is there any other way which enforces the same level of encapsulation and DSL clarity without making the unit tests depending on one another?

You might get a bit more separation by using role interfaces to describe the different parts of the API. For example, if we are writing a unit test about CEO's, then we don't care much about the capability to create other entities. So we might prefer

interface ICreateCEO {
    CEO createCEO(...);
}

class CompanyFactory implements ICreateCEO ... {
    CEO createCEO(...);
}

But that's definitely "might", rather than "should" -- the "extra" interfaces aren't free to maintain, so you want to be sure you are getting compensation somewhere.

  • I really care about the DSL, and cohesion in my domain model. Injecting 3 factories into the service makes a kind of divergent and difficult API to my point of view. – Mohsen Jul 30 '18 at 11:17
-1

Static is almost always a bad word. Try not to type it.

You should be able to allow injection and also have a default factory, since you don't mind coupling a factory to your aggregate type

public class Service
{
    public Service(IFactory fac = new MyFactory()) {...}
}

Now you can skip injection in you appplication and inject a mock in your tests

  • Thanks, @Ewan, but my question was not that general. Is it OK for a single factory service to create aggregate root, and it's aggregates, and value objects? And what are pros/cons of dong so? – Mohsen Jul 30 '18 at 10:28
  • hmm not sure what you mean there, a factory that creates more than one type of object? maybe add some sample code to your question – Ewan Jul 30 '18 at 10:36
  • Sure. I added. But look at the tags at first. – Mohsen Jul 30 '18 at 10:45

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.