The Pip.Services Toolkit has a dedicated component in the RPC module for processing external requests. To make use of this service, create a new class BeaconsHttpServiceV1
, extending the CommandableHttpService
class:
/src/controller/version1/BeaconsHttpcontrollerV1.ts
import { CommandableHttpController } from 'pip-services4-http-node';
import { Descriptor } from 'pip-services4-components-node';
export class BeaconsHttpControllerV1 extends CommandableHttpController {
public constructor() {
super('v1/beacons');
this._dependencyResolver.put('service', new Descriptor('beacons', 'service', '*', '*', '1.0'));
}
}
/service/version1/BeaconsHttpControllerV1.go
package services1
import (
"context"
cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
cservices "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
)
type BeaconsHttpControllerV1 struct {
cservices.CommandableHttpController
}
func NewBeaconsHttpControllerV1() *BeaconsHttpControllerV1 {
c := &BeaconsHttpControllerV1{}
c.CommandableHttpController = *cservices.InheritCommandableHttpController(c, "v1/beacons")
c.DependencyResolver.Put(context.Background(), "service", cref.NewDescriptor("beacons", "service", "*", "*", "1.0"))
return c
}
/src/service/version1/BeaconsHttpServicesV1.py
from pip_services4_components.refer import Descriptor
from pip_services4_http.controller import CommandableHttpController
class BeaconsHttpControllerV1(CommandableHttpController):
def __init__(self):
super(BeaconsHttpControllerV1, self).__init__("v1/beacons")
self._dependency_resolver.put("service", Descriptor('beacons', 'service', '*', '*', '1.0'))
The CommandableHttpService
class from the pip-services3-rpc module implements all of the basic functionality needed by the service, right out of the box. All that we need to do on our side is configure it in the child class. This is done by defining a base route to the API (e.g. ‘v1/beacons’) and by setting references to the service. The rest is taken care of by the parent class and the process container: a service will be searched for and referenced, after which the controller will receive a set of commands, register it, and make those commands available through the API interface. This allows us to run commands by simply posting requests to a URL of the following format:
http://{ip}:{port}/v1/beacons/{command_name}
Even though the BeaconsHttpServiceV1
class barely has any lines of code, there’s a large amount of code being executed in the service itself. To make sure that everything is working as it should, we should add tests for the service itself, as well as for the commands we wrote in the CommandSet. Create a file for the service’s test and paste the following code:
/test/controller/version1/BeaconsHttpControllerV1.test.ts
const assert = require('chai').assert;
import { DataPage } from 'pip-services4-data-node';
import { ConfigParams } from 'pip-services4-components-node';
import { Descriptor } from 'pip-services4-components-node';
import { References } from 'pip-services4-components-node';
import { FilterParams } from 'pip-services4-data-node';
import { PagingParams } from 'pip-services4-data-node';
import { TestCommandableHttpClient } from 'pip-services4-http-node';
import { BeaconV1 } from '../../../src/data/version1/BeaconV1';
import { BeaconTypeV1 } from '../../../src/data/version1/BeaconTypeV1';
import { BeaconsMemoryPersistence } from '../../../src/persistence/BeaconsMemoryPersistence';
import { BeaconsService } from '../../../src/service/BeaconsService';
import { BeaconsHttpControllerV1 } from '../../../src/controller/version1/BeaconsHttpControllerV1';
const BEACON1: BeaconV1 = {
id: '1',
udi: '00001',
type: BeaconTypeV1.AltBeacon,
site_id: '1',
label: 'TestBeacon1',
center: { type: 'Point', coordinates: [ 0, 0 ] },
radius: 50
};
const BEACON2: BeaconV1 = {
id: '2',
udi: '00002',
type: BeaconTypeV1.iBeacon,
site_id: '1',
label: 'TestBeacon2',
center: { type: 'Point', coordinates: [ 2, 2 ] },
radius: 70
};
suite('BeaconsHttpControllerV1', () => {
let persistence: BeaconsMemoryPersistence;
let service: BeaconsService;
let controller: BeaconsHttpControllerV1;
let client: TestCommandableHttpClient;
setup(async () => {
let restConfig = ConfigParams.fromTuples(
'connection.protocol', 'http',
'connection.port', 3000,
'connection.host', 'localhost'
);
persistence = new BeaconsMemoryPersistence();
persistence.configure(new ConfigParams());
controller = new BeaconsService();
controller.configure(new ConfigParams());
service = new BeaconsHttpControllerV1();
service.configure(restConfig);
client = new TestCommandableHttpClient('v1/beacons')
client.configure(restConfig);
let references = References.fromTuples(
new Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'), persistence,
new Descriptor('beacons', 'controller', 'default', 'default', '1.0'), controller,
new Descriptor('beacons', 'service', 'http', 'default', '1.0'), service
);
controller.setReferences(references);
service.setReferences(references);
await persistence.open(null);
await service.open(null);
await client.open(null);
});
teardown(async () => {
await client.close(null);
await service.close(null);
await persistence.close(null);
});
test('CRUD Operations', async () => {
let beacon1: BeaconV1;
// Create the first beacon
let beacon = await client.callCommand<BeaconV1>(
'create_beacon',
null,
{
beacon: BEACON1
}
);
assert.isObject(beacon);
assert.equal(BEACON1.udi, beacon.udi);
assert.equal(BEACON1.site_id, beacon.site_id);
assert.equal(BEACON1.type, beacon.type);
assert.equal(BEACON1.label, beacon.label);
assert.isNotNull(beacon.center);
// Create the second beacon
beacon = await client.callCommand<BeaconV1>(
'create_beacon',
null,
{
beacon: BEACON2
}
);
assert.isObject(beacon);
assert.equal(BEACON2.udi, beacon.udi);
assert.equal(BEACON2.site_id, beacon.site_id);
assert.equal(BEACON2.type, beacon.type);
assert.equal(BEACON2.label, beacon.label);
assert.isNotNull(beacon.center);
// Get all beacons
let page = await client.callCommand<DataPage<BeaconV1>>(
'get_beacons',
null,
{
filter: new FilterParams(),
paging: new PagingParams()
}
);
assert.isObject(page);
assert.lengthOf(page.data, 2);
beacon1 = page.data[0];
// Update the beacon
beacon1.label = 'ABC';
beacon = await client.callCommand(
'update_beacon',
null,
{
beacon: beacon1
}
);
assert.isObject(beacon);
assert.equal(beacon1.id, beacon.id);
assert.equal('ABC', beacon.label);
// Get beacon by udi
beacon = await client.callCommand(
'get_beacon_by_udi',
null,
{
udi: beacon1.udi
}
);
assert.isObject(beacon);
assert.equal(beacon1.id, beacon.id);
// Calculate position for one beacon
let position = await client.callCommand<any>(
'calculate_position',
null,
{
site_id: '1',
udis: ['00001']
}
);
assert.isObject(position);
assert.equal('Point', position.type);
assert.lengthOf(position.coordinates, 2);
assert.equal(0, position.coordinates[0]);
assert.equal(0, position.coordinates[1]);
// Delete the beacon
beacon = await client.callCommand(
'delete_beacon_by_id',
null,
{
beacon_id: beacon1.id
}
);
assert.isObject(beacon);
assert.equal(beacon1.id, beacon.id);
// Try to get deleted beacon
beacon = await client.callCommand(
'get_beacon_by_id',
null,
{
beacon_id: beacon1.id
}
);
assert.isNull(beacon || null);
});
});
/test/services/version1/BeaconsHttpControllerV1_test.go
package test_services1
import (
"context"
"testing"
cclients "github.com/pip-services4/pip-services4-go/pip-services4-http-go/clients"
controllers1 "github.com/pip-services-samples/service-beacons-go/controllers/version1"
data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
persist "github.com/pip-services-samples/service-beacons-go/persistence"
logic "github.com/pip-services-samples/service-beacons-go/service"
cdata "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/data"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
tclients "github.com/pip-services4/pip-services4-go/pip-services4-http-go/test"
"github.com/stretchr/testify/assert"
)
type beaconsHttpControllerV1Test struct {
BEACON1 *data1.BeaconV1
BEACON2 *data1.BeaconV1
persistence *persist.BeaconsMemoryPersistence
service *logic.BeaconsService
controller *controllers1.BeaconsHttpControllerV1
client *tclients.TestCommandableHttpClient
}
func newBeaconsHttpControllerV1Test() *beaconsHttpControllerV1Test {
BEACON1 := &data1.BeaconV1{
Id: "1",
Udi: "00001",
Type: data1.AltBeacon,
SiteId: "1",
Label: "TestBeacon1",
Center: data1.GeoPointV1{Type: "Point", Coordinates: []float32{0.0, 0.0}},
Radius: 50,
}
BEACON2 := &data1.BeaconV1{
Id: "2",
Udi: "00002",
Type: data1.IBeacon,
SiteId: "1",
Label: "TestBeacon2",
Center: data1.GeoPointV1{Type: "Point", Coordinates: []float32{2.0, 2.0}},
Radius: 70,
}
restConfig := cconf.NewConfigParamsFromTuples(
"connection.protocol", "http",
"connection.port", "3000",
"connection.host", "localhost",
)
persistence := persist.NewBeaconsMemoryPersistence()
persistence.Configure(context.Background(), cconf.NewEmptyConfigParams())
service := logic.NewBeaconsService()
service.Configure(context.Background(), cconf.NewEmptyConfigParams())
controller := controllers1.NewBeaconsHttpControllerV1()
controller.Configure(context.Background(), restConfig)
client := tclients.NewTestCommandableHttpClient("v1/beacons")
client.Configure(context.Background(), restConfig)
references := cref.NewReferencesFromTuples(
context.Background(),
cref.NewDescriptor("beacons", "persistence", "memory", "default", "1.0"), persistence,
cref.NewDescriptor("beacons", "service", "default", "default", "1.0"), service,
cref.NewDescriptor("beacons", "controller", "http", "default", "1.0"), controller,
cref.NewDescriptor("beacons", "client", "http", "default", "1.0"), client,
)
service.SetReferences(context.Background(), references)
controller.SetReferences(context.Background(), references)
return &beaconsHttpControllerV1Test{
BEACON1: BEACON1,
BEACON2: BEACON2,
persistence: persistence,
controller: controller,
service: service,
client: client,
}
}
func (c *beaconsHttpControllerV1Test) setup(t *testing.T) {
err := c.persistence.Open(context.Background())
if err != nil {
t.Error("Failed to open persistence", err)
}
err = c.controller.Open(context.Background())
if err != nil {
t.Error("Failed to open service", err)
}
err = c.client.Open(context.Background())
if err != nil {
t.Error("Failed to open client", err)
}
err = c.persistence.Clear(context.Background())
if err != nil {
t.Error("Failed to clear persistence", err)
}
}
func (c *beaconsHttpControllerV1Test) teardown(t *testing.T) {
err := c.client.Close(context.Background())
if err != nil {
t.Error("Failed to close client", err)
}
err = c.controller.Close(context.Background())
if err != nil {
t.Error("Failed to close service", err)
}
err = c.persistence.Close(context.Background())
if err != nil {
t.Error("Failed to close persistence", err)
}
}
func (c *beaconsHttpControllerV1Test) testCrudOperations(t *testing.T) {
var beacon1 data1.BeaconV1
// Create the first beacon
params := cdata.NewAnyValueMapFromTuples(
"beacon", c.BEACON1.Clone(),
)
response, err := c.client.CallCommand(context.Background(), "create_beacon", params)
assert.Nil(t, err)
assert.NotNil(t, response)
beacon, err := cclients.HandleHttpResponse[data1.BeaconV1](response, "")
assert.Nil(t, err)
assert.NotEqual(t, data1.BeaconV1{}, beacon)
assert.Equal(t, c.BEACON1.Udi, beacon.Udi)
assert.Equal(t, c.BEACON1.SiteId, beacon.SiteId)
assert.Equal(t, c.BEACON1.Type, beacon.Type)
assert.Equal(t, c.BEACON1.Label, beacon.Label)
assert.NotNil(t, beacon.Center)
// Create the second beacon
params = cdata.NewAnyValueMapFromTuples(
"beacon", c.BEACON2.Clone(),
)
response, err = c.client.CallCommand(context.Background(), "create_beacon", params)
assert.Nil(t, err)
assert.NotNil(t, response)
beacon, err = cclients.HandleHttpResponse[data1.BeaconV1](response, "")
assert.Nil(t, err)
assert.NotEqual(t, data1.BeaconV1{}, beacon)
assert.Equal(t, c.BEACON2.Udi, beacon.Udi)
assert.Equal(t, c.BEACON2.SiteId, beacon.SiteId)
assert.Equal(t, c.BEACON2.Type, beacon.Type)
assert.Equal(t, c.BEACON2.Label, beacon.Label)
assert.NotNil(t, beacon.Center)
// Get all beacons
params = cdata.NewAnyValueMapFromTuples(
"filter", cquery.NewEmptyFilterParams(),
"paging", cquery.NewEmptyFilterParams(),
)
response, err = c.client.CallCommand(context.Background(), "get_beacons", params)
assert.Nil(t, err)
assert.NotNil(t, response)
page, err := cclients.HandleHttpResponse[cquery.DataPage[data1.BeaconV1]](response, "")
assert.Nil(t, err)
assert.True(t, page.HasData())
assert.Len(t, page.Data, 2)
beacon1 = page.Data[0].Clone()
// Update the beacon
beacon1.Label = "ABC"
params = cdata.NewAnyValueMapFromTuples(
"beacon", beacon1,
)
response, err = c.client.CallCommand(context.Background(), "update_beacon", params)
assert.Nil(t, err)
assert.NotNil(t, response)
beacon, err = cclients.HandleHttpResponse[data1.BeaconV1](response, "")
assert.Nil(t, err)
assert.NotEqual(t, data1.BeaconV1{}, beacon)
assert.Equal(t, c.BEACON1.Id, beacon.Id)
assert.Equal(t, "ABC", beacon.Label)
// Get beacon by udi
params = cdata.NewAnyValueMapFromTuples(
"udi", beacon1.Udi,
)
response, err = c.client.CallCommand(context.Background(), "get_beacon_by_udi", params)
assert.Nil(t, err)
assert.NotNil(t, response)
beacon, err = cclients.HandleHttpResponse[data1.BeaconV1](response, "")
assert.Nil(t, err)
assert.NotEqual(t, data1.BeaconV1{}, beacon)
assert.Equal(t, c.BEACON1.Id, beacon.Id)
// Calculate position for one beacon
params = cdata.NewAnyValueMapFromTuples(
"site_id", "1",
"udis", []string{"00001"},
)
response, err = c.client.CallCommand(context.Background(), "calculate_position", params)
assert.Nil(t, err)
assert.NotNil(t, response)
position, err := cclients.HandleHttpResponse[data1.GeoPointV1](response, "")
assert.Nil(t, err)
assert.NotEqual(t, data1.GeoPointV1{}, position)
assert.Equal(t, "Point", position.Type)
assert.Equal(t, (float32)(0.0), position.Coordinates[0])
assert.Equal(t, (float32)(0.0), position.Coordinates[1])
// Delete the beacon
params = cdata.NewAnyValueMapFromTuples(
"beacon_id", beacon1.Id,
)
response, err = c.client.CallCommand(context.Background(), "delete_beacon_by_id", params)
assert.Nil(t, err)
beacon, err = cclients.HandleHttpResponse[data1.BeaconV1](response, "")
assert.Nil(t, err)
assert.NotNil(t, response)
assert.NotEqual(t, data1.BeaconV1{}, beacon)
assert.Equal(t, c.BEACON1.Id, beacon.Id)
// Try to get deleted beacon
params = cdata.NewAnyValueMapFromTuples(
"beacon_id", beacon1.Id,
)
response, err = c.client.CallCommand(context.Background(), "get_beacon_by_id", params)
assert.Nil(t, err)
assert.NotNil(t, response)
beacon, err = cclients.HandleHttpResponse[data1.BeaconV1](response, "")
assert.Nil(t, err)
assert.Equal(t, data1.BeaconV1{}, beacon)
}
func TestBeaconsCommmandableHttpServiceV1(t *testing.T) {
c := newBeaconsHttpControllerV1Test()
c.setup(t)
t.Run("CRUD Operations", c.testCrudOperations)
c.teardown(t)
}
/test/services/version1/test_BeaconsHttpServiceV1.py
import json
import time
from json import JSONDecodeError
from typing import Union
import requests
from pip_services4_components.config import ConfigParams
from pip_services4_components.refer import References, Descriptor
from pip_services4_commons.reflect import PropertyReflector
from pip_services4_components.exec import Parameters
from src.data.version1 import BeaconV1, BeaconTypeV1
from src.logic.BeaconsService import BeaconsService
from src.persistence.BeaconsMemoryPersistence import BeaconsMemoryPersistence
from src.controllers.version1.BeaconsHttpServiceV1 import BeaconsHttpControllerV1
BEACON1 = BeaconV1("1", "1", BeaconTypeV1.AltBeacon, "00001", "TestBeacon1", {"type": 'Point', "coordinates": [0, 0]},
50.0)
BEACON2 = BeaconV1("2", "1", BeaconTypeV1.iBeacon, "00002", "TestBeacon2", {"type": 'Point', "coordinates": [2, 2]},
70.0)
BEACON3 = BeaconV1("3", "2", BeaconTypeV1.AltBeacon, "00003", "TestBeacon3", {"type": 'Point', "coordinates": [10, 10]},
50.0)
class TestBeaconsHttpControllerV1:
_persistence: BeaconsMemoryPersistence
_controller: BeaconsService
_service: BeaconsHttpControllerV1
@classmethod
def setup_class(cls):
cls._persistence = BeaconsMemoryPersistence()
cls._controller = BeaconsController()
cls._service = BeaconsHttpServiceV1()
cls._service.configure(ConfigParams.from_tuples(
'connection.protocol', 'http',
'connection.port', 3002,
'connection.host', 'localhost'))
references = References.from_tuples(Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'),
cls._persistence,
Descriptor('beacons', 'service', 'default', 'default', '1.0'),
cls._service,
Descriptor('beacons', 'controller', 'http', 'default', '1.0'),
cls._controller)
cls._controller.set_references(references)
cls._service.set_references(references)
cls._persistence.open(None)
cls._controller.open(None)
@classmethod
def teardown_class(cls):
cls._persistence.close(None)
cls._service.close(None)
def test_crud_operations(self):
time.sleep(2)
# Create the first beacon
beacon1 = self.invoke("/v1/beacons/create_beacon",
Parameters.from_tuples("beacon", PropertyReflector.get_properties(BEACON1)))
assert beacon1 is not None
assert beacon1['id'] == BEACON1.id
assert beacon1['site_id'] == BEACON1.site_id
assert beacon1['udi'] == BEACON1.udi
assert beacon1['type'] == BEACON1.type
assert beacon1['label'] == BEACON1.label
assert beacon1['center'] is not None
# Create the second beacon
beacon2 = self.invoke("/v1/beacons/create_beacon",
Parameters.from_tuples("beacon", PropertyReflector.get_properties(BEACON2)))
assert beacon2 is not None
assert beacon2['id'] == BEACON2.id
assert beacon2['site_id'] == BEACON2.site_id
assert beacon2['udi'] == BEACON2.udi
assert beacon2['type'] == BEACON2.type
assert beacon2['label'] == BEACON2.label
assert beacon2['center'] is not None
# Get all beacons
page = self.invoke("/v1/beacons/get_beacons", Parameters.from_tuples("beacons"))
assert page is not None
assert len(page['data']) == 2
beacon1 = page['data'][0]
# Update the beacon
beacon1['label'] = "ABC"
beacon = self.invoke("/v1/beacons/update_beacon", Parameters.from_tuples("beacon", beacon1))
assert beacon is not None
assert beacon1['id'] == beacon['id']
assert "ABC" == beacon['label']
# Get beacon by udi
beacon = self.invoke("/v1/beacons/get_beacon_by_udi", Parameters.from_tuples("udi", beacon1['udi']))
assert beacon is not None
assert beacon['id'] == beacon1['id']
# Calculate position for one beacon
position = self.invoke("/v1/beacons/calculate_position",
Parameters.from_tuples("site_id", '1', "udis", ['00001']))
assert position is not None
assert "Point" == position["type"]
assert 2 == len(position["coordinates"])
assert 0 == position["coordinates"][0]
assert 0 == position["coordinates"][1]
# Delete beacon
self.invoke("/v1/beacons/delete_beacon_by_id", Parameters.from_tuples("id", beacon1['id']))
# Try to get deleted beacon
beacon = self.invoke("/v1/beacons/get_beacon_by_id", Parameters.from_tuples("id", beacon1['id']))
assert beacon is False
def invoke(self, route, entity) -> Union[bool, dict]:
params = {}
route = "http://localhost:3002" + route
response = None
timeout = 10000
# Call the service
data = json.dumps(entity)
try:
response = requests.request('POST', route, params=params, json=data, timeout=timeout)
return response.json()
except JSONDecodeError:
if response.status_code == 404:
return False
Congratulations! This step finishes off the development of our microservice! However, before we can start our service up as a fully fledged microservice, we’ll first need to compose all of its components using a process container. And that’s exactly what we’ll be doing in Step 7. Wrapping microservice into container.