New tool for writing Domain Specific Languages in Drools
When I started working on Drools in January of 2004, my goal was to eventually be able to embed Drools in a web-framework-turned-action-sequencer I was working on, called Shocks. Two weeks ago, to my complete astonishment, I realized I'd implemented the complete inverse of my original idea--and much more.
This May, after a winter-long recovery from post-mercenarial burnout, I began reapproaching the problem of Domain Specific Languages in Drools. I began working with the Drools semantic module framework (drools-smf), trying to figure out how to write DSLs. There's no real documentation on the subject, and I committed myself to writing those docs, just as soon as I could figure out how it all worked. I decided to write a simple example DSL called "Expresso" (sic), to illustrate the concerns of a very specific problem domain--the sacred and immortal rules that guide the behavior of coffee shop patrons. The name of the language was intended to underscore the hilarity of the problem domain. Armed with this irony, I set about coding.
My work came to a head last week when I finished implementing an inversion of my original idea for Shocks. Instead of embedding Drools and a custom Domain Specific Language inside of an action sequencer, I implemented the action sequencer as a domain specific rule language in Drools. Ultimately, I found myself standing on the other side of a mirror I first looked through about two years ago. Only this way actually kills several other birds as well, such as the problem of building extensible and natural sounding rule languages that business analysts can understand.
I love a good surprise.
What I learned over the course of the last month of feverish programming, was that the drools-smf library is so expansive in what it permits, that anyone with solid knowledge of XML and Java (i.e., has written their own XSDs and parsers, eats DTDs for breakfast, etc.) should be able to create just about anything they can imagine in the way of XML-based rule languages.
However, the price of this permissiveness is complexity--Drools' semantic module framework requires that language designers learn about the way the underlying rule engine works. They'll find themselves working with tuples, the working memory, fact handles, declarations and underlying logic closer to the RETE-OO core of Drools than they might care to travel. The more I worked with it, the more I felt it encroaching on what I was trying to do. I became convinced that Ã¼ber-natural sounding language (with proper gammer and fine grained language particles) wasn't really necessary if you could just nail an expression to an XML element name.
Consider the following, for example:
<rule name="When thirsty and has money, buy coffee"> <if> <customerIsTired/> <customerIsThirsty/> <customerHasMoney/> </if> <then> <buyCoffee/> <modifyCustomer/> </then> </rule>
I'm pretty certain this is expressive enough to convey the meaning of the rule, while abstracting the underlying logic. A library of tags like these would more than suffice to help most people get started writing DSLs, especially if the logic behind these tags (buyCoffee, customerIsTired, etc.) could be encapsulated in easily tested, reusable Java components. I mean, who wants to write a MockTuple to test their rules?
All I really want to do is write a quick IoC-style component to represent either a part of a condition or a part of a consequence, and map the name of an XML element (buyCoffee, chow-down-on-a-bagel, etc.,) with a corresponding Java class (CustomerBuysCoffee.java, CustomerEatsBagel.java). Maybe list the bean dependencies as well, so I get simple XML bindings that look like this:
<bindings> . . . <action name="buyCoffee" class="org.eremite.expresso.actions.BuyCoffeeAction" requires="customer, coffeeHaus"/> <bean name="customer" class="org.eremite.expresso.beans.Customer"/> <bean name="coffeeHaus" class="org.eremite.expresso.beans.CoffeeHaus"/> </bindings>
Another problem this solves is the reuse of logic inside the conditions and consequences themselves. Who wants to rewrite consequence blocks with a single line difference in 100 different rules? I found patterns inside my components and with the help of Bob McWhirter was able to decompose conditions and consequences into more fine-grained components that can be mixed and matched to form rules the way that amino acids combine to form protein chains.
I assured myself that a single 5k jar with two interfaces and a pojo to abstract the rule engine's working memory would constitute a sufficiently minimal compile-time API for most component developers. This spares them the trouble of importing the drools-core, drools-spi, drools-io, drools-smf, drools-base, expresso-core and picocontainer dependencies into their editing environment (and their code!) More importantly, it prevents developers from relying on the underlying implementations and complexity of the rule engine.
The results? I have a small (25k) DSL that specifically addresses the concerns I laid out above. It allows me to model complex behaviors using rule engine technology, with reasonably natural (if course grained) language elements and easily testable elemental components, bare minimum framework dependencies and easy extensibility (I'm working on some stuff to autogenerate the necessary XSD files for beta-02).
"Wow." I thought. "This is exactly what I was going to do with Shocks. Scratch that idea off my list."
All in all, the experiment was a complete success, and I'm happy to release the first snapshot of the Expresso codebase (including some unit tests, an example vocabulary, and no documentation whatsoever--be forwarned!) under the Academic Free License. Also, now that I understand how all the pieces fit together, I'm turning my attention to documenting everything people could possibly need to know about writing Domain Specific Languages using Drools'low-level APIs and my neat new tool.
And, for the record, thank you Bob and Mark for your patience and head-kicks while I worked on this. You guys uh, well, you rule ;-)