Step 4. Designing an HTTP Client

The standard way of communicating with a microservice is via the HTTP protocol. It allows calling microservices that work on a separate server, or in their own container. Our example microservice uses a simplified version of the HTTP protocol that is automatically generated using the Commandable pattern.

Then, creates a new class for the Commandable REST client and an implementation for each of the microservice’s methods. This is done by calling the REST API’s methods using the methods of the parent Commandable REST client, passing the necessary set of parameters, and then processing the response’s result. Since the answer from the client is returned as JSON, some programming languages will require that you first convert it to an instance with a specific type. Be sure to remember this when writing your HTTP clients.

The client’s resulting code is listed below:

/src/version1/BeaconsHttpClientV1.ts

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

import { BeaconV1 } from './BeaconV1';
import { IBeaconsClientV1 } from './IBeaconsClientV1';

export class BeaconsHttpClientV1 extends CommandableHttpClient implements IBeaconsClientV1 {
    public constructor() {
        super('v1/beacons');
    }

    public getBeacons(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<BeaconV1>> {
        return this.callCommand(
            'get_beacons',
            correlationId,
            { filter: filter, paging: paging }
        );
    }

    public getBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1> {
        return this.callCommand(
            'get_beacon_by_id',
            correlationId,
            {
                beacon_id: beaconId
            }
        );
    }

    public getBeaconByUdi(correlationId: string, udi: string): Promise<BeaconV1> {
        return this.callCommand(
            'get_beacon_by_udi',
            correlationId,
            {
                udi: udi
            }
        );
    }

    public calculatePosition(correlationId: string, siteId: string, udis: string[]): Promise<any> {
        return this.callCommand(
            'calculate_position',
            correlationId,
            {
                site_id: siteId,
                udis: udis
            }
        );    
    }

    public createBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1> {
        return this.callCommand(
            'create_beacon',
            correlationId,
            {
                beacon: beacon
            }
        );
    }

    public updateBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1> {
        return this.callCommand(
            'update_beacon',
            correlationId,
            {
                beacon: beacon
            }
        );    
    }

    public deleteBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1> {
        return this.callCommand(
            'delete_beacon_by_id',
            correlationId,
            {
                beacon_id: beaconId
            }
        );
    }
}

/src/version1/BeaconsHttpClientV1.cs

public class BeaconsHttpClientV1 : RestClient, IBeaconsClientV1
{
	public BeaconsHttpClientV1(object config = null)
	{
		_baseRoute = "v1/beacons";

		if (config != null)
			Configure(ConfigParams.FromValue(config));
	}

	public async Task<CenterObjectV1> CalculatePositionAsync(string correlationId, string siteId, string[] udis)
	{
		var methodName = "beacons.calculate_position";
		try
		{
			using (Instrument(correlationId, methodName))
			{
				return await ExecuteAsync<CenterObjectV1>(correlationId, HttpMethod.Post, "/calculate_position", new
				{
					site_id = siteId,
					udis = 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 ExecuteAsync<BeaconV1>(correlationId, HttpMethod.Post, "", new
				{
					beacon = 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 ExecuteAsync<BeaconV1>(correlationId, HttpMethod.Put, "", new
				{
					beacon = 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 ExecuteAsync<BeaconV1>(correlationId, HttpMethod.Delete, $"/{id}", new { });
			}
		}
		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 ExecuteAsync<BeaconV1>(correlationId, HttpMethod.Get, $"/{id}", new { });
			}
		}
		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 ExecuteAsync<BeaconV1>(correlationId, HttpMethod.Get, $"/udi/{udi}", new { });
			}
		}
		catch (Exception ex)
		{
			InstrumentError(correlationId, methodName, ex);
			throw ex;
		}
	}

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


/version1/BeaconsHttpClientV1.ts

package clients1

import (
	"context"

	data1 "github.com/pip-services-samples/service-beacons-gox/data/version1"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	cclients "github.com/pip-services3-gox/pip-services3-rpc-gox/clients"
	clients "github.com/pip-services3-gox/pip-services3-rpc-gox/clients"
)

type BeaconsHttpClientV1 struct {
	*cclients.CommandableHttpClient
}

func NewBeaconsHttpClientV1() *BeaconsHttpClientV1 {
	c := &BeaconsHttpClientV1{
		CommandableHttpClient: cclients.NewCommandableHttpClient("v1/beacons"),
	}
	return c
}

func (c *BeaconsHttpClientV1) GetBeacons(ctx context.Context,
	correlationId string, filter cdata.FilterParams,
	paging cdata.PagingParams) (*cdata.DataPage[data1.BeaconV1], error) {

	params := cdata.NewEmptyStringValueMap()
	c.AddFilterParams(params, &filter)
	c.AddPagingParams(params, &paging)

	response, err := c.CallCommand(ctx, "get_beacons", correlationId, cdata.NewAnyValueMapFromValue(params.Value()))

	if err != nil {
		return cdata.NewEmptyDataPage[data1.BeaconV1](), err
	}

	return clients.HandleHttpResponse[*cdata.DataPage[data1.BeaconV1]](response, correlationId)
}

func (c *BeaconsHttpClientV1) GetBeaconById(ctx context.Context,
	correlationId string, beaconId string) (*data1.BeaconV1, error) {
	params := cdata.NewAnyValueMapFromTuples(
		"beacon_id", beaconId,
	)

	response, err := c.CallCommand(ctx, "get_beacon_by_id", correlationId, params)

	if err != nil {
		return nil, err
	}

	return clients.HandleHttpResponse[*data1.BeaconV1](response, correlationId)
}

func (c *BeaconsHttpClientV1) GetBeaconByUdi(ctx context.Context,
	correlationId string, udi string) (*data1.BeaconV1, error) {
	params := cdata.NewAnyValueMapFromTuples(
		"udi", udi,
	)

	response, err := c.CallCommand(ctx, "get_beacon_by_udi", correlationId, params)
	if err != nil {
		return nil, err
	}

	return clients.HandleHttpResponse[*data1.BeaconV1](response, correlationId)
}

func (c *BeaconsHttpClientV1) CalculatePosition(ctx context.Context,
	correlationId string, siteId string, udis []string) (*data1.GeoPointV1, error) {
	params := cdata.NewAnyValueMapFromTuples(
		"site_id", siteId,
		"udis", udis,
	)

	response, err := c.CallCommand(ctx, "calculate_position", correlationId, params)
	if err != nil {
		return nil, err
	}

	return clients.HandleHttpResponse[*data1.GeoPointV1](response, correlationId)
}

func (c *BeaconsHttpClientV1) CreateBeacon(ctx context.Context,
	correlationId string, beacon data1.BeaconV1) (*data1.BeaconV1, error) {
	params := cdata.NewAnyValueMapFromTuples(
		"beacon", beacon,
	)

	response, err := c.CallCommand(ctx, "create_beacon", correlationId, params)
	if err != nil {
		return nil, err
	}

	return clients.HandleHttpResponse[*data1.BeaconV1](response, correlationId)
}

func (c *BeaconsHttpClientV1) UpdateBeacon(ctx context.Context,
	correlationId string, beacon data1.BeaconV1) (*data1.BeaconV1, error) {
	params := cdata.NewAnyValueMapFromTuples(
		"beacon", beacon,
	)

	response, err := c.CallCommand(ctx, "update_beacon", correlationId, params)
	if err != nil {
		return nil, err
	}

	return clients.HandleHttpResponse[*data1.BeaconV1](response, correlationId)
}

func (c *BeaconsHttpClientV1) DeleteBeaconById(ctx context.Context,
	correlationId string, beaconId string) (*data1.BeaconV1, error) {
	params := cdata.NewAnyValueMapFromTuples(
		"beacon_id", beaconId,
	)

	response, err := c.CallCommand(ctx, "delete_beacon_by_id", correlationId, params)
	if err != nil {
		return nil, err
	}

	return clients.HandleHttpResponse[*data1.BeaconV1](response, correlationId)
}

/lib/version1/BeaconsCommandableHttpClientV1.dart

import 'dart:async';
import 'dart:convert';
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 BeaconsCommandableHttpClientV1 extends CommandableHttpClient
    implements IBeaconsClientV1 {
  BeaconsCommandableHttpClientV1([config]) : super('v1/beacons') {
    if (config != null) {
      configure(ConfigParams.fromValue(config));
    }
  }

  @override
  Future<DataPage<BeaconV1>> getBeacons(
      String correlationId, FilterParams filter, PagingParams paging) async {
    var result = await callCommand(
      'get_beacons',
      correlationId,
      {'filter': filter, 'paging': paging},
    );
    return DataPage<BeaconV1>.fromJson(json.decode(result), (item) {
      var beacon = BeaconV1();
      beacon.fromJson(item);
      return beacon;
    });
  }

  @override
  Future<BeaconV1> getBeaconById(String correlationId, String beaconId) async {
    var result = await callCommand(
        'get_beacon_by_id', correlationId, {'beacon_id': beaconId});
    if (result == null) return null;
    var item = BeaconV1();
    item.fromJson(json.decode(result));
    return item;
  }

  @override
  Future<BeaconV1> getBeaconByUdi(String correlationId, String udi) async {
    var result =
        await callCommand('get_beacon_by_udi', correlationId, {'udi': udi});
    if (result == null) return null;
    var item = BeaconV1();
    item.fromJson(json.decode(result));
    return item;
  }

  @override
  Future<Map<String, dynamic>> calculatePosition(
      String correlationId, String siteId, List<String> udis) async {
    var result = await callCommand(
        'calculate_position', correlationId, {'site_id': siteId, 'udis': udis});
    return json.decode(result);
  }

  @override
  Future<BeaconV1> createBeacon(String correlationId, BeaconV1 beacon) async {
    var result =
        await callCommand('create_beacon', correlationId, {'beacon': beacon});
    if (result == null) return null;
    var item = BeaconV1();
    item.fromJson(json.decode(result));
    return item;
  }

  @override
  Future<BeaconV1> updateBeacon(String correlationId, BeaconV1 beacon) async {
    var result =
        await callCommand('update_beacon', correlationId, {'beacon': beacon});
    if (result == null) return null;
    var item = BeaconV1();
    item.fromJson(json.decode(result));
    return item;
  }

  @override
  Future<BeaconV1> deleteBeaconById(
      String correlationId, String beaconId) async {
    var result = await callCommand(
        'delete_beacon_by_id', correlationId, {'beacon_id': beaconId});
    if (result == null) return null;
    var item = BeaconV1();
    item.fromJson(json.decode(result));
    return item;
  }
}

/src/version1/BeaconsHttpClientV1.py

from typing import Optional, List, Any

from pip_services3_commons.data import DataPage, FilterParams, PagingParams
from pip_services3_rpc.clients import CommandableHttpClient

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


class BeaconsHttpClientV1(CommandableHttpClient, IBeaconsClientV1):
    def __init__(self):
        super(BeaconsHttpClientV1, self).__init__("v1/beacons")

    def get_beacons_by_filter(self, correlation_id: Optional[str], filter: FilterParams,
                              paging: PagingParams) -> DataPage:
        result = self.call_command(
            'get_beacons',
            correlation_id,
            {
                'filter': filter,
                'paging': paging
            }
        )
        return DataPage(result['data'], result['total'])

    def get_beacon_by_id(self, correlation_id: Optional[str], id: str) -> dict:
        return self.call_command(
            'get_beacon_by_id',
            correlation_id,
            {
                'id': id
            }
        )

    def get_beacon_by_udi(self, correlation_id: Optional[str], udi: str) -> dict:
        return self.call_command(
            'get_beacon_by_udi',
            correlation_id,
            {
                'udi': udi
            }
        )

    def calculate_position(self, correlation_id: Optional[str], site_id: str, udis: List[str]) -> Any:
        return self.call_command(
            'calculate_position',
            correlation_id,
            {
                'site_id': site_id,
                'udis': udis
            }
        )

    def create_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> dict:
        return self.call_command(
            'create_beacon',
            correlation_id,
            {
                'beacon': entity
            }
        )

    def update_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> dict:
        return self.call_command(
            'update_beacon',
            correlation_id,
            {
                'beacon': entity
            }
        )

    def delete_beacon_by_id(self, correlation_id: Optional[str], id: str) -> dict:
        return self.call_command(
            'delete_beacon_by_id',
            correlation_id,
            {
                'id': id
            }
        )

Not available

To be sure that our code works as intended, we should perform some functional testing. Test the Commandable HTTP REST client using the class with tests that we developed in the previous step. To do this, create an instance of the HTTP REST 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:

All tests should pass successfully.This finishes the development of our clients. As a result, we ended up with 2 clients: one for working from within a monolithic application, and another for working with the microservice from a different application, when utilizing a distributed architecture.

To simulate the service, let’s create a test client in Step 5. Implementing a Mock Client.

Step 5. Implementing a Mock Client.