Business Logic Layer is a very crucial layer for any data base applications. A timely thought when applied to this layer from the beginning of application layers design can save lots of time and complexity. Software architects divide the software into modules, then different layers, and core functioning layers for important application features. But when actual development work starts, complexity of different layers and modules start crawling into code gradually. Reason being:
- We try to mix business rules of different modules wishfully
- Writing methods with abundant codes
- Not clearly separating responsibilities of presentation and data access layers
- Creating code duplicity, i.e.; writing same set of code or methods at various places
- Difficult to debug
- Difficult to understand the flow
- Difficult to maintain and modify business rules correctly when such rules exist across layers and modules
- Difficult to write Unit Tests
We can avoid these things if we take care of these things when writing codes.
- Write methods that do single meaningful task with one call. Do not mix other code logic with the methods. For example, if we write SavePayment() method, then this method should only focus on save task, and not update or delete or check connection status or read Xml files, etc. This is what we call Single Responsibility Principle.
- Encourage use of factory methods for object creation instead of writing lots of If-else constructs based upon some input type values.
- When you need data or result sets (DTO) of other modules, then preferably call business logic layer methods of that module instead of writing that module code logic into yours. This is quite important aspect for any business logic layer.
- Classes in this layer should be loosely coupled. For this different injection patterns like dependency injection or inversion of control, etc can help. Sometimes even a simple Enum type can come to a great rescue.
- Write business methods that accept valid entity class object or DTO object in business rather than single valued parameters like integer or string or array or even optional params. This ensures business logic layer code function even unmodified when there are database table and entity or DTO class changes in behind.
- Avoid lots of business rules in stored procedures or even in presentation UI.
- Business logic layer methods should not be aware of presentation UI controls’ properties or values. These methods should accept values in integer or string instead.
Let me explain all these points by one example. I worked in an accounting module of a project where customers can make payments of their bills in various ways. They can make either full payment or in-partial or even in installments. For each payment mode, there were different rules and validations. So this module had clear separation of implementation with rules of each mode functioning without depending upon others. This way our lots of coding and debugging time got saved.
Let’s see the code snippets.
Enum showing different Payment mode
public enum PaymentMode
public class CustomerBillDTO
private Int64 intBillNo;
private Int16 intBillYear;
private string strCustomerID;
private Int64 intPayAmount;
Payment Processor factory class and its implementation
IPaymentProcessor GetPaymentProcessor(PaymentMode mode);
public class PaymentProcessorFactory : IPaymentProcessorFactory
private IPaymentProcessor objPaymentProcessor = null;
public IPaymentProcessor GetPaymentProcessor(PaymentMode mode)
objPaymentProcessor = new NormalPaymentProcessor();
objPaymentProcessor = new PartPaymentProcessor();
objPaymentProcessor = new InstallmentPaymentProcessor();
Different Payment Processor classpublic interface IPaymentProcessor
bool SavePayment(CustomerBillDTO Bill);
Main class that processes each paymentclass PaymentProcess
private IPaymentProcessorFactory objProcessor = null;
public PaymentProcess(IPaymentProcessorFactory Processor)
this.objProcessor = Processor;
At the calling end, we simply make a generous call as:
private void BtnSave_Click(object sender, EventArgs e)
PaymentProcessorFactory objFactory = new PaymentProcessorFactory();
PaymentProcess objProcess = new PaymentProcess(objFactory);
bool result = objProcess.ProcessPayment(objCustomerBillDTO(), PaymentMode.Normal);
As we see this is how we have clearly separated each logical functioning of a SavePayment() method. Even in future, if Part or Installment payment mode is stopped, we do not have to modify the code logic to add any If-else construct to branch out or skip any code flows. In case a new payment mode is added, then writing a new XModePaymenetProcessor class, adding one more Enum value and finally one more object instantiation code in factory class will do enough.
Adding or removing any Bill or Customer related fields in CustomerBillDTO do not even pose threat to this business logic layer.
Finally, one should always keep in mind that you write class and class methods for others. So you should be very clear here: what the class should offer and how.