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
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 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.