Event Hierarchies: A Blog Written in KRL


I've mentioned several times that I'm writing a book called The Live Web that discusses how events and strong user identity combine to produce new ways of creating Web applications that are significantly different than those in use today. Chapter 12 is about creating event-driven applications that are based on multiple rulesets. At the recent Kynetx Impact developers conference, Ed Orcutt built a blog application using KRL. Actually, he did it twice: once with one ruleset and another time with three.

If you're like to read the book in draft form, contact me and I'll invite you to the Dropbox.

Ed's example intrigued me for several reasons:

  1. I saw DHH do a tutorial once that involved building a blog application in real-time as a method of teaching the audience about Rails. At the time I thought it was cool how easily you could build an interesting application like a blog in a short amount of time and little code.
  2. I believe that KRL applications have to "grow up" beyond the current crop of simple Web augmentations to become full fledged applications. There's nothing wrong with Web augmentations--I have half a dozen running in the KBX (go install it now). But I think evented architectures have much more to offer us.
  3. I've been looking for multi-ruleset application examples to explore event normalization and hierarchies.

I rewrote Ed's multi-ruleset application to make it my own in terms of understanding it and to simplify the HTML a bit. You can explore the resulting blog. In my design, I have three rulesets:

  1. KBlog - the main ruleset that controls the HTML assets and presentation. This ruleset functions like the presentation layer of a traditional Web application, responding to user input and displaying results.
  2. KData - the ruleset that manages the blog data. I wanted to experiment with putting all the data assets and their access rules in one place. In the first instance I use application variables to store the blog data. In a later instance, I'll use an online storage pool.
  3. KBlog Posting - I wanted posting to be separated from the other parts of the application so that only people who had access this ruleset can post. This is not a good solution to blog posting access, but it works for this demo.

Let's explore the highlights of each of these.

Building the Blog

The primary job of the KBlog ruleset is presenting the blog. The ruleset does this by preparing the container for the blog articles and then filling it with the articles. The blog is AJAX enabled, meaning that the framework of the blog including the basic HTML content and CSS files are downloaded once and then everything else is painted on that framework in response to user actions. This is a style of Web site construction that has been made popular by sites like Google, Twitter, Gawker, and others. The idea is that it saves bandwidth by not requiring that things like the HTML and CSS be downloaded each time. KRL is a natural way to build sites that use this style of construction.

The following shows the basic layout of the site.

KBlog Screenshot

(click to enlarge)

A navigation bar at the top contains links that can be used to navigate between different parts of the site. At present, just the home page and contact page. The DOM for that portion of the page looks like this:

<nav id="sitenav">
  <ul id="navlist">
   <li><a href="javascript:..." id="siteNavHome">Home</a></li>
   <li><a href="javascript:..." id="siteNavContact">Contact</a></li>
  </ul>
</nav>

We'll use this later in rules that modify the navigation buttons.

The primary section of the blog--the left container--is where page content is written. Initially it is empty and has the following structure:

<div id="leftcontainer"></div>

The information section of the blog--the about section on the right--is also initially empty:

<div id="sidebarwarp">
  <h2>About KBlog</h2>
  <p id="about"></p>
</div>

The init_html rule is selected when a pageview event occurs. The selector is very general because it should fire whenever the blog is viewed. This rule is responsible for painting the "about" text on the right side and setting watchers on the navigation buttons.

rule init_html {
  select when pageview ".*" setting ()
  {
    replace_html("#about", about_text);
    watch("#siteNavHome",  "click");
    watch("#siteNavContact", "click");
  }
  always {
    raise explicit event blog_ready
  }
}

When this rule is done it raises the explicit event blog_ready because--get ready for this--the blog is ready to be populated with the page content. In this blog there are two pages: the home page with the blog articles and the contact page that has contact information.

The show_home rule is selected when one of two events occurs: someone clicks the home link on the navigation bar or there is an explicit blog_ready event (note this makes the home page the default). The show_home rule sets up the container to receive blog posts and sets the title:

rule show_home {
  select when web click "#siteNavHome"
           or explicit blog_ready 
  pre {
    container = <<
<div id="leftcontainer">
 <h2 class="mainheading">Latest from the blog</h2>
 <div id="blogarticles">Code Monkey was here :)</div>
</div>
   >>;
   title = <<
<title>#{blogtitle}</title>
   >>;
  }
  paint_container(title, container);
  always {
    raise explicit event container_ready;
    raise explicit event need_blog_data for a16x89
  }
}

The show_home rule makes use of a user defined action, paint_container:

paint_container = defaction(title, container) {
  {replace_html("title", title);
   replace_html("#leftcontainer", container);
  }
}

The paint_container action is also used by the show_contact rule that puts up the contact page when someone clicks on that link. A user defined action ensures that we do this the same way both times.

The show_home rule also raises two explicit events, one that indicates that the container is ready and another that says that data is needed. We'll discuss the rules that respond to the latter event in the next section, but ultimately, the result of that process is that the explicit blog_data_ready event is raised.

Putting the actual blog articles in the container is the job of a rule called show_articles. This rule fires when the container_ready and blog_data_ready events are raised. The rule loops over each member of the hash representing the blog data, formats the correct HTML, and inserts it into the page:

rule show_articles {
  select when explicit container_ready
          and explicit blog_data_ready
   foreach event:param("blogdata") setting (postKey,postHash)
    pre {
      postAuthor = postHash.pick("$.author");
      postTitle  = postHash.pick("$.title");
      postBody   = postHash.pick("$.body");
      postTime   = postHash.pick("$.time");
      postArticle = <<
<article class="post">
  <header>
  <h3>#{postTitle}</h3>
  <span class="author">by #{postAuthor}</span>
  </header>
  <p>#{postBody}</p>
  <footer>
   <p class="postinfo">Published on <time>#{postTime}</time></p>
  </footer>
</article>
      >>;
    }
    prepend("div#blogarticles", postArticle);
}

This flow allows the blog to be built in pieces and for navigation actions to simply update the portion of the structure that requires it.

Handling Data

One of the things I wanted to do in this exercise is to separate out handling the data from the presentation pieces of the application. I did that by creating a ruleset that uses it's app variables to store the blog posts. This is not the most efficient way to handle the data, but it raises some awareness of event design, so bear with me. In a later post, I'll show how to use another method to handle the blog data.

Using an app variable to store the blog data is easy enough. We define a function that formats the map representing a single blog post like so:

mk_article = function (author, title, body) {
  postTime = time:now({"tz":"America/Denver"});
  { postTime : {
     "author" : author,
     "title"  : title,
     "body"   : body,
     "time"   : postTime
    }
  }
}

Now we simply use a rule, add_article, to watch for new articles, format the data with the mk_article function, and add it to the app variable BlogArticles:

rule add_article {
  select when explicit new_article_available
  pre {
    postHash = mk_article(event:param("postauthor"),
                          event:param("posttitle"),
                          event:param("postbody"));
    BlogArticles = app:BlogArticles || {};
  }
  always {
    set app:BlogArticles BlogArticles.put(postHash);
    raise explicit event new_article_added for a16x88;
  }
}

Note that this rule takes no action. The benefit is all in the effects. When the rule is fired, it raises an explicit exception to indicate a new article has been added. The KBlog ruleset contains an intermediary rule, show_new_article that transforms this event into a blog_ready event. Of course, the show_home rule is listening for the blog_ready event and this causes the screen to be repainted.

rule show_new_article {
  select when explicit new_article_added
  always {
    raise explicit event blog_ready
  }
}

Reading the data out of the ruleset is also event-driven. The retrieve_data rule selects on the explicit event need_blog_data and raises the explicit event blog_data_ready, attaching the blog data from the app variable as an event parameter:

rule retrieve_data {
  select when explicit need_blog_data
  always {
    raise explicit event blog_data_ready for a16x88 with
    blogdata = app:BlogArticles || []
  }
}

Posting

All that's left to complete the application is the ability to post. As explained earlier, this functionality is in a separate ruleset and layered on top of the basic functionality of the blog. Most users will never need the functionality and won't see it. Only posters need access the rules that control it.

The first order of business is to add a navigation button to the bar at the top of the page exposing the functionality. The place_button rule fires on a pageview event and puts the button in place and makes it active by assigning a watcher to it:

rule place_button {
  select when pageview "kblog"
  pre {
    button = <<
<li><a href="javascript:void(0);" id="siteNavPost">Post</a></li>
    >>;
  }
  {
   prepend("#navlist", button);
   watch("#siteNavPost",  "click");
  }
}

When someone clicks on this button, the Web endpoint will raise a click event and the place_form rule will fire. Place_form creates a form, places it on the page by replacing the left container, and watches for the submit button (some of the HTML in the form has been removed for brevity):

rule place_form {
  select when web click "#siteNavPost"
  pre {
    form = <<
<div id="leftcontainer">
<h2 class="mainheading">Post</h2> 
<article class="post">
 <form onsubmit="return false" method="post" id="blogform">
   <label for="postauthor"><small>Name</small></label>
   <input name="postauthor" id="postauthor" value="" type="text">
   ...
   <input name="submit" ... type="image" src="images/submit.png">
  </form>
 </article>
 </div>
     >>;
  }
  {replace_html("#leftcontainer",form);
   watch("#blogform", "submit");
  }
}

The following screenshot shows the blog after the Post link has been clicked, the form painted on the page, and filled out by the user:

Kblog Posting Screenshot

(click to enlarge)

When the submit link is clicked, the Web endpoint raises a submit event. The handle_submit rule handles the event and raises the new_article_available event with the data from the form. As we've seen, the new_article_event is handled by the add_article rule from the data ruleset.

rule handle_submit {
  select when submit "#blogform"
  always {
    raise explicit event new_article_available for a16x89 with
      postauthor = event:param("postauthor") and
      posttitle = event:param("posttitle") and
      postbody = event:param("postbody");
  }
}

We now have a complete blog application, albeit simple.

Event Hierarchies

Building this application taught me a few things. First, getting the names of events and what they mean right is important. This is called "event normalization." Second, understanding what rules were listening to what events and how they played together was the key design space. Once that was done, writing the rules was pretty easy.

I put together the following event hierarchy graph to represent what I'd designed--mostly in my head--as I went along.

Event hierarchy for KBlog

(click to enlarge)

In the graph, rectangular boxes are rules. They are named as verbs. The ovals represent events. They are nouns. The Web events are the entry point for any path through the graph. Rules that don't produce events are terminals in the graph. I was tempted at first to think of this as a kind of state diagram, but that's not quite right--the transitions don't happen because of some input. The events themselves are the input. Events are most useful when they are moving and so there's no notion of "stopping" at some point in the diagram. Once you enter at the top, you flow through to one of the terminal rules.

The idea of events as nouns and rules as verbs is a useful way to keep your design straight. If you find yourself giving events names that are verbs, you're probably not really creating an event-driven application. Rather you're using events to do a request.

Conclusion

Writing this blog application in KRL was instructive. The rulesets separated different functionality cleanly and allowed special functionality--like the ability to post--to be layered onto the other rules easily.

One objection to the design might be that the controller logic that is not clearly delineated. The controller is in the in the various select statements. The controller logic for the application could be brought together by using more intermediary rules in a controller ruleset.

As I've worked through this demo, I've wondered if good rule design ought not to require that every rule raise an event when it's done to indicate what is true at that point. For example, the show_articles rule is terminal in the event hierarchy I show above. But what is true at that point, the event, is the articles are showing. Nothing in the rulesets I've created here uses that information, but what if someone wants to layer functionality onto this application later in a loosely coupled way? They might want to know when the articles are there so they can take the next step--whatever that is. Any given rule, terminal now, may not always be so as new functionality is created.

I have several things I want to do from here. As I've aluded, I want to replace the ruleset data management with a module that interfaces to an online data repository and test performance differences. This considerably simplified the event hierarchy and introduces the idea of hybrid event and request drive applications. Also, I want to show how jQuery plugins can be added to make things fancier on the UX side from within KRL.

The complete code for the three sample rulesets is available on Github.


Please leave comments using the Hypothes.is sidebar.

Last modified: Tue Feb 18 12:22:29 2020.