In the last issue, I explained that one half of domain modeling is encoding the abstract domain into software.
The other half is figuring out how the real process maps to the abstract domain in the first place. It turns out that’s not so easy. And that’s what I want to talk about this week.
I’ve talked about this topic at meetups and conferences, and when I do someone invariably says that their system is too complicated to model. Their software is an overlay of regulations, laws, common practice, and historical accident. It’s a mess of exceptions with hardly a rule in sight. They are forever doomed to piles of if statements, each more nested than the last.
And they might be right. I never see their requirements, so I can’t be sure. But whenever someone claims that, I imagine medieval bankers saying the same thing. “Banking is a complex business. There’s no way you can find a way to do it only on paper. There are too many laws and too many exceptions.” And yet, banking became abstracted from the vaulting of gold and became an information system long before computing.
I don’t have a complete picture for how to construct these abstractions, but I have identified several keys that I think are vital:
Open eyes
Underlying abstract concepts
Understand like a practitioner, but think like a programmer
Open eyes
When Galileo was revising physics, he was messing with the ~2,000 year legacy of Aristotle. Galileo had to start from scratch and rebuild even the most basic concepts. And he did that by opening his eyes to the real world and refining instruments that would let him see more. In comparison, Aristotle was more of an armchair scientist, pontificating on how things work but never testing his ideas.
Likewise, it must have been the same at the bank. Someone had to see past the chaos of bankers running about, weighing gold, safely storing keys to vaults, sweeping the floor, and wheeling carts full of ingots around. Someone had to ask what was truly important to banking, and what was incidental. Spoiler: It was the quantities of gold, where the gold came from and where it is going to. Those things became abstract concepts that were encoded as a transaction with a quantity, a debit account, and a credit account.
And so it is with any domain we approach. At first glance, it will always appear to be a mess. Not just a mess because humans love to be busy. But also because there are so many choices to make. Any rules we are taught come with a long list of exceptions.
Underlying abstract concepts
When we try to organize a domain, we typically try to apply a schema like a four-quadrant analysis or a hierarchy:
If we can’t fit the domain to one of the organizational templates, we need to go deeper. I explained a useful example for how to do this in my podcast about five years ago. Luckily, you don’t have listen to my incoherent rambling. I’ll explain it right here quickly.
I was working at a company that dealt with the voting laws in the US. Each of the 50 states has its own laws, and they’re changing all the time. We wanted to be able to tell a voter if they were allowed to vote in a certain election and what they needed to do to vote. Some states needed you to register at least two weeks before the election. Some needed you to bring an ID. In some you needed to be a resident. Every state was different. It was a rat’s nest.
People were trying to find order in it. People had started calling states that you didn’t need to register ahead of time “walk in” states because you could just walk in. There were other terms and categories. But these categories were deceptive. There was no hierarchy between them. No other correlations. Just because you were a walk-in state didn’t mean you had the same rules about absentee ballots. It was just a mishmash of rules. We tried to impose an order, but none of it helped make the giant nested if statement any better.
What I realized (too late; I had already left the company) was that we needed to find a better underlying concept. And the concept I found was simple: A function signature that asked the fundamental question we were trying to answer, “Can person X vote in election Y?”
function canVote(person, election) //=> boolean
With this signature, we can break it down into smaller rules:
function ofVotingAge(person, _) //=> boolean
function isResident(person, election) //=> boolean
Then, it’s easy to imagine functions that can combine these rules:
function ruleOr(ruleA, ruleB) //=> Rule
function ruleAnd(ruleA, ruleB) //=> Rule
Now we can build up each state’s rules out of many smaller rules. The reuse is at the bottom, not in the disorder at the top. Likewise, the order in banking is underneath the mess. When we’re staring at a mess, we have to look deeper and find concepts our mess can be made out of. There is usually an underlying order.
Understand like a practitioner, but think like a programmer
When we build a domain model, we have to know the domain at least well enough to be able to see how to translate the rules into code. We need to understand the rules so well, we can see past them.
I strongly believe existing human processes contain a lot of wisdom. But sometimes, things could be improved. You know, made a little bit more consistent. More symmetrical.
Basically, I think we can all be Newtons. We can deal with the horrible nested if statements, but we also want to clean it up. Code can help us clarify the messy domain. But we have to understand it first.
Conclusion
I don’t know where the skill of abstraction comes from. As beginner programmers, we start on the encoding half of the problem. But as we grow, we gain skill in developing the abstraction half of the problem. In the next issue, I want to talk about a perspective I’ve been exploring that seeks to align these two halves.