Step 5. Implementing a controller

Now that we know a bit about how we are going to be storing data and how microservice configuration works, it’s time to add some logic to our service. Our microservice needs to be able to calculate a device’s position based on the beacons it “sees”, as well as initiate CRUD operations for the data it handles. Let’s create a logic folder under the src directory and start by defining an interface:

/src/logic/IBeaconsController.ts

import { FilterParams } from 'pip-services3-commons-nodex';
import { PagingParams } from 'pip-services3-commons-nodex';
import { DataPage } from 'pip-services3-commons-nodex';

import { BeaconV1 } from '../../src/data/version1/BeaconV1';

export interface IBeaconsController {
    getBeacons(correlationId: string, filter: FilterParams,
        paging: PagingParams): Promise<DataPage<BeaconV1>>;

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

    getBeaconByUdi(correlationId: string, beaconId: 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/interface/logic/IBeaconsController.cs

namespace Beacons.Logic
{
    public interface IBeaconsController
    {
        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);
    }
}

/logic/IBeaconsController.go

package logic

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

type IBeaconsController interface {
	GetBeacons(ctx context.Context, correlationId string, filter cdata.FilterParams, paging cdata.PagingParams) (cdata.DataPage[data1.BeaconV1], error)

	GetBeaconById(ctx context.Context, correlationId string, beaconId string) (data1.BeaconV1, error)

	GetBeaconByUdi(ctx context.Context, correlationId string, beaconId string) (data1.BeaconV1, error)

	CalculatePosition(ctx context.Context, correlationId string, siteId string, udis []string) (data1.GeoPointV1, error)

	CreateBeacon(ctx context.Context, correlationId string, beacon data1.BeaconV1) (data1.BeaconV1, error)

	UpdateBeacon(ctx context.Context, correlationId string, beacon data1.BeaconV1) (data1.BeaconV1, error)

	DeleteBeaconById(ctx context.Context, correlationId string, beaconId string) (data1.BeaconV1, error)
}

/lib/logic/IBeaconsController.dart

import 'dart:async';
import 'package:pip_services3_commons/pip_services3_commons.dart';
import '../../src/data/version1/BeaconV1.dart';

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

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

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

  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/logic/IBeaconsController.py

from typing import Any, List, Optional

from pip_services3_commons.data import PagingParams, FilterParams, DataPage

from src.data.version1 import BeaconV1


class IBeaconsController:
    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) -> BeaconV1:
        raise NotImplementedError('Method from interface definition')

    def get_beacon_by_udi(self, correlation_id: Optional[str], udi: str) -> BeaconV1:
        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) -> BeaconV1:
        raise NotImplementedError('Method from interface definition')

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

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


Not available

Once our interface is ready, we can move on to implementing the actual controller. Its code is also going to be quite simple, as all we need to write is one method for calculating a device’s position, and the other methods will just be wrappers for the methods we wrote in our persistence components.

/src/logic/BeaconsController.ts

import { FilterParams } from 'pip-services3-commons-nodex';
import { PagingParams } from 'pip-services3-commons-nodex';
import { DataPage } from 'pip-services3-commons-nodex';
import { ConfigParams } from 'pip-services3-commons-nodex';
import { IConfigurable } from 'pip-services3-commons-nodex';
import { Descriptor } from 'pip-services3-commons-nodex';
import { IReferences } from 'pip-services3-commons-nodex';
import { IReferenceable } from 'pip-services3-commons-nodex';
import { IdGenerator } from 'pip-services3-commons-nodex';
import { CommandSet } from 'pip-services3-commons-nodex';
import { ICommandable } from 'pip-services3-commons-nodex';

import { BeaconV1 } from '../../src/data/version1/BeaconV1';
import { IBeaconsPersistence } from '../../src/persistence/IBeaconsPersistence';
import { IBeaconsController } from './IBeaconsController';
import { BeaconTypeV1 } from '../../src/data/version1/BeaconTypeV1';
import { BeaconsCommandSet } from './BeaconsCommandSet';

export class BeaconsController implements IBeaconsController, IConfigurable, IReferenceable, ICommandable {
    private _persistence: IBeaconsPersistence;
    private _commandSet: BeaconsCommandSet;

    public constructor() { }

    public configure(config: ConfigParams): void {
    }

    public setReferences(references: IReferences): void {
        this._persistence = references.getOneRequired<IBeaconsPersistence>(
            new Descriptor('beacons', 'persistence', '*', '*', '1.0')
        );
    }

    public getCommandSet(): CommandSet {
        if (this._commandSet == null) {
            this._commandSet = new BeaconsCommandSet(this);
        }

        return this._commandSet;
    }

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

    public getBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1> {
        return this._persistence.getOneById(correlationId, beaconId);
    }

    public getBeaconByUdi(correlationId: string, beaconId: string): Promise<BeaconV1> {
        return this._persistence.getOneByUdi(correlationId, beaconId);
    }

    public async calculatePosition(correlationId: string, siteId: string, udis: string[]): Promise<any> {
        if (udis == null || udis.length == 0) {
            return null;
        }

        let page = await this._persistence.getPageByFilter(
            correlationId,
            FilterParams.fromTuples(
                'site_id', siteId,
                'udis', udis
            ),
            null
        );
        let beacons = page.data || [];

        let lat = 0;
        let lng = 0;
        let count = 0;
        for (let beacon of beacons) {
            if (beacon.center != null 
                && beacon.center.type == 'Point'
                && Array.isArray(beacon.center.coordinates)) {
                    lng += beacon.center.coordinates[0];
                    lat += beacon.center.coordinates[1];
                    count += 1;
                }
        }

        if (count == 0) {
            return null;
        }

        let position = {
            type: 'Point',
            coordinates: [lng / count, lat / count]
        }
        return position;
    }

    public createBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1> {
        beacon.id = beacon.id || IdGenerator.nextLong();
        beacon.type = beacon.type || BeaconTypeV1.Unknown;

        return this._persistence.create(correlationId, beacon);
    }

    public updateBeacon(correlationId: string, beacon: BeaconV1): Promise<BeaconV1> {
        beacon.type = beacon.type || BeaconTypeV1.Unknown;
        return this._persistence.update(correlationId, beacon);
    }

    public deleteBeaconById(correlationId: string, beaconId: string): Promise<BeaconV1> {
        return this._persistence.deleteById(correlationId, beaconId);
    }

}

/src/interface/logic/BeaconsController.cs

namespace Beacons.Logic
{
    public class BeaconsController : IBeaconsController, IConfigurable, IReferenceable, ICommandable
    {
        private IBeaconsPersistence _persistence;
        private BeaconsCommandSet _commandSet;

        public BeaconsController()
        {}

        public void Configure(ConfigParams config)
        {}

        public void SetReferences(IReferences references)
        {
            _persistence = references.GetOneRequired<IBeaconsPersistence>(
                new Descriptor("beacons", "persistence", "*", "*", "1.0")
            );
        }

        public CommandSet GetCommandSet()
        {
            if (_commandSet == null)
                _commandSet = new BeaconsCommandSet(this);
            return _commandSet;
        }

        public async Task<DataPage<BeaconV1>> GetBeaconsAsync(string correlationId, FilterParams filter, PagingParams paging)
        {
            return await _persistence.GetPageByFilterAsync(correlationId, filter, paging);
        }

        public async Task<BeaconV1> GetBeaconByIdAsync(string correlationId, string id)
        {
            return await _persistence.GetOneByIdAsync(correlationId, id);
        }

        public async Task<BeaconV1> GetBeaconByUdiAsync(string correlationId, string udi)
        {
            return await _persistence.GetOneByUdiAsync(correlationId, udi);
        }

        public async Task<CenterObjectV1> CalculatePositionAsync(string correlationId, string siteId, string[] udis)
        {
            if (udis == null || udis.Length == 0) return null;

            var page = await this._persistence.GetPageByFilterAsync(
                correlationId,
                FilterParams.FromTuples("site_id", siteId, "udis", udis),
                null
            );
 
            var lat = 0.0;
            var lng = 0.0;
            var count = 0;

            foreach (var beacon in page.Data)
            {
                if (beacon.Center != null
                    && beacon.Center.Type == "Point"
                    && beacon.Center.Coordinates.Length > 1)
                {
                    lng += beacon.Center.Coordinates[0];
                    lat += beacon.Center.Coordinates[1];
                    count += 1;
                }
            }

            if (count == 0)
            {
                return null;
            }

            var point = new CenterObjectV1
            {
                Type = "Point",
                Coordinates = new double[] { lng / count, lat / count }
            };
            return point;
        }

        public async Task<BeaconV1> CreateBeaconAsync(string correlationId, BeaconV1 beacon)
        {
            beacon.Id = beacon.Id ?? IdGenerator.NextLong();
            beacon.Type = beacon.Type ?? BeaconTypeV1.Unknown;

            return await _persistence.CreateAsync(correlationId, beacon);
        }

        public async Task<BeaconV1> UpdateBeaconAsync(string correlationId, BeaconV1 beacon)
        {
            beacon.Type = beacon.Type ?? BeaconTypeV1.Unknown;

            return await _persistence.UpdateAsync(correlationId, beacon);
        }

        public async Task<BeaconV1> DeleteBeaconByIdAsync(string correlationId, string id)
        {
            return await _persistence.DeleteByIdAsync(correlationId, id);
        }
    }
}

/logic/BeaconsController.ts

package logic

import (
	"context"

	data1 "github.com/pip-services-samples/service-beacons-gox/data/version1"
	persist "github.com/pip-services-samples/service-beacons-gox/persistence"
	ccmd "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
)

type BeaconsController struct {
	persistence persist.IBeaconsPersistence
	commandSet  *BeaconsCommandSet
}

func NewBeaconsController() *BeaconsController {
	c := &BeaconsController{}
	return c
}

func (c *BeaconsController) Configure(ctx context.Context, config *cconf.ConfigParams) {
	// Read configuration parameters here...
}

func (c *BeaconsController) SetReferences(ctx context.Context, references cref.IReferences) {
	locator := cref.NewDescriptor("beacons", "persistence", "*", "*", "1.0")
	p, err := references.GetOneRequired(locator)
	if p != nil && err == nil {
		if _pers, ok := p.(persist.IBeaconsPersistence); ok {
			c.persistence = _pers
			return
		}
	}
	panic(cref.NewReferenceError("beacons.controller.SetReferences", locator))
}

func (c *BeaconsController) GetCommandSet() *ccmd.CommandSet {
	if c.commandSet == nil {
		c.commandSet = NewBeaconsCommandSet(c)
	}
	return c.commandSet.CommandSet
}

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

func (c *BeaconsController) GetBeaconById(ctx context.Context, correlationId string,
	beaconId string) (data1.BeaconV1, error) {

	return c.persistence.GetOneById(ctx, correlationId, beaconId)
}

func (c *BeaconsController) GetBeaconByUdi(ctx context.Context, correlationId string,
	beaconId string) (data1.BeaconV1, error) {

	return c.persistence.GetOneByUdi(ctx, correlationId, beaconId)
}

func (c *BeaconsController) CalculatePosition(ctx context.Context, correlationId string,
	siteId string, udis []string) (data1.GeoPointV1, error) {

	if udis == nil || len(udis) == 0 {
		return data1.GeoPointV1{}, nil
	}

	page, err := c.persistence.GetPageByFilter(
		ctx,
		correlationId,
		*cdata.NewFilterParamsFromTuples(
			"site_id", siteId,
			"udis", udis,
		),
		*cdata.NewEmptyPagingParams(),
	)

	if err != nil || !page.HasData() {
		return data1.GeoPointV1{}, err
	}

	var lat float32 = 0
	var lng float32 = 0
	var count = 0

	for _, beacon := range page.Data {
		if beacon.Center.Type == "Point" {
			lng += beacon.Center.Coordinates[0]
			lat += beacon.Center.Coordinates[1]
			count += 1
		}
	}

	pos := data1.GeoPointV1{
		Type:        "Point",
		Coordinates: make([]float32, 2, 2),
	}

	if count > 0 {
		pos.Type = "Point"
		pos.Coordinates[0] = lng / (float32)(count)
		pos.Coordinates[1] = lat / (float32)(count)
	}

	return pos, nil
}

func (c *BeaconsController) CreateBeacon(ctx context.Context, correlationId string,
	beacon data1.BeaconV1) (data1.BeaconV1, error) {

	if beacon.Id == "" {
		beacon.Id = cdata.IdGenerator.NextLong()
	}

	if beacon.Type == "" {
		beacon.Type = data1.Unknown
	}

	return c.persistence.Create(ctx, correlationId, beacon)
}

func (c *BeaconsController) UpdateBeacon(ctx context.Context, correlationId string,
	beacon data1.BeaconV1) (data1.BeaconV1, error) {

	if beacon.Type == "" {
		beacon.Type = data1.Unknown
	}

	return c.persistence.Update(ctx, correlationId, beacon)
}

func (c *BeaconsController) DeleteBeaconById(ctx context.Context, correlationId string,
	beaconId string) (data1.BeaconV1, error) {

	

/lib/logic/BeaconsController.dart

import 'dart:async';

import 'package:pip_services3_commons/pip_services3_commons.dart';

import '../../src/data/version1/BeaconV1.dart';
import '../../src/persistence/IBeaconsPersistence.dart';
import './IBeaconsController.dart';
import '../../src/data/version1/BeaconTypeV1.dart';
import './BeaconsCommandSet.dart';

class BeaconsController
    implements IBeaconsController, IConfigurable, IReferenceable, ICommandable {
  IBeaconsPersistence persistence;
  BeaconsCommandSet commandSet;

  BeaconsController();

  @override
  void configure(ConfigParams config) {}

  @override
  void setReferences(IReferences references) {
    persistence = references.getOneRequired<IBeaconsPersistence>(
        Descriptor('beacons', 'persistence', '*', '*', '1.0'));
  }

  @override
  CommandSet getCommandSet() {
    commandSet ??= BeaconsCommandSet(this);
    return commandSet;
  }

  @override
  Future<DataPage<BeaconV1>> getBeacons(
      String correlationId, FilterParams filter, PagingParams paging) {
    return persistence.getPageByFilter(correlationId, filter, paging);
  }

  @override
  Future<BeaconV1> getBeaconById(String correlationId, String beaconId) {
    return persistence.getOneById(correlationId, beaconId);
  }

  @override
  Future<BeaconV1> getBeaconByUdi(String correlationId, String beaconId) {
    return persistence.getOneByUdi(correlationId, beaconId);
  }

  @override
  Future<Map<String, dynamic>> calculatePosition(
      String correlationId, String siteId, List<String> udis) async {
    var beacons = <BeaconV1>[];
    var position = <String, dynamic>{};
    if (udis == null || udis.isEmpty) {
      return null;
    }

    var page = await persistence.getPageByFilter(correlationId,
        FilterParams.fromTuples(['site_id', siteId, 'udis', udis]), null);
    beacons = page != null ? page.data : [];

    var lat = 0.0;
    var lng = 0.0;
    var count = 0;

    for (var beacon in beacons) {
      if (beacon.center != null &&
          beacon.center['type'] == 'Point' &&
          beacon.center['coordinates'] is List) {
        lng += (beacon.center['coordinates'] as List)[0];
        lat += (beacon.center['coordinates'] as List)[1];
        count += 1;
      }
    }

    if (count > 0) {
      position = {
        'type': 'Point',
        'coordinates': [lng / count, lat / count]
      };
      return position;
    }

    return null;
  }

  @override
  Future<BeaconV1> createBeacon(String correlationId, BeaconV1 beacon) {
    beacon.id = beacon.id ?? IdGenerator.nextLong();
    beacon.type = beacon.type ?? BeaconTypeV1.unknown;
    return persistence.create(correlationId, beacon);
  }

  @override
  Future<BeaconV1> updateBeacon(String correlationId, BeaconV1 beacon) {
    beacon.type = beacon.type ?? BeaconTypeV1.unknown;

    return persistence.update(correlationId, beacon);
  }

  @override
  Future<BeaconV1> deleteBeaconById(String correlationId, String beaconId) {
    return persistence.deleteById(correlationId, beaconId);
  }
}


/src/logic/BeaconsController.py

from typing import List, Any, Optional

from pip_services3_commons.commands import ICommandable, CommandSet
from pip_services3_commons.config import IConfigurable, ConfigParams
from pip_services3_commons.data import FilterParams, PagingParams, DataPage
from pip_services3_commons.refer import IReferenceable, Descriptor, IReferences

from ..data.version1 import BeaconV1
from ..logic.BeaconsCommandSet import BeaconsCommandSet
from ..logic.IBeaconsController import IBeaconsController
from ..persistence import IBeaconsPersistence


class BeaconsController(IBeaconsController, IConfigurable, IReferenceable, ICommandable):

    def __init__(self):
        self.__persistence: IBeaconsPersistence = None
        self.__command_set: BeaconsCommandSet = None

    def configure(self, config: ConfigParams):
        pass

    def get_command_set(self) -> CommandSet:
        if self.__command_set is None:
            self.__command_set = BeaconsCommandSet(self)
        return self.__command_set

    def set_references(self, references: IReferences):
        self.__persistence = references.get_one_required(Descriptor("beacons", "persistence", "*", "*", "1.0"))

    def get_beacons_by_filter(self, correlation_id: Optional[str], filter: FilterParams, paging: PagingParams) -> DataPage:
        return self.__persistence.get_page_by_filter(correlation_id, filter, paging)

    def get_beacon_by_id(self, correlation_id: Optional[str], id: str) -> BeaconV1:
        return self.__persistence.get_one_by_id(correlation_id, id)

    def get_beacon_by_udi(self, correlation_id: Optional[str], udi: str) -> BeaconV1:
        return self.__persistence.get_one_by_udi(correlation_id, udi)

    def calculate_position(self, correlation_id: Optional[str], site_id: str, udis: List[str]) -> Any:
        if udis is None or len(udis) == 0:
            return None

        result = self.__persistence.get_page_by_filter(correlation_id,
                                                       FilterParams.from_tuples("site_id", site_id, "udis", udis), None)
        beacons = result.data

        lat = 0
        lng = 0
        count = 0
        for beacon in beacons:
            if beacon.center is not None and beacon.center['type'] == "Point" and len(
                    beacon.center['coordinates']) > 1:
                lng = lng + beacon.center['coordinates'][0]
                lat = lat + beacon.center['coordinates'][1]
                count = count + 1

        if count == 0:
            return None

        position = {"type": 'Point', "coordinates": [lng / count, lat / count]}
        return position

    def create_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> BeaconV1:
        return self.__persistence.create(correlation_id, entity)

    def update_beacon(self, correlation_id: Optional[str], entity: BeaconV1) -> BeaconV1:
        return self.__persistence.update(correlation_id, entity)

    def delete_beacon_by_id(self, correlation_id: Optional[str], id: str) -> BeaconV1:
        return self.__persistence.delete_by_id(correlation_id, id)

Not available

Pay special attention to the following two methods in the code above:

  • setReferences
  • getCommandSet

The first one sets a dependency upon a persistence using the descriptor beacons:persistence:::1.0. This descriptor reads: we don’t necessarily care which persistence we are given, as long as it implements the IBeaconsPersistence interface via the Referenceable pattern. This way, our controller can be used with the memory persistence, the mongoDB one, or any other one that meets this requirement.

The second method is used to get a set of commands, with which we can control this controller using the Commandable pattern. In our case, it will be used by the commandable HTTP service. If you’re not yet familiar with the Commandable pattern, make sure to find some time and read about it here. To complete this pattern, lets implement a class called BeaconsCommandSet:

/src/logic/BeaconsCommandSet.ts

import { CommandSet } from 'pip-services3-commons-nodex';
import { ICommand } from 'pip-services3-commons-nodex';
import { Command } from 'pip-services3-commons-nodex';
import { ObjectSchema } from 'pip-services3-commons-nodex';
import { FilterParamsSchema } from 'pip-services3-commons-nodex';
import { PagingParamsSchema } from 'pip-services3-commons-nodex';
import { ArraySchema } from 'pip-services3-commons-nodex';
import { TypeCode } from 'pip-services3-commons-nodex';
import { Parameters } from 'pip-services3-commons-nodex';
import { FilterParams } from 'pip-services3-commons-nodex';
import { PagingParams } from 'pip-services3-commons-nodex';

import { BeaconV1Schema } from '../../src/data/version1/BeaconV1Schema';
import { IBeaconsController } from '../../src/logic/IBeaconsController';

export class BeaconsCommandSet extends CommandSet {
    private _controller: IBeaconsController;

    constructor(controller: IBeaconsController) {
        super();

        this._controller = controller;

        this.addCommand(this.makeGetBeaconsCommand());
        this.addCommand(this.makeGetBeaconByIdCommand());
        this.addCommand(this.makeGetBeaconByUdiCommand());
        this.addCommand(this.makeCalculatePositionCommand());
        this.addCommand(this.makeCreateBeaconCommand());
        this.addCommand(this.makeUpdateBeaconCommand());
        this.addCommand(this.makeDeleteBeaconByIdCommand());
    }

    private makeGetBeaconsCommand(): ICommand {
        return new Command(
            'get_beacons',
            new ObjectSchema(true)
                .withOptionalProperty('filter', new FilterParamsSchema())
                .withOptionalProperty('paging', new PagingParamsSchema()),
            async (correlationId: string, args: Parameters) => {
                let filter = FilterParams.fromValue(args.get('filter'));
                let paging = PagingParams.fromValue(args.get('paging'));
                return await this._controller.getBeacons(correlationId, filter, paging);
            }
        );
    }

    private makeGetBeaconByIdCommand(): ICommand {
        return new Command(
            'get_beacon_by_id',
            new ObjectSchema(true)
                .withRequiredProperty('beacon_id', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let beaconId = args.getAsString('beacon_id');
                return await this._controller.getBeaconById(correlationId, beaconId);
            }
        );
    }

    private makeGetBeaconByUdiCommand(): ICommand {
        return new Command(
            'get_beacon_by_udi',
            new ObjectSchema(true)
                .withRequiredProperty('udi', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let udi = args.getAsString('udi');
                return await this._controller.getBeaconByUdi(correlationId, udi);
            }
        );
    }

    private makeCalculatePositionCommand(): ICommand {
        return new Command(
            'calculate_position',
            new ObjectSchema(true)
                .withRequiredProperty('site_id', TypeCode.String)
                .withRequiredProperty('udis', new ArraySchema(TypeCode.String)),
            async (correlationId: string, args: Parameters) => {
                let siteId = args.getAsString('site_id');
                let udis = args.getAsObject('udis');
                return await this._controller.calculatePosition(correlationId, siteId, udis);
            }
        );
    }

    private makeCreateBeaconCommand(): ICommand {
        return new Command(
            'create_beacon',
            new ObjectSchema(true)
                .withRequiredProperty('beacon', new BeaconV1Schema()),
            async (correlationId: string, args: Parameters) => {
                let beacon = args.getAsObject('beacon');
                return await this._controller.createBeacon(correlationId, beacon);
            }
        );
    }   

    private makeUpdateBeaconCommand(): ICommand {
        return new Command(
            'update_beacon',
            new ObjectSchema(true)
                .withRequiredProperty('beacon', new BeaconV1Schema()),
            async (correlationId: string, args: Parameters) => {
                let beacon = args.getAsObject('beacon');
                return await this._controller.updateBeacon(correlationId, beacon);
            }
        );
    }   
    
    private makeDeleteBeaconByIdCommand(): ICommand {
        return new Command(
            'delete_beacon_by_id',
            new ObjectSchema(true)
                .withRequiredProperty('beacon_id', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let beaconId = args.getAsString('beacon_id');
                return await this._controller.deleteBeaconById(correlationId, beaconId);
            }
        );
    }

}

/src/interfaces/logic/BeaconsCommandSet.cs

namespace Beacons.Logic
{
    public class BeaconsCommandSet : CommandSet
    {
        private IBeaconsController _controller;

        public BeaconsCommandSet(IBeaconsController controller)
        {
            _controller = controller;

            AddCommand(MakeGetBeaconsCommand());
            AddCommand(MakeGetBeaconByIdBeaconsCommand());
            AddCommand(MakeGetBeaconByUdiCommand());
            AddCommand(MakeCalculatePositionCommand());
            AddCommand(MakeCreateBeaconCommand());
            AddCommand(MakeUpdateBeaconCommand());
            AddCommand(MakeDeleteBeaconByIdCommand());
        }

        private ICommand MakeGetBeaconsCommand()
        {
            return new Command(
                "get_beacons",
                new ObjectSchema()
                    .WithOptionalProperty("filter", new FilterParamsSchema())
                    .WithOptionalProperty("paging", new PagingParamsSchema()),
                async (correlationId, parameters) =>
                {
                    var filter = FilterParams.FromValue(parameters.Get("filter"));
                    var paging = PagingParams.FromValue(parameters.Get("paging"));
                    return await _controller.GetBeaconsAsync(correlationId, filter, paging);
                });
        }

        private ICommand MakeGetBeaconByIdBeaconsCommand()
        {
            return new Command(
                "get_beacon_by_id",
                new ObjectSchema()
                    .WithRequiredProperty("beacon_id", TypeCode.String),
                async (correlationId, parameters) =>
                {
                    var id = parameters.GetAsString("beacon_id");
                    return await _controller.GetBeaconByIdAsync(correlationId, id);
                });
        }

        private ICommand MakeGetBeaconByUdiCommand()
        {
            return new Command(
                "get_beacon_by_udi",
                new ObjectSchema()
                    .WithRequiredProperty("udi", TypeCode.String),
                async (correlationId, parameters) =>
                {
                    var udi = parameters.GetAsString("udi");
                    return await _controller.GetBeaconByUdiAsync(correlationId, udi);
                });
        }

        private ICommand MakeCalculatePositionCommand()
        {
            return new Command(
                "calculate_position",
                new ObjectSchema()
                    .WithRequiredProperty("site_id", TypeCode.String)
                    .WithRequiredProperty("udis", TypeCode.Array),
                async (correlationId, parameters) =>
                {
                    var siteId = parameters.GetAsString("site_id");
                    string[] udis = ConvertToStringList(parameters.Get("udis"));

                    return await _controller.CalculatePositionAsync(correlationId, siteId, udis);
                });
        }

        private ICommand MakeCreateBeaconCommand()
        {
            return new Command(
                "create_beacon",
                new ObjectSchema()
                    .WithRequiredProperty("beacon", new BeaconV1Schema()),
                async (correlationId, parameters) =>
                {
                    var beacon = ConvertToBeacon(parameters.GetAsObject("beacon"));
                    return await _controller.CreateBeaconAsync(correlationId, beacon);
                });
        }

        private ICommand MakeUpdateBeaconCommand()
        {
            return new Command(
               "update_beacon",
               new ObjectSchema()
                    .WithRequiredProperty("beacon", new BeaconV1Schema()),
               async (correlationId, parameters) =>
               {
                   var beacon = ConvertToBeacon(parameters.GetAsObject("beacon"));
                   return await _controller.UpdateBeaconAsync(correlationId, beacon);
               });
        }

        private ICommand MakeDeleteBeaconByIdCommand()
        {
            return new Command(
               "delete_beacon_by_id",
               new ObjectSchema()
                   .WithRequiredProperty("beacon_id", TypeCode.String),
               async (correlationId, parameters) =>
               {
                   var id = parameters.GetAsString("beacon_id");
                   return await _controller.DeleteBeaconByIdAsync(correlationId, id);
               });
        }

        private BeaconV1 ConvertToBeacon(object value)
        {
            return JsonConverter.FromJson<BeaconV1>(JsonConverter.ToJson(value));
        }

        private string[] ConvertToStringList(object value)
        {
            return JsonConverter.FromJson<string[]>(JsonConverter.ToJson(value));
        }

    }
}

/logic/BeaconsCommandSet.ts

package logic

import (
	"context"
	"strings"

	data1 "github.com/pip-services-samples/service-beacons-gox/data/version1"
	ccmd "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cconv "github.com/pip-services3-gox/pip-services3-commons-gox/convert"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	crun "github.com/pip-services3-gox/pip-services3-commons-gox/run"
	cvalid "github.com/pip-services3-gox/pip-services3-commons-gox/validate"
)

type BeaconsCommandSet struct {
	*ccmd.CommandSet
	controller      IBeaconsController
	beaconConvertor cconv.IJSONEngine[data1.BeaconV1]
}

func NewBeaconsCommandSet(controller IBeaconsController) *BeaconsCommandSet {
	c := &BeaconsCommandSet{
		CommandSet:      ccmd.NewCommandSet(),
		controller:      controller,
		beaconConvertor: cconv.NewDefaultCustomTypeJsonConvertor[data1.BeaconV1](),
	}

	c.AddCommand(c.makeGetBeaconsCommand())
	c.AddCommand(c.makeGetBeaconByIdCommand())
	c.AddCommand(c.makeGetBeaconByUdiCommand())
	c.AddCommand(c.makeCalculatePositionCommand())
	c.AddCommand(c.makeCreateBeaconCommand())
	c.AddCommand(c.makeUpdateBeaconCommand())
	c.AddCommand(c.makeDeleteBeaconByIdCommand())

	return c
}

func (c *BeaconsCommandSet) makeGetBeaconsCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"get_beacons",
		cvalid.NewObjectSchema().
			WithOptionalProperty("filter", cvalid.NewFilterParamsSchema()).
			WithOptionalProperty("paging", cvalid.NewPagingParamsSchema()),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			filter := cdata.NewEmptyFilterParams()
			paging := cdata.NewEmptyPagingParams()
			if _val, ok := args.Get("filter"); ok {
				filter = cdata.NewFilterParamsFromValue(_val)
			}
			if _val, ok := args.Get("paging"); ok {
				paging = cdata.NewPagingParamsFromValue(_val)
			}
			return c.controller.GetBeacons(ctx, correlationId, *filter, *paging)
		})
}

func (c *BeaconsCommandSet) makeGetBeaconByIdCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"get_beacon_by_id",
		cvalid.NewObjectSchema().
			WithRequiredProperty("beacon_id", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			return c.controller.GetBeaconById(ctx, correlationId, args.GetAsString("beacon_id"))
		})
}

func (c *BeaconsCommandSet) makeGetBeaconByUdiCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"get_beacon_by_udi",
		cvalid.NewObjectSchema().
			WithRequiredProperty("udi", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			return c.controller.GetBeaconByUdi(ctx, correlationId, args.GetAsString("udi"))
		})
}

func (c *BeaconsCommandSet) makeCalculatePositionCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"calculate_position",
		cvalid.NewObjectSchema().
			WithRequiredProperty("site_id", cconv.String).
			WithRequiredProperty("udis", cvalid.NewArraySchema(cconv.String)),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			return c.controller.CalculatePosition(
				ctx,
				correlationId,
				args.GetAsString("site_id"),
				strings.Split(args.GetAsString("udis"), ","),
			)
		})
}

func (c *BeaconsCommandSet) makeCreateBeaconCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"create_beacon",
		cvalid.NewObjectSchema().
			WithRequiredProperty("beacon", data1.NewBeaconV1Schema()),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {

			var beacon data1.BeaconV1
			if _beacon, ok := args.GetAsObject("beacon"); ok {
				buf, err := cconv.JsonConverter.ToJson(_beacon)
				if err != nil {
					return nil, err
				}
				beacon, err = c.beaconConvertor.FromJson(buf)
				if err != nil {
					return nil, err
				}
			}
			return c.controller.CreateBeacon(ctx, correlationId, beacon)
		})
}

func (c *BeaconsCommandSet) makeUpdateBeaconCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"update_beacon",
		cvalid.NewObjectSchema().
			WithRequiredProperty("beacon", data1.NewBeaconV1Schema()),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			var beacon data1.BeaconV1
			if _beacon, ok := args.GetAsObject("beacon"); ok {
				buf, err := cconv.JsonConverter.ToJson(_beacon)
				if err != nil {
					return nil, err
				}
				beacon, err = c.beaconConvertor.FromJson(buf)
				if err != nil {
					return nil, err
				}
			}
			return c.controller.UpdateBeacon(ctx, correlationId, beacon)
		})
}

func (c *BeaconsCommandSet) makeDeleteBeaconByIdCommand() ccmd.ICommand {
	return ccmd.NewCommand(
		"delete_beacon_by_id",
		cvalid.NewObjectSchema().
			WithRequiredProperty("beacon_id", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			return c.controller.DeleteBeaconById(ctx, correlationId, args.GetAsString("beacon_id"))
		})
}

/lib/logic/BeaconsCommandSet.dart

import 'dart:async';

import 'package:pip_services3_commons/pip_services3_commons.dart';

import '../../src/data/version1/BeaconV1Schema.dart';
import '../../src/logic/IBeaconsController.dart';
import '../../src/data/version1/BeaconV1.dart';

class BeaconsCommandSet extends CommandSet {
  IBeaconsController _controller;

  BeaconsCommandSet(IBeaconsController controller) : super() {
    _controller = controller;

    addCommand(_makeGetBeaconsCommand());
    addCommand(_makeGetBeaconByIdCommand());
    addCommand(_makeGetBeaconByUdiCommand());
    addCommand(_makeCalculatePositionCommand());
    addCommand(_makeCreateBeaconCommand());
    addCommand(_makeUpdateBeaconCommand());
    addCommand(_makeDeleteBeaconByIdCommand());
  }

  ICommand _makeGetBeaconsCommand() {
    return Command(
        'get_beacons',
        ObjectSchema(true)
            .withOptionalProperty('filter', FilterParamsSchema())
            .withOptionalProperty('paging', PagingParamsSchema()),
        (String correlationId, Parameters args) {
      var filter = FilterParams.fromValue(args.get('filter'));
      var paging = PagingParams.fromValue(args.get('paging'));
      return _controller.getBeacons(correlationId, filter, paging);
    });
  }

  ICommand _makeGetBeaconByIdCommand() {
    return Command('get_beacon_by_id',
        ObjectSchema(true).withRequiredProperty('beacon_id', TypeCode.String),
        (String correlationId, Parameters args) {
      var beaconId = args.getAsString('beacon_id');
      return _controller.getBeaconById(correlationId, beaconId);
    });
  }

  ICommand _makeGetBeaconByUdiCommand() {
    return Command('get_beacon_by_udi',
        ObjectSchema(true).withRequiredProperty('udi', TypeCode.String),
        (String correlationId, Parameters args) {
      var udi = args.getAsString('udi');
      return _controller.getBeaconByUdi(correlationId, udi);
    });
  }

  ICommand _makeCalculatePositionCommand() {
    return Command(
        'calculate_position',
        ObjectSchema(true)
            .withRequiredProperty('site_id', TypeCode.String)
            .withRequiredProperty('udis', ArraySchema(TypeCode.String)),
        (String correlationId, Parameters args) async {
      var siteId = args.getAsString('site_id');
      var udis = List<String>.from(args.getAsObject('udis'));
      return _controller.calculatePosition(correlationId, siteId, udis);
    });
  }

  ICommand _makeCreateBeaconCommand() {
    return Command('create_beacon',
        ObjectSchema(true).withRequiredProperty('beacon', BeaconV1Schema()),
        (String correlationId, Parameters args) {
      var beacon = BeaconV1();
      beacon.fromJson(args.get('beacon'));
      return _controller.createBeacon(correlationId, beacon);
    });
  }

  ICommand _makeUpdateBeaconCommand() {
    return Command('update_beacon',
        ObjectSchema(true).withRequiredProperty('beacon', BeaconV1Schema()),
        (String correlationId, Parameters args) {
      var beacon = BeaconV1();
      beacon.fromJson(args.get('beacon'));
      return _controller.updateBeacon(correlationId, beacon);
    });
  }

  ICommand _makeDeleteBeaconByIdCommand() {
    return Command('delete_beacon_by_id',
        ObjectSchema(true).withRequiredProperty('beacon_id', TypeCode.String),
        (String correlationId, Parameters args) {
      var beaconId = args.getAsString('beacon_id');
      return _controller.deleteBeaconById(correlationId, beaconId);
    });
  }
}


/src/logic/BeaconsCommandSet.py

from pip_services3_commons.commands import CommandSet, Command, ICommand
from pip_services3_commons.convert import TypeCode
from pip_services3_commons.data import FilterParams, PagingParams
from pip_services3_commons.validate import ObjectSchema, FilterParamsSchema, PagingParamsSchema, ArraySchema

from . import IBeaconsController
from ..data.version1 import BeaconV1Schema


class BeaconsCommandSet(CommandSet):

    def __init__(self, controller: IBeaconsController):
        super(BeaconsCommandSet, self).__init__()

        self.__controller: IBeaconsController = controller

        self.add_command(self.__make_get_beacons_command())
        self.add_command(self.__make_get_beacon_by_id_command())
        self.add_command(self.__make_get_beacon_by_udi_command())
        self.add_command(self.__make_calculate_position_command())
        self.add_command(self.__make_create_beacon_command())
        self.add_command(self.__make_update_beacon_command())
        self.add_command(self.__make_delete_beacon_by_id_command())

    def __make_get_beacons_command(self) -> ICommand:
        def handler(correlation_id, args):
            filter = FilterParams.from_value(args.get("filter"))
            paging = PagingParams.from_value(args.get("paging"))
            return self.__controller.get_beacons_by_filter(correlation_id, filter, paging)

        return Command("get_beacons", ObjectSchema().with_optional_property("filter", FilterParamsSchema())
                       .with_optional_property("paging", PagingParamsSchema()), handler)

    def __make_get_beacon_by_id_command(self) -> ICommand:
        def handler(correlation_id, args):
            id = args.get_as_string("id")
            return self.__controller.get_beacon_by_id(correlation_id, id)

        return Command("get_beacon_by_id", ObjectSchema().with_required_property("id", TypeCode.String), handler)

    def __make_get_beacon_by_udi_command(self) -> ICommand:
        def handler(correlation_id, args):
            id = args.get_as_string("udi")
            return self.__controller.get_beacon_by_udi(correlation_id, id)

        return Command("get_beacon_by_udi", ObjectSchema().with_required_property("udi", TypeCode.String), handler)

    def __make_calculate_position_command(self) -> ICommand:
        def handler(correlation_id, args):
            site_id = args.get_as_string("site_id")
            udis = args.get_as_nullable_string("udis")
            return self.__controller.calculate_position(correlation_id, site_id, udis)

        return Command("calculate_position", ObjectSchema().with_required_property("site_id", TypeCode.String)
                       .with_required_property("udis", ArraySchema("String")), handler)

    def __make_create_beacon_command(self) -> ICommand:
        def handler(correlation_id, args):
            entity = args.get("beacon")
            return self.__controller.create_beacon(correlation_id, entity)

        return Command("create_beacon", ObjectSchema().with_optional_property("beacon", BeaconV1Schema()), handler)

    def __make_update_beacon_command(self) -> ICommand:
        def handler(correlation_id, args):
            entity = args.get("beacon")
            return self.__controller.update_beacon(correlation_id, entity)

        return Command("update_beacon", ObjectSchema().with_optional_property("beacon", BeaconV1Schema()), handler)

    def __make_delete_beacon_by_id_command(self) -> ICommand:
        def handler(correlation_id, args):
            id = args.get_as_string("id")
            return self.__controller.delete_beacon_by_id(correlation_id, id)

        return Command("delete_beacon_by_id", ObjectSchema().with_required_property("id", TypeCode.String), handler)


Not available

To sum up this class’s code: we’re creating commands for each of the controller’s methods, and then registering them in the constructor. To create a command, we give it a name, a validation schema (if needed), and a callback function with the following three parameters:

  • correlationId: string – used to identify the operation,
  • args: Parameters - the set of parameters received from the command being called,
  • callback – callback function for returning the command’s result, or an error, if one occurs.

To be sure that our new methods are working correctly, let’s add some tests for the controller. The code for testing the controller is listed below:

/test/logic/BeaconsController.test.ts

const assert = require('chai').assert;

import { ConfigParams } from 'pip-services3-commons-nodex';
import { Descriptor } from 'pip-services3-commons-nodex';
import { References } from 'pip-services3-commons-nodex';
import { FilterParams } from 'pip-services3-commons-nodex';
import { PagingParams } from 'pip-services3-commons-nodex';

import { BeaconV1 } from '../../src/data/version1/BeaconV1';
import { BeaconTypeV1 } from '../../src/data/version1/BeaconTypeV1';
import { BeaconsMemoryPersistence } from '../../src/persistence/BeaconsMemoryPersistence';
import { BeaconsController } from '../../src/logic/BeaconsController';

const BEACON1: BeaconV1 = {
    id: '1',
    udi: '00001',
    type: BeaconTypeV1.AltBeacon,
    site_id: '1',
    label: 'TestBeacon1',
    center: { type: 'Point', coordinates: [ 0, 0 ] },
    radius: 50
};
const BEACON2: BeaconV1 = {
    id: '2',
    udi: '00002',
    type: BeaconTypeV1.iBeacon,
    site_id: '1',
    label: 'TestBeacon2',
    center: { type: 'Point', coordinates: [ 2, 2 ] },
    radius: 70
};

suite('BeaconsController', () => {
    let persistence: BeaconsMemoryPersistence;
    let controller: BeaconsController;

    setup(async () => {
        persistence = new BeaconsMemoryPersistence();
        persistence.configure(new ConfigParams());

        controller = new BeaconsController();
        controller.configure(new ConfigParams());

        let references = References.fromTuples(
            new Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'), persistence,
            new Descriptor('beacons', 'controller', 'default', 'default', '1.0'), controller
        );

        controller.setReferences(references);

        await persistence.open(null);
    });

    teardown(async () => {
        await persistence.close(null);
    });

    test('CRUD Operations', async () => {
        // Create the first beacon
        let beacon = await controller.createBeacon(
            null,
            BEACON1
        );
        assert.isObject(beacon);
        assert.equal(BEACON1.udi, beacon.udi);
        assert.equal(BEACON1.site_id, beacon.site_id);
        assert.equal(BEACON1.type, beacon.type);
        assert.equal(BEACON1.label, beacon.label);
        assert.isNotNull(beacon.center);

        // Create the second beacon
        beacon = await controller.createBeacon(
            null,
            BEACON2
        );
        assert.isObject(beacon);
        assert.equal(BEACON2.udi, beacon.udi);
        assert.equal(BEACON2.site_id, beacon.site_id);
        assert.equal(BEACON2.type, beacon.type);
        assert.equal(BEACON2.label, beacon.label);
        assert.isNotNull(beacon.center);

        // Get all beacons
        let page = await controller.getBeacons(
            null,
            new FilterParams(),
            new PagingParams()
        );
        assert.isObject(page);
        assert.lengthOf(page.data, 2);

        let beacon1 = page.data[0];

        // Update the beacon
        beacon1.label = 'ABC';

        beacon = await controller.updateBeacon(
            null,
            beacon1
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);
        assert.equal('ABC', beacon.label);

        // Get beacon by udi
        beacon = await controller.getBeaconByUdi(
            null, 
            beacon1.udi
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);

        // Delete the beacon
        beacon = await controller.deleteBeaconById(
            null,
            beacon1.id
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);

        // Try to get deleted beacon
        beacon = await controller.getBeaconById(
            null,
            beacon1.id
        );
        assert.isNull(beacon || null);
    });


    test('Calculate Positions', async () => {
        // Create the first beacon
        let beacon = await controller.createBeacon(
            null,
            BEACON1
        );
        assert.isObject(beacon);
        assert.equal(BEACON1.udi, beacon.udi);
        assert.equal(BEACON1.site_id, beacon.site_id);
        assert.equal(BEACON1.type, beacon.type);
        assert.equal(BEACON1.label, beacon.label);
        assert.isNotNull(beacon.center);

        // Create the second beacon
        beacon = await controller.createBeacon(
            null,
            BEACON2
        );
        assert.isObject(beacon);
        assert.equal(BEACON2.udi, beacon.udi);
        assert.equal(BEACON2.site_id, beacon.site_id);
        assert.equal(BEACON2.type, beacon.type);
        assert.equal(BEACON2.label, beacon.label);
        assert.isNotNull(beacon.center);

        // Calculate position for one beacon
        let position = await controller.calculatePosition(
            null, '1', ['00001']
        );
        assert.isObject(position);
        assert.equal('Point', position.type);
        assert.lengthOf(position.coordinates, 2);
        assert.equal(0, position.coordinates[0]);
        assert.equal(0, position.coordinates[1]);

        // Calculate position for two beacons
        position = await controller.calculatePosition(
            null, '1', ['00001', '00002']
        );
        assert.isObject(position);
        assert.equal('Point', position.type);
        assert.lengthOf(position.coordinates, 2);
        assert.equal(1, position.coordinates[0]);
        assert.equal(1, position.coordinates[1]);
    });
});

/test/service.test/logic/BeaconsControllerTest.cs

namespace Beacons.Logic
{
    public class BeaconsControllerTest: IDisposable
    {
        private BeaconV1 BEACON1 = new BeaconV1
        {
            Id = "1",
            Udi = "00001",
            Type = BeaconTypeV1.AltBeacon,
            SiteId = "1",
            Label = "TestBeacon1",
            Center = new CenterObjectV1 { Type = "Point", Coordinates = new double[] { 0, 0 } },
            Radius = 50
        };
        private BeaconV1 BEACON2 = new BeaconV1
        {
            Id = "2",
            Udi = "00002",
            Type = BeaconTypeV1.iBeacon,
            SiteId = "1",
            Label = "TestBeacon2",
            Center = new CenterObjectV1 { Type = "Point", Coordinates = new double[] { 2, 2 } },
            Radius = 70
        };

        private BeaconsController _controller;
        private BeaconsMemoryPersistence _persistence;

        public BeaconsControllerTest()
        {
            _persistence = new BeaconsMemoryPersistence();
            _persistence.Configure(new ConfigParams());

            _controller = new BeaconsController();

            var references = References.FromTuples(
                new Descriptor("beacons", "persistence", "memory", "*", "1.0"), _persistence,
                new Descriptor("beacons", "controller", "default", "*", "1.0"), _controller
            );

            _controller.SetReferences(references);

            _persistence.OpenAsync(null).Wait();
        }

        public void Dispose()
        {
            _persistence.CloseAsync(null).Wait();
        }

        [Fact]
        public async Task TestCrudOperationsAsync()
        {
            // Create the first beacon
            var beacon = await _controller.CreateBeaconAsync(null, BEACON1);

            Assert.NotNull(beacon);
            Assert.Equal(BEACON1.Udi, beacon.Udi);
            Assert.Equal(BEACON1.SiteId, beacon.SiteId);
            Assert.Equal(BEACON1.Type, beacon.Type);
            Assert.Equal(BEACON1.Label, beacon.Label);
            Assert.NotNull(beacon.Center);

            // Create the second beacon
            beacon = await _controller.CreateBeaconAsync(null, BEACON2);

            Assert.NotNull(beacon);
            Assert.Equal(BEACON2.Udi, beacon.Udi);
            Assert.Equal(BEACON2.SiteId, beacon.SiteId);
            Assert.Equal(BEACON2.Type, beacon.Type);
            Assert.Equal(BEACON2.Label, beacon.Label);
            Assert.NotNull(beacon.Center);

            // Get all beacons
            var page = await _controller.GetBeaconsAsync(
                null,
                new FilterParams(),
                new PagingParams()
            );

            Assert.NotNull(page);
            Assert.Equal(2, page.Data.Count);

            var beacon1 = page.Data[0];

            // Update the beacon
            beacon1.Label = "ABC";

            beacon = await _controller.UpdateBeaconAsync(null, beacon1);

            Assert.NotNull(beacon);
            Assert.Equal(beacon1.Id, beacon.Id);
            Assert.Equal("ABC", beacon.Label);

            // Get beacon by udi
            beacon = await _controller.GetBeaconByUdiAsync(null, beacon1.Udi);

            Assert.NotNull(beacon);
            Assert.Equal(beacon1.Id, beacon.Id);

            // Delete the beacon
            beacon = await _controller.DeleteBeaconByIdAsync(null, beacon1.Id);

            Assert.NotNull(beacon);
            Assert.Equal(beacon1.Id, beacon.Id);

            // Try to get deleted beacon
            beacon = await _controller.GetBeaconByIdAsync(null, beacon1.Id);

            Assert.Null(beacon);
        }

        [Fact]
        public async Task TestCalculatePositionsAsync()
        {
            // Create the first beacon
            var beacon = await _controller.CreateBeaconAsync(null, BEACON1);

            Assert.NotNull(beacon);
            Assert.Equal(BEACON1.Udi, beacon.Udi);
            Assert.Equal(BEACON1.SiteId, beacon.SiteId);
            Assert.Equal(BEACON1.Type, beacon.Type);
            Assert.Equal(BEACON1.Label, beacon.Label);
            Assert.NotNull(beacon.Center);

            // Create the second beacon
            beacon = await _controller.CreateBeaconAsync(null, BEACON2);

            Assert.NotNull(beacon);
            Assert.Equal(BEACON2.Udi, beacon.Udi);
            Assert.Equal(BEACON2.SiteId, beacon.SiteId);
            Assert.Equal(BEACON2.Type, beacon.Type);
            Assert.Equal(BEACON2.Label, beacon.Label);
            Assert.NotNull(beacon.Center);

            // Calculate position for one beacon
            var position = await _controller.CalculatePositionAsync(
                null, "1", new string[] { "00001" }
            );

            Assert.NotNull(position);
            Assert.Equal("Point", position.Type);
            Assert.Equal(2, position.Coordinates.Length);
            Assert.Equal(0, position.Coordinates[0]);
            Assert.Equal(0, position.Coordinates[1]);

            // Calculate position for two beacons
            position = await _controller.CalculatePositionAsync(
                null, "1", new string[] { "00001", "00002" }
            );

            Assert.NotNull(position);
            Assert.Equal("Point", position.Type);
            Assert.Equal(2, position.Coordinates.Length);
            Assert.Equal(1, position.Coordinates[0]);
            Assert.Equal(1, position.Coordinates[1]);
        }

    }
}
        

/test/logic/BeaconsController_test.go

package test_logic

import (
	"context"
	"testing"

	data1 "github.com/pip-services-samples/service-beacons-gox/data/version1"
	logic "github.com/pip-services-samples/service-beacons-gox/logic"
	persist "github.com/pip-services-samples/service-beacons-gox/persistence"
	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	"github.com/stretchr/testify/assert"
)

type beaconsControllerTest struct {
	BEACON1     *data1.BeaconV1
	BEACON2     *data1.BeaconV1
	persistence *persist.BeaconsMemoryPersistence
	controller  *logic.BeaconsController
}

func newBeaconsControllerTest() *beaconsControllerTest {
	BEACON1 := &data1.BeaconV1{
		Id:     "1",
		Udi:    "00001",
		Type:   data1.AltBeacon,
		SiteId: "1",
		Label:  "TestBeacon1",
		Center: data1.GeoPointV1{Type: "Point", Coordinates: []float32{0.0, 0.0}},
		Radius: 50,
	}

	BEACON2 := &data1.BeaconV1{
		Id:     "2",
		Udi:    "00002",
		Type:   data1.IBeacon,
		SiteId: "1",
		Label:  "TestBeacon2",
		Center: data1.GeoPointV1{Type: "Point", Coordinates: []float32{2.0, 2.0}},
		Radius: 70,
	}

	persistence := persist.NewBeaconsMemoryPersistence()
	persistence.Configure(context.Background(), cconf.NewEmptyConfigParams())

	controller := logic.NewBeaconsController()
	controller.Configure(context.Background(), cconf.NewEmptyConfigParams())

	references := cref.NewReferencesFromTuples(
		context.Background(),
		cref.NewDescriptor("beacons", "persistence", "memory", "default", "1.0"), persistence,
		cref.NewDescriptor("beacons", "controller", "default", "default", "1.0"), controller,
	)

	controller.SetReferences(context.Background(), references)

	return &beaconsControllerTest{
		BEACON1:     BEACON1,
		BEACON2:     BEACON2,
		persistence: persistence,
		controller:  controller,
	}
}

func (c *beaconsControllerTest) setup(t *testing.T) {
	err := c.persistence.Open(context.Background(), "")
	if err != nil {
		t.Error("Failed to open persistence", err)
	}

	err = c.persistence.Clear(context.Background(), "")
	if err != nil {
		t.Error("Failed to clear persistence", err)
	}
}

func (c *beaconsControllerTest) teardown(t *testing.T) {
	err := c.persistence.Close(context.Background(), "")
	if err != nil {
		t.Error("Failed to close persistence", err)
	}
}

func (c *beaconsControllerTest) testCrudOperations(t *testing.T) {
	var beacon1 data1.BeaconV1

	// Create the first beacon
	beacon, err := c.controller.CreateBeacon(context.Background(), "", c.BEACON1.Clone())
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, c.BEACON1.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON1.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON1.Type, beacon.Type)
	assert.Equal(t, c.BEACON1.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)

	// Create the second beacon
	beacon, err = c.controller.CreateBeacon(context.Background(), "", c.BEACON2.Clone())
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, c.BEACON2.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON2.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON2.Type, beacon.Type)
	assert.Equal(t, c.BEACON2.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)

	// Get all beacons
	page, err := c.controller.GetBeacons(context.Background(), "", *cdata.NewEmptyFilterParams(), *cdata.NewEmptyPagingParams())
	assert.Nil(t, err)
	assert.NotNil(t, page)
	assert.True(t, page.HasData())
	assert.Len(t, page.Data, 2)
	beacon1 = page.Data[0].Clone()

	// Update the beacon
	beacon1.Label = "ABC"
	beacon, err = c.controller.UpdateBeacon(context.Background(), "", beacon1)
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, beacon1.Id, beacon.Id)
	assert.Equal(t, "ABC", beacon.Label)

	// Get beacon by udi
	beacon, err = c.controller.GetBeaconByUdi(context.Background(), "", beacon1.Udi)
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, beacon1.Id, beacon.Id)

	// Delete the beacon
	beacon, err = c.controller.DeleteBeaconById(context.Background(), "", beacon1.Id)
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, beacon1.Id, beacon.Id)

	// Try to get deleted beacon
	beacon, err = c.controller.GetBeaconById(context.Background(), "", beacon1.Id)
	assert.Nil(t, err)
	assert.Equal(t, data1.BeaconV1{}, beacon)
}

func (c *beaconsControllerTest) testCalculatePositions(t *testing.T) {
	// Create the first beacon
	beacon, err := c.controller.CreateBeacon(context.Background(), "", c.BEACON1.Clone())
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, c.BEACON1.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON1.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON1.Type, beacon.Type)
	assert.Equal(t, c.BEACON1.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)

	// Create the second beacon
	beacon, err = c.controller.CreateBeacon(context.Background(), "", c.BEACON2.Clone())
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, c.BEACON2.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON2.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON2.Type, beacon.Type)
	assert.Equal(t, c.BEACON2.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)

	// Calculate position for one beacon
	position, err := c.controller.CalculatePosition(context.Background(), "", "1", []string{"00001"})
	assert.Nil(t, err)
	assert.NotEqual(t, data1.GeoPointV1{}, position)
	assert.Equal(t, "Point", position.Type)
	assert.Equal(t, (float32)(0.0), position.Coordinates[0])
	assert.Equal(t, (float32)(0.0), position.Coordinates[1])

	// Calculate position for two beacons
	position, err = c.controller.CalculatePosition(context.Background(), "", "1", []string{"00001", "00002"})
	assert.Nil(t, err)
	assert.NotEqual(t, data1.GeoPointV1{}, position)
	assert.Equal(t, "Point", position.Type)
	assert.Equal(t, (float32)(1.0), position.Coordinates[0])
	assert.Equal(t, (float32)(1.0), position.Coordinates[1])
}

func TestBeaconsController(t *testing.T) {
	c := newBeaconsControllerTest()

	c.setup(t)
	t.Run("CRUD Operations", c.testCrudOperations)
	c.teardown(t)

	c.setup(t)
	t.Run("Calculate Positions", c.testCalculatePositions)
	c.teardown(t)
}

/test/logic/BeaconsController_test.dart

import 'package:test/test.dart';
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services_beacons_dart/pip_services_beacons_dart.dart';

final BEACON1 = BeaconV1(
    id: '1',
    udi: '00001',
    type: BeaconTypeV1.altBeacon,
    site_id: '1',
    label: 'TestBeacon1',
    center: {
      'type': 'Point',
      'coordinates': [0.0, 0.0]
    },
    radius: 50.0);
final BEACON2 = BeaconV1(
    id: '2',
    udi: '00002',
    type: BeaconTypeV1.iBeacon,
    site_id: '1',
    label: 'TestBeacon2',
    center: {
      'type': 'Point',
      'coordinates': [2.0, 2.0]
    },
    radius: 70.0);

void main() {
  group('BeaconsController', () {
    BeaconsMemoryPersistence persistence;
    BeaconsController controller;

    setUp(() async {
      persistence = BeaconsMemoryPersistence();
      persistence.configure(ConfigParams());

      controller = BeaconsController();
      controller.configure(ConfigParams());

      var references = References.fromTuples([
        Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'),
        persistence,
        Descriptor('beacons', 'controller', 'default', 'default', '1.0'),
        controller
      ]);

      controller.setReferences(references);

      await persistence.open(null);
    });

    tearDown(() async {
      await persistence.close(null);
    });

    test('CRUD Operations', () async {
      BeaconV1 beacon1;

      // Create the first beacon
      var beacon = await controller.createBeacon(null, BEACON1);
      expect(beacon, isNotNull);
      expect(BEACON1.udi, beacon.udi);
      expect(BEACON1.site_id, beacon.site_id);
      expect(BEACON1.type, beacon.type);
      expect(BEACON1.label, beacon.label);
      expect(beacon.center, isNotNull);

      // Create the second beacon
      beacon = await controller.createBeacon(null, BEACON2);
      expect(beacon, isNotNull);
      expect(BEACON2.udi, beacon.udi);
      expect(BEACON2.site_id, beacon.site_id);
      expect(BEACON2.type, beacon.type);
      expect(BEACON2.label, beacon.label);
      expect(beacon.center, isNotNull);

      // Get all beacons
      var page =
          await controller.getBeacons(null, FilterParams(), PagingParams());
      expect(page, isNotNull);
      expect(page.data.length, 2);
      beacon1 = page.data[0];

      // Update the beacon
      beacon1.label = 'ABC';

      beacon = await controller.updateBeacon(null, beacon1);
      expect(beacon, isNotNull);
      expect(beacon1.id, beacon.id);
      expect('ABC', beacon.label);

      // Get beacon by udi
      beacon = await controller.getBeaconByUdi(null, beacon1.udi);
      expect(beacon, isNotNull);
      expect(beacon1.id, beacon.id);

      // Delete the beacon
      beacon = await controller.deleteBeaconById(null, beacon1.id);
      expect(beacon, isNotNull);
      expect(beacon1.id, beacon.id);

      // Try to get deleted beacon
      beacon = await controller.getBeaconById(null, beacon1.id);
      expect(beacon, isNull);
    });

    test('Calculate Positions', () async {
      // Create the first beacon
      var beacon = await controller.createBeacon(null, BEACON1);
      expect(beacon, isNotNull);
      expect(BEACON1.udi, beacon.udi);
      expect(BEACON1.site_id, beacon.site_id);
      expect(BEACON1.type, beacon.type);
      expect(BEACON1.label, beacon.label);
      expect(beacon.center, isNotNull);

      // Create the second beacon
      beacon = await controller.createBeacon(null, BEACON2);
      expect(beacon, isNotNull);
      expect(BEACON2.udi, beacon.udi);
      expect(BEACON2.site_id, beacon.site_id);
      expect(BEACON2.type, beacon.type);
      expect(BEACON2.label, beacon.label);
      expect(beacon.center, isNotNull);

      // Calculate position for one beacon
      var position = await controller.calculatePosition(null, '1', ['00001']);
      expect(position, isNotNull);
      expect('Point', position['type']);
      expect((position['coordinates'] as List).length, 2);
      expect(0, (position['coordinates'] as List)[0]);
      expect(0, (position['coordinates'] as List)[1]);

      // Calculate position for two beacons
      position =
          await controller.calculatePosition(null, '1', ['00001', '00002']);
      expect(position, isNotNull);
      expect('Point', position['type']);
      expect((position['coordinates'] as List).length, 2);
      expect(1, (position['coordinates'] as List)[0]);
      expect(1, (position['coordinates'] as List)[1]);
    });
  });
}

/test/logic/test_BeaconsController.py

from pip_services3_commons.data import FilterParams, PagingParams
from pip_services3_commons.refer import References, Descriptor

from src.data.version1 import BeaconV1, BeaconTypeV1
from src.logic.BeaconsController import BeaconsController
from src.persistence.BeaconsMemoryPersistence import BeaconsMemoryPersistence

BEACON1 = BeaconV1("1", "1", BeaconTypeV1.AltBeacon, "00001", "TestBeacon1", {"type": 'Point', "coordinates": [0, 0]},
                   50)
BEACON2 = BeaconV1("2", "1", BeaconTypeV1.iBeacon, "00002", "TestBeacon2", {"type": 'Point', "coordinates": [2, 2]}, 70)
BEACON3 = BeaconV1("3", "2", BeaconTypeV1.AltBeacon, "00003", "TestBeacon3", {"type": 'Point', "coordinates": [10, 10]},
                   50)


class TestBeaconsController():
    _persistence: BeaconsMemoryPersistence
    _controller: BeaconsController

    @classmethod
    def setup_class(cls):
        cls._persistence = BeaconsMemoryPersistence()
        cls._controller = BeaconsController()
        references = References.from_tuples(Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'),
                                            cls._persistence,
                                            Descriptor('beacons', 'controller', 'default', 'default', '1.0'),
                                            cls._controller)

        cls._controller.set_references(references)
        cls._persistence.open(None)

    @classmethod
    def teardown_class(cls):
        cls._persistence.close(None)

    def test_crud_operations(self):
        # Create the first beacon
        beacon1 = self._controller.create_beacon(None, BEACON1)

        assert beacon1 is not None
        assert beacon1.id == BEACON1.id
        assert beacon1.site_id == BEACON1.site_id
        assert beacon1.udi == BEACON1.udi
        assert beacon1.type == BEACON1.type
        assert beacon1.label == BEACON1.label
        assert beacon1.center is not None

        # Create the second beacon
        beacon2 = self._controller.create_beacon(None, BEACON2)

        assert beacon2 is not None
        assert beacon2.id == BEACON2.id
        assert beacon2.site_id == BEACON2.site_id
        assert beacon2.udi == BEACON2.udi
        assert beacon2.type == BEACON2.type
        assert beacon2.label == BEACON2.label
        assert beacon2.center is not None

        # Get all beacons
        page = self._controller.get_beacons_by_filter(None, FilterParams(), PagingParams())
        assert page is not None
        assert len(page.data) == 2

        beacon1 = page.data[0]

        # Update the beacon
        beacon1.label = "ABC"
        beacon = self._controller.update_beacon(None, beacon1)
        assert beacon is not None
        assert beacon1.id == beacon.id
        assert "ABC" == beacon.label

        # Get beacon by udi
        beacon = self._controller.get_beacon_by_udi(None, beacon1.udi)
        assert beacon is not None
        assert beacon.id == beacon1.id

        # Delete beacon
        self._controller.delete_beacon_by_id(None, beacon1.id)

        # Try to get deleted beacon
        beacon = self._controller.get_beacon_by_id(None, beacon1.id)
        assert beacon is None

    def test_calculate_position(self):
        # Create the first beacon
        beacon1 = self._controller.create_beacon(None, BEACON1)

        assert beacon1 is not None
        assert beacon1.id == BEACON1.id
        assert beacon1.site_id == BEACON1.site_id
        assert beacon1.udi == BEACON1.udi
        assert beacon1.type == BEACON1.type
        assert beacon1.label == BEACON1.label
        assert beacon1.center is not None

        # Create the second beacon
        beacon2 = self._controller.create_beacon(None, BEACON2)

        assert beacon2 is not None
        assert beacon2.id == BEACON2.id
        assert beacon2.site_id == BEACON2.site_id
        assert beacon2.udi == BEACON2.udi
        assert beacon2.type == BEACON2.type
        assert beacon2.label == BEACON2.label
        assert beacon2.center is not None

        # Calculate position for one beacon
        position = self._controller.calculate_position(None, '1', ['00001'])
        assert position is not None
        assert "Point" == position["type"]
        assert 2 == len(position["coordinates"])
        
Not available

These tests can be run using the same pip test command that we used to run the persistence tests.

Our service is now just one step away from being completed! All that we have left to write is Step 6. Implementing an HTTP service.

Step 6. Implementing an HTTP service.