July 15, 2017
Bonzai Canvas Architecture
In this post, I will talk about the high-level architecture of our design tool, Canvas which is used to create Ads on Bonzai platform. This will be a high-level view, and we will not get into the implementation details, as that would be better written separately for various modules.
Philosophy
To make sure we are not making mutually contradictory architectural decisions for the application, we follow some guidelines. These guidelines are not too strict and not too formal, but something that we have learned while working on the application.
- All architectural decisions have been taken to make sure we can move fast while maintaining quality.
- Quality is a vague term and difficult to measure, so we replace quality by the ease of modifications. If we face issues modifying some part of the application or it takes too much time, we mark that part of requiring refactoring and requires thinking over. Note that, just because some task is taking time does not mean that the system is not designed properly. There may be a case where the application has not been designed to handle the requirement.
- Since we also need to move fast now as well as in future, not all designs are fully fleshed out initially. Rather, we design to be flexible and make sure we can decide stuff incrementally.
Canvas UI

As you can see from the diagram above, the canvas application UI is designed to have a core, which understands the various entities present in the system and how they interact. Such as, it knows that Ad Formats have ad pages, pages have elements, pages and elements have properties etc. It also provides some basic functionality such as transforming (resizing, moving) elements, adding/removing pages. There are other modules in the core such as the timeline and the asset gallery which currently mildly tightly coupled but can be separated out with some effort.
In the future, this architecture will allow us to release elements (button, gallery, map, 360 Image etc.) and ad formats independently, i.e. without releasing the canvas application. This will have a huge impact on how we take stuff to market and also on the team structure.

Canvas is built using ReactJS handling the view and Elm as the reducer. We try to write all logic for the core in Elm. This ensures that it has fewer bugs and our logic is more reliable. We found this to be a nice way to integrate Elm into our application. We are looking into how we can introduce Elm in the view layer too. This will require a greater amount of work, as we have a component library already written in React. Also, we have to decide, how elements and ad formats, which add their own React components integrate with the Elm view.
To integrate Elm as reducer with React, we are using the redux-elm-middleware library. Since we started using Elm, refactoring has become easy due to the excellent compiler errors. There were two instances where we needed to change the shape of data stored in the store, and it turned out to be pretty easy to do it on the Elm side.
It is still possible to write reducers for individual modules using JavaScript and for now we have kept the choice open. In future, as we integrate Elm deeper, we may close this choice.
Code Generation
A huge part of the application logic also consists of generating JS code from the ad specification that the canvas UI generates. This is done on the server side in a different application.
This application is written in Haskell and is executed on the server, to make sure we can do any kind of optimizations on the code or assets as required. We will not talk about this application much for now, as it is subject for a different blog post but will add that the design is based on the tagless final DSL style.
The Ad code to be generated is written using a custom DSL that reads the Ad specification and generates an Ad Code AST. This AST is then converted (interpreted in tagless final terms) to a JS AST.
Tagless final allows us to write any number of interpretations of the Ad Code, such as we can decide to generate some Web Assembly code in the future, just by writing a new interpreter.
A massive refactoring is scheduled for the code generation module which will add support for lambdas in our DSL which will make it much more powerful. This refactoring will give it the ability to represent callback directly in the DSL.
Conclusion
There is still a lot of work to be done, to fully realize the benefits of the architecture that we have designed. Want to be part of this journey, come join us.