1

We are currently beginning an upgrade to our system and have decided to follow DDD methodology. The system is highly customizable by the users to allow them to tailor the software to their specific needs. This has led to some confusion on the proper way to implement properties on some of our aggregate roots.

Let us say we have the AR Provider

public sealed class Provider : IAggregateRoot
{
    public Guid Id { get; private set; }
    public int Version { get; private set; }

    public Name Name { get; private set; }
    // Other value objects
}

Now, we have to add a property relating to the Provider's race. The valid choices for the setting are controlled by the user. Here is where the questions arise.

There is going to be some area of the app that controls these settings, so for now let's refer to it as Settings.

The valid races as entered by the user in the Settings seem to be value objects, but are they?

  1. From the perspective of identity, they really don't have one. They are entirely defined by their values, thus value objects.
  2. If Race is a value object, do we just make it a value object in the domains that use the Race value and keep it consistent with changes in Settings?
  3. If Race is not a value object and instead an Entity with an Id, do we keep an id reference in the Provider aggregate root? Is it even valid to keep a reference to an entity that isn't an aggregate root in another domain?

We are leaning towards making them value objects for the moment but want to make sure that is the correct way to go before we move forward. We have a lot of these user defined ranges for various properties and once we choose a way forward, we want to stick with it for consistency.

In a related area of inquiry, when we are doing validation on a new Provider we want to verify that the value being chosen is a valid value in that instance. At that point do we just use domain communication methods to request a list of valid Race values then pass those into the AR creation logic for validation purposes?

  • Which way is going to be easiest to maintain? – Robert Harvey Mar 1 '18 at 2:28
  • what amazes me is the number of questions which start "we have decided to use X" I mean, what if you cant find the answer, do you go ahead anyway? – Ewan Mar 1 '18 at 15:43
  • why does your AR have an interface? – Ewan Mar 1 '18 at 15:45
1

Let's start with a brief discussion about Entities, Aggregate Roots, Value Objects, and identity as some confusion here seems to be a big contributor to your problem.

Entities are objects that have a conceptual life-cycle (not to be confused with an object life-cycle). Because of the above conceptual behavior, there must be a way to distinguish one from another. How that is done is purely an implementation detail of the system. Often, because an RDBMS is chosen as a backing store for the system, Entities will contain an arbitrary Id attribute that allows the system to distinguish one from another. In other cases, a natural key may exist (through either a single or multiple attributes in combination). But again, how two Entities are distinguished is an implementation detail.

Aggregate Roots are simply Entities that require other Entities to enforce their invariants. Because of this, they must encapsulate access/modification to those other Entities for transactional consistency. From the "outside" there is no difference (and often no distinguishing characteristic) between an Entity and an Aggregate Root. A client of a system needn't know whether they are working with an Entity or an Aggregate Root.

This brings us to Value Objects. A Value Object has no conceptual identity, no life-cycle and can, therefore, be created and destroyed with ease. The above does not mean a Value Object does not have a physical identity (a primary key in RDBMS terms), rather, that this identity is hidden from your domain. After all, you still may need to store/relate data for normalization purposes.

From the above we can now see that it is the behavior of an object that classifies it as an Entity or Value Object, not some analysis of the data the object contains (implementation details). For example, Address is the quintessential example of a Domain Object that often confuses developers as to whether it should be treated as a Value Object or Entity. After all, the entire point of an Address is to be unique right? Because of this, Address will often be treated as it's own object (table) by the backing store and simply related to Persons (or whatever) as needed (multiple Persons can share the same Address right?). This implementation decision is where the confusion begins. Although there is a unique identifier for each Address it should still be treated as a Value Object. When a Person changes their Address, we don't modify any fields, we simple assign them a new one (from our list of Addresses).

Now that we covered some ground here about what constitutes each of these building blocks, let's see how they apply to your Race object. Does your concept of Race have a life-cycle? Probably not. It should be a Value Object. Whether or not or how it can be distinguished from other Races is immaterial.

The real question here is why do multiple domains need to know this information? Trying to rig together some system of Domain Events (e.g. ProviderChangedRace) in order to keep the information consistent across multiple contexts creates quite a bit of added complexity and points of failure.

The cornerstone of DDD is that systems are designed around behavior (not data), and it is often the case that it is a deficient model that creates this kind of awkwardness, not some missing technical rule/detail. Is there a way to organize your system such that every object that needs to use Race can be refactored into a single Entity/Aggregate? By changing your system in a way that the object that uses Race is the same object that modifies available Races, you can completely avoid many of the issues you bring up in your question. For example, your Provider could be loaded with the available Races and the chosen Race. Now adding new options, and making new choices are coupled in a single transactional boundary where the enforcement of invariants is trivial. On a similar note, one does not "do" validation on a Provider, a Provider should be validating itself.

I understand there may be more pieces to this puzzle, but refactoring towards deeper insight is something that should be considered here. Try to find a way to "slice" your data vertically in a way to bring behavior together.

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.