@abhilashlr Navigate to home page

Micro-services in Ember using ember-engines: The What, What Not and Why?

9 min read • 11th October 2019
Giant jenga blocks that falls when you take away a small block

Originally titled: "Ember Engines: The What, What Not and Why?"

Giant jenga blocks that falls when you take away a small block
Source: Google images

All of us want our code base to be way more stable than a tumbling block of Jenga! Don't we?

So then, what is that smart choice that makes sure our code does not collapse or break with the slightest of modification a.k.a in the frontend world, inserting new code changes to an existing code base. Let's discuss this concept in the course of this blog post.

As a developer, if your web application is already built on a huge code base and has many inter-dependencies, you might already be on the same page of wanting to build or add features without disturbing the existing code base.

Not just that, a huge code base could demand greater shipping & QA timelines, i.e. a feature that would generally take a sprint, might take two or more with the addition of the new code since the code needs to be tested for dependent breakages.

Microservices architecture
Source: Google images

For instance, assume your app has multiple routes, like the one shown in the animation above — having modules like calendar, files, home, and many more complex modules. These modules may not have a dependent codebase amongst themselves except for certain UI elements like buttons, input boxes, etc. To build them as independent modules, you need to answer real questions like, "Can we deploy these modules independently? Can we test them individually? And can we share them across multiple applications?". The answer to all of these questions is: "A big fat 'Yes'. But, only if we can build the modules as micro-services."

For your ease of understanding, we will be calling our parent or host application as "consumer".

Link to this sectionSo, what are micro-services

According to https://microservices.io/, Micro-services, also known as the Micro-service architecture — is an architectural style that structures an application as a collection of services that are

  • Highly maintainable and testable;
  • Loosely coupled;
  • Independently deployable;
  • Organized around business capabilities; and
  • Owned by a small team;

Micro-services in frontend can be achieved without JavaScript frameworks and still work across browsers,

  1. Using Iframes; or
  2. Insertion of DOM elements inside another DOM node of the consumer application, like:
document.getElementById('placeholder-dom-node').innerHTML = `<div id="calender-microservice'>...</div>`;

// OR in jQuery it would be

$("#placeholder-dom-node").html(<div id="calendar-microservice">...</div>)

Link to this sectionIframes

iframe in HTML stands for Inline Frame. The iframe tag defines a rectangular region within the document in which the browser can display a separate document, including scrollbars and borders. An iframe is used to embed a document within the current HTML document.

Since the iframe's approach is to embed a new document in a parent document, it is an effective strategy that works across browsers. There is also complete isolation and separation of concerns in terms of HTML, CSS, and JavaScript.

Link to this sectionAdvantages

  1. Since iframes allow for complete isolation, they keep the codebase loosely coupled.
  2. They can be independently deployed and owned by small teams.
  3. On top of all this, documents rendered by an iframe can be built using a different JavaScript framework, while the consumer can be in Ember.

Link to this sectionDrawbacks

  1. 3rd party cookie tracking restriction: Recently Chrome, and Firefox announced updates that blocks 3rd party cookies by default. Read here for more details on it.

  2. Sharing data between consumer and iframe: The problem of an iframe is in the way it has to be tweaked to make it work with sharing data between the consumer and the iframe itself. Though there are different strategies, those tend to be maintenance heavy. Read here on how to share data between the iframe and its consumers using postMessage.

  3. Lack of theming capabilities: Iframes do not let the consumer access their DOMs. Assuming the consumer is styled differently from the iframe, we would be required to write custom CSS styles and also maintain it. And, custom CSS styles are typically hard to maintain. If there is a change of class in documents rendered by the iframe, it would require us to work on the custom styles as well. For example, consumer A with a button style of background: black; padding: 16px; would be hard to reconfigure for consumer B whose button style follows background: blue; padding: 8px;. On the other hand, if the consumer changes their button style, the custom CSS that the iframe uses also requires the same change. So eventually we intend to change the code at two places instead of one. This cannot scale to efficiency in the longer run.

The approach in the Ember world is a combination of the tech implementation of Iframes while using the strategy to render the modules as DOM nodes instead of isolating it like Iframes — Welcome Ember-engines!

Link to this sectionWhat is an ember-engine

As per the documentation, ember-engines are composable applications for ambitious user experiences. To understand more about ember-engines, check this link out. A simple Google search would have given most of you a basic understanding of what ember-engines are along with a few facts to back it up. In layman's terms, ember-engine is a transliteration (in the Developer world) of an iframe.

"What can't be scaled can't be sustained. And so is the case with iframe. It's always important for a developer to choose his cards smart, i.e., to prefer longevity over ease of execution. a.k.a — ember-engine over iframe."

Link to this sectionWhat is not an ember-engine

As the ember-engines guide says:

If you are considering splitting up your application into engines just to reduce the amount of data that needs to be initially downloaded and increase the performance, Engines are not the right solution. Please check out the section on tree shaking and code splitting in projects like Embroider.

Neither should you consider ember-engines for building a simple 1-page feature nor for features that share multiple ember objects like utils, mixins, or helpers.

How ember-engines behave as micro-services, and why is it cool?

  1. Modules built as an ember-engine are rendered as DOM nodes, thereby giving more control for theming.
  2. They help in writing manageable code that does not overlap with parts of the consumer.
  3. They help in writing isolated unit/integration/acceptance/E2E testing, since the engine is rendered as a standalone application while running an ember test.
  4. They do offer total isolation resulting in sharing only data — using Ember.Services.

Link to this sectionHow do you share components from consumer and engine

Ember-addons! Addons are super useful in such scenarios. They make sure all the components have uniform UI across the consumer as well as the engine(s).

Writing an add-on is a great way to organize code, share it with others, or get the foundational knowledge to contribute to Open Source addons. By separating features into an addon instead of leaving it as an in-app component, more developers can work in parallel, and breaking changes can be managed independently. Maintainability goes up, and testing becomes easier.

Link to this sectionHow ember-engine solves theming

Theming could be done in 2 ways.

  1. If the consumer does not use SCSS/SASS/LESS for compiling CSS, then add an overriding CSS file that changes the attributes of the corresponding classes of the DOM. This works effectively since the consumer renders the engine as HTML, CSS, and JavaScript and not in an isolated iframe. This is similar to the jQuery world of using $(dom).html('<div id="ember-engine">Your engine goes here</div>');. Iframes do not give this possibility, but building as an ember-engine you do get this advantage.

    <!-- Engine's HTML that renders a button -->
    <button class="sample-button">
      Sample button
    /* Engine's button styles - engine.css */
    .sample-button {
      background: purple;
      padding: 10px;
    /* Consumer A's overriding styles - consumer-app-a.css */
    .sample-button {
      background: black;
      padding: 16px;
      margin: 8px;
    /* Consumer B's overriding styles - consumer-app-b.css */
    .sample-button {
      background: blue;
      padding: 8px;
  2. If the consumer uses an SCSS/SASS/LESS compiler, then the engine's SCSS/SASS/LESS files are available during build time. If the styles are written in a way that provides customizations through variables, then the consumer can simply change the values of those variables and voila!, you have the customized engine running under the consumer.

Link to this sectionWhy are ember-engines uber cool

Assume there are 5 different child routes that the new feature is required to be built and they are totally independent of the consumer. Moving them to an engine is the best choice going forward. Why? Simply put, it satisfies the separation of concerns convention and keeps the code uncluttered from the consumer.

What is cool about this is that, the code written for this feature can:

a. be separately tested (with mock data and the default dummy app)

b. have their own set of unit/integration/acceptance test cases and

c. be version controlled independently

The coolest idea of all is that the engine's source code can be asynchronously loaded when the engine is mounted and not be clubbed with the consumer's source code. Here's how:

lazyLoading: {  
  enabled: true  

Enjoyed this article? Tweet it

I guess you might be looking to add your comments? Glad to tell you that this section is under construction. But don't hold on to your thoughts! DM them to me on Twitter