Building even more ambitious web applications!
Ember engines is an exciting new feature released in ember 2.8 that supports splitting application functionality into specialized addons known as ‘engines’.
Engines have several unique properties over addons that make it possible to compose different logical applications together into a single user experience. Basically if you have an application that has multiple distinct domains and workflows, it allows you to maintain those concerns independent of the master application, while still being able to release as a single unified application.
“Engines allow multiple logical applications to be composed together into a single application from the user’s perspective. ~ Ember Engines RFC”
Ember applications are actually specialized instances of a lower primitive in ember known as an ‘engine’. It’s actually from engines that ember gets the ability to load addons and engines. It also makes it possible for engines to manage their own dependencies independent of the root application instance.
The root application can load multiple engines with all engine namespaces being de-duplicated (including nested engines) to a resolved namespace within the given application container. The end result is a system that behaves a lot like nested addons in ember today, but as we will see provides more powerful application extending options than an addon alone provides.
The most notable characteristic of an engine instance compared to an application instance is its isolated interface. Applications can always see loaded addon and engine content such as services and components. However, engines can only see content the root application explicitly provides to them. As such, engines and applications have strict boundaries with manually defined relationships.
However, this is actually a feature of engines that provides a few key advantages:
- Engines can maintain their own unit & integration tests separately from the root application.
- Non-API related engine changes have very limited scope effects with the root application.
- Engines can potentially be lazy loaded as needed by the root application, increasing performance.
In short, this isolation provides a sandboxing effect that constrains the relationship between the root application and engine to an explicitly defined interface.
One of the biggest features of engines is the potential to provide their own routes for sub-navigation within a given engine. For example imagine we have a ‘blog engine’ we want to mount to our application. First we explicitly provide a ‘mount point’ to our engine:
All routes that match the provided mount point are passed to the blog engine’s own relative router. We can even pass routable parameters to controllers and routes within the engine. Try doing that with an ember addon!
Engine rendering and outlets
Engines can also serve templates to the root application outlet. This provides some very powerful opportunities for application UX consistency. For instance, it allows the application to provide the primary UI for navigation, main layout and other globally applied styles.
Basically the application can act as the primary application-wide layout template, and the engine can provide the specific content for the given page.
Potentially the most powerful feature of engines is the ability to publish and subscribe to services with the root application. The major advantage of this is that it keeps the details of how to implement a specific concern isolated inside the engine, limiting interaction with that domain to the published service API. This dynamic is especially interesting when you consider the possibility of an application with multiple engines that have dependencies on each other:
Note that the dependencies are always mediated by the root application, and never between the engines themselves due to engine isolation.
Imagine an application with both a ‘user engine’ that handles authentication and user settings, and a ‘blog engine’ that handles creating and serving blog posts. Maybe we want to create a user experience where we want a ‘quick view’ of a user’s most recent posts in their user profile.
The user engine could actually depend on the
post service published via the blog engine (explicitly provided to it by the root application, of course). In this service there could be a method such as
blog.mine() which would return the current user’s blog posts. The blog engine itself could also declare it has a dependency on a
current-user service so it could look up the current user’s id.
The beauty of this setup is that all the domain specific concerns of what the
post service expects, the serialization and data model itself is cleanly contained within the blog engine. This means the details of its implementation are abstracted away from the API that requests for it. The blog engine could be completely refactored and rebuilt and as long as that API contract of the service isn’t violated, everything will be fine in the root application.
Where do we go from here?
Engines are a potentially very powerful tool to orient your ember application architecture around. However it’s still a very early technology with a whole litany of improvements and enhancements still on the way (lazy loading and sharable engine components for example).
During my research of ember engines I built a demo to explore the interactions of services between multiple engines. Please check it out if you are interested in seeing ember engines in action: Ember Multi-Engine Demo
Only time will tell what the potential of engines might have in store for Wombat and the larger web application world at large.
Till next time!