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 operationId
s 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.