Last month I wrote a post about Rule Driven User Interfaces. We've used that technique at work but we weren't able to integrate the web layer (an HTTP Session) particularly smoothly. We wrote a layer in between to handle some shuffling of data, asserts and retracts and stuff like that. Then, yesterday, I saw this post on Peter's blog (unfortunately Peter's blog is private so you might not be able to read it).
In the post Peter describes some techniques for handling state in a Rule Engine, it got me thinking about the HTTP Session problem again and I've been spamming Peter's blog with comments about it but they get quite long them so I thought I'll post my thoughts here as well.
Since this post is only about the handling of state, you'll have to pretend that we've designed a number of rules that use a Session object to create another object (we'll call it Page) that can be used to render the UI.
The plan is to have as thin a layer as possible between the code that accepts the HTTP Request and the Rule Engine. Whenever a Request enters we either create a new Session value object or look up an existing one. Then we copy all of the POST or GET values from the Request into the Session value object and update it's timestamp to the current time. If it's a new object we call assert_fact otherwise we call modify_fact.
The whole process is really a loop of: read the Request, copy values to Session, assert or modify Session, call run, fetch the resulting Page object from working memory and print the result of Page.render_as_HTML() to the HTTP Response object.
As long as the user keeps interacting with the web server we can control everything using rules. But when the user stops he/she will leave a Session object in the working memory. To get rid of it you'd have to perform a retract whenever the HTTP Session times out.
This is not a big deal, it can be done in all sorts of ways but that's not the issue. We'll try to handle everything in the Rule Engine this time. So we need a way to do some garbage collection.
>>> @pyRete.RuleThe above rule will match any Session object that is older than 20 minutes. The problem is that since the Rete algorithm holds state you won't get an activation because the Session object won't match (remember that whenever a Session is modified it's timestamp is set to current time). If you want to test again you have to manually call modify_fact (or do retract/assert_fact) from the outside.
... @salience = 1000
... def retract_expired_session(session = Session):
... limit = ... # calc time - 20 min
... if session.timestamp > limit:
So, I'm thinking about adding a "stateless" option for rules. If a rule is declared stateless, facts will be re-asserted (only to this rule) whenever run is called (and possibly also assert/modify/retract_fact). The effect, in our case, is that whenever someone interacts with the server, old Session objects are retracted first.
I'll do a working example of all this when I get the time. I can't say whether it's a better way than having all of this in the Controller or not... it's probably not, but it will be interesting to try.