Just Hard Code Everything

We often forget the point why we extracted certain kind of application logic out into some sort of configuration XML or database.

Recently I worked on a system that has a dynamic screen flow for a sales process. This is very common functionality in CRM applications. Basically in the sales process, the user is presented with a series of questions (and sets of possible answers in dropdown/radio-button or free-text), and the flow of the questions varies depending on how the user answers the previous questions based on certain business rule. These end-to-end flows and rules are called a script.

So to accomodate this, the developers built a mechanism to store these rules and flows in a set of database tables. Everyone would get terrified if I suggest “just hardcode all of them!.” Typical reaction is that these business-logics are so volatile, subject to change every once in a while. We need XML configuration, or mastered in database, or build DSL. Hardcode just sounds insanely terrifying. They wanted some mechanism that allows easy changes.

So we’ve had this implemented as a set of database tables such like these (among others):

  • SCRIPTS, storing several different scripts (representing an end-to-end flows for specific use-case)
  • STEPS, represents each step (question/calculation) within a flow
  • SCRIPT_STEPS, assign a step into part of a script. Few of its columns:
    • SCRIPT_ID
    • STEP_ID
  • STEP_NAVIGATIONS, governs flow between steps of a script. Few of its columns are self-explanatory:
    • SCRIPT_ID
    • FROM_STEP_ID
    • TO_STEP_ID
    • CONDITION
  • WIDGETS, specifies set of widgets need to be presented for each step. Not every step has UI presentation.
    • WIDGET_ID
    • WIDGET_TYPE (textbox, textlabel, dropdown, button, etc)
    • LABEL
    • VALUES
    • VALIDATION
  • STEP_WIDGETS, finally this is to link several widgets to each step
    • STEP_ID
    • WIDGET_ID

We put this configuration in database to allow easy change and restructuring. But look around, does that make it somewhat easy?

Reconfiguring the scripts is achieved by writing a series of insert and update SQL statements, changing these obscure columns and those flags in trial-error fashion. Each step/widget/property/navigation are represented in numeric IDs and flags. Even the simplest change to the business flow (e.g. adding a new step) takes careful examination of various tables, changing/adding data in numerous tables, some obscure data manipulation SQL over several tables full of magic numbers and flags to get all stars alligned perfectly. (I always need at least a pencil, a piece of paper, and strong determination just to make the slightest change to the navigation flow).
It feels like programming on punch-cards. And no unit-test is possible to make sure we’re doing it right.

Everyone seems to forget, “why did we build all these again”?
We often get trapped by ellusive purpose of building a configurable system with the premise of allowing runtime reconfiguration of the business logic. But in real practice, any change to business logic is a serious manner, requiring a proper release cycle. Runtime change to business flow while the users are still actively working will jeopardize system integrity. And most change-requests to the business logic need code changes anyway. And most importantly, all these configuration are CACHED at runtime! So we never really have any capability making immediate change to the script configuration in the database in the first place.

What’s the purpose of having a rule engine?
The main purpose of a configurable rule engine is NOT so that we can change it during runtime. It’s more to provide developers a Domain Specific Language to express the rule and business flow, translating business flow-chart into a working application, and probably allowing the domain-expert to read or verify the logic.
This is a good reason to take the application-logic out from the code, and put that into some sort of “readable” configuration. Some kind of xml structure, or DSL file, or database-backed authoring interface.

But this DB-based configuration we’re having right now, it doesn’t bring that value. We extract the logic into configurable database tables only to make it harder to express, almost impossible to read, and terribly tedious to change. It left us nothing.
Now, if you’re not providing DSL or authoring interface, you better off hardcode it. C# code is still way more readable and easier to write and change than bunch of numeric database collumns and tables.

Sometimes we need to step back a little bit, forget about neat configuration and just hardcode everything!

// ---- Step Definition ----
var askIfLinkedToHandset = script.CreateStep<bool>()
	.Ask("Do you want to link this product to a handset")
	.RadioButton("Yes", true)
	.RadioButton("No", false);

var askHandsetSerial = script.CreateStep<string>()
	.Ask("Please enter handset IMEI")
	.Textbox();
	.Validate(x=>x.Answer.Length == 20, "IMEI needs to be 20 digits");

var calculateFinance = script.CreateStep()
	.Execute(data=> data.FinanceAmount = CalculateHandsetFinance(askHandsetSerial.Answer));

var askOutrightAmount = script.CreateStep<Decimal>()
	.Ask("Finance amount for the handset is {0}. How much do you wish to pay outright?"
		, financeAmount)
	.Textbox();

var askBonusChoice = scripts.CreateStep<BonusOption>()
	.Ask("Please choose your preferred bonus option")
	.Dropdown(()=>bonusRepository.GetBonusOptionsForProduct(data.Product))
	.WithText(bonus=>bonus.Name);
// ---- Navigation flow ----
Navigate.From(askIfLinkedToHandset)
	.When(x=>x.Answer == true).GoTo(askHandsetSerial)
	.Else().GoTo(askBonusChoice);

Navigate.From(askHandsetSerial)
	.GoTo(calculateFinance);

Navigate.From(calculateFinance)
	.When(x=> x.Data.FinanceAmount == 0).GoTo(askBonusChoice))
	.When(x=> x.Data.FinanceAmount < x.Data.MinimumOutright).GoTo(fullOutrightPayment))
	.Else().GoTo(askOutrightAmount));
	
Navigate.From(askOutrightAmount)()
	.GoTo(askBonusChoice);

This is much easier to read! More importantly, it’s easier to change! It takes little effort to modify existing logic/calculation, or to radically swap over some navigation flows, or add bunch of new steps. We are dealing with concrete objects in OO fashion. Navigation flow is defined very clearly. It’s no longer a set of obscure numbers and flags scattered all across many normalized database tables. Shall domain-experts choose, they would prefer reading this source-code than the previous ultra-complicated DB config.

“RDBMS-based configurations are better in accomodating future change in business logic?” It does remove compilation step, but what’s so difficult about compiling this single .cs file? Is that really harder than writing database-migration SQL script?

Hardcoded logic like this is also much better in version-control. We can track the changes of the logics in CVS/SVN history. Alas what I have right now with our sophisticated DB configuration is just a series of long SQL update statements that keeps being added overtime as the business-flow evolves. Almost impossible to make out what business-rule has changed in each SQL update statement.

Finally, this piece of code is really easy to test. I can execute that navigation tree and easily poke it with various combination of data/answers and observe the flow of the steps.

Advertisements

2 thoughts on “Just Hard Code Everything

  1. Pingback: Twitter / ayende
  2. This is what i have been thinking for on how to avoid configuring our application’s business logics in database and put them in C# codes. What a brilliant solution you written in this article. Thanks Hendry 😀

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s