0.5.0 • Published 6 years ago

gateway-service v0.5.0

Weekly downloads
-
License
ISC
Repository
bitbucket
Last release
6 years ago

GatewayService

A set of Typescript decorators to easily build clients to connect to remote services over HTTP and WebSocket.

Quick start

  • Install gateway-service NPM package
npm install gateway-service
  • Create a class, annotate it with @GatewayService and set the base url to your HTTP remote service
@GatewayService("http://localhost:8080")
class MyService {
}
  • Add a method to your class, annotate it with @Get and provide a relative path to a remote resource
@GatewayService("http://localhost:8080")
class MyService {

    @Get("/teams")
    getTeams(): Promise<Array<Team>> { return null; }
}
  • And that's it! Now you can access your remote resource by simply creating an instance of your service and invoking your method
const service: MyService = new MyService();

service.getTeams()
.then((teams: Array<Teams>) => {

    // ... here you are your teams

}).catch((error) => { 
    
    // ... handle error

});

Reference guide

HHTP services

  • @GatewayService decorator

In order to use a class as a gateway service you must annotate it with @GatewayService decorator. This annotation is also useful to provide the base url to your remote service.

@GatewayService("http://localhost:8080")
class MyService {
}

Once you have your class annotated with @GatewayService, you can get its methods implemented 'on the fly' by decorating them with one of the provided annotations (see below).

  • HTTP method decorators: @Get, @Post, @Patch, @Put and @Delete

gateway-service provides five annotations (@Get, @Post, @Patch, @Put and @Delete) to access your remote service using HTTP requests. These decorators are the counterpart of its corresponding HTTP verbs. Simply annotate your method with one of them and you are ready to go...

@Get("/teams")
getAllTeams(): Promise<Array<Team>> { return null; }

@Post("/teams")
addTeam(team): Promise<Team> { return null; }

@Delete("/teams/1")
deleteTeam(): Promise<any> { return null; }

... and so on.

Note: If we assign a type for the method output, such as Promise<Array<Team>> in the first example, we must return something (for example, a null value) to avoid compilation errors. Don't worry about this return statement, as it will never be executed.

Note: When you annotate a method with one of these decorators, the output is always a Promise.

  • Path parameters: @Path

Commonly, when accessing a resource we need to set an "id" as part of the url (for example, GET /teams/1) and this "id" is provided at run-time. To handle this situation you must use @Path decorator to annotate a method parameter. The value of this parameter will be added to the resource url.

The @Path decorator accepts an argument that is used to indicate the place in resource url where the value must be placed.

For example, given the following method declaration:

@Get("/teams/${id}")
getTeamById(@Path("id") myId) { }

then if we execute getTeamById(25); the request will be: GET /teams/25

  • Query parameters: @QueryParameters

If we annotate a method parameter with @QueryParameters decorator, the attributes of this parameter will be used to build a query string which will be added to the resulting URL.

For example, given the following method declaration:

@Get('/players')
getByQuery(@QueryParameters params: Object) { }

then if we execute getByQuery({ name: 'leo', surname: 'messi' }); the request will be: GET /players?name=leo&surname=messi

  • Adding a body: @Body

To supply an object as the body of the http request we use @Body decorator. The method parameter annotated with @Body will be copied in the body of the request at run-time.

For example, given the following method declaration:

@Post('/players')
addPlayer(@Body player) { }

then if we execute addPlayer({ name: 'leo', surname: 'messi' }); the request will be: POST /players and the request body will be: { name: 'leo', surname: 'messi' }

  • Mapping server response: @Mapping

Sometimes, the server response is not "flat", but built based on embedded collections or wrapper objects. This makes it difficult for clients to map responses to model objects. @Mapping decorator allows us to flatten complex server responses to easily map them to model objects.

@Mapping accepts an argument which is a function that receives the server response as a parameter.

For example, suppose that after a GET /players request, you obtain this response:

{
    result: {
        players: [ { ... }, { ... }, ... ]
    }
}

then we could map that response with this method definition:

@Get("/players")
@Mapping(httpResponse => httpResponse.result.players)
getAllPlayers(): Promise<Array<Player>> { return null; }
  • Handling errors

When an error is thrown during the execution of a gateway-service method, a GatewayServiceError object is created with the following properties and types:

error: string;
message: string;
cause: string;
statusCode: number;
path: string;
stackTrace: Array<string>;
timeStamp: number;

Depending on the error type and its source (client/server) some of the properties will be filled in or left empty. Also, some of the error properties are specially focused on (but not limited to) catching errors coming from Java servers: for example error, message and cause will be filled in when a Java exception is thrown from server side.

GatewayServiceError class provides "getter" methods to access to error properties. Here is an example of error handling:

service.getTeams()
.then((teams: Array<Teams>) => {

    // ... here you are your teams

}).catch((error: GatewayServiceError) => { 
    
    console.log(`
        Your request could not be executed.
        Status code: ${error.getStatusCode()}
        Message: ${error.getMessage()}
    `);
});

WebSocket services

  • WebSocket support

You can also connect to a STOMP broker over WebSocket with gateway-service. Annotate your class with @GatewayServiceWebSocket and you will be able to send and receive messages. As STOMP is used, messages must be of type string (you can use JSON.stringify/JSON.parse to deal with objects).

  • Setting up a WebSocket connection

You can set up a WebSocket connection by using the class level @GatewayServiceWebSocket decorator. This annotation accepts a mandatory parameter (the WebSocket server URI).

@GatewayServiceWebSocket("ws://localhost:8000")
class MyWebSocketService {
}

This decorator also accepts a second optional parameter (a boolean value) that allow you to activate debug. If you need to pass extra parameters as well (like login and password) you can use an optional third parameter, which is an object, that will be included in the connection call.

In the next example we activate debug and pass headers with credentials information.

@GatewayServiceWebSocket("ws://localhost:8000", true, { "login": "fakelogin", "passcode": "fakepass" })
class MyWebSocketService {
}

Once you have your class annotated with @GatewayServiceWebSocket, you can get its methods implemented 'on the fly' by decorating them with one of the provided annotations (see below).

  • Sending messages over WebSocket

To send a message, annotate your method with @SendMessage and add a destination as parameter. Then declare a parameter annotated with @MessageBody and that's it. Remember that gateway-service use STOMP over WebSocket, so messages must be strings.

Example:

@SendMessage("/soccer/news")
sendMessage(@MessageBody body: string): Promise<any> { return null; }

You can then invoke this method to send a message to this particular destination, as shown below:

service.sendMessage('Ohhh, what a goal...!')
  • Receiving messages over WebSocket

To receiving messages, you must to subscribe to a destination and provide a callback. After that, received messages will be delivered to this particular callback. Use @Subscribe and @SubscriptionCallback decorators as shown in the example below.

@Subscribe("/soccer/news")
subscribe(@SubscriptionCallback callback: Function): Promise<any> { return null; }

You can then receive messages sent to /soccer/news simply by doing this:

service.subscribe((message: string) => { console.log(`New message: ${message}`); })
  • Force disconnection

To disconnect from the server you must annotate a method with @DisconnectWebSocket decorator. Then add a parameter to this method and annotate it with @DisconnectionCallback. This parameter points to a callback function that will be called after closing connection.

@DisconnectWebSocket
disconnect(@DisconnectionCallback callback: Function): Promise<any> { return null; }

After declaring a method like this, you can close connection with:

service.disconnect(() => { console.log(`DISCONNECTED`); })
  • Handling Promises

Remember that all WebSockets methods (as we shown earlier for Http methods) always return a Promise. So, if you need to execute an action just after a WebSocket operation has finished, you should use then and catch clauses. You can see an example below.

service.sendMessage('Ohhh, what a goal...!')
.then(() => {

    // actions to be done just after sending message ...

})
.catch((error: GatewayServiceError) => { 
    
    console.log(`
        Your message could not be delivered.
        Message: ${error.getMessage()}
    `);
});
0.5.0

6 years ago

0.1.0

6 years ago

1.0.0

6 years ago