Skip to content
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

Service Registration: Cleanup of service.js #8698

Merged
merged 11 commits into from Apr 25, 2017

Conversation

kmh287
Copy link
Contributor

@kmh287 kmh287 commented Apr 11, 2017

Now that no services use the fromClass and fromClassForDoc codepaths, service.js can be cleaned up a bit. This PR:

  • removes fromClass and fromClassForDoc. The same functionality is provided by registerService + getService and registerServiceForDoc + getServiceForDoc
  • Removes getExistingService{,forDoc} methods whose functionality is provided by asserts elsewhere.
  • getExistingServiceForWindowOrNull has been renamed to be consistent with other function names
  • Removes legacy code path related to the removed functions

/to @choumx @dvoytenko

Closes #4986

export function fromClass(win, id, constructor) {
win = getTopWindow(win);
return getServiceInternal(win, win, id, constructor);
if (!isServiceRegistered(win, id)) {

Choose a reason for hiding this comment

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

The existence of opt_factory here and in getServiceForDoc breaks the registration pattern. What do you think we should do about this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current behavior is far from ideal: if you call getService with a factory but the service is already registered then the factory is ignored.

I can look into removing them, but I would rather not add that to this PR.

Choose a reason for hiding this comment

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

if you call getService with a factory but the service is already registered then the factory is ignored.

No different from trying to register a service twice, right?

I would rather not add that to this PR.

That's fine, just thinking about how to make this API simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No different from trying to register a service twice, right?

Right, and incidentally this is how these functions worked before the service registration PRs. The recent changes just make this more apparent.

That's fine, just thinking about how to make this API simpler.

Gotcha. I will definitely followup after this.

src/service.js Outdated
* @param {string} id
* @return {?Object}
*/
function getExistingServiceForEmbedWinOrNull(embedWin, id) {

Choose a reason for hiding this comment

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

Can you undo the change in position for these methods? It makes the diff harder to read.

In general, recommend minimizing diffs in sensitive changes like this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

src/service.js Outdated
* @return {Object}
* @template T
*/
function getServiceInternal(holder, context, id) {

Choose a reason for hiding this comment

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

Ditto re: position.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

''

if (s.build) {
s.obj = s.build();
s.resolve(s.obj);
}

Choose a reason for hiding this comment

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

Removed because this is already done in getServicePromiseOrNullInternal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. getServicePromiseOrnullInternal should only return null if the service has never been registered. This means that there won't be an existing service.

src/service.js Outdated
/**
* @param {!Window} embedWin
* @param {string} id
* @return {?Object}
*/
function getLocalExistingServiceForEmbedWinOrNull(embedWin, id) {
function getExistingServiceForEmbedWinOrNull(embedWin, id) {

Choose a reason for hiding this comment

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

Why remove the "Local" qualifier? Seems important vis-a-vis getExistingServiceInEmbedScope since the former falls back to the top-level window.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could go either way on this. I assumed that the distinction came form explicitly mentioning the embed window in the function name vs the other function which just mentioned a more nebulous 'scope'. I'll revert it.

id,
constructor);
const holder = getAmpdocServiceHolder(ampdoc);
if (!isServiceRegistered(holder, id)) {
Copy link

@dreamofabear dreamofabear Apr 13, 2017

Choose a reason for hiding this comment

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

Update the documentation to state that the service will be registered with opt_factory if unregistered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

src/service.js Outdated
// The service may have been requested already, in which case we have a
// pending promise we need to fulfill.
if (s.resolve) {
s.resolve(s.obj);
if (s.promise && s.resolve) {

Choose a reason for hiding this comment

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

Is there an instance where s.resolve is not null but s.promise is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I just thought this would make the intention more explicit. (IIRC we were checking for both elsewhere as well) I'll update all other similar checks to only check for resolve

export function fromClass(win, id, constructor) {
win = getTopWindow(win);
return getServiceInternal(win, win, id, constructor);
if (!isServiceRegistered(win, id)) {

Choose a reason for hiding this comment

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

if you call getService with a factory but the service is already registered then the factory is ignored.

No different from trying to register a service twice, right?

I would rather not add that to this PR.

That's fine, just thinking about how to make this API simpler.

@dreamofabear
Copy link

Thanks for reverting the positioning changes. Diff is much more readable now.

@kmh287 kmh287 mentioned this pull request Apr 17, 2017
src/service.js Outdated
/* opt_ctor */ undefined,
() => service);
// Force service to build
getServiceInternal(embedWin, embedWin, id);

Choose a reason for hiding this comment

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

This can be reverted. Let's address the future of the convenience builder separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd rather we didn't as reverting this means reverting getServiceInternal as well. The public interface nor its behavior should change due to this.

src/service.js Outdated
}
s.build = null;

Choose a reason for hiding this comment

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

Please add a unit test for this.

src/service.js Outdated
if (s.resolve) {
s.obj = s.build();
s.resolve(/** @type {!Object} */(s.obj));
s.build = null;

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

line deleted

}


/**
* Returns a promise for service `id` if the service has been registered
* on `holder`.

Choose a reason for hiding this comment

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

Need to set s.build to null in this method. And a unit test. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

routing all of this through getServiceInternal to avoid duplication

src/service.js Outdated
// The service may have been requested already, in which case we have a
// pending promise we need to fulfill.
if (s.resolve) {
s.resolve(s.obj);
s.resolve(/** @type {!Object} */(s.obj));
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we get rid of these typecasts by marking s.build correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like it can just be deleted.

src/service.js Outdated
s.obj = obj;
s.resolve(obj);
if (s.resolve) {
s.obj = s.build();
Copy link
Contributor

Choose a reason for hiding this comment

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

Just call getServiceInternal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea

Copy link
Contributor

@jridgewell jridgewell left a comment

Choose a reason for hiding this comment

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

Now that you're familiar with the service.js functions, are there any duplicated ones that we can get rid of? Some of them seem very similar.

src/service.js Outdated
@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) {
} else {
dev().assert(s.build,
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this handled by getServiceInternal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can get rid of it

@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) {
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we collapse the previous block into this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure

src/service.js Outdated
// The service may have been requested already, in which case we have a
// pending promise we need to fulfill.
if (s.resolve) {
s.resolve(s.obj);
s.resolve((s.obj));
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks

Copy link
Contributor Author

@kmh287 kmh287 left a comment

Choose a reason for hiding this comment

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

@jridgewell This PR set out to remove quite a few redundant functions. I think there's more work to do eliminating calls to getService that provide a factory, but I'm not sure how much cleaner this can get. We need to worry about

  • services on a window vs services on an ampdoc
  • embedded services vs top-level services
  • services that can be returned directly vs services that need to be resolved by promise
  • functions that get a service or service promise and expect them to exist vs functions that get a service or service promise if one exists and return null otherwise

We could probably clean those up some more, but I'm not sure how much more we could do without (further) altering the interface.

src/service.js Outdated
// The service may have been requested already, in which case we have a
// pending promise we need to fulfill.
if (s.resolve) {
s.resolve(s.obj);
s.resolve((s.obj));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks

src/service.js Outdated
@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) {
} else {
dev().assert(s.build,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can get rid of it

@@ -596,8 +512,9 @@ function getServicePromiseOrNullInternal(holder, id) {
} else {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure

src/service.js Outdated
s.obj = obj;
s.resolve(obj);
if (s.resolve) {
// getServiceInternal will resolve the project.

Choose a reason for hiding this comment

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

"resolve the promise."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

whoops, thanks

fromClassForDoc(
ampdoc, 'amp-analytics-instrumentation', MockInstrumentation);
registerServiceBuilderForDoc(
ampdoc, 'amp-analytics-instrumentation', MockInstrumentation);

Choose a reason for hiding this comment

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

Nit: +2 spaces

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

getService(window, 'a');
expect(window.services['a'].obj).to.not.be.null;
expect(window.services['a'].build).to.be.null;
});

Choose a reason for hiding this comment

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

Thanks!

src/service.js Outdated
s.obj = s.build();
return s.promise = Promise.resolve(s.obj);
if (!s.obj) {
// Instantiate service

Choose a reason for hiding this comment

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

I think we can remove the conditional and change this comment to "Instantiate service if not already instantiated."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea

/**
* Returns a service for the given id and ampdoc (a per-ampdoc singleton).
* If the service is not yet available the factory function is invoked and
* expected to return the service.
* @param {!Node|!./service/ampdoc-impl.AmpDoc} nodeOrDoc
* @param {string} id of the service.
* @param {function(!./service/ampdoc-impl.AmpDoc):T=} opt_factory
* Should create the service if it does not exist yet. If the factory is
* not given, it is an error if the service does not exist yet.
* Factory to create the service if the service does not exist yet.
Copy link
Contributor

Choose a reason for hiding this comment

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

So, we now have getExistingService* for window and embed scope, but not for doc scope? Does this help to conflate registration and use-service cases/APIs?

Choose a reason for hiding this comment

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

I noticed this too but looks like performanceForOrNull uses it. If those calls can be changed to performanceFor, then we can remove the getExisting... methods altogether.

Recommend making any additional changes after this PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are getExistingService*orNull functions, but I've completely removed functions that will fail an assertion if a service doesn't already exist as getServiceInternal now handles that case.

There may not be some forDoc ones as they weren't needed anywhere. I can add some in if you'd like.

As for performaneForOrNull, my understanding is that there are times we don't expect the performance service to exist. If that's not the case then we can remove performanceForOrNull as @choumx suggested.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think performanceForOrNull is used as you describe and thus it's needed. I mostly reacted to absence of getExistingServiceForDoc because I thought we were trying to remove API confusion between service registration and service use. E.g. Viewer is registered only once, but is being used via viewerFor all over the place.

But, I'm ok if we reconsider this in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Callers shouldn't need to worry about registered vs built. I removed those functions since they were redundant with changes made to getServiceInternal.

@kmh287
Copy link
Contributor Author

kmh287 commented Apr 25, 2017

@choumx @dvoytenko @jridgewell Thank you all for the very thorough reviews.

@kmh287 kmh287 merged commit 9041fc1 into ampproject:master Apr 25, 2017
mrjoro pushed a commit to mrjoro/amphtml that referenced this pull request Apr 28, 2017
KenneyE pushed a commit to spotxchange/amphtml that referenced this pull request May 3, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants