I have a domain where an Organization has People. Organization Entity public cla
ID: 639823 • Letter: I
Question
I have a domain where an Organization has People.
Organization Entity
public class Organization {
private readonly List<Person> _people = new List<Person>();
public Person CreatePerson(string name) {
var person = new Person(organization, name);
_people.Add(person);
return person;
}
public IEnumerable<Person> People {
get { return _people; }
}
}
Person Entity
public class Person
{
public Person(Organization organization, string name) {
if (organization == null) {
throw new ArgumentNullException("organization");
}
Organization = organization;
Name = name;
}
public Organization { get; private set; }
public Name { get; private set; }
}
The rule for this relationship is that a Person must belong to exactly one Organization.
The invariants I want to guarantee are:
A person must have an organization
this is enforced via the Person's constuctor
An organization must know of its people
this is why the Organization has a CreatePerson method
A person must belong to only one organization
this is why the organization's people list is not publicly mutable (ignoring the casting to List, maybe ToEnumerable can enforce that, not too concerned about it though)
What I want out of this is that if a person is created, that the organization knows about its creation.
However, the problem with the model currently is that you are able to create a person without ever adding it to the organizations collection.
Here's a failing unit-test to describe my problem
[Test]
public void AnOrganizationMustKnowOfItsPeople()
{
var organization = new Organization();
var person = new Person(organization, "Steve McQueen");
CollectionAssert.Contains(organization.People, person);
}
What is the most idiomatic way to enforce the invariants and the circular relationship?
Explanation / Answer
Multiple points:
When I was reading DDD, I remembered one thing: You want to avoid double-sided relationships if possible. You should only define one direction and have the other direction be accessible through query in repository. In your case, you could have Organization as attribute of Person, but not have list of Person as part of Organization. And make it so repository contains GetPeopleOfOrganization method to query for that information.
Next thing to note is that it is often acceptable to have the data in invalid state for short period of time. The point is that system doesn't allow you to persist this state and the invalid state is not visible to any other part of the system. This is the case with transactions. It is possible to have invalid state during transaction, but transaction will fail when you try to commit it with the invalid state. And system doesn't allow different transactions to see what is happening inside other transactions.
And related to above: The entity is not really part of the model until it is added to the list/repository of all entities. It is fine to try having the data valid before that, but it might cause problems, that might easily be solved when the integrity check is done in repository when data is saved.
And last thing. I agree that being able to enforce invariants and integrity with just code and during compilation is great. But C# simply doesn't have type and language facilities to allow for that. It is no shame to rely on run-time checking and exceptions. As long as you ensure that the code crashes in development, instead of production, everything should be fine.
Related Questions
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.