The Rules Pattern in Business Central - AL Language
The Rules Pattern is a Design Pattern to simplify the complex business logic which is based on multiple if-else statements. Using this Design Pattern each if-else branch in the business logic is segregated into a separate rules and also the processing logic is separated from the rules.
Class Diagram

Demo Scenario
To understand the Rules Design Pattern better, the following scenarios are being used:
If the Customer's "Customer Price Group" is 'GOLD' and the "Business Posting Group" is 'DOM' then 15% discount will be given, else if the "Business Posting Group" is other than 'DOM' 25% discount will be given.
If the Customer's "Customer Price Group" is 'SILVER' and the "Business Posting Group" is 'DOM' then 10% discount will be given, else if the "Business Posting Group" is other than 'DOM' 15% discount will be given.
Rule Interface (DiscountRule.Interface.al)
This is the implementation of Rule Interface for Discount Calculation. The method "Process" is going to return the Discount Percent based on conditions defined in the "CanProcess" method. If the "CanProcess" method returns false, it means there is no discount applicable for the given customer.
interface DiscountRule
{
procedure CanProcess(CustomerNo: Code[20]): Boolean;
procedure Process(CustomerNo: Code[20]): Decimal;
}
Evaluator (DiscountEvaluator.Codeunit.al)
The following logic calculates maximum possible discount. This evaluates the incoming Rule and updates the discount, if the previous discount is less than the current discount.
codeunit 50106 DiscountEvaluator
{
procedure Evaluate(DiscountRule: Interface DiscountRule; CustomerNo: Code[20]; var Discount: Decimal)
var
NewDiscount: Decimal;
begin
if not DiscountRule.CanProcess(CustomerNo) then
exit;
NewDiscount := DiscountRule.Process(CustomerNo);
if NewDiscount > Discount then
Discount := NewDiscount;
end;
}
Rules
End number of Discount Rules can be implemented, in this example two discount rules are being used. First Rule is for Gold Customers, and the second one for the Silver Customers.
Rule One (GoldCustomerDiscountRule.Codeunit.al)
codeunit 50110 GoldCustomerDiscountRule implements DiscountRule
{
procedure CanProcess(CustomerNo: Code[20]): Boolean;
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
if Customer."Customer Price Group" = 'GOLD' then
exit(true);
end;
procedure Process(CustomerNo: Code[20]): Decimal;
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
case Customer."Gen. Bus. Posting Group" of
'DOM':
exit(0.15);
else
exit(0.25);
end;
end;
}
Rule Two (SilverCustomerDiscountRule.Codeunit.al)
codeunit 50111 SilverCustomerDiscountRule implements DiscountRule
{
procedure CanProcess(CustomerNo: Code[20]): Boolean;
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
if Customer."Customer Price Group" = 'SILVER' then
exit(true);
end;
procedure Process(CustomerNo: Code[20]): Decimal;
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
case Customer."Gen. Bus. Posting Group" of
'DOM':
exit(0.1);
else
exit(0.15);
end;
end;
}
Rule Engine / Processor (DiscountCalculator.Codeunit.al)
This is the main codeunit to calculate the Discount. This follows Open Closed Principle which means it is closed for modification and open for extension.
To modify the discount logic, there is no need to change anything in this codeunit. To modify any condition or the logic in the Discount Rule, the appropriate Discount Rule codeunit has to be modified. To add a new Discount Rule, a new codeunit can be created that implements the Rule Interface and it has to be attached to the Processor (in OnExecute subscriber).
codeunit 50105 DiscountCalculator
{
procedure Execute(CustomerNo: Code[20]): Decimal
var
DiscountEvaluator: Codeunit DiscountEvaluator;
Discount: Decimal;
begin
OnExecute(DiscountEvaluator, CustomerNo, Discount);
exit(Discount);
end;
[BusinessEvent(false)]
local procedure OnExecute(DiscountEvaluator: Codeunit DiscountEvaluator; CustomerNo: Code[20]; var Discount: Decimal)
begin
end;
}
Rules Collection (DiscountRuleSubscriber.Codeunit.al)
This codeunit will attach the available Discount Rules to the Processor (DiscountCalculator).
codeunit 50112 DiscountRuleSubscriber
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::DiscountCalculator, 'OnExecute', '', false, false)]
local procedure OnExecute(DiscountEvaluator: Codeunit DiscountEvaluator; CustomerNo: Code[20]; var Discount: Decimal)
var
GoldCustomerDiscountRule: Codeunit GoldCustomerDiscountRule;
SilverCustomerDiscountRule: Codeunit SilverCustomerDiscountRule;
begin
DiscountEvaluator.Evaluate(GoldCustomerDiscountRule, CustomerNo, Discount);
DiscountEvaluator.Evaluate(SilverCustomerDiscountRule, CustomerNo, Discount);
end;
}
Conclusion
Design Patterns represents the best practices used by the experienced developers to solve general problems. In my opinion, the objective and the solution are more important than the implementation itself. Design patterns can be implemented in different ways depending on the Language features and the capability of the developer. And the most important thing is design patterns should not be implemented forcefully when it is not necessary.
The Rules Pattern solves complexity of the business problem by dividing it into multiple classes / codeunits so that the code becomes readable and extendable for future enhancements.
Happy Coding!!!
Source Code can be downloaded from GitHub
Credits to Steve Smith who is the originator of this design pattern.
#MSDyn365 #MSDyn365BC #BusinessCentral #DynamicsNAV #DesignPatterns