Step 3. Business operations

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 controller, 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 service 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 service.

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-services3-commons-nodex';
import { IReferences } from 'pip-services3-commons-nodex';
import { Descriptor } from 'pip-services3-commons-nodex'; 
import { DependencyResolver } from 'pip-services3-commons-nodex';
import { RestOperations } from 'pip-services3-rpc-nodex';

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('');
    }
    
}

/src/service/operations/version1/BeaconsOperationsV1.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Pip.Services.SampleFacade.Clients.Version1;
using PipServices3.Commons.Convert;
using PipServices3.Commons.Refer;
using PipServices3.Rpc.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Pip.Services.SampleFacade.Operations.Version1
{
	public class BeaconsOperationsV1 : RestOperations
	{
		private IBeaconsClientV1 _beaconsClient;

		public BeaconsOperationsV1()
		{
			_dependencyResolver.Put("beacons", new Descriptor("beacons", "client", "*", "*", "1.0"));
		}

		public new void SetReferences(IReferences references)
		{
			base.SetReferences(references);
			_beaconsClient = _dependencyResolver.GetOneRequired<IBeaconsClientV1>("beacons");
		}

		public async Task GetBeaconsAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user,
			RouteData routeData)
		{
			var filter = GetFilterParams(request);
			var paging = GetPagingParams(request);
			var parameters = GetParameters(request);

			var siteId = parameters.GetAsString("site_id");
			filter.SetAsObject("site_id", siteId);

			var page = await _beaconsClient.GetBeaconsAsync(null, filter, paging);
			await SendResultAsync(response, page);
		}

		public async Task GetBeaconAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData)
		{
			var parameters = GetParameters(request);
			var siteId = parameters.GetAsString("site_id");
			var beaconId = parameters.GetAsString("beacon_id");

			var beacon = await _beaconsClient.GetBeaconByIdAsync(null, beaconId);
			await SendResultAsync(response, beacon);
		}

		public async Task CalculatePositionAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData)
		{
			var parameters = GetParameters(request);
			var siteId = parameters.GetAsString("site_id");
			var udis = parameters.GetAsString("udis")?.Split(',');

			var center = await _beaconsClient.CalculatePositionAsync(null, siteId, udis);
			await SendResultAsync(response, center);
		}

		public async Task CreateBeaconAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData)
		{
			var parameters = GetParameters(request);
			var beacon = JsonConverter.FromJson<BeaconV1>(parameters.RequestBody);

			beacon = await _beaconsClient.CreateBeaconAsync(null, beacon);
			await SendResultAsync(response, beacon);
		}

		public async Task ValidateBeaconUdiAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData)
		{
			var parameters = GetParameters(request);
			var udi = parameters.GetAsString("udi");

			var beacon = await _beaconsClient.GetBeaconByUdiAsync(null, udi);
			await SendResultAsync(response, beacon?.Id ?? "");
		}

		public async Task UpdateBeaconAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData)
		{
			var parameters = GetParameters(request);
			var beacon = JsonConverter.FromJson<BeaconV1>(parameters.RequestBody);

			beacon = await _beaconsClient.UpdateBeaconAsync(null, beacon);
			await SendResultAsync(response, beacon);
		}

		public async Task DeleteBeaconAsync(HttpRequest request, HttpResponse response, ClaimsPrincipal user, RouteData routeData)
		{
			var parameters = GetParameters(request);
			var beaconId = parameters.GetAsString("beacon_id");

			var beacon = await _beaconsClient.DeleteBeaconByIdAsync(null, beaconId);
			await SendResultAsync(response, beacon);
		}
	}
}

/operations/version1/BeaconsOperationsV1.go

package services1

import (
	"net/http"

	operations1 "github.com/pip-services-samples/pip-samples-facade-go/operations/version1"
	cconf "github.com/pip-services3-go/pip-services3-commons-go/config"
	cref "github.com/pip-services3-go/pip-services3-commons-go/refer"
	rpcservices "github.com/pip-services3-go/pip-services3-rpc-go/services"
)

type FacadeServiceV1 struct {
	*rpcservices.RestService
	sessionsOperations *operations1.SessionsOperationsV1
	beaconsOperations  *operations1.BeaconsOperationsV1
}

func NewFacadeServiceV1() *FacadeServiceV1 {
	c := &FacadeServiceV1{
		sessionsOperations: operations1.NewSessionsOperationsV1(),
		beaconsOperations:  operations1.NewBeaconsOperationsV1(),
	}
	c.RestService = rpcservices.InheritRestService(c)
	c.BaseRoute = "api/v1"
	return c
}

func (c *FacadeServiceV1) Configure(config *cconf.ConfigParams) {
	c.RestService.Configure(config)

	c.sessionsOperations.Configure(config)
	c.beaconsOperations.Configure(config)
}

func (c *FacadeServiceV1) SetReferences(references cref.IReferences) {
	c.RestService.SetReferences(references)

	c.sessionsOperations.SetReferences(references)
	c.beaconsOperations.SetReferences(references)
}

func (c *FacadeServiceV1) Register() {
	auth := NewAuthorizerV1()

	// Restore session middleware
	c.RegisterInterceptor("",
		func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
			c.sessionsOperations.LoadSession(res, req, next)
		})

	c.registerContentManagementRoutes(auth)
	c.registerUsersRoutes(auth)
}

func (c *FacadeServiceV1) registerContentManagementRoutes(auth *AuthorizerV1) {
	// Beacons routes
	c.RegisterRouteWithAuth("get", "/beacons", nil, auth.Signed(),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.GetBeacons(res, req) })
	c.RegisterRouteWithAuth("get", "/beacons/{id}", nil, auth.Owner("user_id"),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.GetBeaconById(res, req) })
	c.RegisterRouteWithAuth("get", "/beacons/udi/{udi}", nil, auth.Owner(""),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.GetBeaconByUdi(res, req) })
	c.RegisterRouteWithAuth("post", "/beacons", nil, auth.Signed(),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.CreateBeacon(res, req) })
	c.RegisterRouteWithAuth("put", "/beacons", nil, auth.Signed(),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.UpdateBeacon(res, req) })
	c.RegisterRouteWithAuth("delete", "/beacons/{id}", nil, auth.Signed(),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.DeleteBeaconById(res, req) })
	c.RegisterRouteWithAuth("post", "/beacons/position", nil, auth.Signed(),
		func(res http.ResponseWriter, req *http.Request) { c.beaconsOperations.CalculatePosition(res, req) })
}

func (c *FacadeServiceV1) registerUsersRoutes(auth *AuthorizerV1) {
	// Session Routes
	c.RegisterRouteWithAuth("post", "/users/signup", nil, auth.Anybody(),
		func(res http.ResponseWriter, req *http.Request) { c.sessionsOperations.Signup(res, req) })
	c.RegisterRouteWithAuth("post", "/users/signin", nil, auth.Anybody(),
		func(res http.ResponseWriter, req *http.Request) { c.sessionsOperations.Signin(res, req) })
	c.RegisterRouteWithAuth("post", "/users/signout", nil, auth.Anybody(),
		func(res http.ResponseWriter, req *http.Request) { c.sessionsOperations.Signout(res, req) })
}
Not available

/pip_facades_sample_python/operations/version1/BeaconsOperationsV1.py

# -*- coding: utf-8 -*-

from typing import Optional

import bottle
from pip_services3_commons.convert import JsonConverter, TypeCode
from pip_services3_commons.refer import Descriptor, IReferences
from pip_services3_rpc.services 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 ''

Not available

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.

Step 4. Authentication and sessions