With any software system, there are business rules. Rules dictate what a user can and cannot do. The order in which they're allowed to submit data, the possible values the system is willing to accept from them, the state of the system before they're allowed to perform a specific action. The list goes on. Some might view business rules as a contract — a set of acceptable behaviours when interacting with software. Not only are these rules contractual, but they're also enforceable. One thing software developers try to do is to decouple the business rules from the actual code. Not only is this a best practice in terms of separating concerns — what the software does versus how it does it — it also provides a means to change the business rules without changing the software.
What this gives us is a set of policies, business policies that operate independently of the application we've built. When the policy changes, the behaviour of the software changes too. This all sounds great, having a complete separation of business concerns and implementation concerns. But what kind side-effects are we introducing when the business rules aren't interwoven throughout the code? Are we limiting what users can actually do with the software, or worse, are we introducing deadlocks into the system by giving business policies free reign?
If we're to embed a work-flow policy engine into our code, perhaps a good starting point is looking at the basics. That is, what kind of questions can we ask about the overall state of the system? From there, we can ask if this is something we would want to hard-code, or allow as a parameter to the business end of things. For example, let's say I'm building a simple file upload interface. Let's also say that a requirement of the system is that there must be enough disk space to accommodate the new file. The first option: write some sanity-checking code that ensures there is enough disk capacity before the upload is allowed to start. The safe route — you'll never start an upload only to fail half way through.
Now for the second option. Let's say we want to continuously check for disk space as the upload is happening. That is, we don't want to forbid an upload because only 90% of the file will fit. Files are deleted all the time in this particular system we're building. That means there is a good chance the 10% of our required disk space will become available during the upload. This is something we would most certainly want to avoid hard-coding. Not only is this logic experimental, but since it is far from straightforward, it will likely impact other components in our system should we decide to implement it. Could we express this latter option as a business policy?
Let's go back to the first option for a moment, where we're simply checking for space before the upload starts. What if we were to implement this as a policy instead of codifying our logic? That depends, and this is a good question to ask because it forces us to validate our fundamental assumptions about how the system should work. Should the file uploader check once for disk space and fail immediately? How likely is that to change due to other factors in the system and due to user feedback? Apprehension in any one of these areas lead us to rethink our assumptions about more than just the file uploader — how it is connected to other components — can a more elaborate work-flow be supported without major architectural change?
What I find most useful about these mental exercises on what should be hard-coded and what shouldn't is the evolutionary path that you're prepared to support. Maybe a file uploader that checks for disk in-flight is far-fetched and unlikely. But it opens the door to other potential changes and how hard-coding certain scenarios might become a problem. The opposite is also true — too much embedded business policy. Just like the hard-coded logic means stuff is hard to change, so to is overloading code with external business logic.
At this point in the development game, we've answered some of our fundamental questions about business policies, and where they're beneficial to the long-term evolution of our software. That's half the battle. The other half is the policies we're designing and how they work compared to coding the same logic using a programming language. Is it really all that different? Can we escape some of the pitfalls associated with programming languages and their paradigms of expressing logic? And this is where problems with rigid business rules rear their head — when we're making an assumption about reduced complexity.
Take rigid rules set forth by a business policy as an example. By rigid, I'm talking about validating the state of something to an exact value — not within a range, or almost equal, but dead-on accurate. Like when the database must be connected. There is no in-between for these types of rules, they either pass the test or they don't. These types of business policies are easy to create and plug into slots we've deemed suitable within the code base. Perhaps a little too easy. Too much freedom for exacting requirements in business work-flow leads to scenarios that are very difficult to debug.
Rules that require a condition of system state are fine on their own, but the whole point of adding a business policy engine to the project is to afford the flexibility of building work-flows incrementally. Stacking one rule on top of another. Instead of achieving flexibility, we're building up a disconnect between the code and the business engine. When everything works, we still have that degree of separation between the pure software components, and the written rules of the business domain. So, if we have fundamental rigidity in our policies, when something does go wrong, it is very difficult to sort out.
There are two ends of the spectrum when in comes to implementing business policies. The one end gives rules that must match the state of the system exactly in order to resolve as true. The other views business rules as a guidance tool. That is, business rules simply point certain flows of control in the right direction. Consider the business practice of online retailers providing their shoppers with relevant product suggestions. This type of behaviour fits the business policy candidacy quite nicely because it doesn't require exact values that overly-restrict the actions of users. A store needs flexibility, an easy way to tweak this logic.
Business policies shouldn't be too low-level or even expect high-accuracy. There is no real harm in creating plain-old software components that do these types of calculations. They're really good at it. What they're not so good at is taking a pure business principle and applying it within the confines of the software system. Not from an evolutionary perspective at least. It takes software development effort and that equates to time and money. Business policies are a nice way to experiment with different work-flow implementations to gain a competitive edge.