anmonteiro Code ramblings

An Exploration of Object Recursion Design Patterns with Om Next Recursive Queries - Part 1

The recent landing of recursive union queries in Om Next allows for defining recursive, heterogeneous data in a simple, expressive way. In a series of posts, I will explore how this conjugates with well-known Software Engineering structural design patterns that are based on object recursion. In this part 1 we will put together a simple component hierarchy that uses the Composite design pattern. Let's dig into it.

The Composite design pattern

Composite design pattern

The Composite is part of the design pattern collection in the Gang of Four book. Its purpose is to compose trees of objects that fulfill the same contract by building and iterating over them. The secret weapon here is that, while iterating, there is no need to know if we're working on a leaf node or an inner node — we can simply treat every node in the same way. Inner nodes will take care of iterating over their children in the process.

Our example

In our simplified example, we want to render a square that can arbitrarily contain other squares. Using the nomenclature in the above diagram, we need to define Om Next components for both the Leaf and the Composite. Composite components can have children, whereas Leaf components cannot. We will also have our version of Component, which aggregates the queries for both others, and dispatches rendering to them 1.

The data

We start with the data below. Each square contains attributes for its width, height and color. Each item also has an id; this reveals helpful in normalizing the data.

The Om Next components

Our Leaf and Composite components are shown below 2. Their queries are trivial, in the sense that they just declare the attributes we have talked about before. The only exception, which you might have not seen before, is the {:children ...} part. This is how we declare recursion in Om Next. Refer to my post about Om Next's query syntax where I explain this and other bits in detail.

(defui Leaf
  static om/IQuery
  (query [this]
    '[:id :width :height :color]))

(defui Composite
  static om/IQuery
  (query [this]
    '[:id :width :height :color {:children ...}]))

Now that we have declared our most concrete components, we need to declare the one that is analogous to the Component in the Composite pattern diagram. Our Component needs to aggregate the queries of the others, and declare its Ident, a unique key by which each data item is identified in our example. Besides defining an id, we could also have a the type of an item in our data; in our simple example this is not necessary as we know leaves can't have children. It looks like this:

(defui Component
  static om/Ident
  (ident [this {:keys [id children]}]
    (if-not (nil? children)
      [:composite id]
      [:leaf id]))
  static om/IQuery
  (query [this]
    {:leaf (om/get-query Leaf)
     :composite (om/get-query Composite)}))

We will need a root component, which we will call CompositeApp. This one helps define the union query in Component.

(defui CompositeApp
  static om/IQuery
  (query [this]
    [{:composite/item (om/get-query Component)}]))

Once we have properly implemented our parser and render methods, we get the following result, which is exactly what we intended. Squares that contain other squares.

You can find the complete source code for this post here.

In the next post we will talk about the Decorator design pattern. Stay tuned!

1 these components are not really heterogeneous in the sense that they only differ in the children attribute and we could get away with just one component (a leaf would be an item without children). However, the purpose here is to demonstrate the Composite design pattern and, as such, we'll be a little more verbose.

2 render methods are omitted for brevity.

3 at the time of this writing, you'll need to clone Om Next from master and install it in your local repository in order to run the example code