Since facades are usually the point of entry into a system, they can contain dozens or even hundreds of REST operations. The classic microservices structure, when all the logic is contained in a single service, becomes quite impractical in this case. Furthermore, it’s critical for a facade to support versioning. When the interface is changed, the facade must continue to provide stability for existing clients using interface versioning. Usually around 80% of the logic remains the same when an interface is changed, so duplicating the logic would just increase the amount of code and make it more difficult to support.
To solve these problems, the Pip.Services Toolkit offers a new pattern that breaks up logic into separate operations. The operations can be developed and tested individually, and then integrated into the RESTful controller using unique routes. When implementing a new version, only the operations that require changes need to be rewritten. The remaining operations are simply imported from the old version by being reregistered in the new RESTful controller.
The example facade in this tutorial will contain just 2 sets of operations:
- Operations that work with Beacons
- Operations for managing sessions and users
We’ll be creating a separate file for each set of operations and placing them in the folder operations/version1
Let’s start with the first set of operations - the ones responsible for working with Beacons.
Create a file named BeaconsOperationsV1.py and place the following code inside:
/src/operations/version1/BeaconsOperationsV1.ts
import { ConfigParams } from 'pip-services4-components-node';
import { IReferences } from 'pip-services4-components-node';
import { Descriptor } from 'pip-services4-components-node';
import { DependencyResolver } from 'pip-services4-components-node';
import { RestOperations } from 'pip-services4-http-node';
import { IBeaconsClientV1 } from '../../clients/version1/IBeaconsClientV1';
import { BeaconV1 } from '../../clients/version1/BeaconV1';
export class BeaconsOperationsV1 extends RestOperations {
private _beaconsClient: IBeaconsClientV1;
public constructor() {
super();
this._dependencyResolver.put('beacons', new Descriptor('beacons', 'client', '*', '*', '1.0'));
}
public setReferences(references: IReferences): void {
super.setReferences(references);
this._beaconsClient = this._dependencyResolver.getOneRequired<IBeaconsClientV1>('beacons');
}
public async getBeacons(req: any, res: any): Promise<void> {
let filter = this.getFilterParams(req);
let paging = this.getPagingParams(req);
let siteId = req.params.site_id;
filter.setAsObject('site_id', siteId);
let beacons = await this._beaconsClient.getBeacons(null, filter, paging);
this.sendResult(req, res, beacons);
}
public async getBeacon(req: any, res: any): Promise<void> {
let beaconId = req.params.beacon_id;
let beacon = await this._beaconsClient.getBeaconById(null, beaconId);
this.sendResult(req, res, beacon);
}
public async calculatePosition(req: any, res: any): Promise<void> {
let siteId = req.params.site_id;
let udis = req.param('udis');
if (typeof(udis) == 'string')
udis = udis.split(',');
let position = await this._beaconsClient.calculatePosition(
null, siteId, udis
);
this.sendResult(req, res, position);
}
public async createBeacon(req: any, res: any): Promise<void> {
let beacon = req.body || {};
let result = await this._beaconsClient.createBeacon(
null, beacon
);
this.sendResult(req, res, result);
}
public async updateBeacon(req: any, res: any): Promise<void> {
let beaconId = req.params.beacon_id;
let beacon = req.body || {};
beacon.id = beaconId;
let result = await this._beaconsClient.updateBeacon(
null, beacon
);
this.sendResult(req, res, result);
}
public async deleteBeacon(req: any, res: any): Promise<void> {
let beaconId = req.params.beacon_id;
let result = await this._beaconsClient.deleteBeaconById(
null, beaconId
);
this.sendResult(req, res, result);
}
public async validateBeaconUdi(req: any, res: any): Promise<void> {
let udi = req.param('udi');
let beacon = await this._beaconsClient.getBeaconByUdi(
null, udi
);
if (beacon) res.json(beacon.id);
else res.json('');
}
}
/operations/version1/BeaconsOperationsV1.go
package operations1
import (
"context"
"net/http"
clients1 "github.com/pip-services-samples/client-beacons-go/clients/version1"
services1 "github.com/pip-services-samples/service-beacons-go/data/version1"
cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
httpcontr "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
)
type BeaconsOperationsV1 struct {
*httpcontr.RestOperations
beaconsClient clients1.IBeaconsClientV1
correlationId string
}
func NewBeaconsOperationsV1() *BeaconsOperationsV1 {
c := BeaconsOperationsV1{
RestOperations: httpcontr.NewRestOperations(),
}
c.DependencyResolver.Put("beacons", cref.NewDescriptor("beacons", "client", "*", "*", "1.0"))
c.correlationId = "beacons_operations"
return &c
}
func (c *BeaconsOperationsV1) SetReferences(references cref.IReferences) {
c.RestOperations.SetReferences(context.Background(), references)
dependency, _ := c.DependencyResolver.GetOneRequired("beacons")
client, ok := dependency.(clients1.IBeaconsClientV1)
if !ok {
panic("BeaconsOperationsV1: Cant't resolv dependency 'client' to IBeaconsClientV1")
}
c.beaconsClient = client
}
func (c *BeaconsOperationsV1) GetBeacons(res http.ResponseWriter, req *http.Request) {
var filter = c.GetFilterParams(req)
var paging = c.GetPagingParams(req)
page, err := c.beaconsClient.GetBeacons(
c.correlationId, filter, paging)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, page, nil)
}
}
func (c *BeaconsOperationsV1) GetBeaconById(res http.ResponseWriter, req *http.Request) {
id := c.GetParam(req, "id")
item, err := c.beaconsClient.GetBeaconById(c.correlationId, id)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, item, nil)
}
}
func (c *BeaconsOperationsV1) GetBeaconByUdi(res http.ResponseWriter, req *http.Request) {
udi := c.GetParam(req, "udi")
item, err := c.beaconsClient.GetBeaconByUdi(c.correlationId, udi)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, item, nil)
}
}
func (c *BeaconsOperationsV1) CreateBeacon(res http.ResponseWriter, req *http.Request) {
data := services1.BeaconV1{}
err := c.DecodeBody(req, &data)
if err != nil {
c.SendError(res, req, err)
}
item, err := c.beaconsClient.CreateBeacon(c.correlationId, &data)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, item, nil)
}
}
func (c *BeaconsOperationsV1) UpdateBeacon(res http.ResponseWriter, req *http.Request) {
data := services1.BeaconV1{}
err := c.DecodeBody(req, &data)
if err != nil {
c.SendError(res, req, err)
}
item, err := c.beaconsClient.UpdateBeacon(c.correlationId, &data)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, item, nil)
}
}
func (c *BeaconsOperationsV1) DeleteBeaconById(res http.ResponseWriter, req *http.Request) {
id := c.GetParam(req, "id")
item, err := c.beaconsClient.DeleteBeaconById(c.correlationId, id)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, item, nil)
}
}
func (c *BeaconsOperationsV1) CalculatePosition(res http.ResponseWriter, req *http.Request) {
bodyParams := make(map[string]interface{}, 0)
err := c.DecodeBody(req, &bodyParams)
if err != nil {
c.SendError(res, req, err)
}
udiValues, _ := bodyParams["udis"].([]interface{})
udis := make([]string, 0, 0)
for _, udi := range udiValues {
v, _ := udi.(string)
udis = append(udis, v)
}
siteId, _ := bodyParams["site_id"].(string)
position, err := c.beaconsClient.CalculatePosition(c.correlationId, siteId, udis)
if err != nil {
c.SendError(res, req, err)
} else {
c.SendResult(res, req, position, nil)
}
}
/pip_facades_sample_python/operations/version1/BeaconsOperationsV1.py
# -*- coding: utf-8 -*-
from typing import Optional
import bottle
from pip_services4_commons.convert import JsonConverter, TypeCode
from pip_services4_components.refer import Descriptor, IReferences
from pip_services4_http.controller import RestOperations
from pip_facades_sample_python.clients.version1.BeaconV1 import BeaconV1
from pip_facades_sample_python.clients.version1.IBeaconsClientV1 import IBeaconsClientV1
class BeaconsOperationsV1(RestOperations):
def __init__(self):
super(BeaconsOperationsV1, self).__init__()
self.__beacons_client: IBeaconsClientV1 = None
self._dependency_resolver.put('beacons',
Descriptor('beacons', 'client', '*', '*', '1.0'))
def set_references(self, references: IReferences):
super().set_references(references)
self.__beacons_client = self._dependency_resolver.get_one_required('beacons')
def get_beacons(self, site_id) -> Optional[str]:
filter_params = self._get_filter_params()
paging = self._get_paging_params()
filter_params.set_as_object('site_id', site_id)
result = self.__beacons_client.get_beacons(None, filter_params, paging)
return self._send_result(result)
def get_beacon(self, site_id, beacon_id) -> Optional[str]:
result = self.__beacons_client.get_beacon_by_id(None, beacon_id)
return self._send_result(result)
def calculate_position(self, site_id) -> Optional[str]:
params = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(TypeCode.Map,
bottle.request.json)
udis = params.get('udis')
if isinstance(udis, str):
udis = udis.split(',')
result = self.__beacons_client.calculate_position(None, site_id, udis)
return self._send_result(result)
def create_beacon(self, site_id) -> Optional[str]:
json_data = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(
TypeCode.Map, bottle.request.json)
beacon = BeaconV1(**json_data)
result = self.__beacons_client.create_beacon(None, beacon)
return self._send_result(result)
def update_beacon(self, site_id, beacon_id) -> Optional[str]:
beacon = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(TypeCode.Map,
bottle.request.json)
beacon['id'] = beacon_id
beacon = BeaconV1(**beacon)
result = self.__beacons_client.update_beacon(None, beacon)
return self._send_result(result)
def delete_beacon(self, site_id, beacon_id) -> Optional[str]:
result = self.__beacons_client.delete_beacon_by_id(None, beacon_id)
return self._send_result(result)
def validate_beacon_udi(self, site_id) -> Optional[str]:
params = dict(bottle.request.query.decode())
udi = params.get('udi')
beacon = self.__beacons_client.get_beacon_by_udi(None, udi)
if beacon:
return JsonConverter.to_json(beacon)
else:
return ''
This component’s logic is based on calling the Beacons microservice via any client that implements the IBeaconsClientV1 interface. The component receives a link to the client through its SetReferences method (see the Component References recipe). The component’s business methods mainly just wrap the client’s methods to convert facade’s RESTful requests into calls to the client. Generally speaking, all of these methods perform the same set of steps: extract parameters from the request, call the corresponding method in the Beacons client, receive any results or errors, and send this information back as a response.
In the next (third) Step 4 - Authentication and sessions - we’ll be examining the second set of operations, which manage sessions and authenticate users.