Skip to content

Conversation

@bajtos
Copy link
Member

@bajtos bajtos commented Aug 27, 2018

Implement "ServiceMixin" for applications. This mixin enhances component
registration so that service providers exported by a component are
automatically registered for dependency injection; and adds a new sugar
API for registering service providers manually:

app.serviceProvider(MyServiceProvicer);

The method name "serviceProvider" was chosen deliberately to make it
clear that we are binding a Provider, not a class constructor. Compare
this to app.repository(MyRepo) that accepts a class construct. In the
future, we may add app.service(MyService) method if there is enough
user demand.

This pull request is the first step towards automated service registration via boot, see #1439.

Checklist

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

@bajtos bajtos requested a review from raymondfeng as a code owner August 27, 2018 09:02
@bajtos bajtos self-assigned this Aug 27, 2018
@bajtos bajtos added the feature label Aug 27, 2018
@bajtos bajtos added this to the August Milestone milestone Aug 27, 2018
@bajtos bajtos force-pushed the feat/register-services branch from e96a5d1 to fd1e05a Compare August 27, 2018 09:04

**NOTE**: Once we start to support declarative datasources with
`@loopback/boot`, the datasource configuration files can be dropped into
`src/datasources` to be discovered and bound automatically.
Copy link
Member Author

Choose a reason for hiding this comment

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

This doc fix is not strictly related to the changes made by my pull request, the section should have been removed when the datasource booter was added. Since it's just a small change, I didn't feel like creating a new commit or a new pull request.

/**
* Interface for classes with `new` operator.
*/
export interface Class<T> {
Copy link
Contributor

@raymondfeng raymondfeng Aug 27, 2018

Choose a reason for hiding this comment

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

Should we somehow leverage the Class/Constructor type from https://github.com/strongloop/loopback-next/blob/master/packages/context/src/value-promise.ts#L13 or refactor it into a common package?

Copy link
Member Author

Choose a reason for hiding this comment

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

Having a shared definition of Class and Constructor types would be definitely nice.

However, I am not sure if such small typedefs are enough to justify the cost of creating a new package?

Also please note that in RepositoryMixin (which I based my work on), Class is coming from a different package - see https://github.com/strongloop/loopback-next/blob/f68a45ced9a392286d484b72cc690a6599aeb0c0/packages/repository/src/common-types.ts#L11-L19

Can we leave this improvement out of scope of this pull request please?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure. +1 to have a separate PR.

*
* @param component The component to mount services of
*/
mountComponentRepository(component: Class<{}>) {
Copy link
Contributor

@raymondfeng raymondfeng Aug 27, 2018

Choose a reason for hiding this comment

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

Shouldn't it be named as mountComponentServiceProviders?

BTW, the mountComponentRepository in RepositoryMixin should be named as mountComponentRepositories too. (plural)

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I'll apply both suggestions.

// tslint:disable-next-line:no-any
serviceProvider<S>(provider: Class<Provider<S>>): void;
component(component: Class<{}>): void;
mountComponentRepository(component: Class<{}>): void;
Copy link
Contributor

Choose a reason for hiding this comment

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

It should be mountComponentServiceProviders too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I prefer to call this method mountComponentServices. In the future, if we add support for app.service(serviceCtor: Class), then I'd like mountComponentServices to handle both service classes and providers.

/**
* Interface for an Application mixed in with ServiceMixin
*/
export interface AppWithServices extends Application {
Copy link
Contributor

Choose a reason for hiding this comment

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

ApplicationWithServices?

*
* @param component The component to mount services of
*/
mountComponentRepository(component: Class<{}>) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

mountComponentServiceProviders


// tslint:disable:no-any

describe('RepositoryMixin', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

It should be ServiceProviderMixin.

Copy link
Member Author

Choose a reason for hiding this comment

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

See #1649 (comment), I prefer to use ServiceMixin instead of ServiceProviderMixin.

In my mind, the mixin is adding APIs for working with services. The fact that services are registered via Providers is just an implementation detail to me.

Copy link
Contributor

@raymondfeng raymondfeng left a comment

Choose a reason for hiding this comment

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

Nice abstraction. Please address my comments.

@bajtos
Copy link
Member Author

bajtos commented Aug 27, 2018

@raymondfeng comments addressed in 767c54c, LGTY now?

@marioestradarosa
Copy link
Contributor

marioestradarosa commented Aug 27, 2018

Nice work @bajtos . From the app developer perspective in the current way of registering the services and the proposed way of registering the provider and then injecting the service in the controller I can see a better code design and the addition of the ServiceMixin automatically by the boolean switch is great!.

Can you highlight if there is a gain in performance in this way?. And finally I believe the next step would be to have the provider discovery , to avoid the registration of the provider, imagine an app with a lot of providers ?

Copy link
Contributor

@marioestradarosa marioestradarosa left a comment

Choose a reason for hiding this comment

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

Excelent! .

Copy link
Contributor

@virkt25 virkt25 left a comment

Choose a reason for hiding this comment

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

Awesome effort 👏 Just some minor comments.

if (project.enableRepository) {
classWithOptionalMixins = `RepositoryMixin(${classWithOptionalMixins})`;
}
if (project.enableServices) {
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 look to compute classWithOptionalMixins within the generator instead of the template to keep the template simple?

"@loopback/repository": "<%= project.dependencies['@loopback/repository'] -%>",
"@loopback/rest": "<%= project.dependencies['@loopback/rest'] -%>"
"@loopback/rest": "<%= project.dependencies['@loopback/rest'] -%>",
"@loopback/service-proxy": "<%= project.dependencies['@loopback/service-proxy'] -%>"
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be limited to an application with serviceProxy option enabled? Then again we are loading the repository package for an application regardless ... I'll leave it up to you to decide.

* app.serviceProvider(GeocoderServiceProvider);
* ```
*/
serviceProvider<S>(provider: Class<Provider<S>>): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we just call this service()?

Copy link
Member Author

Choose a reason for hiding this comment

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

Cross-posting from the pull request description:

The method name "serviceProvider" was chosen deliberately to make it
clear that we are binding a Provider, not a class constructor. Compare
this to app.repository(MyRepo) that accepts a class construct. In the
future, we may add app.service(MyService) method if there is enough
user demand.

@bajtos
Copy link
Member Author

bajtos commented Aug 28, 2018

@marioestradarosa thank you for chiming in.

Can you highlight if there is a gain in performance in this way?

I am not aware of any performance gains. My goal here is to make it easier for LB4 app developers to register their services (service providers).

And finally I believe the next step would be to have the provider discovery , to avoid the registration of the provider, imagine an app with a lot of providers ?

Exactly! My next step is to implement a Booter in @loopback/boot package that will scan project files, discover service providers and register them via app.serviceProvider.

See #1439 for a bigger picture.

@bajtos bajtos force-pushed the feat/register-services branch from 767c54c to 6b7fa83 Compare August 28, 2018 12:02
@bajtos
Copy link
Member Author

bajtos commented Aug 28, 2018

@virkt25 thank you for pointing out opportunities to make the code cleaner! I addressed your first two comments in 6b7fa83

Implement "ServiceMixin" for applications. This mixin enhances component
registration so that service providers exported by a component are
automatically registered for dependency injection; and adds a new sugar
API for registering service providers manually:

    app.serviceProvider(MyServiceProvicer);

The method name "serviceProvider" was chosen deliberately to make it
clear that we are binding a Provider, not a class constructor. Compare
this to `app.repository(MyRepo)` that accepts a class construct. In the
future, we may add `app.service(MyService)` method if there is enough
user demand.
@bajtos bajtos force-pushed the feat/register-services branch from 6b7fa83 to 42ca652 Compare August 28, 2018 12:03
@bajtos bajtos merged commit fb01931 into master Aug 28, 2018
@bajtos bajtos deleted the feat/register-services branch August 28, 2018 12:54
@bajtos bajtos mentioned this pull request Aug 28, 2018
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants