one year ago by James Hickey

Ever struggle with where to place your app's business rules and validation?





Should you put them in your MVC models?





Should you create special classes for validation?





I'll show you a battle-tested technique used in Domain Driven Design!





A Short DDD Primer





Domain Driven Design is a holistic approach and framework for designing software. It places a heavy emphasis on exploring business problems by starting with the business problem, how people involved currently deal with the problem, figuring out who the experts in this field are, consulting with the experts, exploring potential ways of modelling the problem, etc.





DDD is a huge subject. For today, we'll be looking at a technique that comes from DDD.





Let's look at two DDD concepts to lay the foundation.





Entity





An entity is simply a model that is identified by some unique identifier. Maybe a Customer, Order, Insurance Policy, etc.





When testing whether two entities are the same or equal, we test the unique ids of each to see if they match.





Value Object





Value objects correspond to things that aren't uniquely identifiable. Things like an address, a person's name, or a date range could be value objects.





They can hold multiple pieces of data like, as mentioned, a date range (which has a start and end date).





When testing whether two value objects are the same or equal, we test to see if all values are the same.





Combining The Two





Entities and value objects usually go hand-in-hand.





An entity is usually comprised of (a) other entities and (b) value objects.





For example, an Order entity might hold a reference to a few OrderItem entities. But it might also hold a reference to the price of the purchase (value object).





Where Does My Validation Go?





In DDD, it's a good practice for business rules and validation to go into the value objects.





There are two basic properties that make this a great technique - immutability and immediate validation at creation time.





Immediate Validation





By testing whether a value object is valid at creation time we ensure that it's not possible to have a value object that is in an invalid state.





Immutability





Making value objects immutable means that once they are created they cannot be modified.





Both of these properties ensure that you cannot have an object that is in an invalid state - either by creating it with invalid data or by causing side-effects that change the data.





It's quite simple yet powerful to know that you will always have instances of valid objects.





Insurance Policy Scenario





Let's look at a real-world example of how you might use value objects to perform business validation.





Imagine we had requirements to validate an insurance policy. This policy has:





Person Insured Age

Person Insured Gender

Person Insured Address

Date Started





Let's imagine the following rules exist:





Base price is $15 /month

When age is above 50 the price is doubled

When gender is Male the price is doubled (because men can be more prone to injure themselves more often than not...)

When an address is in Canada then the date started must be the beginning of the current month





Modelling Techniques





There's an entire area of study around modelling entities and value objects. At the end of the day, the general rule is to keep data that changes together in the same place.





This is actually a fundamental Object Oriented principle. One we often choose to ignore...





So, we need to figure out, based on business requirements, what pieces of data change together:





Age and Gender affect the Price

Address affects the Date Started





We can see from tracking what data changes together what our models might look like:





public class Object1 { public int Age { get ; private set ; } public Gender Gender { get ; private set ; } public decimal Price { get ; private set ; } public Object1 ( int age, Gender gender ) { this .Age = age; this .Gender = gender; this .Price = 15.00 m; } }





public class Object2 { public Address Address { get ; private set ; } public DateTime DateStarted { get ; private set ; } public Object2 ( Address address, DateTime dateStarted ) { this .Address = address; this .DateStarted = dateStarted; } }





Note About Naming





When creating objects using this technique, is usually recommended to not name your value objects and entities right away.





This ensures you focus on the data and where things belong based on the business. It's all too tempting to label our objects in our own minds - which brings in a bias about what "should" belong in each model.





But we shouldn't decide that - the business requirements should. We can give them meaningful names once we are sure our models are as good as we can make them.





Adding Our Business Rules





Let's add our first 3 rules.





We'll add this validation in our object's constructor. This satisfies the property of value objects needing to be valid at creation time. You should never be able to create an object that holds invalid data.





public class Object1 { public int Age { get ; private set ; } public Gender Gender { get ; private set ; } public decimal Price { get ; private set ; } public Object1 ( int age, Gender gender ) { decimal basePrice = 15.00 m; if (age >= 50 ) { basePrice = basePrice * 2 ; } if (gender == Gender.Male) { basePrice = basePrice * 2 ; } this .Age = age; this .Gender = gender; this .Price = basePrice; } }





How To Process Invalid State





There's a technique we'll use to ensure that our value object can never be in an invalid state:





Test a business requirement in the constructor of a value object Throw an exception if a rule fails





For this, we'll create a new Exception type:





public class BusinessRuleException : Exception { public BusinessRuleException ( string message ) : base ( message ) { } }





We'll use it to add our next business rule:





public class Object2 { public Address Address { get ; private set ; } public DateTime DateStarted { get ; private set ; } public Object2 ( Address address, DateTime dateStarted ) { if (address.IsCanadian && dateStarted.Day != 1 ) { throw new BusinessRuleException( "Canadian policies must begin on the first day of the current month." ); } this .Address = address; this .DateStarted = dateStarted; } }





One might point out that we could just change the Date Started to the beginning of the month automatically.





But, this is part of the requirement. Do we transform the date for the user or not? In this case, our business simply wants to inform the user of this business rule.





Now, further up the caller would catch the exception and display the error message to the user.





But What About Showing Multiple Error Messages?





Glad you asked!





As a total aside - one way would be to replace the constructor with a static factory method. You might return a tuple (in C#) as the result:





public class Object2 { public Address Address { get ; private set ; } public DateTime DateStarted { get ; private set ; } private Object2 () { } public static ( Object2, IEnumerable< string > ) Make ( Address address, DateTime dateStarted ) { var errors = new List< string >(); if (address.IsCanadian && dateStarted.Day != 1 ) { errors.Add( "Canadian policies must begin on the first day of the current month." ); } if (errors.Any()){ return ( null , errors); } return ( new Object2 { Address = address, DateStarted = dateStarted }, errors ); } }





There are other patterns for doing this, such as the Result Object pattern.





Building Our Entity





Let's now name our value objects and create an entity for our overall insurance policy.





Note: My names are not the best. It actually takes a long time and thought to give these meaningful names. If your business is taking DDD to it's fullest, then looking at an ubiquitous language upfront will always inform what names you choose to use, and potentially how you choose the model your objects.





public class InsurancePolicy { public PolicyPricingDetails Pricing { get ; private set ; } public PolicyAddressDetails AddressDetails { get ; private set ; } public InsurancePolicy ( PolicyPricingDetails pricingDetails, PolicyAddressDetails addressDetails ) { this .Pricing = pricingDetails; this .AddressDetails = addressDetails; } }





Using Our Value Objects For Validation





Here's how we would supply user input and create our entity:





try { var pricing = new PolicyPricingDetails(age, gender); var address = new PolicyAddressDetails(userAddress, dateStarted); var entity = new InsurancePolicy(pricing, address); } catch (BusinessRuleException ex) { errorMessage = ex.Message; }





Testing





You might notice that testing your value objects can make testing specific business rules very easy!





Get Going!





I hope this was a helpful introduction to how you might want to incorporate this Domain Driven Design concept in your code.





Using value objects this way can give you a framework to use for:





Modelling your business problems correctly

Figuring out where business validation should go





Get In Touch





If you have any thoughts or comments you can tweet me @jamesmh_dev or connect on LinkedIn!