Implementing Service Responses

In the previous guide we built a server with services and sub-services. But the sub-services still don't know how to respond to requests. In this guide we will read the weather from an online weather service and build the responses to our temperature and humidity sub-services.

Source Code Structure

Before we begine, it's worth having a quick look at how our program source is layed out. In particular we have two source directories: src and gensrc. When we use the bb command to generate the server it will overwrite the files in the gensrc directory, so you should not change these files, or you will lose your changes. All of our custom code will go in the src directory.

Service Class Generation

The simplest way to start writing our service implementation is to generate a the service class with the following command:

$ bb generate service

Which should give the following output:

Generated service class WeatherServiceImpl

This will produce a new Typescript file WeatherServiceImpl.ts in the src directory. Note that this is intended to be a one-time generation, so if you want to regenerate the service class you will need to first delete the old one.

If you open up our new service class you'll notice that it imports a non-existant class:

import Temperature from './Temperature'

This import corresponds to the datatype we created in the previous guide. We will need to create a class or interface corresponding to this datatype. Fortunately, the bb command can generate this for us based on the schema in our OpenAPI document:

bb generate datatype

Response Implementation

The Blackbox server generated by the bb command uses Inversion Of Control (IOC). This means that we simply concentrate on the logic we need for responding to each request and then give that logic a label via Typescript annotations.

Open up WeatherServiceImpl.ts. The first thing to notice is the class annotation:

@serviceClass('weather-service')

This corresponds to the name of the service we created when building the OpenAPI document and tells the server controller that this is where it can find the implementation of all the weather service operations. The method names match the operationIds from the OpenAPI document and are called whenever the server receives a request for the corresponding HTTP method and path. For example, we can see the method that is called when send a GET request to the /weather path:

getWeatherService(props:any):Service {
    return makeServiceObject(this.oasDoc, 'weather')
}

Let's build our solution and run the server:

$ npm run build && npm run start

> a-weather-api@0.0.1 build /Users/ellipsis/weather
> gulp

[19:06:24] Using gulpfile ~/weather/gulpfile.js
[19:06:24] Starting 'default'...
[19:06:24] Starting 'copyApi'...
[19:06:24] Starting 'build'...
[19:06:24] Finished 'copyApi' after 87 ms
[19:06:27] Finished 'build' after 2.86 s
[19:06:27] Finished 'default' after 2.86 s

> a-weather-api@0.0.1 prestart /Users/ellipsis/weather
> npm install

audited 517 packages in 3.134s
found 0 vulnerabilities


> a-weather-api@0.0.1 start /Users/ellipsis/weather
> node dist/index.js

Your server is listening on port 8080 (http://localhost:8080)
Swagger-ui is available on http://localhost:8080/docs

Then visit http://localhost:8080/weather in your browser and you should see the following:

{
    "description": "Weather service: Provides temperature and humidity information.",
    "bbversion": "0.0.2",
    "links": {
        "self": {
            "href": "/weather"
        },
        "temperature": {
            "href": "/weather/temperature",
            "description": "Returns a temperature object.",
            "types": []
        },
        "humidity": {
            "href": "/weather/humidity",
            "description": "Retrieve a list of number objects.",
            "types": []
        }
    }
}

The above JSON was generated by the getWeatherService method, which simply calls the Blackbopx library method makeServiceObject. Looking at the links node we can see that our weather service has temperature and humidity sub-services.

Let's navigate to the temperature service at http://localhost:8080/weather/temperature. We will get an error. If you look in the WeatherServiceImpl class and find the getTemperatureList method you will see where the error is thrown:

getTemperatureList(props:any):Temperature[] {
    throw new Error(`getTemperatureList not yet implemented. Called with props=${JSON.stringify(props)}`)
}

We will now replace the throw statement with our temperature service code. To get our weather data we will use the openweather-apis npm module. So first we'll need to install it:

npm i openweather-apis

Then add the import to the top of our WeatherServiceImpl.js file:

var weather = require('openweather-apis');

The openweather-api module requires us to use a callback function. Fortunately the Blackbox controller can receive a Promise response from our service method. So the first thing we will do is change the return type of our method to a Promise:

getTemperatureList(props:any):Promise<Temperature[]> {

Then we can add the method body to call the weather api and return the temperature response:

getTemperatureList(props:any):Promise {
    weather.setCity('Hobart')
    weather.setAPPID('xxx') // your API key from https://home.openweathermap.org/api_keys
    return new Promise((resolve, reject) => {
        weather.getTemperature((err: any, temp: any) => {
            if(err) {
                reject(err)
            } else {
                resolve([{
                    temp: temp,
                    scale: 'celcius'
                }])
            }
        })
    })
}

Rebuild and restart the server, then reload http://localhost:8080/weather/temperature. You should get the following response (with Hobart's current temperature):

[
    {
        "temp": 14.35,
        "scale": "celcius"
    }
]

Note that we are using an array here. The get response from a service should always be a list of all instances available to the called (it's best to allow for paging of results, but that is out of scope for this guide). In our case we are only returning the temperature for Hobart though, so it is an array of one dimension.

We can implement the getHumidityList operation in the same way. List is left as an exercise for you if you wish.

In the next guide we will see how to build a service that allows us to post data.