Step 3. Designing a Direct Client

Oftentimes systems that are created using a microservices architecture end up being assembled and installed as monoliths. Sometimes this is required as a transitional step, when the operations department isn’t quite yet ready to install and support such a fragmented system. It’s also common for startups, who usually have to deal with limited financial resources, to use this approach. Packing a large amount of microservices into a monolith allows teams to significantly reduce the amount of containers needed to get the system up and running. Such a system can easily be broken up into microservices in the future, when the startup is ready to support an increasing number of clients.

Direct clients are key to creating microservice-based monoliths. A direct client uses direct calls to the microservice’s controller from the shared address space, bypassing external interfaces in the process. On this step, we are going to create such a client. We’ll be placing our code in the src/version1 folder.

First off, let’s define an interface for our clients to implement. This interface should contain a list of all the methods that are provided by our microservice’s API. As a result, we get the following code:

/src/version1/IBeaconClientV1.ts

import { FilterParams } from 'pip-services3-commons-node';
import { PagingParams } from 'pip-services3-commons-node';
import { DataPage } from 'pip-services3-commons-node';
import { BeaconV1 } from '../../../src/data/version1/BeaconV1';
export interface IBeaconsClientV1 {    
  getBeacons(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<BeaconV1>>;  

  getBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1>;

  getBeaconByUdi(correlationId: string, udi: string): Promise<BeaconV1>;  

  calculatePosition(correlationId: string, siteId: string, udis: string[]): Promise<any>;    

  createBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1>;

  updateBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1>;

  deleteBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1>;     
}

/src/version1/IBeaconClientV1.cs

public interface IBeaconsClientV1
{
    Task<DataPage<BeaconV1>> GetBeaconsAsync(string correlationId, FilterParams filter, PagingParams paging);
    Task<BeaconV1> GetBeaconByIdAsync(string correlationId, string id);
    Task<BeaconV1> GetBeaconByUdiAsync(string correlationId, string udi);
    Task<CenterObjectV1> CalculatePositionAsync(string correlationId, string siteId, string[] udis);
    Task<BeaconV1> CreateBeaconAsync(string correlationId, BeaconV1 beacon);
    Task<BeaconV1> UpdateBeaconAsync(string correlationId, BeaconV1 beacon);
    Task<BeaconV1> DeleteBeaconByIdAsync(string correlationId, string id);
}


/version1/IBeaconClientV1.go

package clients1

import (
	data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
	cdata "github.com/pip-services3-go/pip-services3-commons-go/data"
)

type IBeaconsClientV1 interface {
	GetBeacons(correlationId string, filter *cdata.FilterParams,
		paging *cdata.PagingParams) (*data1.BeaconV1DataPage, error)

	GetBeaconById(correlationId string, beaconId string) (*data1.BeaconV1, error)

	GetBeaconByUdi(correlationId string, udi string) (*data1.BeaconV1, error)

	CalculatePosition(correlationId string, siteId string, udis []string) (*data1.GeoPointV1, error)

	CreateBeacon(correlationId string, beacon *data1.BeaconV1) (*data1.BeaconV1, error)

	UpdateBeacon(correlationId string, beacon *data1.BeaconV1) (*data1.BeaconV1, error)

	DeleteBeaconById(correlationId string, beaconId string) (*data1.BeaconV1, error)
}

/lib/version1/IBeaconClientV1.dart

import 'dart:async';
import 'package:pip_services3_commons/pip_services3_commons.dart';

import 'package:pip_services_beacons_dart/pip_services_beacons_dart.dart';

abstract class IBeaconsClientV1 {
  Future<DataPage<BeaconV1>> getBeacons(
      String correlationId, FilterParams filter, PagingParams paging);

  Future<BeaconV1> getBeaconById(String correlationId, String beaconId);

  Future<BeaconV1> getBeaconByUdi(String correlationId, String udi);

  Future<Map<String, dynamic>> calculatePosition(
      String correlationId, String siteId, List<String> udis);

  Future<BeaconV1> createBeacon(String correlationId, BeaconV1 beacon);

  Future<BeaconV1> updateBeacon(
    String correlationId,
    BeaconV1 beacon,
  );

  Future<BeaconV1> deleteBeaconById(String correlationId, String beaconId);
}

/src/version1/IBeaconClientV1.py

from typing import Optional, List, Any

from pip_services3_commons.data import PagingParams, DataPage, FilterParams

from src.data.version1 import BeaconV1


class IBeaconsClientV1:
    def get_beacons_by_filter(self, correlation_id: Optional[str], filter: FilterParams,
                              paging: PagingParams) -> DataPage:
        raise NotImplementedError('Method from interface definition')

    def get_beacon_by_id(self, correlation_id: Optional[str], id: str) -> dict:
        raise NotImplementedError('Method from interface definition')

    def get_beacon_by_udi(self, correlation_id: Optional[str], udi: str) -> dict:
        raise NotImplementedError('Method from interface definition')

    def calculate_position(self, correlation_id: Optional[str], site_id: str, udis: List[str]) -> Any:
        raise NotImplementedError('Method from interface definition')

    def create_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> dict:
        raise NotImplementedError('Method from interface definition')

    def update_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> dict:
        raise NotImplementedError('Method from interface definition')

    def delete_beacon_by_id(self, correlation_id: Optional[str], id: str) -> dict:
        raise NotImplementedError('Method from interface definition')

Not available

Let’s start writing our direct client. This will be a class that implements the interface we defined above, that has our controller set as a dependency in the controller, and that will call the controller’s methods when asked to. To learn more about the referencing and linking mechanisms, be sure to read The Referenceable Recipes. Ultimately, this will just be a wrapper class for the container. The direct client’s code is listed below:

src/version1/BeaconsDirectClientV1.ts

import { FilterParams } from 'pip-services3-commons-node';
import { PagingParams } from 'pip-services3-commons-node';
import { DataPage } from 'pip-services3-commons-node';
import { DirectClient } from 'pip-services3-rpc-node';
import { Descriptor } from 'pip-services3-commons-node';

import { BeaconV1 } from './BeaconV1';
import { IBeaconsClientV1 } from './IBeaconsClientV1';
import { IBeaconsController } from 'pip-data-microservice-node';

export class BeaconsDirectClientV1 extends DirectClient<IBeaconsController> implements IBeaconsClientV1 {
    public constructor() {
        super();
        this._dependencyResolver.put('controller', new Descriptor('beacons', 'controller', '*', '*', '1.0'));
    }

    public async getBeacons(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<BeaconV1>> {
        let timing = this.instrument(correlationId, 'beacons.get_beacons');

        try {
            return await this._controller.getBeacons(correlationId, filter, paging);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        }
    }

    public async getBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1> {
        let timing = this.instrument(correlationId, 'beacons.get_beacon_by_id');

        try {
            return await this._controller.getBeaconById(correlationId, beaconId);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        } 
    }

    public async getBeaconByUdi(correlationId: string, udi: string): Promise<BeaconV1> {
        let timing = this.instrument(correlationId, 'beacons.get_beacon_by_udi');

        try {
            return await this._controller.getBeaconByUdi(correlationId, udi);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        } 
    }

    public async calculatePosition(correlationId: string, siteId: string, udis: string[]): Promise<any> {
        let timing = this.instrument(correlationId, 'beacons.calculate_position');

        try {
            return await this._controller.calculatePosition(correlationId, siteId, udis);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        } 
    }

    public async createBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1> {
        let timing = this.instrument(correlationId, 'beacons.create_beacon');

        try {
            return await this._controller.createBeacon(correlationId, beacon);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        } 
    }

    public async updateBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1> {
        let timing = this.instrument(correlationId, 'beacons.update_beacon');

        try {
            return await this._controller.updateBeacon(correlationId, beacon);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        } 
    }

    public async deleteBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1> {
        let timing = this.instrument(correlationId, 'beacons.delete_beacon_by_id');

        try {
            return await this._controller.deleteBeaconById(correlationId, beaconId);
        } catch (err) {
            timing.endFailure(err);
        } finally {
            timing.endSuccess();
        } 
    }
}

src/version1/BeaconsDirectClientV1.cs

public class BeaconsDirectClientV1 : DirectClient<IBeaconsController>, IBeaconsClientV1
{
    public BeaconsDirectClientV1() : base()
    {
        _dependencyResolver.Put("controller", new Descriptor("beacons", "controller", "*", "*", "1.0"));
    }

    public async Task<DataPage<BeaconV1>> GetBeaconsAsync(
        string correlationId, FilterParams filter, PagingParams paging)
    {
        var methodName = "beacons.get_beacons";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.GetBeaconsAsync(correlationId, filter, paging);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }

    public async Task<BeaconV1> GetBeaconByIdAsync(string correlationId, string id)
    {
        var methodName = "beacons.get_beacon_by_id";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.GetBeaconByIdAsync(correlationId, id);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }

    public async Task<BeaconV1> GetBeaconByUdiAsync(string correlationId, string udi)
    {
        var methodName = "beacons.get_beacon_by_udi";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.GetBeaconByUdiAsync(correlationId, udi);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }

    public async Task<CenterObjectV1> CalculatePositionAsync(string correlationId, string siteId, string[] udis)
    {
        var methodName = "beacons.calculate_position";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.CalculatePositionAsync(correlationId, siteId, udis);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }

    public async Task<BeaconV1> CreateBeaconAsync(string correlationId, BeaconV1 beacon)
    {
        var methodName = "beacons.create_beacon";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.CreateBeaconAsync(correlationId, beacon);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }

    public async Task<BeaconV1> UpdateBeaconAsync(string correlationId, BeaconV1 beacon)
    {
        var methodName = "beacons.update_beacon";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.UpdateBeaconAsync(correlationId, beacon);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }

    public async Task<BeaconV1> DeleteBeaconByIdAsync(string correlationId, string id)
    {
        var methodName = "beacons.delete_beacon_by_id";
        try
        {
            using (Instrument(correlationId, methodName))
            {
                return await _controller.DeleteBeaconByIdAsync(correlationId, id);
            }
        }
        catch (Exception ex)
        {
            InstrumentError(correlationId, methodName, ex);
            throw ex;
        }
    }
}


/version1/BeaconsDirectClientV1.go

package clients1

import (
	data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
	logic "github.com/pip-services-samples/service-beacons-go/logic"
	cdata "github.com/pip-services3-go/pip-services3-commons-go/data"
	cref "github.com/pip-services3-go/pip-services3-commons-go/refer"
	clients "github.com/pip-services3-go/pip-services3-rpc-go/clients"
)

type BeaconsDirectClientV1 struct {
	clients.DirectClient
	controller logic.IBeaconsController
}

func NewBeaconsDirectClientV1() *BeaconsDirectClientV1 {
	c := &BeaconsDirectClientV1{
		DirectClient: *clients.NewDirectClient(),
	}
	c.DependencyResolver.Put("controller", cref.NewDescriptor("beacons", "controller", "*", "*", "1.0"))
	return c
}

func (c *BeaconsDirectClientV1) SetReferences(references cref.IReferences) {
	c.DirectClient.SetReferences(references)

	controller, ok := c.Controller.(logic.IBeaconsController)
	if !ok {
		panic("BeaconsDirectClientV1: Cant't resolv dependency 'controller' to IBeaconsController")
	}
	c.controller = controller
}

func (c *BeaconsDirectClientV1) GetBeacons(
	correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (*data1.BeaconV1DataPage, error) {
	timing := c.Instrument(correlationId, "beacons.get_beacons")
	result, err := c.controller.GetBeacons(correlationId, filter, paging)
	timing.EndTiming(err)
	return result, err
}

func (c *BeaconsDirectClientV1) GetBeaconById(
	correlationId string, beaconId string) (*data1.BeaconV1, error) {
	timing := c.Instrument(correlationId, "beacons.get_beacon_by_id")
	result, err := c.controller.GetBeaconById(correlationId, beaconId)
	timing.EndTiming(err)
	return result, err
}

func (c *BeaconsDirectClientV1) GetBeaconByUdi(
	correlationId string, udi string) (*data1.BeaconV1, error) {
	timing := c.Instrument(correlationId, "beacons.get_beacon_by_udi")
	result, err := c.controller.GetBeaconByUdi(correlationId, udi)
	timing.EndTiming(err)
	return result, err
}

func (c *BeaconsDirectClientV1) CalculatePosition(
	correlationId string, siteId string, udis []string) (*data1.GeoPointV1, error) {
	timing := c.Instrument(correlationId, "beacons.calculate_position")
	result, err := c.controller.CalculatePosition(correlationId, siteId, udis)
	timing.EndTiming(err)
	return result, err
}

func (c *BeaconsDirectClientV1) CreateBeacon(
	correlationId string, beacon *data1.BeaconV1) (*data1.BeaconV1, error) {
	timing := c.Instrument(correlationId, "beacons.create_beacon")
	result, err := c.controller.CreateBeacon(correlationId, beacon)
	timing.EndTiming(err)
	return result, err
}

func (c *BeaconsDirectClientV1) UpdateBeacon(
	correlationId string, beacon *data1.BeaconV1) (*data1.BeaconV1, error) {
	timing := c.Instrument(correlationId, "beacons.update_beacon")
	result, err := c.controller.UpdateBeacon(correlationId, beacon)
	timing.EndTiming(err)
	return result, err
}

func (c *BeaconsDirectClientV1) DeleteBeaconById(
	correlationId string, beaconId string) (*data1.BeaconV1, error) {
	timing := c.Instrument(correlationId, "beacons.delete_beacon_by_id")
	result, err := c.controller.DeleteBeaconById(correlationId, beaconId)
	timing.EndTiming(err)
	return result, err
}

/lib/version1/BeaconsDirectClientV1.dart

import 'dart:async';
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';

import 'package:pip_services_beacons_dart/pip_services_beacons_dart.dart';
import './IBeaconsClientV1.dart';


class BeaconsDirectClientV1 extends DirectClient<IBeaconsController>
    implements IBeaconsClientV1 {
  BeaconsDirectClientV1() : super() {
    dependencyResolver.put(
        'controller', Descriptor('beacons', 'controller', '*', '*', '1.0'));
  }

  @override
  Future<DataPage<BeaconV1>> getBeacons(
      String correlationId, FilterParams filter, PagingParams paging) async {
    var timing = instrument(correlationId, 'beacons.get_beacons');
    var page = await controller.getBeacons(correlationId, filter, paging);
    timing.endTiming();
    return page;
  }

  @override
  Future<BeaconV1> getBeaconById(String correlationId, String beaconId) async {
    var timing = instrument(correlationId, 'beacons.get_beacon_by_id');
    var beacon = await controller.getBeaconById(correlationId, beaconId);
    timing.endTiming();
    return beacon;
  }

  @override
  Future<BeaconV1> getBeaconByUdi(String correlationId, String udi) async {
    var timing = instrument(correlationId, 'beacons.get_beacon_by_udi');
    var beacon = await controller.getBeaconByUdi(correlationId, udi);
    timing.endTiming();
    return beacon;
  }

  @override
  Future<Map<String, dynamic>> calculatePosition(
      String correlationId, String siteId, List<String> udis) async {
    var timing = instrument(correlationId, 'beacons.calculate_position');
    var position =
        await controller.calculatePosition(correlationId, siteId, udis);
    timing.endTiming();
    return position;
  }

  @override
  Future<BeaconV1> createBeacon(String correlationId, BeaconV1 beacon) async {
    var timing = instrument(correlationId, 'beacons.create_beacon');
    var result = await controller.createBeacon(correlationId, beacon);
    timing.endTiming();
    return result;
  }

  @override
  Future<BeaconV1> updateBeacon(String correlationId, BeaconV1 beacon) async {
    var timing = instrument(correlationId, 'beacons.update_beacon');
    var result = await controller.updateBeacon(correlationId, beacon);
    timing.endTiming();
    return result;
  }

  @override
  Future<BeaconV1> deleteBeaconById(
      String correlationId, String beaconId) async {
    var timing = instrument(correlationId, 'beacons.delete_beacon_by_id');
    var beacon = await controller.deleteBeaconById(correlationId, beaconId);
    timing.endTiming();
    return beacon;
  }
}

src/version1/BeaconsDirectClientV1.py

from typing import Optional, List, Any

from pip_services3_commons.data import PagingParams, FilterParams
from pip_services3_commons.refer import Descriptor
from pip_services3_rpc.clients import DirectClient

from .IBeaconsClientV1 import IBeaconsClientV1
from ...data.version1 import BeaconV1


class BeaconsDirectClientV1(DirectClient, IBeaconsClientV1):
    def __init__(self):
        super(BeaconsDirectClientV1, self).__init__()
        self._dependency_resolver.put('controller', Descriptor('beacons', 'controller', '*', '*', '1.0'))

    def get_beacons_by_filter(self, correlation_id: Optional[str], filter: FilterParams, paging: PagingParams) -> dict:
        timing = self._instrument(correlation_id, 'beacons.get_beacons')
        result = self._controller.get_beacons_by_filter(correlation_id, filter, paging)
        timing.end_timing()
        return result

    def get_beacon_by_id(self, correlation_id: Optional[str], id: str) -> dict:
        timing = self._instrument(correlation_id, 'beacons.get_beacon_by_id')
        result = self._controller.get_beacon_by_id(correlation_id, id)
        timing.end_timing()
        return result

    def get_beacon_by_udi(self, correlation_id: Optional[str], udi: str) -> dict:
        timing = self._instrument(correlation_id, 'beacons.get_beacon_by_udi')
        result = self._controller.get_beacon_by_udi(correlation_id, udi)
        timing.end_timing()
        return result

    def calculate_position(self, correlation_id: Optional[str], site_id: str, udis: List[str]) -> Any:
        timing = self._instrument(correlation_id, 'beacons.calculate_position')
        result = self._controller.calculate_position(correlation_id, site_id, udis)
        timing.end_timing()
        return result

    def create_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> dict:
        timing = self._instrument(correlation_id, 'beacons.create_beacon')
        result = self._controller.create_beacon(correlation_id, entity)
        timing.end_timing()
        return result

    def update_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> dict:
        timing = self._instrument(correlation_id, 'beacons.update_beacon')
        result = self._controller.update_beacon(correlation_id, entity)
        timing.end_timing()
        return result

    def delete_beacon_by_id(self, correlation_id: Optional[str], id: str) -> dict:
        timing = self._instrument(correlation_id, 'beacons.delete_beacon_by_id')
        result = self._controller.delete_beacon_by_id(correlation_id, id)
        timing.end_timing()
        return result

Not available

Now that we’re done writing the client, we should test it. To be sure that our code works as intended, we need to perform some functional testing. Let’s start with creating, in a separate class, a set of tests that will be common to all our clients. This will help us simplify the process of testing multiple clients, as well as make sure that they all work the same. We’ll place the code for our tests in the test/version1 folder. The code for this class can be found in the repository.

Now, let’s test the direct client. To do this, create an instance of the direct client and pass it as a parameter to our set of tests. An example implementation of the tests can be found in the example’s repository

Run the tests using the testing methods that are standard for the programming language you are using. All tests should pass successfully.This finishes the development of the Direct client. Move on to Step 4. Designing an HTTP Client.

Step 4. Designing an HTTP Client