Services: A Rumination on Introducing a New Role into the Ember Programming Model

Thanks for the great discussion, everyone, and apologies for being away from the thread for so long. (I was caught up launching Skylight, where we’ve needed something like services for awhile.)

First, I’ll reiterate that I’d prefer to abandon the line of argument that hinges on offering the “maximally flexible” API. I am strongly against offering an arbitrary injection API that allows you to pull in any object from any other object.

I’ve really enjoyed reading the discussion around clarifying the role of services, especially vis à vis controllers. I think defining the roles of services by comparing and contrasting them to controllers is the right way to carry the discussion forward.

First, let’s discuss what exists right now. Even though routes and controllers are different things, in my head, they are both conceptually in the “controller layer.”

Let’s turn to Cocoa, which inspired many of the design decisions in Ember.

Cocoa has two types of controllers:

  • Mediating controllers
  • Coordinating controllers

According to Apple: “A mediating controller facilitates the flow of data between view objects and model objects.”

While

Coordinating controllers oversee and manage the functioning of an entire application, or part of one. They are often the places where application-specific logic is injected into the application. A coordinating controller fulfills a variety of functions, including:

  • Responding to action messages (which are are sent by controls such as buttons when users tap or click them)
  • Establishing connections between objects and performing other setup tasks, such as when the application launches

https://developer.apple.com/library/mac/documentation/general/conceptual/devpedia-cocoacore/ControllerObject.html

It’s obvious to me that Ember’s controllers are mediating controllers and route objects are coordinating controllers. Apple’s description of controllers in Cocoa maps almost perfectly onto what we have in Ember. The key difference is that, on the web, coordinating controllers have the additional responsibility of saving application state to the URL. (In Cocoa, the behavior of persisting and sharing state is done in an ad hoc way across different apps)

Perhaps Yehuda and I misnamed route objects when we created them, but our goal was to emphasize the fact that these were objects that set up the state of the application based on the URL. Perhaps we should have named them RouteControllers or something, but the time for that particular bikeshed has unfortunately come and gone. :wink:

On to services. Services, in my mind, are another specialized controller role.

If routes (coordinating controllers) control which templates and models are on screen
And controllers (mediating controllers) facilitate the flow between those templates and models
Then the role of services is to bridge the world of models and {mediating|coordinating} controllers.

Put another way, I see Ember Data’s store as a service and I want to, as they say in the standards bodies, provide features that “explain existing features.

My desired goal is to delete (almost) everything in ember-data/lib/initializers, change DS.Store to inherit from Ember.Service, and have everything continue working.

I would also like to easily add new services to my apps that make it easy for controllers and routes to marshal additional model data. For example, I’ve been working on an app for looking up bus arrival times in Portland called pdxb.us. One thing you need to calculate nearby arrival times, obviously, is the user’s location. I consider this location information to be “model” data just as much as the arrival times that come from TriMet’s JSON API.

As you can see, I defined a location service that gets injected into routes and controllers, then use that service in my route, just like how I’d use Ember Data’s store.

Hopefully this explanation helps you understand a little bit better my thinking behind services. I’m also not married to the name “service,” and am happy to entertain alternatives that may be clearer to new users.

TL;DR We currently have views, models, and coordinating controllers (routes). Mediating controllers (controllers) serve as the bridge between the view and controller layer; services should serve as the bridge between the model and controller layer.

15 Likes

Sounds good, I’ve got a few of these “services” in my app which are currently wired up in initializers and would be great for that to be automagic.

Eg.

  • queue - injected onto routes to publish background events to be run on the server
  • api - object to interact with our app’s REST API
  • store - using Fireplace, the same deal as Ember Data’s store

Another pattern I’ve been using to extract behaviour from the route is command objects which I talk about here which are kind of like services in that they go between the route and the model, but feel conceptually different in how they are used.

1 Like

I really like the direction @tomdale is headed with this. Giving services a clear purpose instead of trying to provide something that will be infinitely flexible goes a long way toward educating people how to use it.

I also like the example of geolocation as a service. We have a similar service in our app, but for desktop notifications. I’ve currently implemented it as a controller, though it never actually controls a view. It’s implemented as a controller so that other controllers can use it through dependency injection (i.e. “needs”).

Another example of a service we have in our app is a local cache for model objects. To continue the parallel with Cocoa, our model cache functions more or less like CoreData’s managed object context, but more lightweight. Basically, it manages object instances to make sure that controllers that need them always get the same instance. It also takes care of setting up and tearing down model objects when they aren’t needed anymore. I assume it functions similarly to Ember Data’s Store class, though I haven’t had time to dive into Ember Data yet.

1 Like

@tomdale Thanks for linking the examples in the pdxb.us app. I wanted to implement a similar service layer in my own ember(-cli) project and the example code made it really easy to get going.

I struggled with using the more powerful inject and register methods, so personally I would love if services became more of a convention that could be officially documented.

I just found another example of something that would make a good service candidate in our app: localStorage.

Just thinking about this a bit more, I could see modeling many common capabilities of browsers as services that are able to smooth out the differences in implementation. For example, XHR, getUserMedia, etc.

This sounds good, however I’m concerned with unnecessarily bloating the API/file size. This seems like it doesn’t need to be directly baked into the framework, but rather an official Ember library for creating services.

Is Project Svelte still a thing? If we’re talking about adding new primitives I would also like to here how the size of the framework is going to get smaller, but at the same time the surface area of API is going to expand. That discussion probably needs to be in it’s own thread.

As linked to in the pdxb.us example, the actual implementation of this feature builds heavily on top of existing features. All said and done, I imagine it would be implemented in under 100 lines of code. Just moving the view infrastructure from Metamorph to HTMLbars will probably be an order of magnitude reduction compared to that.

2 Likes

I like the idea of defining a role for Services. I can see some good use-cases where I could use this in some applications I’ve built.

I am wondering if are there cases where services could depend on each other. So like in @tomdale’s example, but instead of a NearbyTweets controller we might have a more general Twitter service which in turn uses the Geolocation service. This example is a bit off, but you get the idea.

@tomdale when I see the word “services” I can’t help but thinking about the services menu concept in OS X. Is this concept analogous?

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/SysServices/introduction.html

Services are features exported by your application for the benefit of other applications. Services let you share the resources and capabilities of your application with other applications in the system.

I think it we substituted the word applications with the word controllers we might have an approximation of what services are about in an ember context.

@tomdale I’d really like to see an architecture overview such as this but fleshed out more appear in the ofifcial ember docs. It really needs it.

Good clarification! This would make sense in our app too.

We’ve got something similar (albeit not named ‘services’) wrapping the W3C Geo API and device APIs (e.g. the push notification permissions API on iPhone).

I’m interested in how this fits into the world of (ES6) modules.

I like that Ember manages the Route and Controller instances automagically. The application, routes and controllers are all somewhat tightly coupled to one another, and this approach allows me to declare how my application works, rather than taking a more imperative approach.

A “service” seems like it could (should?) exist independently of the application. Given that, maybe the best advice would be to wrap the service up as a module that can be imported wherever it’s needed. The module loader already handles dependency management for loosely-coupled modules, so maybe it makes sense to just use that.

I’m not sure how this maps to “globals”-style Ember apps. Perhaps the best advice there is to tack service-providing objects onto the app namespace or the app controller.

(I apologize if I’m missing the point here, in that all of this “how” may be irrelevant in deciding whether there should be some class of objects in Ember called Services.)

1 Like

As a though-experiment, I forked @tomdale’s pdxb.us app and made the geolocation service into a stand-alone module.

This works in this instance, because the geolocation service doesn’t have any dependencies on other parts of the system, and it’s stateless. But how would you accomplish something like Ember Data’s store, where it needs to both maintain a cache and lookup models the user has defined?

Hey, so what’s the status on this? Can we play it with right now? I do like the idea of some sort of service object.

1 Like

I submitted a PR, but after discussion in that thread, the proposed API has changed and I have not yet updated the PR to reflect that. If you’d like to tackle it, feel free to fork the PR and update it with the new API.

Yes! I played around with extracting the model from pdxb.us into a module. That worked out OK (I actually really like the idea of an isolated, stand-alone data model), but pdxb.us doesn’t use Ember-Data…

I also experimented with an Ember-Data-based model. In doing so, I realized that it wouldn’t work because the Ember framework needs access to the model so that it can, for instance, automatically resolve URL segments into model objects (e.g., /books/:book_id).

I learned a lot from this thought experiment, and my feeling now is that there may be a fundamental difference between a loosely-coupled service like a geolocator or a roll-your-own model, and an integrated service like an Ember-Data-based model.

Being relatively noob I agree. Understand DI and Ember’s container is a bit foggy for me right now but this proposal is extremely straight forward and easily implemented.

I really like this proposal. With respect to injection, it is yet another case where declarative mixin/injection makes sense which could then be controlled/configured externally. Would be awful to maintain on a per instance basis directly in the code IMO. Just doesn’t scale, too easy to make mistakes.

See my Declarative Mixer RFC https://github.com/emberjs/rfcs/pull/7

A similar approach could be used for declarative authorization rules on routes, etc. etc. So many use cases really! The notion of Aspects were introduced in Java for the same reason. Many concerns are horizontal and best controlled externally to the code, much like CSS for layout.

I like this proposal because having a solid DI is vital. Will Ember support any form of scopes or contexts in the DI container, for example like Symfony does? I found it very useful when working on a large Symfony application.