To OVERVIEW

Client Library

Step 2. 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,     callback: (err: any, page: DataPage<BeaconV1>) => void): void;    
  getBeaconById(correlationId: string, beaconId: string, callback: (err: any,     beacon: BeaconV1) => void): void;    
  getBeaconByUdi(correlationId: string, udi: string, callback: (err: any,    beacon: BeaconV1) => void): void;    
  calculatePosition(correlationId: string, siteId: string, udis:     string[], callback: (err: any, position: any) => void): void;    
  createBeacon(correlationId: string, beacon: BeaconV1, callback: (err: any,     beacon: BeaconV1) => void): void;    
  updateBeacon(correlationId: string, beacon: BeaconV1, callback: (err: any,     beacon: BeaconV1) => void): void;    
  deleteBeaconById(correlationId: string, beaconId: string, callback: (err: any,     beacon: BeaconV1) => void): void;            
}

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 '../../../src/data/version1/BeaconV1';
import { IBeaconsClientV1 } from './IBeaconsClientV1';
import { IBeaconsController } from '../../../src/logic/IBeaconsController';

export class BeaconsDirectClientV1 extends DirectClient<IBeaconsController> implements IBeaconsClientV1 {    
  public constructor() {        
    super();        
    this._dependencyResolver.put('controller', new Descriptor('beacons',       'controller', '*', '*', '1.0'));    
  }
  public getBeacons(correlationId: string, filter: FilterParams, paging:     PagingParams, callback:     (err: any, page: DataPage<BeaconV1>) => void): void {        
    let timing = this.instrument(correlationId, 'beacons.get_beacons');        
    this._controller.getBeacons(correlationId, filter, paging, (err, page) => {            
      timing.endTiming();            
      callback(err, page);        
    });    
  }
  public getBeaconById(correlationId: string, beaconId: string, callback: (err: any, beacon: BeaconV1)     => void): void {        
    let timing = this.instrument(correlationId,     'beacons.get_beacon_by_id');        
    this._controller.getBeaconById(correlationId, beaconId, (err, beacon) =>   {            
      timing.endTiming();            
      callback(err, beacon);        
    });     
  }
  public getBeaconByUdi(correlationId: string, udi: string,        callback: (err: any, beacon:     BeaconV1) => void): void {        
    let timing = this.instrument(correlationId,   'beacons.get_beacon_by_udi');        
    this._controller.getBeaconByUdi(correlationId, udi, (err, beacon) => {            
      timing.endTiming();            
      callback(err, beacon);        
    });     
  }
  public calculatePosition(correlationId: string, siteId: string, udis: string[],         
    callback: (err: any, position: any) => void): void {        
    let timing = this.instrument(correlationId, 'beacons.calculate_position');        
    this._controller.calculatePosition(correlationId, siteId, udis, (err, position) => {            
      timing.endTiming();           
      callback(err, position);        
    });     
  }
  public createBeacon(correlationId: string, beacon: BeaconV1,        callback: (err: any, beacon:     BeaconV1) => void): void {        
    let timing = this.instrument(correlationId, 'beacons.create_beacon');        
    this._controller.createBeacon(correlationId, beacon, (err, beacon) => {            
      timing.endTiming();            
      callback(err, beacon);        
    });     
  }
  public updateBeacon(correlationId: string, beacon: BeaconV1,        callback: (err: any, beacon:     BeaconV1) => void): void {        
    let timing = this.instrument(correlationId, 'beacons.update_beacon');        
    this._controller.updateBeacon(correlationId, beacon, (err, beacon) => {           
      timing.endTiming();            
      callback(err, beacon);        
    });    
 }
  public deleteBeaconById(correlationId: string, beaconId: string,        
    callback: (err: any, beacon: BeaconV1) => void): void {        
    let timing = this.instrument(correlationId, 'beacons.delete_beacon_by_id');        
    this._controller.deleteBeaconById(correlationId, beaconId, (err, beacon) => {            
      timing.endTiming();            
      callback(err, beacon);        
    });     
  }
}

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

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 clients/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:

clients/version1/IBeaconsClientV1.go

package clients
import (
bdata "github.com/pip-services-samples/pip-data-microservice-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) (page *bdata.BeaconV1DataPage, err error)
  GetBeaconById(correlationId string, beaconId string) (beacon *bdata.BeaconV1,     err error)
  GetBeaconByUdi(correlationId string, udi string) (beacon *bdata.BeaconV1, err     error)
  CalculatePosition(correlationId string, siteId string, udis []string)     (position *bdata.GeoPointV1, err error)
  CreateBeacon(correlationId string, beacon bdata.BeaconV1) (res     *bdata.BeaconV1, err error)
  UpdateBeacon(correlationId string, beacon bdata.BeaconV1) (res     *bdata.BeaconV1, err error)
  DeleteBeaconById(correlationId string, beaconId string) (beacon    *bdata.BeaconV1, err error)
}

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: 

clients/version1/BeaconsDirectClient.go

package clientsimport (
bdata "github.com/pip-services-samples/pip-data-microservice-go/data/version1"
cdata "github.com/pip-services3-go/pip-services3-commons-go/data"
cref "github.com/pip-services3-go/pip-services3-commons-go/refer"
rpcclient "github.com/pip-services3-go/pip-services3-rpc-go/clients"
)
type BeaconsDirectClientV1 struct {
  rpcclient.DirectClient
  specificController IBeaconsClientV1
}

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

func (c *BeaconsDirectClientV1) SetReferences(references cref.IReferences) {   c.DirectClient.SetReferences(references)
  specificController, ok := c.Controller.(IBeaconsClientV1)
  if !ok {
    panic("BeaconsDirectClientV1: Cant't resolv dependency 'controller' to IBeaconsClientV1")
  }
  c.specificController = specificController
}

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

func (c *BeaconsDirectClientV1) GetBeaconById(correlationId string, beaconId string) (beacon *bdata.BeaconV1, err error) {
  timing := c.Instrument(correlationId, "beacons.get_beacon_by_id")
  res, err := c.specificController.GetBeaconById(correlationId, beaconId) timing.EndTiming()
  return res, err
}

func (c *BeaconsDirectClientV1) GetBeaconByUdi(correlationId string, udi string) (beacon *bdata.BeaconV1, err error) {
  timing := c.Instrument(correlationId, "beacons.get_beacon_by_udi")
  res, err := c.specificController.GetBeaconByUdi(correlationId, udi)
  timing.EndTiming()
  return res, err
}

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

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

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

func (c *BeaconsDirectClientV1) DeleteBeaconById(correlationId string, beaconId string) (beacon *bdata.BeaconV1, err error) {
  timing := c.Instrument(correlationId, "beacons.dee_beacon_by_id")
  beacon, err = c.specificController.DeleteBeaconById(correlationId, beaconId)
  timing.EndTiming()
  return beacon, err
}

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/clients/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.

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 clients/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:

/lib/src/clients/version1/IBeaconsClientV1.dart

import 'dart:async';
import 'package:pip_services3_commons/pip_services3_commons.dart';
import '../../../src/data/version1/BeaconV1.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);
}

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: 

/lib/src/clients/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 '../../../src/data/version1/BeaconV1.dart';
import './IBeaconsClientV1.dart';import '../../../src/logic/IBeaconsController.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;  
  }
}

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/client/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 3 to create the HTTP client.