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

Throwing my hat into the ring with a generic dependency injection computed property: https://github.com/jayphelps/ember-computed-injection

var injection = Ember.computed.injection;

var MyController = Ember.Controller.extend({
  geolocation: injection('service:geolocation')
});

export default MyController;
7 Likes

Jay’s pitch is a clever way to declare things (which I will likely adopt in my own apps!) but I still don’t like moving forward without a solid understanding of what a service is and what a service isn’t. We don’t get all of the advantages of having common nomenclature and patterns with services if they’re just a thin veneer over a anything-goes container-loaded object. Luke’s post very neatly sums that up.

Regardless, as we’re heading down this path, I believe that “Services” is the kind of thing that lends itself well to being built as an addon. If the addon proves useful, matures well, and finds itself commonplace we can then move to include it in core.

1 Like

Yup, this would be my preference. Services could then just be a convention in ember-cli i.e. a services folder alongside controllers.

I’d like to see this approach replace ‘needs’ completely i.e.

var injection = Ember.computed.injection;

var MyController = Ember.Controller.extend({
  geolocation: injection('service:geolocation'),
  otherController: injection('controllers:otherController'),
});

export default MyController;
1 Like

Seems like the right place to bring up the discrepancy between Route-driven vs. non-Route-driven controllers. They seem like different objects to me. The former should manage state pertaining to a route, while the latter should manage state that wraps a model (eg. isSelected). The former should be singletons, while the latter should not. The former should have strict DI restrictions, while the latter should be instantiable wherever you want to wrap state around a model, similar to an ArrayProxy but with a little more itemController-flavor to it.

Looking forward to seeing @matchy’s work on model dependent state to clarify the roles of controllers.

Also, a backwards compatible, extensible API for services:

var MyController = Ember.Controller.extend({
  needs: {
    controllers: ['post', 'comments'],
    services: ['websocket', 'geolocation']
  }
});
4 Likes

Absolutely! Would be nice to have “one injection method to rule them all” (that isn’t type based, i.e. app.inject('controller', 'store', 'store:main')), getting rid of needs.

Also–I just saw that you previously mentioned basically the same technique. Wasn’t intentionally trying to steal the credit :smile: we just happened to come to the same conclusion. We basically mind-melded

1 Like

Ha, no I’m just glad someone else had the same idea and pushed it forwards, I really do think it solves a lot of the problems with the ‘needs’ approach.

I agree with the previous poster that this would make sense if service were clearly defined, but it really sounds like it’s definition is “an abstraction for everything that’s outside the abstracted roles defined so far” – which to me translates as an abstraction for the sake of abstraction. As someone who doesn’t program in Ember all day, but is trying to keep up while working jobs that are mostly in Rails + Angular, mainly cause I just sort of like Ember and would one day like to code in it professionally, it’s frustrating to turn away for a week and come back to yet another new role to learn. While all these conventions might theoretically make it easier for the everyday programmer to be more productive, honestly to me it just makes for a ridiculously steep learning curve to even get started with. How bout just take a break from new roles and give everyone a little bit of time to catch up?

@nathanhammond I have a feeling Tom was trying to elicit what a service is to the community, and from our actual uses… without “putting his fingers through it” by providing a definition. After all, frameworks should be extracted, right? :smile:

1 Like

Thought this might be relevant: http://davej.github.io/angular-classy/

They are using needs: [...] for injection.

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.