Domain Driven Design in C# - implementing immutable value objects

- select the contributor at the end of the page -
In his book, Domain Driven Design (DDD), Eric Evans encourages the use of Value Objects in domain models. Here at Pluralsight we are using the DDD approach to modeling, and we are taking his advice and using immutable objects to implement all of our Value Objects in our domain layer. What we're finding is that we are programming in C# but our code is becoming more and more functional in nature, which has lots of benefits. Less mutable state tends to make things easier to test, and tends to reduce bugs.

Sure you can hide an object's state behind public methods and property getters, but if you really want to implement immutable objects, you should consider surfacing the class's internal state via readonly fields. Indeed it becomes as simple as this:

public class Cooldown {

public readonly string Ability;

public readonly DateTime ExpiresOn;

// ... several more fields

public Cooldown(string ability, DateTime expiresOn, ...)

{

Ability = ability;

ExpiresOn = expiresOn;

// etc.

}

}

Prior to .NET 4.0 this wasn't very easy to do. And that's because objects in C# with readonly fields require that those fields be initialized by a constructor. Imagine a call to a constructor for an object with several arguments - this can get unreadable very quickly. What does "Foo" correspond to? "Bar"? Ugh.

new Cooldown(

"Invisibility",

DateTime.UtcNow.AddSeconds(30),

"Foo",

42,

"Bar",

...);

But this awkwardness goes away with .NET 4.0. Now I can construct a readonly object and not lose any clarity around what each argument means, because I can call the constructor using named arguments.

new Cooldown(

ability: "Invisibility",

expiresOn: DateTime.UtcNow.AddSeconds(30),

...);

This looks remarkably similar to object initialization syntax, which uses property setters. It's certainly no harder to type, and it's just as easy to read.

One gotcha to this approach is that many data binding frameworks will only bind to property getters, ignoring public fields. This isn't a problem for us here at Pluralsight, because our domain model is isolated from tech like this. Data binding frameworks don't come into play until you're up in our application layer, and in that layer we use separate view model classes that do have property getters and setters to play nice with ASP.NET MVC, the Razor view engine, etc.

A second gotcha is that there aren't any mainstream serializers or O/R mappers that I know of that know how to rehydrate an immutable object via its constructor. Even AutoMapper can't handle this today. Most of this tech relies on calling a default constructor to get an instance of an object, then calling property setters (or setting the values of fields) to rehydrate its state. But once again, this is not a problem at all here at Pluralsight, because our domain objects aren't directly serialized - our persistence layer has a set of data transfer objects that are designed to be serializer friendly. And we explicitly avoid AutoMapper in favor of code gen'd mappers which are friendlier to refactoring tools like ReSharper.

The beauty of all of this isolation is that we are establishing a pristine domain layer devoid of tech, leaving only concise business logic. We've even managed to eliminate the need for GetHashCode until it's absolutely needed!

Get our content first. In your inbox.

Contributor

Keith Sparkjoy

is a Culture Coach at Pluralsight. As a cofounder, Keith was the Chief Technology Officer for many years, building and hosting the website and all things IT. These days there's a whole team of folks taking care of the tech, and Keith is focusing more on company culture, which is one of the most important aspects of a fast-growing business.