To be or not to be? Maybe.
Yes, you are right, too much time has passed from our previous blog post. We will try to provide new content regularly, while keeping the quality bar high. The aim of this blog post is to show how we can use optional values (Maybe) in context of Domain Driven Design and store our model in Mongo DB storage without affecting domain model with various mappings. We will also describe the iterative process in which we have chosen this approach.
Working on one e-commerce system recently, we wanted to put functional approach in use along with the DDD. We had experience in some functional languages like Scala, but this project demanded .NET technology stack, so we found great inspiration in works by Eric Evans and Vladimir Khorikov. As you probably know Eric has written The Big Blue Book on DDD and Vladimir has two great courses on Pluralsight: Domain Driven Design in Practice and Applying functional principles in C#.
OK, before the project kick-off, we needed to have a discussion about system architecture. Among other things, we wanted to keep our code clean, and one of the ways to do this is to get rid of the
null values and
null checking. Our idea was to use library created by Vladimir, which provides functional extensions to
C#. One of the things you can find there is the
Maybe class (
struct to be more precise). It is a simple wrapper that wraps optional values. It has direct analogy with the
Option class in
Scala (which we described in our last blog post). Since this is Web based application,
Maybe really comes in handy while going from application layer (Web API Controllers in this case) into domain layer (Domain service). Looking at the
Maybe class, we can see that there is an implicit conversion from some type
What this actually means is that if a method in a service expects for example
Maybe, and we pass in
null, implicit conversion will convert this
Maybe.None. On the other hand Maybe is declared as a
struct is a value type in .NET, it cannot be
null in any case :). Using this approach we are trying to mitigate the Billion Dollar Mistake.
Our idea was to eliminate nulls totally from our projects, and in order to ensure this, we used NullGuard, a Fody add-in. NullGuard utilizes Fody to insert
null checking in the generated MSIL (Microsoft intermediate language). It is up to a user is to define what they want to check for
nulls, – arguments to methods, return values, out values, properties, etc. For Release builds, NullGuard will weave code that throws
ArgumentNullException. For Debug builds, NullGuard weaves
Debug.Assert. But this can be changed easily through configuration and for more details please check the link provided above.
Let’s get back to our project. Another important decision we have made was to use MSSQL server. Among two choices: MS Entity Framework and NHibernate, we settled with the latter one as our default ORM. You can find really good article on comparison of these two ORMs here from DDD perspective. But we made one promise to ourselves, which was to create repositories in a such a way, so we can later easily switch from NHibernate to something else should a need arise. And, as it turned out later on, this was very important, as you will see.
After project kick-off, everything was going fine until we stumbled upon the first issue. We had to map Maybe wrapper on some value type or string using NHibernate Fluent Mapping. One example would be Customer mapping with secondary email which is optional and in this case Maybe. NHibernate Fluent Mapping for Customer class using CustomType you can find here on github.
The real problem came when we had optional fields, which was not value type or string, but some complex type, with its own object graph.
Consider example with Customer class given bellow:
So we had a class
Customer that among other usual fields (
Age, etc.) has optional
Billing address, which we don’t need until the customer makes the first purchase. This was one of the UX requirements. So billing address is needed for the purchase but we will not ask user to put it in until it’s really necessary. Address, on the other hand is a value object with its own properties.
We didn’t find the way to properly map something like this in NHibernate (probably due to the lack of the documentation). Still we were not 100% sure even if this is possible, but we already didn’t like previous approach with the Custom Type mappings we mentioned earlier. After two days of trying we decided to make an architectural change on a project! We are going to try things with Mongo DB, and this is why that promise we made earlier was so important!
But wait, going from relational databases to document database, can this somehow be justified? Actually it can, and it makes perfect sense when adding DDD in the context, – making one document collection for one aggregate root in the domain model. While working with the relational database model, we were constantly wrestling where and how to store value objects of particular aggregate root, especially if we have a list of value objects. In this case, we needed a separate table in relational database and had to make artificial entities in the model. From this perspective, I don’t know why we didn’t try using Mongo DB initially. One can say that we lose atomicity when using No SQL database, but this can be avoided by changing architecture and applying Event Sourcing for example.
OK, let’s now return to our example and mapping of our
Customer class. You will see that our custom Maybe serializer solved all the mapping issues we had earlier.
Mongo mappers for domain classes:
Our custom Mongo Maybe serializer:
You can find our whole test project (.NET Core) on github and check it out. This is our approach which may not be the best one, but we are open for suggestions and opinions.