Where Do I Put My Business Rules And Validation?

James Hickey - Mar 18 '19 - - Dev Community

This article originally appeared on the builtwithdot.net Blog.

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?

To answer the question, 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.00m; // 1st business requirement: Base price is $15/month
  }
}
Enter fullscreen mode Exit fullscreen mode
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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
  {
    // Rule #1: Base price is $15/month.
    decimal basePrice = 15.00m;

    // Rule #2: When age is above 50 then price is doubled.
    if(age >= 50)
    {
      basePrice = basePrice * 2;
    }

    // Rule #3: When gender is Male then price is doubled.
    if(gender == Gender.Male)
    {
      basePrice = basePrice * 2;
    }

    this.Age = age;
    this.Gender = gender;
    this.Price = basePrice;
  }
}
Enter fullscreen mode Exit fullscreen mode

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) { }
}
Enter fullscreen mode Exit fullscreen mode

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)
  {
    // Rule #4: If address is in Canada then Date Started must be the beginning of the current month.
    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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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; }

  // Make sure no one can create this object with a constructor.
  private Object2() { }

  // Static Factory Method that returns (a) the value object and (b) any errors.
  public static (Object2, IEnumerable<string>) Make(Address address, DateTime dateStarted)
  {
    var errors = new List<string>();

    // Rule #4: If address is in Canada then Date Started must be the beginning of the current month.
    if(address.IsCanadian && dateStarted.Day != 1)
    {
      errors.Add("Canadian policies must begin on the first day of the current month.");
    }

    // Imagine that there might be more business rules here.

    if(errors.Any()){
      return (null, errors); // Never return an invalid instance of the value object ;)
    }

    return (
      new Object2
      {
        Address = address,
        DateStarted = dateStarted
      }, 
      errors
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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 PolicyPricing Pricing { get; private set; }
  public PolicyAddress Address { get; private set; }

  public InsurancePolicy(PolicyPricing pricing, PolicyAddress address)
  {
    this.Pricing = pricing;
    this.Address = address;
  }
}
Enter fullscreen mode Exit fullscreen mode

Using Our Value Objects For Validation

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

try {
  var pricing = new PolicyPricing(age, gender);
  var address = new PolicyAddress(userAddress, dateStarted);

  // You can never get here unless all business rules are valid.
  var entity = new InsurancePolicy(pricing, address);

  // Now you might save the entity in a DB?
}
catch(BusinessRuleException ex)
{
  errorMessage = ex.Message; // This would be returned to the UI, etc.
}
Enter fullscreen mode Exit fullscreen mode

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

Keep In Touch

Don't forget to connect with me on twitter or LinkedIn!

Navigating Your Software Development Career Newsletter

An e-mail newsletter that will help you level-up in your career as a software developer! Ever wonder:

āœ” What are the general stages of a software developer?
āœ” How do I know which stage I'm at? How do I get to the next stage?
āœ” What is a tech leader and how do I become one?
āœ” Is there someone willing to walk with me and answer my questions?

Sound interesting? Join the community!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player