Skip to content

Commit

Permalink
Initial Engines RFC
Browse files Browse the repository at this point in the history
  • Loading branch information
tomdale committed Oct 25, 2014
1 parent cf9b423 commit e3dfc6d
Showing 1 changed file with 202 additions and 0 deletions.
202 changes: 202 additions & 0 deletions active/0000-engines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
- Start Date: 2014-10-24
- RFC PR:
- Ember Issue:

# Summary

Engines allow multiple logical applications to be composed together into
a single application from the user's perspective. They also allow addons
to provide encapsulated functionality normally associated with apps,
such as routes and templates that integrate seamlessly with the primary
app.

# Motivation

Large companies are increasingly adopting Ember.js to power their entire
product lines. Often, this means separate teams (sometimes distributed
around the world) working on the same app. Typically, responsibility is
shared by dividing the application into one or more "sections". How this
division is actually implemented varies from team to team.

Sometimes, each "section" will be a completely separate Ember app, with
a shared navigation bar allowing users to switch between each app. This
allows teams to work quickly without stepping on each others' toes, but
switching apps feels slow (especially compared to the normally speedy
route transitions in Ember) because the entire page must be thrown out,
then an entirely new set of the same assets downloaded and parsed.
Additionally, code sharing is largely accomplished via copy-and-paste.

Other times, the separation is enforced socially, with each team
claiming a section of the same app in the same repository.
Unfortunately, this approach leads to frequent conflicts around shared
resources, and feedback from tests gets slower and slower as test suites
grow in size.

Engines allow each section of an application to be developed in
isolation, including separate test suites and naming conventions. These
isolated units can be composed together into a single app from the
perspective of the user.

Additionally, engines allow addons to augment applications with
off-the-shelf fully-functional UI. For example, imagine an
authentication library that offers a prefab login screen that
application developers can opt in to, dramatically increasing the speed
at which prototypes can be developed.

Or imagine a product like Discourse allowing you to mount their Ember
app at `/forums/`, so the user needn't leave your app to read your
discussion forum.

# Detailed Design

Your app can gain access to an engine in one of two ways:

1. By placing an Ember CLI app inside another Ember CLI app's `engines/`
directory (a sibling of `app/`).
2. By installing an Ember CLI addon that contains an `app` directory.

Engines are not loaded until the primary application mounts them
explicitly. The primary application uses the new `mount()` router DSL
method to mount an engine:

```js
Router.map(function() {
this.mount('an-engine');
});
```

Engines' routes are prefixed with the name of the engine. For example,
let's write an engine that adds authentication, and includes a default
login and logout page. Imaginatively, we'll call this engine
`authentication`.

```js
// engines/authentication/router.js

import Ember from 'ember';

var Router = Ember.Router.extend();

Router.map(function() {
this.resource('login');
this.resource('logout');
});

export default Router;
```

Now, in the primary app, we mount the `authentication` engine:

```js
// app/router.js

import Ember from 'ember';

var Router = Ember.Router.extend();

Router.map(function() {
this.mount('authentication');
});

export default Router;
```

Now the primary app gains two new URLs:

1. `/authentication/login`
2. `/authentication/logout`

If I want to customize the prefix used, I can pass options to `mount()`
that specify a path:

```js
this.mount('authentication', { path: 'auth' });
```

Now the URLs become:

1. `/auth/login`
2. `/auth/logout`

Engines otherwise behave like addons. Thanks to [the PR adding
namespaces to the Ember CLI resolver][namespaces], container keys can
specify an engine. For example, if the `authentication` engine contains
`engines/authentication/models/user.js`, this could be resolved via the
container with:

```js
container.lookup('authentication@model:user');
```

[namespaces]: https://github.com/stefanpenner/ember-resolver/pull/70

Other APIs in Ember would need to be extended to support namespaces to
take full advantage of this feature. For example, components that ship
with an engine could be accessed from the primary application like this:

```handlebars
{{authentication@login-form obscure-password=true}}
```

However, these changes are outside the scope of this RFC.

## Generator

Create a new engine by running `ember g engine-name`.

# Drawbacks

This RFC introduces the new concept of engines, which increases the
learning curve of the framework. However, I believe this issue is
mitigated by the fact that engines are an opt-in packaging around
existing concepts.

In the end, I believe that "engines" are just a small API for composing
existing concepts. And they can be introduced at the top of the
conceptual ladder, once users are comfortable with the basics of Ember,
and only for those working on large teams or distributing addons.

# Alternatives

One alternative is to simply allow addons to merge app-like assets
(templates, routes, etc.) with the primary app (e.g. via Broccoli's
`mergeTrees`). However, this is highly likely to lead to naming
collisions. By providing an isolated app that users can mount at a path
of their choosing, we can ensure that collisions are next to impossible.

# Unresolved questions

## Non-CLI Users

This RFC assumes Ember CLI. I would prefer to prove this out in Ember
CLI before locking down the public APIs/hooks the router exposes for
locating and mounting engines. Once this is done, however, we should
expose and document those hooks so users who cannot use Ember CLI for
whatever reason can still take advantage of composability.

## Dependencies (Shared vs. Isolated)

Because each engine is an isolated app, it will have its own set of
dependencies. Frequently, those dependencies will be the same as the
dependencies of the primary app. For example, it would be bad to ship
multiple copies of Ember, one for each engine. Or, imagine both the app
and an engine required a Bootstrap add-on that included CSS. It would
not do to include multiple (perhaps subtly different) copies of the same
CSS.

Ideally, dependencies would be deduplicated using a strategy described
by @wycats in [this Google doc](https://docs.google.com/a/tomdale.net/document/d/12CsR-zli5oP2TDWOef_-D28zjmbVD83hU4q9_VTk-9s/edit).

## App/Container/File Layout

Does each engine have its own container? Or does it register factories
with the host app's container?

Do engines subclass `Ember.Application`, or do we introduce something
like `Ember.Engine`?

How do engines use initializers? Can they inject into their own
namespace, or into the primary app also? How do you disambiguate?

Where does configuration go? Do engines put all of their app code in
`app/` like full-blown apps?

5 comments on commit e3dfc6d

@jasonmit
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘

@OpakAlex
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@johnnyshields
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘

@davidlormor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As someone doing multiple Ember apps on a single Rails platform...I'm definitely interested to see this take shape! Big +1

@kriswill
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making Ember.Engine the ancestor to Ember.Application could resolve some of the issues around composability. Where Ember.Application is simply a bootable engine. Then use config/environment.js to configure namespace aliases (to resolve collisions) and declare shared resources, or perhaps make the build pipeline skip Less or SCSS compilation for an app-local alternative, which could bring in those resources and incorporate them into the local pipeline.

Please sign in to comment.