Keechma Design Decisions
In this blog post, I’ll write about the different parts of Keechma, problems that Keechma solves and tradeoffs in the provided solutions.
A bit of history
I spent most of 2015 working on Keechma. It started out as a set of utilities that were working in tandem with Secretary and Re/Frame, but somewhere around the end of the year, I decided to rewrite everything from scratch and to produce a holistic solution.
Although I liked working with Secretary and Re/Frame, it felt wrong to use globals in all layers of the application. I wanted a solution that would be as functional and pure as possible, and that would have no shared globals.
No shared globals
I believe this was one of the most important decisions I made about Keechma and one that affected the design and architecture the most. Stuart Sierra’s Clojure in the Large and Components - Just Enough Structure talks had a lot of influence on the design of Keechma.
Although I decided not to use Component library in the end, most of the moving parts in Keechma (applications and controllers) have an explicit lifecycle.
One of the things that are possible due to its design is that you can have multiple applications running at the same time. I implemented this in the login example where the main application is not even started until the user session exists. This allows the main application to completely avoid any kind of branching based on the user session status. Concerns are completely separated between the login and the main application.
Applications in Keechma implement the glue between the application parts. In other words, they bind to the history changes, provide the application state, provide the communication channels, etc. Each application keeps the whole state in its config map which is inspectable from the outside.
URLs are the main driving force for changes in the Keechma apps. Each URL change can cause multiple changes to the application state. URL changes are handled by the controllers.
Since there are no globals in Keechma, the router must be able to work in the pure, functional way. Based on my experience with CanJS I was convinced that its router would be a great fit for Keechma. Keechma’s router is an almost complete (it doesn’t support one edge case) port of the CanJS router but implemented without any shared globals.
The biggest philosophical difference between Keechma’s router and the majority of others is that in Keechma routes are not mapped to actions; the router only translates the data between the formats (map -> URL and URL -> map).
For instance, this allowed me to use the core.match library in the UI to decide which component to render. You can use whichever approach makes sense for your application, seeing that the route params are just data.
Controllers implement boundaries between the pure, functional part of your app and the rest of the world. Controllers run only when they express interest in the route. You can read more about it in the documentation, but the idea is that you can determine which controllers are alive based on the page URL.
Controllers are designed to be minimal, but they still provide a lot of power. Some examples:
- Load data from the server when the controller is started, but only if the entity has not already been loaded
- Connect to the WebSocket when the controller is started, process the messages while it runs and disconnect from the WebSocket when user leaves the URL
- Listen to the user commands and mutate the application state
Controllers allow you to group the data loading and the data mutation in one place. They are also a bridge between the UI and the domain logic of your application. They are purposefully open-ended and try to impose as little constraints as possible.
EntityDB is the central place to store “identifiable” (for instance anything that has the
:id attribute) data in your application. EntityDB has convenience functions that allow you to handle the simple relations between items, but the big idea behind the EntityDB is to ensure that you always have only one instance of each entity in the application.
This enables you to write simpler code and avoid a whole class of bugs related to the data synchronization. It also allows you to avoid modeling your application state in a tree which becomes unmanageable very fast.
EntityDB can also handle the graph data as demonstrated in the graph data example, even though I have to admit that the whole relationships part of the EntityDB was built not only because it was easy to do so, but also because it was convenient in certain cases. It was not part of some grand design, so there might be better solutions for your applications - like the DataScript library. EntityDB is not coupled with the rest of Keechma, and it should cover 80% of use cases, but don’t shy away from other solutions if they turn out to be a better fit.
Relay / Falcor / Om Next
I have received a few questions where I was asked to compare Keechma (mostly related to EntityDB) to Relay / Falcor / Om Next. Keechma was built in a very conservative fashion, and I tried very hard to invent as little as possible. I would say that Keechma has a lower level approach, which will require you to write more code, but it will also give you more control. Depending on the architecture of your application and the backend API, it could be a better fit or it could make you write more boilerplate code.
Keechma’s architecture eliminates one of the problems present in most of the applications that use Flux - like architecture. Since requests are made by the controllers before the UI is even rendered, you shouldn’t end up with the UI that is in a partially correct state. This is usually caused by components that trigger requests when mounted, which in Keechma is prevented by design.
It’s hard to keep UI components decoupled. They need to represent the data present in the application state, render other components and trigger the application state mutations.
Each component has its dependencies injected (partially applied) when the application is started which allows the component to be aware only of its own concerns. By adding another level of indirection, parent components don’t need to know anything about their children, including the component type or its dependencies. You can find more about it in the documentation.
In this article, I would also like to touch on one subtle difference in which Keechma components communicate with the rest of the world. In other systems, components have a direct mapping between the user interactions (like clicking on a button) and the function which is called as a result. In Keechma, components send commands which are routed to the controller. This design was inspired by the old way of doing things - where you used events to communicate between the components. I talked about this long time ago in my Event Oriented Applications talk.
ClojureScript allows a better implementation of that pattern. Commands in Keechma are sent through the channel which means that you could rewrite the commands (if needed) by using the transducer on the commands channel. This design decision ensures that components can send commands which make sense for that component, keeping them generalized and decoupled.
Keechma is a new framework, but it was refined over a long time and built on years of experience. There’s a bunch of documentation and example apps, but don’t hesitate to contact me if you have any questions. You can reach me at @mihaelkonjevic or via email.