Skip to content

Commit c73e8d0

Browse files
committed
fix(service-proxy): change default binding scope to TRANSIENT for service proxies
1 parent 649cfc2 commit c73e8d0

File tree

3 files changed

+76
-20
lines changed

3 files changed

+76
-20
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright IBM Corp. 2019. All Rights Reserved.
2+
// Node module: @loopback/service-proxy
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {Application, Context, inject, Provider} from '@loopback/core';
7+
import {expect} from '@loopback/testlab';
8+
import {ServiceMixin} from '../..';
9+
10+
describe('ServiceMixin - service providers and proxies', () => {
11+
class AppWithServiceMixin extends ServiceMixin(Application) {}
12+
13+
interface GeoPoint {
14+
lat: number;
15+
lng: number;
16+
}
17+
18+
interface GeocoderService {
19+
geocode(address: string): Promise<GeoPoint>;
20+
}
21+
22+
class DummyGeocoder implements GeocoderService {
23+
constructor(public apiKey: string) {}
24+
25+
geocode(address: string) {
26+
return Promise.resolve({lat: 0, lng: 0});
27+
}
28+
}
29+
30+
class GeocoderServiceProvider implements Provider<GeocoderService> {
31+
constructor(@inject('apiKey') private apiKey: string) {}
32+
value(): Promise<GeocoderService> {
33+
// Returns different instances so that we can verify the TRANSIENT
34+
// binding scope, which is now the default for service proxies
35+
return Promise.resolve(new DummyGeocoder(this.apiKey));
36+
}
37+
}
38+
39+
it('binds a service provider in TRANSIENT scope by default', async () => {
40+
const myApp = new AppWithServiceMixin();
41+
myApp.serviceProvider(GeocoderServiceProvider);
42+
const myReq1 = new Context(myApp, 'request1');
43+
myReq1.bind('apiKey').to('123');
44+
const service1 = await myReq1.get<DummyGeocoder>(
45+
'services.GeocoderService',
46+
);
47+
expect(service1.apiKey).to.eql('123');
48+
49+
const myReq2 = new Context(myApp, 'request2');
50+
myReq2.bind('apiKey').to('456');
51+
const service2 = await myReq2.get<DummyGeocoder>(
52+
'services.GeocoderService',
53+
);
54+
expect(service2.apiKey).to.eql('456');
55+
});
56+
});

packages/service-proxy/src/__tests__/unit/mixin/service.mixin.unit.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
import {Application, Component, Provider} from '@loopback/core';
6+
import {Application, Component, Provider, BindingScope} from '@loopback/core';
77
import {expect} from '@loopback/testlab';
88
import {Class, ServiceMixin} from '../../../';
99

@@ -15,7 +15,7 @@ describe('ServiceMixin', () => {
1515
expect(typeof myApp.serviceProvider).to.be.eql('function');
1616
});
1717

18-
it('binds repository from app.serviceProvider()', async () => {
18+
it('binds service from app.serviceProvider()', async () => {
1919
const myApp = new AppWithServiceMixin();
2020

2121
expectGeocoderToNotBeBound(myApp);
@@ -56,16 +56,17 @@ describe('ServiceMixin', () => {
5656
geocode(address: string): Promise<GeoPoint>;
5757
}
5858

59-
// A dummy service instance to make unit testing easier
60-
const GeocoderSingleton: GeocoderService = {
59+
class DummyGeocoder implements GeocoderService {
6160
geocode(address: string) {
6261
return Promise.resolve({lat: 0, lng: 0});
63-
},
64-
};
62+
}
63+
}
6564

6665
class GeocoderServiceProvider implements Provider<GeocoderService> {
6766
value(): Promise<GeocoderService> {
68-
return Promise.resolve(GeocoderSingleton);
67+
// Returns different instances so that we can verify the TRANSIENT
68+
// binding scope, which is now the default for service proxies
69+
return Promise.resolve(new DummyGeocoder());
6970
}
7071
}
7172

@@ -74,15 +75,19 @@ describe('ServiceMixin', () => {
7475
}
7576

7677
async function expectGeocoderToBeBound(myApp: Application) {
77-
const boundRepositories = myApp.find('services.*').map(b => b.key);
78-
expect(boundRepositories).to.containEql('services.GeocoderService');
79-
const repoInstance = await myApp.get('services.GeocoderService');
80-
expect(repoInstance).to.equal(GeocoderSingleton);
78+
const boundServices = myApp.find('services.*').map(b => b.key);
79+
expect(boundServices).to.containEql('services.GeocoderService');
80+
const binding = myApp.getBinding('services.GeocoderService');
81+
expect(binding.scope).to.equal(BindingScope.TRANSIENT);
82+
const serviceInstance1 = await myApp.get('services.GeocoderService');
83+
expect(serviceInstance1).to.be.instanceOf(DummyGeocoder);
84+
const serviceInstance2 = await myApp.get('services.GeocoderService');
85+
expect(serviceInstance2).to.not.be.equal(serviceInstance1);
8186
}
8287

8388
function expectGeocoderToNotBeBound(myApp: Application) {
84-
const boundRepos = myApp.find('services.*').map(b => b.key);
85-
expect(boundRepos).to.be.empty();
89+
const boundServices = myApp.find('services.*').map(b => b.key);
90+
expect(boundServices).to.be.empty();
8691
}
8792

8893
function expectComponentToBeBound(

packages/service-proxy/src/mixins/service.mixin.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
import {
7-
Provider,
8-
createBindingFromClass,
9-
BindingScope,
10-
Binding,
11-
} from '@loopback/context';
6+
import {Binding, createBindingFromClass, Provider} from '@loopback/context';
127
import {Application} from '@loopback/core';
138

149
/**
@@ -75,7 +70,7 @@ export function ServiceMixin<T extends Class<any>>(superClass: T) {
7570
name: serviceName,
7671
namespace: 'services',
7772
type: 'service',
78-
}).inScope(BindingScope.SINGLETON);
73+
});
7974
this.add(binding);
8075
return binding;
8176
}

0 commit comments

Comments
 (0)