Eradicating the Primitive Obsession
Tags: ddd, webdev, csharp, architecture
Published: 2023-09-14 17:17:35

> Before we delve deeper into the intricacies of domain-driven design, I highly recommend taking a moment to familiarize yourself with the [essential terms and concepts](https://www.dddcommunity.org/resources/ddd_terms/) that will be discussed throughout this article.

Recalling our [previous discussion](https://dev.to/maxarshinov/always-valid-a-myth-or-a-tangible-reality-19e6) where we initiated the design of a user class and identified issues that were preventing us from implementing the Rich Domain Model, let’s take it a step further by rectifying the anemic implementation that we previously derived. Here is what the anemic implementation might look like:

```csharp
public class User
{
   public int Id { get; init; }

   public string? FirstName { get; set; } // Profile
   public string? LastName { get; set; } // Profile
   public string? MiddleName { get; set; } // Profile

   public string? Phone { get; set; } // Contact
   public string? Email { get; set; } // Contact  
}
```

This is a perfect example of a common anti-pattern, referred to as "[primitive obsession](https://www.google.com/url?q=https://medium.com/the-sixt-india-blog/primitive-obsession-code-smell-that-hurt-people-the-most-5cbdd70496e9&sa=D&source=docs&ust=1694714080507597&usg=AOvVaw3l94YY4-sGHmO4ulIftL0V)," where developers lean heavily on primitive data types instead of crafting dedicated classes or structures to house domain ideas. This is often considered as a significant code smell.

> One can claim that this `User` hasn't decided if he/she is [a unicorn or a goat](https://dev.to/maxarshinov/always-valid-a-myth-or-a-tangible-reality-19e6) :) There is no *invariant* here because the class is no more than a bag of properties.

To resolve this, we introduce a technique called “[Make illegal states unrepresentable](https://blog.janestreet.com/effective-ml-revisited/)” to help eradicate this obsession and reinforce the robustness of our design through the integration of new `UserProfile` and `UserContact` classes.

```csharp
public class UserProfile
{
   // now required
   public required string FirstName { get; init; }

   // now required
   public required  string LastName { get; init; }

   public string? MiddleName { get; init; }
}


public class UserContact
{
   public string? Phone { get; init; }
   public string? Email { get; init; }
}
```


Thanks to [C#9's init keyword](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init) coupled with [C#11 required modifier](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required), enforcing the setting of the first and last names is possible, even without a constructor. Here is how I can initialize a profile:

```csharp
var profile = new UserProfile
{
   FirstName = "Max",
   LastName = "Arshinov"
};
```
## Adding Constructor

Despite these improvements, a loophole exists - it's still possible to initialize the profile with empty strings. To fix this, we introduce a constructor that validates these fields to prevent such initialization:


```csharp
public class UserProfile
{
   public UserProfile(string firstName, string lastName,
      string? middleName = null)
   {
       FirstName = firstName;
       LastName = lastName;
       MiddleName = middleName;
       this.EnsureInvariant();
   }


   [Required]
   public string FirstName { get; protected set; }

   [Required]
   public string LastName { get; protected set; }
  
   public string? MiddleName { get; protected set; }
}

```
Now, our constructor ensures that first and last names are provided, making our class more robust against erroneous inputs.

## Ensure Invariant

To further beef up our class, we introduce a method, `EnsureInvariant`, which helps in validating the object state after its initialization. Here is the implementation of this method.

```csharp
public static class FunctionalExtensions
{
   private static ConcurrentDictionary<Type, IValidator> 
       _validators = new();

   public static void EnsureInvariant(this object obj,
       bool validateAllProperties = true)
   {
       Validator.ValidateObject(
           obj,
           new ValidationContext(obj),
           validateAllProperties);
   }
}
```

Or slightly different if you prefer [FluentValidaton](https://docs.fluentvalidation.net/en/latest/specific-properties.html).

```csharp
public static void EnsureInvariant<TEntity, TValidator>(
   this TEntity obj)
   where TValidator: AbstractValidator<TEntity>, new()
{
   var ctx = new ValidationContext<TEntity>(obj,
       new PropertyChain(),
       ValidatorOptions
         .Global
         .ValidatorSelectors
         .DefaultValidatorSelectorFactory());
  
   var result = _validators.GetOrAdd(typeof(TEntity), 
      _ => new TValidator()).Validate(ctx);
  
   if(!result.IsValid)
   {
       throw new ValidationException(
        "Validation failed: " + string.Join(",
        result.Errors.Select(e => e.ErrorMessage)));
   }
}


//…
this.EnsureInvariant<UserProfile, UserProfileInvariantValidator>();
```

The above skeleton can be fleshed out with actual validation logic to ensure the object's state's correctness.

## User Contact

Turning our attention to the contact information, we encounter a similar predicament. We aim to define parameters that are not merely optional but follow certain validation rules. Here is an evolved `UserContact` class adhering to this principle:

```csharp
public class UserContact
{
   const string PhonePattern = @"\+?\d";
  
   [EmailAddress]
   public string? Email { get; protected set; }
  
   [RegularExpression(PhonePattern)]
   public string? Phone { get; protected set; }

   public UserContact(string? email, string? phone)
   {
       Email = email;
       Phone = phone;
       this.EnsureInvariant();
   }
}

```

I  could use such a constructor, but in C# there is no way to indicate that one of the parameters is mandatory. The method signature will indicate that both parameters are optional. Therefore, let's make the constructor private and instead provide two public methods with more descriptive names. In C#, the prefix `Try` is usually used for operations that can fail but do not throw exceptions. We can implement the constructor in the following way.


```csharp
public class UserContact
{
   const string PhonePattern = @"\+?\d";
  
   [EmailAddress]
   public string? Email { get; protected set; }
  
   [RegularExpression(PhonePattern)]
   public string? Phone { get; protected set; }

   private Contact(string? email, string? phone)
   {
       Email = email;
       Phone = phone;
   }

   public static bool TryParsePhone(string phone,
      out Contact? c)
   {
       if (PhoneRegex.IsMatch(phone))
       {
           c = new Contact(null, phone);
           return true;
       }

       c = null;
       return false;
   }
}
```

## Wrapping up

We now have a `User` type that embodies two subtypes: `UserContact` and `UserProfile`. With these changes, our `User` class has transitioned from being anemic to a more rich and robust representation, promoting easier maintenance and fewer errors during development.

```csharp
public class User
{
   public required int Id { get; init; }
  
   public required UserContact Contact { get; init; }
  
   public UserProfile? Profile { get; init; }
}
```

That’s all for today. The next time, we’ll evolve this design even more towards the Rich Domain Model.