Masoud
Masoud

Reputation: 8181

Specification pattern for implementing business rules

I want to use Specifications to apply business rules in my N-Layerd DDD application. I used CQRS pattern in my Application Layer also. So I defined following Interface in my Domain:

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Predicate { get; }
    bool IsSatisfiedBy(T entity);
}

and also some Specifications that implement above interface such as BigOrderSpecification : ISpecification<Order> and SpecialOrderSpecification:ISpecification<Order>.

In my OrderProcessCommandHandler class I used these Specifications:

public class OrderProcessCommandHandler : ICommandHandler<Order>
{
    OrderCommand _command;
    public OrderProcessCommandHandler(OrderCommand command) 
    {
       _command = command;
    }
    public Handle()
    {
        var bigOrderSpec = new BigOrderSpecification();
        var specialOrderSpec = new SpecialOrderSpecification();
        var spec = bigOrderSpec.And(specialOrderSpec);
        if (spec.IsSatisfiedBy(_commnand.Order))
           // do some things
        else
            throw new BusinessException("Some business rules violated.") 
    }      
}

As you see, if during order processing, one or more specification don't satisfy, I could not throw a BusinessExceptionwith clear message to top layer, only

Some business rules violated.

How could I create clear massage that contain all BR violation reasons and pass it throw my BusinessException to top layers?

Upvotes: 2

Views: 2851

Answers (4)

GraemeMiller
GraemeMiller

Reputation: 12253

You could consider validating separately before calling the command handler. Make command fire and forget. Validate first and then send to business layer assuming that it will work. Have a command validation handler that returns for example IEnumerable<IErrorMessage>

On failure make the domain just throw and not handle it in UI layer so no need to pass it all the way back. Handle in some other way e.g. send email with exception or raise an event in that is picked up async at a later point and displayed in UI.

In domain how likely is it that it validates correctly and then something occurs between validating and executing? It comes down to business value in informing users of certain sort of errors.

This Udi Dahan video covers the concept https://skillsmatter.com/skillscasts/1250-udi-dahan-command-query-responsibility-segregation

This blog post also covers it http://www.udidahan.com/2009/12/09/clarified-cqrs/ see Reasons valid commands fail and what to do about it

Upvotes: 0

RussellEast
RussellEast

Reputation: 153

Using a event, that is raised from the specification and handled in the command handler.

public class BusinessRuleFailure : EventArgs
{
    public BusinessRuleFailure(string reason)
    {
        Reason = reason;
    }

    public string Reason { get; private set; }
}

public delegate void BusinessRuleFailureHandler(BusinessRuleFailure failure);

public interface ISpecification<T>
{
    event BusinessRuleFailureHandler NotSatisified;

    Expression<Func<T, bool>> Predicate { get; }
    bool IsSatisfiedBy(T entity);
}

public class OrderProcessCommandHandler : ICommandHandler<Order>
{
    OrderCommand _command;
    public OrderProcessCommandHandler(OrderCommand command) 
    {
       _command = command;
    }
    public Handle()
    {
        List<string> failures = new List<string>();

        var bigOrderSpec = new BigOrderSpecification();
        var specialOrderSpec = new SpecialOrderSpecification();

        bigOrderSpec.NotSatisified += failure => failures.Add(failure.Reason);
        specialOrderSpec.NotSatisified += failure => failures.Add(failure.Reason);

        var spec = bigOrderSpec.And(specialOrderSpec);
        if (spec.IsSatisfiedBy(_commnand.Order))
           // do some things
        else
        throw new BusinessException("Some business rules violated.", failures);
    } 
}

Upvotes: 3

RussellEast
RussellEast

Reputation: 153

Add a event to ISpecification interface, which returns a string (or an object which contains the string). For each business rule, if it is not satisfied then fire the event containing the reason why. In your command handler, listen to the event of each specification (in a loop) and collect the strings in to a collection and then if no rules have been satisfied, throw your exception using your collection of errors.

Upvotes: 0

Yugang Zhou
Yugang Zhou

Reputation: 7283

Inspired by a comment some days ago. Change

bool IsSatisfiedBy(T entity);

to

Result IsSatisfiedBy(T entity);

public class Result 
{
    public boolean IsSatisfied{}
    public List<String> message() {}
}

But you have to implement && , ! and || :

&& Result r1 = spec1.satisfied(o);
    if (r1.isSatisfied()) {
        Result r2 = spec2.satisfied(o);
        if (r2.isSatisfied()) {
            return new Result();
        } else {
            return r2;
        }
    } else {
        return r1;
    }

 || Result r1 = spec1.satisfied(o);
    if (r1.isSatisfied()) {
        return new Res();           
    } else {
        Result r2 = spec2.satisfied(o);
        if (r2.isSatisfied()) {
            return new Result();
        } else {
            return r2.append(r1.message());
        }
    }

Upvotes: 1

Related Questions