Tuesday, June 1, 2010

Identifying domain rules hidden in if-else

I have noticed that sometime we tend to hide domain rules in if-else block .Here is one example to illustrate .Applying this simple change we can make rules easily configured into your sytem without making any modification to client (like you can plugin your rules )

public class CancellationService {

public void cancelTicket(Ticket ticket ){
if(isCancellationAllowed()){
ticket.setStatus("Cancelled");
}

//there might be transaction related stuffs here

}
private boolean isCancellationAllowed(Ticket ticket){
if(new Date() > ticket.showDate()){
return false;
}

if(ticket.bookingType()=="WINDOW_BOOKING"){
return false;
}

if(ticket.BookingType()=="PHONE_BOOKING"){
return false;
}


if(ticket.BookingType() = "SPECIAL_OFFER"){
return false;
}
return true;
}
}

As you might have already noticed that later this application might need you to add more rules .This would mix cancellation class with rules .And if you do it this way ..rules are not easy to review by domain experts.


Another better way is to --

public interface cancellationValidator {
public boolean cancellationAllowed(Ticket);
}

public class WindowTicketCancellationValidator implemets cancellationValidator{
public boolean cancellationAllowed(Ticket ticket){
if(ticket.cancellationType() == "WINDOW_TICKET"){
return false;
}
return true;
}
}

public class TicketStatusCancellationValidator implemets cancellationValidator {
public boolean cancellationAllowed(Ticket ticket){
if(!ticket.status .equals("BOOKED")){
return false;
}
return true;
}

}


//now make a TicketCancellation Rule Generator
//a static factory for now

public class TicketCancellationRuleGenerator {
public static List getCancellationValidators() {
//you should return immutable List
List validators = new ArrayList();
validators.add (new WindowTicketCancellationValidator() );
validators.add(new TicketStatusCancellationValidator());
//you should add all your validators here
return validators;
}
}

//now write a master cancellationValidator --
//using composition pattern

public class MasterCancellationValidator inmplemets cancellationValidator {

public boolean cancellationAllowed(Ticket ticket){
for(CancellationValidator val = TicketCancellationRuleGenerator.getCancellationValidators()){
if(!val.cancellationAllowed){
return false;
}
}
return true;
}
}

//Now lets refactor cancellation Service class

public class CancellationService {

//autowire this dependency
private CancellationValidator masterCancellationValidator;

public void cancelTicket(Ticket ticket ){
if(masterCancellationValidator.isCancellationAllowed()){
ticket.setStatus("Cancelled");
}

//there might be transaction related stuffs here

}

}

Using this simple technique now you can add more rules without making any changes to cancellationService .Now each cancellationRule is simple class that makes it very easy to review and test.

Also you can enable/disable rule with few changes in CancellationValidator.

If you are using Spring in your app.You can make your rules really plugable .For that to happen -your MasterCancellationValidator should implement Springs'
ApplicationContext interface .Now you can find all beans of type CancellationValidator .This way no need to add rules manullay in TicketCancellationRuleGenerator.I should show that in my next post.