When I listened to Martin Fowlers Talk last week at Java User Group Cologne, I felt the urge to talk about one of my projects at codecentric. Martin said that DSLs are very useful because the business people can actually read the code programmers produce. This is in fact more powerful than the often brought argument, that business people can write code.
Martin did a fair amount of ranting on the structure of Java code. He said that you need to decode it like a detective, so he built his own language with a parser. But noting on that, he made a great quote:
Some people think that writing parsers hard, because they did not attend a compiler class at college. And those who did attend such a class know that writing a parser is hard.
And because of that, he showed that his custom built DSL is pretty much valid Ruby code.
But the point is that once you have your concepts laid out, the actual formatting doesn’t matter that much and you can write your DSL in a host language like Ruby, or, as we did, in Drools .
Drools offers a Rule Engine. That means the semantics behind it are already optimized for evaluating stuff.
We needed three DSLs for the project:
- A recommendation engine, which uses profile data to come up with recommended products
- A validation engine, which checks the profile for consistency
- A calculation engine, which uses profile data to calculate prices
All three were similar enough to consider using one technology for them, but they are in practice quite different. The recommendation engine would check for certain properties and come up with a recommendation, while the calculation needs formulas or tabular data. The validation works similar to the recommendation, but returns errors instead.
With knowing that, JBoss Drools came immediately to our mind. It was a perfect fit from technology and we only had to work on a nice way integrating our domain into the Drools DSL.
All three need to work on what we call a profile. We already have a domain object representing that, but invoking plain getters was not wat we wanted. For example we wanted to check for a age but only had a birthdate in our profile. So we came up with the idea of creating a wrapper around our domain object. This wrapper takes then all accessors needed to formulate the DSL from a business perspective.
Output is handled by global objects in Drools where you can set values on, so we used a nicely named output object in the global space.
The rest of the excercise was just to tweak it, so that it became readable. And in fact this worked out for us. Our customer can read recommendation and validation rules to verify them, and they can fill in the pricing data.
Judge for yourself. Do you like how it looks? Yes, that is a DSL. And not even a fancy one.
Here, take a look at our validation DSL using Drools:
1global ValididationErrors errors; 2 3rule "age needs to be less than 101" 4 when 5 Profile( age > 100 ) 6 then 7 errors.add("If you are older than 100, you need to contact your local sales consultant.", Ids.BIRTHDAY); 8end 9 10rule "Birthday needs to be in the past" 11 when 12 Profile( birthday > today ) 13 then 14 errors.add("We can only provide a calculation for already born people.", Ids.BIRTHDAY); 15end 16
And here our recommendation DSL:
1global Productportfolio products; 2 3rule "children" 4when 5 Profile( age <= 15 ) 6then 7 products.recommend( "PRODUCT_A", 5 ); 8 products.recommend( "PRODUCT_B", 0 ); 9 products.recommend( "ANOTHER_PRODUCT", 3 ); 10end 11
and a screenshot of our calculation engine. You can even fold the spreadsheet to hide more implementation details:
We had great success with that, as we can hand those DSLs and spreadsheets to business people, who can validate that the “programmed logic” is what they want to see.