How We Built Dstillery’s Newest Platform: Audience Studio

Eidan Spiegel
Engineering Dstilled
6 min readFeb 26, 2019

A few months ago my team at Dstillery was given the task of creating a unified platform for our client-facing applications. This meant taking webapps with different codebases that are hosted on different web servers, and creating an experience where the user feels as though they are using one single application. Specifically we wanted a left navigation bar in our UI that allows the user to navigate back and forth between these apps without reloading the entire page.

The goal for this project was to streamline customer onboarding and provide a great user experience. By combining the applications, we would expose our customers to more parts of our platform.

At first the task looked daunting. All of these apps have their own Java + Spring Boot backend and were using either Vue.js 2 or Angular 1 on the frontend. How could we take these very different apps and combine them into one?

We considered a few options, but most fell short.

1. Combine all the codebases into one

Although this is a straightforward approach, we would ultimately end up with a monolithic code base. These apps already had tens of thousands of lines of code, and by combining them we might end up with an unmaintainable mess. We would also have to deploy a new build if we made a change to one of the applications and not the others, which would make testing and deploying in isolation harder.

2. Separate apps but same left nav design

We could keep the apps on separate hosts but give them all a similar left nav. This approach was relatively simple apart from having to include a copy of the left nav in multiple apps. The problem with this approach was that any time you navigate to a different app the entire page needs to reload, which wouldn’t be a very seamless experience for the user.

3. Use single-spa as a frontend framework

We found a third option which was to use a frontend framework called single-spa. Single-spa allows you to use multiple frameworks in a single-page application. This means that each app can remain its own separate project and deployable. The javascript for each app is downloaded and executed dynamically using SystemJS, a dynamic ES module loader.

We were a bit skeptical at first because this framework didn’t seem to be widely used. It only had about ~1600 stars on GitHub and there weren’t a lot of companies using it in production.

Nevertheless, we built a prototype with it and liked it very much. Below is a rough diagram of how we loaded an app dynamically.

Codewise, we had to make a few changes to our current applications in order to fit into this framework. Each project had to be bundled as a single-spa parcel. This means that we needed to expose a “bootstrap,” “mount,” and “unmount” function for the single-spa framework to use. We used single-spa-vue to create a parcel for our Vue applications.

Below is an example of what a single-spa parcel might look like for a Vue application.

The appOptions object is used when instantiating a new Vue instance. You can add any options you would normally use when creating a Vue instance: el, template, name, data, etc. (https://vuejs.org/v2/guide/instance.html).

We also export three arrays of functions (these can also be just functions) that correspond to the single-spa lifecycle.

For the wrapper application, which we called “Audience Platform”, we needed to specify which child applications we were loading, where to load them from, and when to load them based on the browser url.

Here is a code example:

The registerApplication function expects 3 arguments:

  1. The name of the application you want to register.
  2. A function which loads the child application. This can be a SystemJS call, an import from another file, or even an inline application (an object with the 3 lifecycle functions).
  3. A function that receives window.location as an argument and returns a truthy value whenever the application should be active.

You can also pass in a 4th optional argument that are custom properties which can be used by the registered child application.

For our purposes, we passed in a few custom properties down to the child applications: A Vue parent instance and an event bus. Let’s elaborate further on this.

Parent instance:

In order to have both the wrapper application and child applications appear in vue-devtools when developing locally, we needed to set the child application’s parent to be the wrapper component.

We set the parent in the “appOptions” object when instantiating singleSpaVue (as seen below):

Once we did that, we had both applications available for inspection in the devtools:

Event bus:

For bi-directional communication between the wrapper application and the child applications, we used an event bus.

For the event bus implementation we use a Vue instance. It already has built in event handling using the methods $emit, $on, and $off (https://vuejs.org/v2/api/#Instance-Methods-Events).

export const eventBus = new Vue();

One specific case where we used the event bus was to send over the vuex modules and the vue-router routes from the child application so that they can be registered in the wrapper application. Respectively we unregister modules of an app that’s being unmounted. This allows us to access store modules and routes globally. It also allows us to see the vuex modules in the devtools (as shown below):

The store modules that are prefixed with “root” belong to the wrapper application and the other ones belong to the currently loaded child application.

Furthermore, this diagram below describes the flow of the single-spa lifecycle functions along with messages sent over the eventBus.

Hot Reloading

For local development, we ran one webpack-dev-server for the wrapper application and one for each child app. This gave us the hot reloading ability, where changes you make to the project are automatically reflected in the browser without needing to refresh. In most cases, we were working on only one child app at a time so we only had two dev servers running at a time. In order to get that to work, we had to ensure that each dev server was running on a different port and was also using a different url to communicate changes to the browser.

In conclusion, we managed to use the single-spa framework effectively to create a unified platform for multiple disparate applications. Going forward, it will allow us to easily add different frontend applications to our platform.

The setup is very reminiscent of how backend microservices are orchestrated and seems to be a way to create “frontend microservices”. The whole process took us about 2 months and we are very pleased with the outcome. We’d love for you to try it out and give us feedback! You can check it out here: https://app.dstillery.com.

--

--