Is it ok for entities to access repositories?
Asked Answered
P

5

32

I've just started working with DDD, so maybe this is a silly question...

Is it ok for an entity to access a repository (via some IRepository interface) to get a value at runtime? For example, I want to enforce a "default" selection for a property:

class Person {
    private Company _employer;

    public Company Employer {
        get { return _employer; }
        set { 
            if(value != null) {
                _employer = value;
            } else {
                _employer = employerRepository.GetDefaultEmployer();
            }
        }
    }

    ...
}

My question is whehter doing something like this is a horrible violation of DDD principles. And if it isn't, my next question would be what it the best way to provide the repository to use? Should it be supplied when the Person object is created?

Thanks, P

Picco answered 6/5, 2009 at 1:39 Comment(1)
Domain driven design is a lot about what constitutes boundaries and where responsibilities lie within those boundaries. A good rule to follow is if certain functionality on an entity seems to overstep the boundaries of that entity; to extract said functionality into a domain service. I would create a PersonDefaultsService with a method such as ApplyDefaults (that replaces certain null values with default values) that should be called when a Person gets created or before it's updated in your application service.Thach
A
26

it's not a horrible violation of DDD it's a horrible violation of... well... it's just plain horrible (i say this tongue in cheek) :).

First off, your entity becomes dependent on having a repository... that's not ideal. Ideally you'd want to have your repository create the Person and then assign it everything it needs to be effective in the current domain context.

So when you need a Person, you'll go personRepository.GetPersonWithDefaultEmployer() and get back a person which has default employer populated. The personRepository will have a dependency on an employerRepository and use that to populate the person before returning it.

PersonReposotory : IPersonRepository
{
    private readonly IEmployerRepository employerRepository;

    //use constructor injection to populate the EmployerRepository
    public PersonRepository(IEmployerRepository employerRepository)
    {
        this.employerRepository = employerRepository;
    }

    public person GetPersonWithDefaultEmployer(int personId)
    {
        Person person = GetPerson(personId);
        person.Employer = employerRepository.GetDefaultEmployer(personId);
        return person;
    }
}
Angers answered 6/5, 2009 at 1:54 Comment(5)
I'm glad to know that having the entity know about the repository was a bad code smell! That said, I like your solution above for creating new Person objects (and in fact already do something like that), but how I handle the situation where the Employer is being changed after the Person has been created? One solution would be to move the logic out to a separate domain service class that has the logic for selecting the default Employer. But that service would still need access to the Employer repository - perhaps that's preferable?Picco
why can't you just save the person using the personRepository and put the encapsulating logic in there?Angers
I'm not sure if I follow you - this is not an issue of saving, but rather, updating the in-memory Person object. The rule of not allowing the Employer to be null seems like it would be a business rule that shouldn't be in the repository. My app is not a web app, but rather one with a full object graph in memory at all times. I'm not constantly loading objects from the database, only when the app starts.Picco
regardless, you don't want a dependency on the EmployerRepository in your Person entity. When the person is created it is the job of the repository or a seperate service that calls the repository and returns the person to make sure the person object is in a valid state. When you need a person, call the person service which will call the person repository and the employer repository and construct a valid person object for you to use. It's even more important you do this with your full object graph as you will want your service to keep track of your person instancesAngers
If Entity should not now anything about repository (I'm talking about Interface), how would you implement, for example user->activate() method? (user is domain entity). Who should delegate to users repository to update user status to "active"?Lenny
C
7

The sort-of answer to your question is the standard: It depends .

As a rule of thumb, don't ever do this. Keep your entities without references to repositories.

[putting practical hat on] In some extremely rare cases where you have a very, very, very good reason for doing this, add a big comment explaining why you're doing it and do it: add the reference or use Double Dispatch to pass the repository[hat off]

Also, if you wish to follow DDD principles, it's highly recommended you have access to a domain expert and an iterative process of development ( see Eric Evans - what i've learned since the book ).

With your domain expert you should define bounding contexts and most importantly the aggregates and their aggregate roots and their entities and value objects. Going down the DDD road is not easy at first but the rewards are worth it.

A few things regarding the code you posted:

  1. It's not recommended to have public setters on your entities. Use methods instead that express the intent better.

  2. If a person instance is created without initializing the _employer field, the getter for the Employer property will return null. If you then set the value of the Employer property to null, the next call to the getter will return a non-null value. This is probably unexpected by the users of your class.

  3. The caller setting the Employer of the Person (either by public setter or public method) should know the exact Company instance it wants to set, even if it's the default one. Maybe the caller can have the reference to the repository.

  4. Depending on your concrete domain, the Company might be a value object. In that case instead of initializing the _employer with null you could initialize it with the default value for the value object. This could be the case if you only have very few companies (1-2) and they are immutable and don't have specific behavior.

Corriveau answered 22/3, 2011 at 9:58 Comment(0)
S
5

I think it is easy to say an entity shouldn't be aware of repositories, but hard to put it into practice. Especially when an aggregate gets a big collection of vo's inside it, we have to refactor it and delegate operations like add to some domain service that actually acts as a repository to avoid the overhead of loading entire collection into memory.
But I don't think it's reasonable to let entities know of repositories. If we have to , then use domain services instead.We should also consider if a repository violates the Single Responsibility principle -- it should have been thought of as a Collection of aggregate roots, not a normal factory.

Sulfatize answered 11/8, 2013 at 18:2 Comment(0)
F
0

Is it really what is recommanded doing ddd ?

And what if you don't have an in-memory repository, but a relationnal database for your repository, and you want to get 1000 persons with their employer ? Are you going to make 1000 queries through employerRepository call...?

I would use NHibernate or any ORM to help implement personRepository. With NHibernate, I will use Hibernate Query close to this one : "from Person join fetch Employer" which will load an "Employer" instance in each "Person" instance, with only one SQL query.

Is it a violation to DDD ?

Firsthand answered 20/3, 2011 at 18:25 Comment(0)
B
0

First of all, I think the entity itself and how to assembly the entity are 2 duties in fact. So ideally it is better to distribute them into different class. But that's depends on too.

Breughel answered 10/8, 2013 at 5:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.