8 Business Rules as Data
In this chapter, business rules are treated as data instead of hard-coded control flow, which makes them easier to model, inspect, reuse, and evolve. The example centers on routing customer accounts to sales organizations based on a messy set of human-defined conditions, and shows how plain records and enums can capture just enough of the domain to express those rules without dragging in heavyweight abstractions.
The core idea is to define a small algebra of rule data, including equality, conjunction, disjunction, and negation, and then interpret that data in different ways. One interpreter evaluates a rule against an account to produce a Boolean result, while another returns a richer result that explains why a rule matched or failed. By separating the representation of the rule from its meaning, the same data can drive execution, debugging, documentation, and even visualization.
The chapter then improves safety and expressiveness by adding typed attributes, generic equality, comparison operators, and helper combinators such as contains, any, and all. Java records, sealed interfaces, and pattern matching make it practical to build this tiny rules language in only a small amount of code, while still preserving portability and enabling future tooling. The main takeaway is that modeling operations as data gives Java a flexible, inspectable, and extensible foundation for solving real business-rule problems without overengineering.
The challenge of routing a customer account to the right sales organization
What we’re working with
Good modeling hides complexity we don’t care about.
Rules must each map to a unique org.
What shouldn’t happen. How would we pick which rule was right?
The job of a rule is to partition accounts and OrgIds into disjoint sets
Capturing what we know after evaluating a condition as data
Modeling operations as data
Creating an enum that models the relevant attributes
Modeling logical AND and OR as plain data
Modeling negation as plain data
Viewing higher level operations like contains in terms of simpler primitives
How do we get from data back to code?
Translating data back into code
This same simple data model could power a fancy UI in the future
Changing what our data denotes
Parameterizing Equality with a generic type variable
Somehow, we have to relate these two input values at the type level
Forcing that types all be the same
Giving concrete evidence to the type system that we know what we’re doing
Visualizing rules
Summary
- Records and pattern matching have changed the cost of building custom DSLs in Java
- In addition to nouns, “things we know,” and decisions, data can also model operations
- Modeling operations as data decouples what we want to do from how we do it
- It also decouples when we do it. Data describes operations we’ll evaluate later
- Operations as data are just data! They can be stored, serialized, transformed, and transmitted like any other data
- Expressing operations as data types allows us to build algebras on top of that data
- Complex algebraic operations can be built from simpler primitives (like And and Or)
- Algebras aren’t “stuck” with one data type; we can build layers that transform more convenient data types into our algebra
- Designing around an algebra yields compositional APIs without any effort on our part
- Interpreters are just denotations in reverse. We decide what the data means
- Data can be interpreted however we want (and in more than one way!)
- The line between interpreter, DSL, and “just writing code” is blurry in the world of data-oriented programming.
- The more well modeled your data, the more it will fall into interpreter-like shapes
- It’s OK to build small “interpreters” that solve the problem at hand and nothing else!
Data-Oriented Programming in Java ebook for free