Introduction
When you browse the net about best practices regarding the business layer, you realize a lot of things have been said and stated. When it comes to practice though, you surprisingly notice many things are missing and too many pieces are not tied up in the overall theories.
One thing I faced many times and gave me headaches is how to architect or where business rules should go and how they fit in the layer. Is it the responsibility of service layer to invoke them or domain entities already contain some sort of validation logic? The answer is not simple because it depends, as it always does, on your business layer pattern.
I will not dive into the business layer patterns but for this article I will focus on domain model as this is my preferred one.
In this pattern, I usually use pocos to define or express my domain model. The entity framework code first goes from there to create or update the database.
I then organize Dbcontext
classes by my domain contexts. I will not dive more on this but as you can see there is a lot of DDD going here.
Once those pieces in place, I want a clean way to validate my business rules before, let's say, any attempt to save to database. If something goes wrong, I need to notify the user by displaying messages according to the failed business rules.
To be able to do that in a clean way and respecting some principles as separation of concerns, I usually combine two patterns: specification and notification. We don't see often patterns combination, but when you get a good fit for some of them, you power your application with some good practices and all the benefice that comes from that.
Specification is the DDD pattern and I suggest you the Eric Evan's book or Jimmy Nilsson’s one to learn more about the DDD and its principles.
Notification is a way of giving back information to user about what was going in the backend. The way I achieve it is by using observer pattern based on a condition, whether the specification is satisfied or not. Bear with me I will get in depth to that.
Specification
In a nutshell, specification is an interface that defines one method: IsSatisfiedBy
which return boolean to assert that the specification is satisfied. So, every single business rule should be represented by a class that implements the ISpecification
interface.
To represent my example, I will walk you through transport bus system with a couple of rules on passengers.
- A passenger should have 12 years or more
- A passenger under 12 years should be attended by an adult
- A passenger should provide phone number
- A passenger should provide his full name
Of course, those rules are simplified versions. As you may guessed, there is a lot more to story in this business. But the power of specification pattern will break down your complexity and give you the opportunity to handle your rules validation as if you’ve got only one.
To achieve that, let's get to the code and start with interface that defines the specification.
public interface ISpecification<T>
{
bool IsSatisfiedBy(T entity);
}
Then wrote the specifications that validate your rules and scenarios as follows:
Here to validate that a passenger is an adult (adult, senior, student), I need to make sure that is not a child (0-4 or 5-12). Those passenger types are specific to my domain.
public class IsPassengerAnAdult : ISpecification<Passenger>
{
public bool IsSatisfiedBy(Passenger passenger)
{
return (passenger.PassengerType != PassengerType.Child04 && passenger.PassengerType != PassengerType.Child512);
}
}
Verify that a passenger is attended.
public class IsPassengerAttended : ISpecification<Passenger>
{
private readonly IEnumerable<Passenger> _passengers;
public IsPassengerAttended (IEnumerable<Passenger> passengers)
{
_passengers = passengers;
}
public bool IsSatisfiedBy(Passenger passenger)
{
return _passengers,Any(attendant => attendant.PassengerId == passenger.AttendantPassengerId)
}
}
Verify that the passenger name is provided.
public class IsPassengerNameProvided : ISpecification<Passenger>
{
public bool IsSatisfiedBy(Passenger passenger)
{
return ! string.IsNullOrWhiteSpace(passenger,Name);
}
}
public class IsPassengerPhoneNumberProvided : ISpecification<Passenger>
{
public bool IsSatisfiedBy(Passenger passenger)
{
return ! string.IsNullOrWhiteSpace(passenger,PhoneNumber);
}
}
To be able to do some rules validation chaining, you could code the following extension methods:
public static class ExtensionMethods
{
public static ISpecification<T> And<T>(this ISpecification<T> spec1, ISpecification<T> spec2)
{
return new AndSpecification<T>(spec1, spec2);
}
public static ISpecification<T> Or<T>(this ISpecification<T> spec1, ISpecification<T> spec2)
{
return new OrSpecification<T>(spec1, spec2);
}
public static ISpecification<T> Not<T>(this ISpecification<T> spec)
{
return new NotSpecification<T>(spec);
}
}
public class AndSpecification<T> : ISpecification<T>
{
private readonly ISpecification<T> _spec1;
private readonly ISpecification<T> _spec2;
public AndSpecification(ISpecification<T> spec1, ISpecification<T> spec2)
{
if (spec1 == null)
throw new ArgumentNullException("spec1");
if (spec2 == null)
throw new ArgumentNullException("spec2");
_spec1 = spec1;
_spec2 = spec2;
}
public bool IsSatisfiedBy(T candidate)
{
return _spec1.IsSatisfiedBy(candidate) && _spec2.IsSatisfiedBy(candidate);
}
}
Then I code a validator layer to be able to isolate some context but in this example, my only context is passenger. But the same thing could be done for other contexts. When I have a lot of rules to validate as it often happens, I combine them and hook them up one general message to display if they fail. I always work out a message that can be general enough to represent all rules and specific enough to focus on my context.
In the following validator, I will combine all the rules together but as you guessed it doesn't make sense to validate that a passenger is an adult and then validate that is attended. You should always combine what make sense to you and you can even avoid combination and validate rules one by one. Again it depends on your context.
public class PassengerValidator : IValidate
{
private readonly Passenger _passenger;
private readonly IEnumerable<Passenger> _passengers;
public PassengerValidator(Passenger passenger, IEnumerable<Passenger> passengers)
{
_passenger = passenger;
_passengers = passengers;
}
public bool ValidatePassengerIsAttendedAndIsAnAdult()
{
var rule = new IsPassengerAnAdult()
Or(new IsPassengerAttended(_passengers ))
.And(new IsPassengerPhoneNumberProvided()) .
.And(new IsPassengerNameProvided()));
var isOk = rule.IsSatisfiedBy(_passenger);
if (!isOk) EventPublisher.OnRaiseNotificationEvent
(new NotificationEventArgs(Messages.GetMessage("AnyPossibleMessageThatSuitsYourNeed")));
return isOk;
}
The condition I mentioned earlier for notification pattern lays in isOk
which indicate if rules are ok or not.
Then in your service build your passenger object and list of passengers and call the validator to validate the rules.
At this point, your validator is just returning a boolean to indicate if the specifications are satisfied. If they are, you are done and allowed to sell the ticket. But if not, you would probably want to know what was wrong because in the real world systems, you may have dozens of rules for each use case.
Here comes the notification pattern to the rescue.
Notification
Notification pattern could be implemented in different ways. Mine is in an observer pattern like. I mean when a validator or service subscribe for validation, a publisher is hooked up and then validator no longer returns a boolean but a notification object. This object contains the messages of the rules that weren't satisfied.
namespace m2a.WebClient.Services.Services
{
public class PassengerService : NotifierService, IService
{
public Notification ValidatePassenger()
{
var passenger= BuildPassengerFromViewModelObject();
var passengers= BuildPassengersListFromViewModelObject();
var validator = new PassengerValidator(passenger, passengers);
EventPublisher.RaiseNotificationEvent += HandleNotificationEvent;
var isvalid = validator.ValidatePassenger();
return Notifier;
}
public void HandleNotificationEvent(object sender, Validators.NotificationEventArgs e)
{
Notifier.Errors.Add(e.Message);
}
}
}
namespace m2a..WebClient.Services.Services
{
public abstract class NotifierService
{
public readonly Notification Notifier;
protected NotifierService()
{
Notifier= new Notification();
}
}
}
And finally the event publisher object:
namespace m2a..WebClient.Services.Validators
{
public static class EventPublisher
{
public static event EventHandler<NotificationEventArgs> RaiseNotificationEvent;
public static void OnRaiseNotificationEvent(NotificationEventArgs e)
{
var handler = RaiseNotificationEvent;
if (handler == null) return;
handler(new object(), e);
}
}
}
namespace m2a..WebClient.Services.Validators
{
public class NotificationEventArgs : EventArgs
{
public NotificationEventArgs(string s)
{
Message = s;
}
public string Message { get; set; }
}
}
Once your service returns the Notification
object, you check the HasError
property to see if there is any error. If the answer is yes, browse the Errors
list to get any messages you set up for them.
This example is kind of Hello World sample that many developers like to throw to the net. But, believe it or not (in this case give it a try), you don't need anything more except more specification classes for your rules and validator for combinations.
Hope this will give you a hint or two about the importance and simplicity of architecting a nice piece of software to manage the most important thing you should care about in your business layer: domain rules.