Skip to content

Commit b9e2d4e

Browse files
committed
feat: add integration for service-oriented backends
1 parent 532f153 commit b9e2d4e

17 files changed

+585
-6
lines changed

docs/site/Calling-other-APIs-and-Web-Services.md

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,121 @@ summary:
99
---
1010

1111
Your API implementation often needs to interact with REST APIs, SOAP Web
12-
Services or other forms of APIs.
12+
Services, gRPC microservices, or other forms of APIs.
1313

14-
How to:
14+
To facilitate calling other APIs or web services, we introduce `@loopback/service-proxy`
15+
module to provide a common set of interfaces for interacting with backend services.
1516

16-
- Set up a Service
17-
- Use services with controllers
18-
- Reuse models between services and controllers
17+
## Installation
1918

20-
{% include content/tbd.html %}
19+
```
20+
$ npm install --save @loopback/service-proxy
21+
```
22+
23+
## Usage
24+
25+
### Define a data source for the service backend
26+
27+
```ts
28+
import {DataSourceConstructor, juggler} from '@loopback/service-proxy';
29+
30+
const ds: juggler.DataSource = new DataSourceConstructor({
31+
name: 'GoogleMapGeoCode',
32+
connector: 'rest',
33+
options: {
34+
headers: {
35+
'accept': 'application/json',
36+
'content-type': 'application/json'
37+
}
38+
},
39+
operations: [
40+
{
41+
template: {
42+
method: 'GET',
43+
url: 'http://maps.googleapis.com/maps/api/geocode/{format=json}',
44+
query: {
45+
address: '{street},{city},{zipcode}',
46+
sensor: '{sensor=false}'
47+
},
48+
responsePath: '$.results[0].geometry.location[0]'
49+
},
50+
functions: {
51+
geocode: ['street', 'city', 'zipcode']
52+
}
53+
}
54+
]
55+
});
56+
```
57+
58+
### Bind data sources to the context
59+
60+
```ts
61+
import {Context} from '@loopback/context';
62+
63+
const context = new Context();
64+
context.bind('dataSources.geoService').to(ds);
65+
```
66+
67+
**NOTE**: Once we start to support declarative datasources with `@loopback/boot`,
68+
the datasource configuration files can be dropped into `src/datasources` to be
69+
discovered and bound automatically.
70+
71+
### Declare the service interface
72+
73+
To promote type safety, we recommend you to declare data types and service
74+
interfaces in TypeScript and use them to access the service proxy.
75+
76+
```ts
77+
interface GeoCode {
78+
lat: number;
79+
lng: number;
80+
}
81+
82+
interface GeoService {
83+
geocode(street: string, city: string, zipcode: string): Promise<GeoCode>;
84+
}
85+
```
86+
87+
Alternately, we also provide a weakly-typed generic service interface as follows:
88+
89+
```ts
90+
/**
91+
* A generic service interface with any number of methods that return a promise
92+
*/
93+
export interface GenericService {
94+
[methodName: string]: (...args: any[]) => Promise<any>;
95+
}
96+
```
97+
98+
To reference the `GenericService`:
99+
100+
```ts
101+
import {GenericService} from '@loopback/service-proxy';
102+
```
103+
104+
**NOTE**: We'll introduce tools in the future to generate TypeScript service
105+
interfaces from service specifications such as OpenAPI spec.
106+
107+
### Declare service proxies for your controller
108+
109+
If your controller needs to interact with backend services, declare such
110+
dependencies using `@serviceProxy` on constructor parameters or instance
111+
properties of the controller class.
112+
113+
```ts
114+
import {serviceProxy} from '@loopback/service-proxy';
115+
116+
export class MyController {
117+
118+
@serviceProxy('geoService')
119+
private geoService: GeoService;
120+
121+
}
122+
```
123+
124+
### Get an instance of your controller
125+
126+
```ts
127+
context.bind('controllers.MyController').toClass(MyController);
128+
const myController = await context.get<MyController>('controllers.MyController');
129+
```

packages/service-proxy/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

packages/service-proxy/LICENSE

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright (c) IBM Corp. 2018. All Rights Reserved.
2+
Node module: @loopback/service-proxy
3+
This project is licensed under the MIT License, full text below.
4+
5+
--------
6+
7+
MIT license
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.

packages/service-proxy/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# @loopback/service-proxy
2+
3+
This module provides a common set of interfaces for interacting with service
4+
oriented backends such as REST APIs, SOAP Web Services, and gRPC microservices.
5+
6+
## Installation
7+
8+
```
9+
$ npm install @loopback/service-proxy
10+
```
11+
12+
## Basic use
13+
14+
See https://loopback.io/doc/en/lb4/Calling-other-APIs-and-web-services.html
15+
16+
## Contributions
17+
18+
- [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing##guidelines)
19+
- [Join the team](https://github.com/strongloop/loopback-next/issues/110)
20+
21+
## Tests
22+
23+
run 'npm test' from the root folder.
24+
25+
## Contributors
26+
27+
See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors).
28+
29+
## License
30+
31+
MIT

packages/service-proxy/docs.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"content": [
3+
"./index.ts",
4+
"./src/index.ts",
5+
"./src/decorators/service.decorator.ts",
6+
"./src/legacy-juggler-bridge.ts"
7+
],
8+
"codeSectionDepth": 4
9+
}

packages/service-proxy/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright IBM Corp. 2017. 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+
export * from './dist';

packages/service-proxy/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright IBM Corp. 2017. 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+
module.exports = require('./dist');

packages/service-proxy/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright IBM Corp. 2017,2018. 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+
// DO NOT EDIT THIS FILE
7+
// Add any additional (re)exports to src/index.ts instead.
8+
export * from './src';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@loopback/service-proxy-proxy",
3+
"version": "0.2.0",
4+
"description": "Service integration for LoopBack 4",
5+
"engines": {
6+
"node": ">=8"
7+
},
8+
"main": "index",
9+
"scripts": {
10+
"acceptance": "lb-mocha \"DIST/test/acceptance/**/*.js\"",
11+
"build": "lb-tsc es2017",
12+
"build:apidocs": "lb-apidocs",
13+
"clean": "lb-clean loopback-service-proxy*.tgz dist package api-docs",
14+
"integration": "lb-mocha \"DIST/test/integration/**/*.js\"",
15+
"pretest": "npm run build",
16+
"test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/acceptance/**/*.js\" \"DIST/test/integration/**/*.js\"",
17+
"unit": "lb-mocha \"DIST/test/unit/**/*.js\"",
18+
"verify": "npm pack && tar xf loopback-service-proxy*.tgz && tree package && npm run clean"
19+
},
20+
"publishConfig": {
21+
"access": "public"
22+
},
23+
"author": "IBM",
24+
"copyright.owner": "IBM Corp.",
25+
"license": "MIT",
26+
"devDependencies": {
27+
"@loopback/build": "^0.2.0",
28+
"@loopback/testlab": "^0.2.0"
29+
},
30+
"dependencies": {
31+
"@loopback/context": "^0.8.1",
32+
"@loopback/core": "^0.6.1",
33+
"loopback-datasource-juggler": "^3.15.2"
34+
},
35+
"files": [
36+
"README.md",
37+
"index.js",
38+
"index.d.ts",
39+
"dist/src",
40+
"src"
41+
],
42+
"repository": {
43+
"type": "git",
44+
"url": "https://github.com/strongloop/loopback-next.git"
45+
}
46+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright IBM Corp. 2018. 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 {
7+
MetadataAccessor,
8+
inject,
9+
Context,
10+
Injection,
11+
InjectionMetadata,
12+
} from '@loopback/context';
13+
import {getService, juggler} from '..';
14+
15+
/**
16+
* Type definition for decorators returned by `@serviceProxy` decorator factory
17+
*/
18+
export type ServiceProxyDecorator = PropertyDecorator | ParameterDecorator;
19+
20+
export const SERVICE_PROXY_KEY = MetadataAccessor.create<
21+
string,
22+
ServiceProxyDecorator
23+
>('service.proxy');
24+
25+
/**
26+
* Metadata for a service proxy
27+
*/
28+
export class ServiceProxyMetadata implements InjectionMetadata {
29+
decorator = '@serviceProxy';
30+
dataSourceName?: string;
31+
dataSource?: juggler.DataSource;
32+
33+
constructor(dataSource: string | juggler.DataSource) {
34+
if (typeof dataSource === 'string') {
35+
this.dataSourceName = dataSource;
36+
} else {
37+
this.dataSource = dataSource;
38+
}
39+
}
40+
}
41+
42+
export function serviceProxy(dataSource: string | juggler.DataSource) {
43+
return function(
44+
target: Object,
45+
key: symbol | string,
46+
parameterIndex?: number,
47+
) {
48+
if (key || typeof parameterIndex === 'number') {
49+
const meta = new ServiceProxyMetadata(dataSource);
50+
inject('', meta, resolve)(target, key, parameterIndex);
51+
} else {
52+
throw new Error(
53+
'@serviceProxy can only be applied to properties or method parameters',
54+
);
55+
}
56+
};
57+
}
58+
59+
/**
60+
* Resolve the @repository injection
61+
* @param ctx Context
62+
* @param injection Injection metadata
63+
*/
64+
async function resolve(ctx: Context, injection: Injection) {
65+
const meta = injection.metadata as ServiceProxyMetadata;
66+
if (meta.dataSource) return getService(meta.dataSource);
67+
if (meta.dataSourceName) {
68+
const ds = await ctx.get<juggler.DataSource>(
69+
'datasources.' + meta.dataSourceName,
70+
);
71+
return getService(ds);
72+
}
73+
throw new Error(
74+
'@serviceProxy must provide a name or an instance of DataSource',
75+
);
76+
}

0 commit comments

Comments
 (0)