-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE services] Initial feature #4861
Conversation
With this approach we lose the declarative upfront validation step needs. Which is a big bummer. I suppose with SA we can validate... Another concern with using "services" as our controllers are proxies to the underlying data models, this addition is a breaking change. I know we don't like it, but maybe we should prefix these things with something like angular's |
@@ -60,6 +60,29 @@ var defaultControllersComputedProperty = computed(function() { | |||
}; | |||
}); | |||
|
|||
if (Ember.FEATURES.isEnabled('services')) { | |||
var defaultServicesComputedProperty = computed(function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the whole property should also be marked as readOnly
We don't do that with the controllers
proxy yet Likely to simplify overriding the controllers property for testing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this form of CP proxy, is now duplicated 3 times.
- controller.controllers
- controller.services
- route.services
It should share a common implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stefanpenner - You are correct, we used to have this readOnly
for controllers
, but removed that for testability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would really like if we could re-enable when not testing. This is a common self troll for people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, there is a specific test below that asserts that you can simply specify the services
property for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One option would be to offer a testing helper for stubbing services. Something like this:
injectServices(controller, {
geolocation: myGeolocationStub
});
It could use defineProperty to override the read-only CP. That being said, I'd really hate to lose the obviousness of just replacing the property and creating yet-another concept that people have to understand in order to be able to test properly.
@stefanpenner Can you maybe expand on what the self-troll case is? This isn't something I've ever run into so I want to understand why it's been so painful for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice people do silly things, or during refactoring accidents happen. This seems to happen more the larger the code base, and the larger the teams, especially if they are inexperienced. Additionally, when this does happen it often manifests as an intermittent failure and is very tricky to track down.
controller.controllers
should never be set directly, if it is set, remember that consumption of controller.controllers.foo.bar
often is only via CP or Binding. So if controllers
is not what it should be the CP and binding value will just be null
or undefined
.
Even worse data can flow back up, and set to controller.controllers.foo
, resulting in unexpected and distracting behavior.
var obj = Ember.Object.create();
Ember.set(obj, 'controllers.foo.bar', 2)
// => Property set failed: object in path "foo" could not be found or was destroyed.
I also assure your that when I originally made this property readOnly
it was after encountering the issue in the wild multiple times. I was very surprised, and each occurrence was a very unpleasant debugging experience.
Ideally controllers property should not be directly set-able, but overridable at construction time.
another thing to consider is that other users could already be using Although the container isn't public api |
should we consider moving |
Everyone is always on my case about semver but services on an ObjectController is a breaking change. It is a common word likely to be used as a model association. |
I agree with @krisselden about taking away the top level term That being said, we just need to decide on a prefix to use that doesn't indicate private (like an I know that @stefanpenner votes for ☃, but I am unsure we can get a concensus on that one... |
maybe namespace it like |
After discussing this at the core team meeting, we all have concerns about reserving a One thing that we noted is that nearly EVERY controller we've looked at has the following pattern: App.PostController = Ember.ObjectController.extend({
needs: ['application'],
application: Ember.computed.alias('controllers.application')
}); The same thing would probably happen with the services API as implemented in its current form. This is obviously extremely redundant, and if there's anything I hate, it's boilerplate. What if we could have a mechanism for specifying both the "needs" and the property to alias to, all in one? App.PostController = Ember.ObjectController.extend({
application: Ember.controller('application')
}); Services derive quite nicely from this as well: App.PostController = Ember.ObjectController.extend({
geolocation: Ember.service('geolocation')
}); This approach let's us enforce layering (since we will raise an error if you try to use, e.g., Ember.service CPs inside a component) and also allows us to raise early if we detect that you've requested a controller or service that wasn't found in the container. |
@tomdale - This also provides the benefit of allowing the developer to choose the alias to use therefore avoiding the name collision concerns mentioned above. |
What is our justification for |
@lukemelia, the justification is:
|
I really like the idea of having That said, @lukemelia's comment resonates with me. I think one of the problems with Ember is it's MASSIVE public api surface area. Choosing In addition to creating another abstraction to learn, it also buts up against the 0, 1, or many rule. If controllers and services are so similar, it seems
I'd love to hear more about your reasons for this. From what I've been seeing in blogs and conference talks, most devs who are writing and speaking about the container are already talking about container keys as being first class public APIs. Personally, I'm a big fan -- container keys are a a simple and well designed way of organizing dependencies and I've enjoyed using the api in my app.
This comment makes me think you've been seeing DI used in bizarre and unsustainable ways. What's your experience been? How are devs misusing it? If abuse is rampant, "enforcement" is probably wise. However, if this is a attempt to prevent abuse, it feels premature to me. |
@scottmessinger it's not that DI is itself unsustainable, but injecting anything on anything without thought will lead to much pain. If one wishes todo this one can use the existing container @tomdale would like the sugar he proposes to guide best practices. |
@tomdale why not also support
where the service is inferred by the property name unless you provide a string arg to target another service. |
@machty - I like that. Could make some pretty nice syntax with a stashed var: //app/controllers/post.js
var service = Ember.service;
export default Ember.ObjectController.extend({
zomgAwesome: service()
}); |
Is the testing API not public? One argument for exposing container keys as part of the API is that it is necessary knowledge for testing (and also makes testing more intuitive). |
@tomdale This would replace the |
@ahx yes |
I'm concerned with the ambiguity of |
I'm also interested in the implementation details... |
@ebryn ya that ambiguity isn't great |
@ebryn @stefanpenner what do you think of the idea of a more general concept whereby ember-metal will collect properties that are instanceof some special macro type and expose them so that libraries like ED, EM, EPF, et al don't need to manually loop over class definitions to detect these special properties themselves? |
@machty the idea is good. I would be concerned about more instanceof checks, we should instead consider branding these objects... Speaking of this we should brand Alias instead of the constant instanceof checks... |
We already have this basically, it's called On Wed, May 21, 2014 at 1:53 PM, Stefan Penner notifications@github.comwrote:
|
@ebryn @stefanpenner I think the ambiguity goes away with ES6 modules. |
Super excited about the prospect of a formalized service type in Ember. I already have several 'services' that fit the mold perfectly (authentication manager, analytics tracking, etc.). Also happy about the decision to make consumers specify their own dependencies instead of the service being aware of consumers. The potential to remove all of the @tomdale I'm confused about how modules would resolve the import { Controller } from 'ember';
export default Controller.extend({ ... }); And that injection helpers would never (or couldn't) be imported in the same way? Another solution would be to namespace them under export default Ember.Controller.extend({
zomg: Ember.inject.service(),
awesome: Ember.inject.controller('awesomeThings'),
...
}); @machty forgive my ignorance, but why would libraries like ED, etc. need to do anything with these injections? It seems to me like these helpers map to container types, which those libraries aren't concerned with. Do you anticipate giving libs the ability to create custom injection helpers? export default Ember.Controller.extend({
store: DS.store('main'),
...
}); If so, wouldn't that defeat the point of these helpers by enabling "injecting anything on anything without thought" as @stefanpenner said? Or perhaps the process of creating a custom injection helper qualifies as 'thought'? 😉 |
@tomdale - Updates? Is this in progress? |
@tomdale, @stefanpenner, @rjackson: just dropped a PR that takes a stab at this #5162. Note to self: don't reference issue numbers from commits when rewriting history. |
@slindberg nice PR, its a good start :) |
Yeah, for serious. |
Closing in favor of #5162. |
This is an initial spike of the described in this thread on the discussion forums.
The TL;DR is that both controllers and routes get a
services
hash that they can look up services on; it's very similar to thecontrollers
hash on controllers and, indeed, is implemented very similarly.To create a geolocation service, for example, just define an
Ember.Service
class like so:If you're using
ember-cli
, just export anEmber.Service
fromapp/services/geolocation.js
.Once that's done, you have access to your service in both routes and controllers:
(Note that this deviates slightly from my original post on the forums; in order to avoid naming collisions, everything is namespaced under the
services
hash instead of being injected directly onto the route/controller.)Tasks left to do before this should be shipped to master:
services
hash generation across controllers/routesservices
hash andcontrollers
hashI'm a little embarrassed at how small the actual implementation is. It feels like it's doing almost nothing, which is basically true. But I hope this can be a starting point for the community to start evolving on our shared understanding of what services are.