Micro-services in Ember using ember-engines: The What, What Not and Why?
Originally titled: “Ember Engines: The What, What Not and Why?”
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.
For instance, assume your app has multiple routes, like the one shown in the animation above — having modules like
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”.
So, 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;
- Using Iframes; or
- 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>)
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 iframes allow for complete isolation, they keep the codebase loosely coupled.
- They can be independently deployed and owned by small teams.
- 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.
- 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.
- 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!
What 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
What 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?
- Modules built as an ember-engine are rendered as DOM nodes, thereby giving more control for theming.
- They help in writing manageable code that does not overlap with parts of the consumer.
- They help in writing isolated unit/integration/acceptance/E2E testing, since the engine is rendered as a standalone application while running an ember test.
- They do offer total isolation resulting in sharing only data — using Ember.Services.
How 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.
How ember-engine solves theming?
Theming could be done in 2 ways.
$(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</button>// Engine's button styles - engine.css
}// Consumer A's overriding styles - consumer-app-a.css
}// Consumer B's overriding styles - consumer-app-b.css
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.
Why 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:
Part 2 – how we used ember engines for one of our features is in draft stage.
If you liked this post, hit the like button. If you found something misleading or confusing, feel free to drop in the comments and we can collaborate this post with those changes.
*- Sharing a micro-service will be part 2