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

From your API suggestions it looks like services will have to be singletons. I have written controllers I would consider services that need to be instantiated per host object. How does that mesh with your concept of services?

1 Like

Yeah, Tom, naming is intensely important. :slight_smile: For example, devs coming from other frameworks often assume they know what a “Route” is in Ember because it’s called a route and they’ve seen that before, so of course they should know all about what it means and how it behaves. As you know, totally not the case!

One of the worst things about programming is that problem of miscommunication because of differing context.

So, I reckon it doesn’t matter so much what we call these things so long as we define what they are… so we’re talking about services here: I notice you were careful about speaking about “pulling in additional functionality beyond what is provided out of the box” as your definition of a service originally. Maybe you’re being intentionally vague here so we can find out what we mean by this vague amorphously defined area that includes functionality not included out of the box :wink: :wink:

The question on my lips is… do we ask the community what a service is… or do we say “hey what’s a good name for something that fulfils this particular role? and we’re going to choose “service” for now as a discussion point. This will then go into the codebase as a beta feature and we’ll all use it, then inform our naming and definition later”. This seems to be what you’re saying… a community driven defintiion of services. Seems to be what happened with components.

The issue I encounter with that is that the definitions aren’t pinned down anywhere in a really hard way that explains examples very clearly. Point in case it took me a fair time to work out what a Route was and how it should be used… this could be solved with a much clearer architecture explanation… using non-trivial real-world examples.

A constant issue I have, for example, is I don’t know the Ember way to build a route that has access to sidechained models (for example, an edit screen that needs five lists for dropdowns which are model-driven). I’ve done this in a way in my app but I can’t shake the bad feeling that maybe it’s not “right”. I feel like this a lot when devving on Ember.

Having said all this, this services proposal sounds like a massively awesome idea, and reckon we should put it into swing and see where we end up. :smile: I reckon Ember has an amazing community… <3

1 Like

I agree, this feels like a junk drawer to me in the way it was described. This seems like something that could be implemented separately from Ember. Maybe that approach makes sense, where people who want it, pull it in and use it. The way people use it can then further inform whether it belongs in ember, what it should be called and how it should behave.

It seems that the “job to be done” for a service is not clear here. It might be helpful to think about the ways a service differs from a controller (and what they have in common).

Jobs common to Service & Controller

  • global
  • singleton (?)
  • managed by the container
  • Manages (unpersisted) application state

Jobs for service only

  • ?

Jobs for controller only

  • ?

I am not super experienced with Ember so I’m sure others can come up with a more complete list but currently it seems that controllers and services are very similar.

This might be (read: probably is) a daft idea but perhaps we should consider a way of blessing access to certain controllers from other parts of the app e.g.

App.BlahController = Ember.ObjectController.extend({
  exposeAsServiceTo: ['routes', 'controllers'], // routes|controllers|models|all|none etc.
  // ...
});

and then elsewhere

Ember.Object.extend({
  inject: ['blahService', 'fooService'], // append 'Service' to be more explicit
  // ... 
});

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: 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

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.