1

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:

  1. A person must have an organization

    • this is enforced via the Person's constuctor
  2. An organization must know of its people

    • this is why the Organization has a CreatePerson method
  3. 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?

  • 2
    The most idiomatic way of handling circular dependencies is to avoid them in the first place. – Pete Jun 11 '14 at 19:02
5

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.

5

Seems like this would be easier to do if there were a third entity: Membership. Your three rules are enforced:

  1. Person handles - must have a Membership (which requires an Organization)
  2. Organization - gets it's list of people from the membership, so it knows about them.
  3. Membership - maintains unique Person and prevents same person from joining another Organization.

As part of the new membership process, you can pass the Person to the Organization to see if it meets any criteria and visa versa. You can enforce mutual agreement to membership or not.

I realize in the real world, Membership is an abstract concept, but exists in a way. It's typically a mutual agreement that binds these two entities together. Can an Organization make a Person a member without the Person's approval, cooperation, etc? Spam is a form of non-mutual membership into an organization's list. It's not practical in this case to enforce any rules in either direction: You're a member, pay your dues and I paid my dues so provide the services that comes with my membership.

  • Maybe that's a better model for the described requirements, maybe not. But honestly, I don't see how this is an answer to the original question. An additional Membership entity IMHO won't make it neither easier nor harder to enforce the correct relations when a new Person entitity gets created. – Doc Brown Jun 11 '14 at 18:54
  • 2
    I agree with this answer. It much more realistically models how the business likely works (I doubt each Person contains and Organization, nor can an Organization create a Person), which ultimately is what DDD is all about. – Eric King Jun 11 '14 at 20:09
2

Make either the Person constructor "internal", or make the _people list accessible by an internal property, so the Person constructor can add this to _people right after creation.

Of course, this solution is not perfect since it will still allow other classes belonging to the same domain assembly wrong usage of your objects, but at least it will forbid any wrong usage from outside (which is typically sufficient for most real world cases).

If you are thinking of a more restricted model, where only the Person class can access a certain method of Organization, or vice versa: the C# designers did not implement such a thing, AFAIK intentionally. See, for example, this SO post and the topmost answer.

1

You can guarantee that the bi-directional association between the Person and Organization objects cannot be broken by code outside the classes themselves by using events:

public class PersonCreatedEventArgs : EventArgs
{
    public readonly Person Person;
    public readonly Organization Organization;

    public PersonCreatedEventArgs(Person person, Organization organization)
    {
        this.Organization = organization;
        this.Person = person;
    }
}

public class Person
{
    public static event EventHandler<PersonCreatedEventArgs> PersonAdded;
    public static event EventHandler<PersonCreatedEventArgs> PersonRemoved;

    private Organization organization;
    private string name;

    public string Name { get { return name; } private set { name = value; }}

    public Organization Organization { 
        get { 
            return organization;
        }
        set {
            if (organization == value)
                return;
            if (organization != null && PersonRemoved != null)
                PersonRemoved(this, new PersonCreatedEventArgs(this, organization));
            organization = value;
            if (organization != null && PersonAdded != null)
                PersonAdded(this, new PersonCreatedEventArgs(this, organization));
        }
    }

    public Person(Organization organization, string name)
    {
        if (organization == null)
        {
            throw new ArgumentNullException("organization");
        }

        Name = name;
        Organization = organization;
    }
}

public class Organization
{
    static Organization()
    {
        Person.PersonAdded += Person_PersonAdded;
        Person.PersonRemoved += Person_PersonRemoved;
    }

    static void Person_PersonRemoved(object sender, PersonCreatedEventArgs e)
    {
        e.Organization._people.Remove(e.Person);
    }

    static void Person_PersonAdded(object sender, PersonCreatedEventArgs e)
    {
        e.Organization._people.Add(e.Person);
    }

    private readonly List<Person> _people = new List<Person>();

    public IEnumerable<Person> People
    {
        get { return _people.AsReadOnly(); }
    }
}

In this scheme, setting the Organization of a Person fires events notifying the Organization of the association.

Since the "PersonRemoved" and "PersonAdded" events can only be fired from within the Person class itself, a Person can only be constructed with a given Organization, and the "People" list is returned read-only, all code outside the two classes is prevented from damaging the bi-directional association. In practice you'll want to add some asserts in the events to make sure the data stays consistent (in case somebody does something nasty with reflection, or in case of serialization errors, or etc.)

  • While this looks great, It might be problematic to implement when handling a persistent data. Eg. when organization is not actually loaded when handling person or other way around. But I guess OP is not concerned about this. – Euphoric Jun 18 '14 at 13:04
  • @Euphoric - To serialize the entire data set, OP would probably want to see the list of persons as XML children of their organization. Marking the "Organization" property as "[NonSerialized]" and adding a setter for "People" that loops through the incoming enumerable and links everything up could do the job. Special code would be required to independently serialize/deserialize a specific Person though. – dbc Jun 18 '14 at 21:13

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.