0

I am new to DDD and I am designing a roommate matching system. One of my Aggregate Roots is a "Profile"

public class Profile
{
    ...
    public Traits Traits { get; private set; }
    public Traits Preferences { get; private set; }
    ...
}

As you can see a profile has a relationship to two entities of type Traits, "Traits" is the users own traits such as the max rent they can contribute, weather they smoke or not etc. and "Preferences" is the same type of info but it represents what they are looking for in a roomate.

I am struggling to understand how to best enforce an invariant like the following example "A profile cannot prefer a roommate who contributes more rent then they do themselves."

If I add a method to profile ChangeMaxRent() like such

public class Profile
{
    ...
    public Traits Traits { get; private set; }
    public Traits Preferences { get; private set; }
    ...
   
    public void ChangeMaxRent(int newMaxRent)
    {
      //enforce variant here
      Traits.SetMaxRent(newMaxRent);
    }

}

Then a consumer could just circumvent this by accessing the Traits property directly like so

profile.traits.SetMaxRent(x)

The only way to avoid this is to make the getter for Traits and Preferences private so that a consumer cannot directly access it;

but this would cause a lot of code to be written in the Profile class since I would now need a getter for every property of Trait and Preference

I am struggling to see a clear path forward here, any help would be greatly appreciated.

2 Answers2

0

Generally, you should follow Law of Demeter while designing your aggregates.

Try to avoid sharing mutable objects (e.g entities). This rule prevents reference leaks, making your domain much more secure. Otherwise, as you said, anyone can avoid your aggregate methods and do whatever they want with your entities.

If you really want to use your Traits entity data you could return a copy of an object or other read-only representation. Yep, this add you some additional work, but this way you can avoid a big headache in the future.

AlmostDev
  • 85
  • 1
  • 5
0

Here are a few different ways to achieve entity encapsulation within an aggregate in .NET:

  1. Create the aggregate in a separate project. Define what can be accessed from the outside as public and the rest as internal. In your example, Profile.Traits getter would be public, but Traits.SetMaxRent would be internal.

  2. Define the aggregate root's entities as nested classes of the root. Define what can be accessed only from the root as private. If the file grows, you can define the nested classes in separate files by using partial classes, so you'd have a Profile.cs file and a Profile.Traits.cs file.

  3. Hide the entity reference (make Traits property getter private) and expose its properties (when necessary) directly from the root. You can expose TraitsMaxRent or TraitsInformation. Although Traits.SetMaxRent is public, nobody will be able to get a reference to the actual Traits object.

My personal preference is number 2. I find 1 overkill and 3 requires more code unless you can avoid mapping most of the entities' properties.

Francesc Castells
  • 2,692
  • 21
  • 25