If you’ve developed software for long enough, you’ve most certainly heard of a “business logic layer.” It’s supposed to be the layer (or “tier”) containing an application’s business logic and is sandwiched between a “persistence layer” and a “presentation layer.” Some call that the “standard three tiers of an application.” But what it really is, however, is a bad design that leads to bad software. Or at the very least, dangerously poor semantics. In lieu of your standard WTF article, allow me to explain why.
First and foremost, we need to define the term “business logic.” Unlike so many other entries in the IT lexicon, “business logic” has no standard meaning. We’re left with what you think it is, what your colleague wants it to be, and what some article you read says it is. So, for the purpose of this article (and hopefully beyond), here is my definitive definition.
business logic (n.) — any program code (“logic”) that pertains to the purpose (“business”) of a software application
It’s a fairly broad definition for a fairly broad term. If you think about it, virtually every line of code in a software application is business logic:
- The Customers database table, with its Customer_Number (CHAR-13), Approved_Date (DATETIME), and SalesRep_Name (VARCHAR-35) columns: business logic. If it wasn’t, it’d just be Table032 with Column01, Column02, and Column03.
- The subroutine that extends a ten-percent discount to first time customers: definitely business logic. And hopefully, not soft-coded.
- And the code that highlights past-due invoices in red: that’s business logic, too. Internet Explorer certainly doesn’t look for the strings “unpaid” and “30+ days” and go, hey, that sure would look good with a #990000 background!
So how then is possible to encapsulate all of this business logic in a single layer of code? With terrible architecture and bad code of course!
No One Likes Business
When it comes down to it, us software developers don’t like writing business software. It’s terribly, mind numbingly boring. Just look at the dreadful specs we’re given to work with:
When a Sale is Cleared, only Managers with Void Approval and Executives may issue a Cancellation Request. If the Propagation Status for the Transferable Receivable is not Pending and the Expense Allocation Type is Reversible, the Cancellation Request is issued for Processing; otherwise, it is issued for Approval.
I’m sure those of you who managed to make it through that spec did not have visions of IF-ELSE code blocks swirling through your head. I’ll bet some of you, without even seeing the rest of specs, excitedly envisioned a CancelationWorkflowProvider that inherited from the abstract RequestWorkflowProvider and implemented the IPermissionRequired, IPropogationStatusRequired, and IExpenseAllocationTypeRequired interfaces, and was powered by the all-encompassing WorkflowManager. Why? Because that’s so much more challenging than writing a simple IF-ELSE code block.
Michael A. Jackson (no, not that Michael Jackson) observed this exact phenomenon in his 1975 book, Principles of Program Design.
Programmers… often take refuge in an understandable, but disastrous, inclination towards complexity and ingenuity in their work. Forbidden to design anything larger than a program, they respond by making that program intricate enough to challenge their professional skill.
So what does this have to do with the “business layer” being a poor design? Everything. By implying that a system’s architecture should include a layer dedicated to business logic, many developers employ all sorts of horribly clever techniques to achieve that goal. And it always ends up in a disaster.
The Enterprise Rules Engine, Revisited. Again
Yes, I realize that the Enterprise Rules Engine — the ultimate example of a soft-coded business layer — has become my go-to example for bad software. But it’s for good reason. The ERE truly represents the absolute worst kind of software. It was as if its architects were given a perfectly good hammer and gleefully replied, neat! With this hammer, we can build a tool that can pound in nails.
One “problem” that was immediately apparent to the designers of the ERE was user-input validation. Certain forms had certain fields that were required in certain circumstances, and end users needed those fields identified with an asterisk. While coding the validation rules in the HTML form’s onsubmit() method would most certainly do the trick, the ERE team found that to be completely unacceptable. After all, business logic had no place in the presentation layer.
The answer to that was surprisingly simple. They built a two-way data-binding utility that would save to and load from fields on the business entity with controls on the page. It got pretty tricky when it came to dropdown lists, file uploads, and the like, but they coded around that as well.
As the ERE developers continued on their quest of freeing the presentation layer from business logic, they came across another “problem.” In order to display and constrain certain fields in the user interface, they had to implement business logic. Thing like, the “years old” field should only be two characters big. So they coded around that, too. In addition to a validation rules, the business entity classes returned field lengths so that the presentation layer knew how to size the controls.
As more business logic “problems” arose, the ERE team pushed the business logic a layer down. Eventually, they found the perfect balance. Using XML “display templates,” they could simply serialize their business entities into XML, mash the two together with an XSLT, and send the resulting XML “display instructions” to the presentation layer to convert into a usable UI. I’ll let you imagine how well that worked out in the end.
The Persistence Plague
Unfortunately, the obsession to create a single layer encapsulating the all business logic has crept its way to the back-end. Not content with “mucking up” the database with business logic, some developers have gone the route of “persistence.”
The idea behind persistence (at least, as far as most in the industry have defined it) is that business layer objects (e.g. instances of a Customer class) are persisted in some storage mechanism. Note that this is a drastically different approach than simply coding classes to retrieve and save the appropriate data from a database. With persistence, very little (if any) business logic needs to exist in the storage mechanism.
One of the most flagrant examples of this persistence paradigm is a framework called Prevayler. Its premise is simple. The framework serves as a container for all business objects and stores them in memory. Changes to these objects are accomplished through a Transaction interface, where the framework makes the change (e.g. set the Status property on a Customer object to “Accepted”) and logs it.
While it may seem like a novel concept (it certainly is clever), it’s a terrible solution for most problems. Any framework — Prevayler certainly isn’t the only one — that attempts to remove business logic from the database will consequently remove useful information as well. Tom Kyte elaborated on this point exactly in The Ultimate Extensibility:
One table (no need to pester those mean DBAs asking them to create tables or indexes or anything). It can hold any object on the planet:
I have a table with a blob field, and type among other details. Say,CREATE TABLE trx ( trxId NUMBER(18), trxType VARCHAR2(20), objValue BLOB, ... )
Blob Fields contains a java serialised object, different objects based on types, though all of them implements same interface. We access this object always thru' j2ee container, so it works fine so far.
Unfortunately, the "architects" of this system discovered that:
Now users want to use reports using sqlplus, crystal reports etc. So they want a solution to this blob issue.
The core problem with all persistence approaches, as Tom pointed out, is change. A relational database — something actually designed for indefinite storage of a changing data model — can easily be modified with a few ALTER TABLE queries and accessed by virtually any other application. The data stored for a persisted object, however, is meaningful only to the object’s class and can’t readily be accessed or manipulated with anything else.
It’s actually a rather familiar problem. MUMPS has “business logic free” persistence. Other ancient languages do, too. Relational databases are a solution to that problem, not another problem to be overcome.
All About Semantics
The Enterprise Rules Engine and the Persistence Plague are fueled by two factors: bored and clever developers, and dangerously poor semantics. Thinking of an application as existing over three separable layers — persistence, business, and presentation — encourages development towards that goal.
As demonstrated, it’s completely infeasible to encapsulate an application’s business logic into a single layer. It’s also logically impossible.
By the time a developer creates the perfect persistence layer — something that takes in any type of data, tucks it away somewhere, and provides an easy mechanism to retrieve it — he has created a separate infrastructure application. Recreated, actually. The operating system’s file system already does exactly that.
By the time a developer creates the perfect presentation layer — something that takes in any type of data and displays it in a flexible manner — he too has recreated an infrastructure application. ASP/PHP/etc with HTML already does a fantastic job of implementing that goal.
There is absolutely nothing wrong with having a multi-layered application. In many cases, anything but that would be a bad design. It’s absolutely critical, however, to not think of these layers as persistence, business, and presentation. Database, processing, and user interface are much more appropriate terms.
The Ideal Approach
Separating application code into different layers is important: it’s a core principal (“loose coupling”) of structured programming. But it’s just as important — if not more so — to keep in mind that almost all of an application’s code will be business logic. There are already enough tools out there; your application does not need a generic layer.
A good system (as in, one that’s maintainable by other people) has no choice but to duplicate, triplicate, or even-more-licate business logic. If Account_Number is a seven-digit required field, it should be declared as CHAR(7) NOT NULL in the database and have some client-side code to validate it was entered as seven digits. If the system allows data entry in other places by other means, that means more duplication of the Account_Number logic is required.
It almost goes without saying that business logic changes frequently and in unpredictable ways. The solution to this problem is not a cleverly coded business layer. Unfortunately, it’s much more boring than that. Accommodating change can only be accomplished through careful analysis and thorough testing.
With simple, consistent code, change analysis is a breeze. A simple trace through the layers of code with a text-search tool will generally reveal exactly what needs to be changed, and where. A solid test plan will identify what changes broke the system and how, and allow developers to respond appropriately.
No, it’s not fun and not challenging. It’s just good software.