Monday, 29 January 2007

The Business Logic Layer

Almost every enterprise application in the world today is divided into layers. We have a presentation layer (thick, smart or thin client), business logic layer (BL), containing the domain logic and data access layer (DAL).
The idea is that different layers have different responsibilities, thus allowing us to manage the inherent complexity of software applications.

This separation, among the other benefits, allows us (the developers) to concentrate on a particular task during the process of building the application.
If for example you have to deal with connection strings, SQL commands and recordsets / datasets in the event handler of a button click, this increases complexity, reduces manageability and is, in general, the shortest road to hell. This is a reasonable approach only when building some very small and very dirty application, but is a pure madness for anything bigger.

Today I'm going to share some thoughts about the BL layer.

As the name implies, this is the place, where you should code your business logic (the logic of the problem domain). This layer contains your business objects (Customer, Agent, Contract etc.), as well as some business services (CustomerService, AgentService etc.).
Relations between the objects in this layer often, but not always represent some real-world relations (e.g. Agent has Customers, Customer has Invoices etc.).
Objects and services in the business layer usually have a well defined and well-thought interface. A CustomerService for example may contain methods as

GetCustomers(){...}
GetCustomerPreferences(Customer customer){...}
UpdateCustomerPreferences(CustomerPreferences cp){...}

and so on. It must provide a good abstraction, closely oriented to the problem domain.
Let's say you are building an accounting application, and you have a class named InvoiceService.
Then, I found that a good rule of thumb is that if you show your InvoiceService public interface to an accountant, he should be able to understand what at least 80% of the methods are doing. If he doesn't (and yes, you are sure he's really an accountant), you should consider to redesign your service's interface.
Some may ask why 80%, why not 95 or 100%. Well, I think it is normal and almost inevitable at some point to introduce some more technically oriented methods in the BL services and objects. For example our InvoiceService may have a method like this:

GetInvoiceItemsProductsForReport(...){...}

While this method may not be clear at a first glance for our professional accountant, it should be clear for developers, who know the application object model and architecture. So it is normal to have methods like this as long as they remain a relative minority. But if they become too many, they start to severely obscure the domain orientation of the business service.
At this point you should consider to separate the business service to two or more services, in our example, it may be benefical to have InvoiceService and InvoiceReportingService.
As the name implies the second service contains methods that are used for reporting purposes and thus it is OK this service to be more technically oriented. I prefer to call services like this "class 2 business services" to distinguish them from "class 1 business services" which are purely oriented to the domain logic.
Another useful way to think about this is to imagine, that you must make your InvoiceService a public XML WebService. Then some accounting company, "ACME Accounting", probably located on the other side of the earth, should be able to use your service and build an invoicing application arround it. ACME Accounting should be able to understand how to use your service properly for their purposes with as little difficulties as possible. This is achieved by keeping the service as tightly oriented to the problem domain as possible.
InvoiceReportingService, as class 2 service, is OK to be more closely related to your particular application and its object model. It is more unlikely to export this service directly to public end users, and if you really have to, perhaps you will hide it behind some fscade layer. This difference is what usually distinguishes class 1 from class 2 services.

Some Common Pitfalls in BL

Validation. BL services and objects are the place, where enforcement of business rules and validation should occur. Here you code logic as "An invoice should have at least one invoice item and each invoice item should have only one product". A common mistake is to make validation entirely responsibility of the presentation layer or the database data integrity. Yes, it is perfectly OK to have validation in the presentation layer possibly to avoid unnecessary roundtrips to BL, but it should always be backed up in the BL.
I often see web applications that rely on client side javascript field validation in the web forms. While this is OK in the sense that it improves user experience and the UI responsiveness, it should be never left as the primary validation point.

No business logic at all. Too many times I've seen "business services" that more or less do the following:

...
public Customer GetCustomer (int customerId){
return this.dataService.GetCustomer(customerId);
}
...

or the same, but with some data conversion:

...
public Customer GetCustomer (int customerId){
DataRow customerRow = this.dataService.GetCustomer(customerId);
return new Customer(customerRow);
}
...

The problem here is that there is absolutely no business logic in these methods. What they do is just to serve as a bridge between the presentation layer and the data access layer. While it is usually OK to have some "bridge" methods, if they are a majority (which is a very common practice), your business layer is in trouble. It is not a Business layer anymore, it is a Bridge layer - at least the "B" in the BL stays.
In this case, because the application should still have some logic, the business domain logic is either moved to the presentation layer, either to the database backend (using stored procedures), or some mix between the two. Managing such kind of system is hard and it is getting harder with time, because the logic is spreaded among UI components, stored procedures and sometimes in business objects.
Symptoms of such kind of application are too many hollow business methods which just call the corresponding DAL methods, long and messy code in the UI components (sometimes several hundred and even thousands of lines in a button click), and/or complicated stored procedures in the database.
The solution is to try as much as possible to keep layer responsibilities consistent. Presentation is presentation. If it contains complex code, this code must be related to presentation problems, not business problems. Database is the persistent storage, and not the place where the business logic is implemented.
Using stored procedures is OK, because they increase speed, but stored procedures are not the place to implement business rules. I've seen a large scale enterprise application that in 90% of the cases is doing something like this:

1. User clicks the "Save Order" button
2. UI sends a message to the BL layer (a COM+ component in this case), containing the Order object
3. BL layer builds a huge and complex XML document (serializes the Order object) and sends it to a database stored procedure
4. The stored procedure parses the XML, analyzes the content and inserts in some tables and updates others, validating integrity on the way.

As a result of this architecture, database stored procedures quickly become unmanageable monsters, which are hard to debug and modify. The company is hopelessly tied to the database vendor, because translating stored procedures from PL/SQL to T-SQL or vice versa is just too expensive and risky. So if it's Oracle, it will stay Oracle forever. Period.
Also, the database server is now doing a job it never has been designed to do, and the load is huge.
What I'm trying to say is to avoid placing business logic code inside the database. If you have stored procedures, be careful and minimize the amount of code contained in them. Database is the storage place.

Another interesting phenomena I've frequently observed, is the very close mapping between BL interface and the DAL interface. So if you have a method

GetCustomerInvoices(Customer customer){...}

in the BL, you have the corresponding method in DAL:

GetCustomerInvoices(int customerId){...}

While this is OK in many cases, it usually introduces a very high coupling between BL and DAL. Even a small change in the BL will propagate down to the DAL and vice versa. The problem is that in most enterprise applications, business rules may change frequently, so it is a very good idea to isolate business logic as much as possible. In a perfect world changes in one layer should not propagate to the other layers. While this is not a realistic goal to achieve, we as software engineers should do our best to minimize the domino effect as much as possible.
The solution is the Bridge pattern, in which we have an abstraction (the BL services), and the implementor (the DAL services). Usually DAL services should provide more data-oriented interfaces. In the above example, possible solutions for methods in the DAL may be:

GetInvoiceList (Constraint constraint){...}

or even:

GetObjectList (Type objectType, Constraint constraint){...}

This ensures, that if we change the BL logic, changes are more likely to remain in BL, because BL is responsible for proper construction of the constraint and objectType objects.
After all, the main purpose of layers in a software application is managing the complexity and isolating change propagation.

Well, that's all for now, it became a pretty long post, I just hope it is not desperately boring. Of course you may or may not agree with what I said. I just wanted to share some observations I made through the years I've spent working in the field, struggling (but also having much fun) with one of the hardest intellectual jobs in the world today - designing and implementing complex business software solutions.