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 microsservice. 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/service/IBeaconsService.ts
import { FilterParams, PagingParams, DataPage } from 'pip-services4-data-node';
import { Context } from 'pip-services4-components-node';
import { BeaconV1 } from '../../src/data/version1/BeaconV1';
export interface IBeaconsService {
getBeacons(ctx: Context, filter: FilterParams,
paging: PagingParams): Promise<DataPage<BeaconV1>>;
getBeaconById(ctx: Context, beaconId: string): Promise<BeaconV1>;
getBeaconByUdi(ctx: Context, beaconId: string): Promise<BeaconV1>;
calculatePosition(ctx: Context, siteId: string, udis: string[]): Promise<any>;
createBeacon(ctx: Context, beacon: BeaconV1): Promise<BeaconV1>;
updateBeacon(ctx: Context, beacon: BeaconV1): Promise<BeaconV1>;
deleteBeaconById(ctx: Context, beaconId: string): Promise<BeaconV1>;
}
/logic/IBeaconsController.go
package logic
import (
"context"
data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
)
type IBeaconsService interface {
GetBeacons(ctx context.Context, filter cquery.FilterParams, paging cquery.PagingParams) (cquery.DataPage[data1.BeaconV1], error)
GetBeaconById(ctx context.Context, beaconId string) (data1.BeaconV1, error)
GetBeaconByUdi(ctx context.Context, beaconId string) (data1.BeaconV1, error)
CalculatePosition(ctx context.Context, siteId string, udis []string) (data1.GeoPointV1, error)
CreateBeacon(ctx context.Context, beacon data1.BeaconV1) (data1.BeaconV1, error)
UpdateBeacon(ctx context.Context, beacon data1.BeaconV1) (data1.BeaconV1, error)
DeleteBeaconById(ctx context.Context, beaconId string) (data1.BeaconV1, error)
}
/src/logic/IBeaconsService.py
from typing import Any, List, Optional
from pip_services4_data.query import PagingParams, FilterParams, DataPage
from pip_services4_components.context import IContext
from src.data.version1 import BeaconV1
class IBeaconsService:
def get_beacons_by_filter(self, context: Optional[IContext], filter: FilterParams, paging: PagingParams) -> DataPage:
raise NotImplementedError('Method from interface definition')
def get_beacon_by_id(self, context: Optional[IContext], id: str) -> BeaconV1:
raise NotImplementedError('Method from interface definition')
def get_beacon_by_udi(self, context: Optional[IContext], udi: str) -> BeaconV1:
raise NotImplementedError('Method from interface definition')
def calculate_position(self, context: Optional[IContext], site_id: str, udis: List[str]) -> Any:
raise NotImplementedError('Method from interface definition')
def create_beacon(self, context: Optional[IContext], entity: BeaconV1) -> BeaconV1:
raise NotImplementedError('Method from interface definition')
def update_beacon(self, context: Optional[IContext], entity: BeaconV1) -> BeaconV1:
raise NotImplementedError('Method from interface definition')
def delete_beacon_by_id(self, context: Optional[IContext], id: str) -> BeaconV1:
raise NotImplementedError('Method from interface definition')
Once our interface is ready, we can move on to implementing the actual service. 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/service/BeaconsService.ts
import { FilterParams } from 'pip-services4-data-node';
import { PagingParams } from 'pip-services4-data-node';
import { DataPage } from 'pip-services4-data-node';
import { ConfigParams } from 'pip-services4-components-node';
import { IConfigurable } from 'pip-services4-components-node';
import { Descriptor } from 'pip-services4-components-node';
import { IReferences } from 'pip-services4-components-node';
import { IReferenceable } from 'pip-services4-components-node';
import { Context } from 'pip-services4-components-node';
import { IdGenerator } from 'pip-services4-data-node';
import { CommandSet } from 'pip-services4-rpc-node';
import { ICommandable } from 'pip-services4-rpc-node';
import { BeaconV1 } from '../../src/data/version1/BeaconV1';
import { IBeaconsPersistence } from '../../src/persistence/IBeaconsPersistence';
import { IBeaconsService } from './IBeaconsService';
import { BeaconTypeV1 } from '../../src/data/version1/BeaconTypeV1';
import { BeaconsCommandSet } from './BeaconsCommandSet';
export class BeaconsService implements IBeaconsService, 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(ctx: Context, beacon: BeaconV1): Promise<BeaconV1> {
beacon.id = beacon.id || IdGenerator.nextLong();
beacon.type = beacon.type || BeaconTypeV1.Unknown;
return this._persistence.create(ctx, beacon);
}
public updateBeacon(ctx: Context, beacon: BeaconV1): Promise<BeaconV1> {
beacon.type = beacon.type || BeaconTypeV1.Unknown;
return this._persistence.update(ctx, beacon);
}
public deleteBeaconById(ctx: Context, beaconId: string): Promise<BeaconV1> {
return this._persistence.deleteById(ctx, beaconId);
}
}
/logic/BeaconsService.ts
package logic
import (
"context"
data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
persist "github.com/pip-services-samples/service-beacons-go/persistence"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
cdata "github.com/pip-services4/pip-services4-go/pip-services4-data-go/keys"
cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
ccmd "github.com/pip-services4/pip-services4-go/pip-services4-rpc-go/commands"
)
type BeaconsService struct {
persistence persist.IBeaconsPersistence
commandSet *BeaconsCommandSet
}
func NewBeaconsService() *BeaconsService {
c := &BeaconsService{}
return c
}
func (c *BeaconsService) Configure(ctx context.Context, config *cconf.ConfigParams) {
// Read configuration parameters here...
}
func (c *BeaconsService) 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(ctx, locator))
}
func (c *BeaconsService) GetCommandSet() *ccmd.CommandSet {
if c.commandSet == nil {
c.commandSet = NewBeaconsCommandSet(c)
}
return &c.commandSet.CommandSet
}
func (c *BeaconsService) GetBeacons(ctx context.Context,
filter cquery.FilterParams, paging cquery.PagingParams) (cquery.DataPage[data1.BeaconV1], error) {
return c.persistence.GetPageByFilter(ctx, filter, paging)
}
func (c *BeaconsService) GetBeaconById(ctx context.Context,
beaconId string) (data1.BeaconV1, error) {
return c.persistence.GetOneById(ctx, beaconId)
}
func (c *BeaconsService) GetBeaconByUdi(ctx context.Context,
beaconId string) (data1.BeaconV1, error) {
return c.persistence.GetOneByUdi(ctx, beaconId)
}
func (c *BeaconsService) CalculatePosition(ctx context.Context,
siteId string, udis []string) (data1.GeoPointV1, error) {
if udis == nil || len(udis) == 0 {
return data1.GeoPointV1{}, nil
}
page, err := c.persistence.GetPageByFilter(
ctx,
*cquery.NewFilterParamsFromTuples(
"site_id", siteId,
"udis", udis,
),
*cquery.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 *BeaconsService) CreateBeacon(ctx context.Context,
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, beacon)
}
func (c *BeaconsService) UpdateBeacon(ctx context.Context,
beacon data1.BeaconV1) (data1.BeaconV1, error) {
if beacon.Type == "" {
beacon.Type = data1.Unknown
}
return c.persistence.Update(ctx, beacon)
}
func (c *BeaconsService) DeleteBeaconById(ctx context.Context,
beaconId string) (data1.BeaconV1, error) {
return c.persistence.DeleteById(ctx, beaconId)
}
/src/logic/BeaconsService.py
from typing import List, Any, Optional
from pip_services4_rpc.commands import ICommandable, CommandSet
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_data.query import FilterParams, PagingParams, DataPage
from pip_services4_components.refer import IReferenceable, Descriptor, IReferences
from pip_services4_components.context import IContext
from ..data.version1 import BeaconV1
from ..logic.BeaconsCommandSet import BeaconsCommandSet
from ..logic.IBeaconsController import IBeaconsController
from ..persistence import IBeaconsPersistence
class BeaconsService(IBeaconsService, 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, context: Optional[IContext], filter: FilterParams, paging: PagingParams) -> DataPage:
return self.__persistence.get_page_by_filter(context, filter, paging)
def get_beacon_by_id(self, context: Optional[IContext], id: str) -> BeaconV1:
return self.__persistence.get_one_by_id(context, id)
def get_beacon_by_udi(self, context: Optional[IContext], udi: str) -> BeaconV1:
return self.__persistence.get_one_by_udi(context, udi)
def calculate_position(self, context: Optional[IContext], site_id: str, udis: List[str]) -> Any:
if udis is None or len(udis) == 0:
return None
result = self.__persistence.get_page_by_filter(context,
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, context: Optional[IContext], entity: BeaconV1) -> BeaconV1:
return self.__persistence.create(context, entity)
def update_beacon(self, context: Optional[IContext], entity: BeaconV1) -> BeaconV1:
return self.__persistence.update(context, entity)
def delete_beacon_by_id(self, context: Optional[IContext], id: str) -> BeaconV1:
return self.__persistence.delete_by_id(context, id)
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/service/BeaconsCommandSet.ts
import { CommandSet } from 'pip-services4-rpc-node';IBeaconsService
import { ICommand } from 'pip-services4-rpc-node';
import { Command } from 'pip-services4-rpc-node';
import { ObjectSchema } from 'pip-services4-data-node';
import { FilterParamsSchema } from 'pip-services4-data-node';
import { PagingParamsSchema } from 'pip-services4-data-node';
import { ArraySchema } from 'pip-services4-data-node';
import { TypeCode } from 'pip-services4-commons-node';
import { Parameters } from 'pip-services4-components-node';
import { IContext } from 'pip-services4-components-node';
import { FilterParams } from 'pip-services4-data-node';
import { PagingParams } from 'pip-services4-data-node';
import { BeaconV1Schema } from '../../src/data/version1/BeaconV1Schema';
import { IBeaconsService } from '../../src/service/IBeaconsService';
export class BeaconsCommandSet extends CommandSet {
private _service: IBeaconsService;
constructor(service: IBeaconsService) {
super();
this._service = service;
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 (ctx: IContext, args: Parameters) => {
let filter = FilterParams.fromValue(args.get('filter'));
let paging = PagingParams.fromValue(args.get('paging'));
return await this._service.getBeacons(ctx, filter, paging);
}
);
}
private makeGetBeaconByIdCommand(): ICommand {
return new Command(
'get_beacon_by_id',
new ObjectSchema(true)
.withRequiredProperty('beacon_id', TypeCode.String),
async (ctx: IContext, args: Parameters) => {
let beaconId = args.getAsString('beacon_id');
return await this._service.getBeaconById(ctx, beaconId);
}
);
}
private makeGetBeaconByUdiCommand(): ICommand {
return new Command(
'get_beacon_by_udi',
new ObjectSchema(true)
.withRequiredProperty('udi', TypeCode.String),
async (ctx: IContext, args: Parameters) => {
let udi = args.getAsString('udi');
return await this._service.getBeaconByUdi(ctx, udi);
}
);
}
private makeCalculatePositionCommand(): ICommand {
return new Command(
'calculate_position',
new ObjectSchema(true)
.withRequiredProperty('site_id', TypeCode.String)
.withRequiredProperty('udis', new ArraySchema(TypeCode.String)),
async (ctx: IContext, args: Parameters) => {
let siteId = args.getAsString('site_id');
let udis = args.getAsObject('udis');
return await this._service.calculatePosition(ctx, siteId, udis);
}
);
}
private makeCreateBeaconCommand(): ICommand {
return new Command(
'create_beacon',
new ObjectSchema(true)
.withRequiredProperty('beacon', new BeaconV1Schema()),
async (ctx: IContext, args: Parameters) => {
let beacon = args.getAsObject('beacon');
return await this._service.createBeacon(ctx, beacon);
}
);
}
private makeUpdateBeaconCommand(): ICommand {
return new Command(
'update_beacon',
new ObjectSchema(true)
.withRequiredProperty('beacon', new BeaconV1Schema()),
async (ctx: IContext, args: Parameters) => {
let beacon = args.getAsObject('beacon');
return await this._service.updateBeacon(ctx, beacon);
}
);
}
private makeDeleteBeaconByIdCommand(): ICommand {
return new Command(
'delete_beacon_by_id',
new ObjectSchema(true)
.withRequiredProperty('beacon_id', TypeCode.String),
async (ctx: IContext, args: Parameters) => {
let beaconId = args.getAsString('beacon_id');
return await this._service.deleteBeaconById(ctx, beaconId);
}
);
}
}
/logic/BeaconsCommandSet.ts
package logic
import (
"context"
"strings"
data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
cconv "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/convert"
exec "github.com/pip-services4/pip-services4-go/pip-services4-components-go/exec"
cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
cvalid "github.com/pip-services4/pip-services4-go/pip-services4-data-go/validate"
ccmd "github.com/pip-services4/pip-services4-go/pip-services4-rpc-go/commands"
)
type BeaconsCommandSet struct {
ccmd.CommandSet
controller IBeaconsService
beaconConvertor cconv.IJSONEngine[data1.BeaconV1]
}
func NewBeaconsCommandSet(controller IBeaconsService) *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, args *exec.Parameters) (result any, err error) {
filter := cquery.NewEmptyFilterParams()
paging := cquery.NewEmptyPagingParams()
if _val, ok := args.Get("filter"); ok {
filter = cquery.NewFilterParamsFromValue(_val)
}
if _val, ok := args.Get("paging"); ok {
paging = cquery.NewPagingParamsFromValue(_val)
}
return c.controller.GetBeacons(ctx, *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, args *exec.Parameters) (result any, err error) {
return c.controller.GetBeaconById(ctx, 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, args *exec.Parameters) (result any, err error) {
return c.controller.GetBeaconByUdi(ctx, 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, args *exec.Parameters) (result any, err error) {
return c.controller.CalculatePosition(
ctx,
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, args *exec.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, beacon)
})
}
func (c *BeaconsCommandSet) makeUpdateBeaconCommand() ccmd.ICommand {
return ccmd.NewCommand(
"update_beacon",
cvalid.NewObjectSchema().
WithRequiredProperty("beacon", data1.NewBeaconV1Schema()),
func(ctx context.Context, args *exec.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, 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, args *exec.Parameters) (result any, err error) {
return c.controller.DeleteBeaconById(ctx, args.GetAsString("beacon_id"))
})
}
/src/logic/BeaconsCommandSet.py
from pip_services4_rpc.commands import CommandSet, Command, ICommand
from pip_services4_commons.convert import TypeCode
from pip_services4_data.query import FilterParams, PagingParams
from pip_services4_data.validate import ObjectSchema, FilterParamsSchema, PagingParamsSchema, ArraySchema
from . import IBeaconsController
from ..data.version1 import BeaconV1Schema
class BeaconsCommandSet(CommandSet):
def __init__(self, service: IBeaconsService):
super(BeaconsCommandSet, self).__init__()
self.__controller: IBeaconsService = service
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(context, args):
filter = FilterParams.from_value(args.get("filter"))
paging = PagingParams.from_value(args.get("paging"))
return self.__service.get_beacons_by_filter(context, 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(context, args):
id = args.get_as_string("id")
return self.__service.get_beacon_by_id(context, 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(context, args):
id = args.get_as_string("udi")
return self.__service.get_beacon_by_udi(context, id)
return Command("get_beacon_by_udi", ObjectSchema().with_required_property("udi", TypeCode.String), handler)
def __make_calculate_position_command(self) -> ICommand:
def handler(context, args):
site_id = args.get_as_string("site_id")
udis = args.get_as_nullable_string("udis")
return self.__service.calculate_position(context, 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(context, args):
entity = args.get("beacon")
return self.__service.create_beacon(context, entity)
return Command("create_beacon", ObjectSchema().with_optional_property("beacon", BeaconV1Schema()), handler)
def __make_update_beacon_command(self) -> ICommand:
def handler(context, args):
entity = args.get("beacon")
return self.__service.update_beacon(context, entity)
return Command("update_beacon", ObjectSchema().with_optional_property("beacon", BeaconV1Schema()), handler)
def __make_delete_beacon_by_id_command(self) -> ICommand:
def handler(context, args):
id = args.get_as_string("id")
return self.__service.delete_beacon_by_id(context, id)
return Command("delete_beacon_by_id", ObjectSchema().with_required_property("id", TypeCode.String), handler)
To sum up this class’s code: we’re creating commands for each of the service’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:
context
: IContext – 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 service. The code for testing the service is listed below:
/test/service/BeaconsService.test.ts
const assert = require('chai').assert;
import { ConfigParams } from 'pip-services4-components-node';
import { Descriptor } from 'pip-services4-components-node';
import { References } from 'pip-services4-components-node';
import { FilterParams } from 'pip-services4-data-node';
import { PagingParams } from 'pip-services4-data-node';
import { BeaconV1 } from '../../src/data/version1/BeaconV1';
import { BeaconTypeV1 } from '../../src/data/version1/BeaconTypeV1';
import { BeaconsMemoryPersistence } from '../../src/persistence/BeaconsMemoryPersistence';
import { BeaconsService } from '../../src/service/BeaconsService';
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('BeaconsService', () => {
let persistence: BeaconsMemoryPersistence;
let service: BeaconsService;
setup(async () => {
persistence = new BeaconsMemoryPersistence();
persistence.configure(new ConfigParams());
service = new BeaconsService();
service.configure(new ConfigParams());
let references = References.fromTuples(
new Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'), persistence,
new Descriptor('beacons', 'service', 'default', 'default', '1.0'), service
);
service.setReferences(references);
await persistence.open(null);
});
teardown(async () => {
await persistence.close(null);
});
test('CRUD Operations', async () => {
// Create the first beacon
let beacon = await service.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 service.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 service.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 service.updateBeacon(
null,
beacon1
);
assert.isObject(beacon);
assert.equal(beacon1.id, beacon.id);
assert.equal('ABC', beacon.label);
// Get beacon by udi
beacon = await service.getBeaconByUdi(
null,
beacon1.udi
);
assert.isObject(beacon);
assert.equal(beacon1.id, beacon.id);
// Delete the beacon
beacon = await service.deleteBeaconById(
null,
beacon1.id
);
assert.isObject(beacon);
assert.equal(beacon1.id, beacon.id);
// Try to get deleted beacon
beacon = await service.getBeaconById(
null,
beacon1.id
);
assert.isNull(beacon || null);
});
test('Calculate Positions', async () => {
// Create the first beacon
let beacon = await service.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 service.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 service.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 service.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/logic/BeaconsController_test.go
package test_logic
import (
"context"
"testing"
data1 "github.com/pip-services-samples/service-beacons-go/data/version1"
persist "github.com/pip-services-samples/service-beacons-go/persistence"
logic "github.com/pip-services-samples/service-beacons-go/service"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
"github.com/stretchr/testify/assert"
)
type BeaconsServiceTest struct {
BEACON1 *data1.BeaconV1
BEACON2 *data1.BeaconV1
persistence *persist.BeaconsMemoryPersistence
service *logic.BeaconsService
}
func newBeaconsServiceTest() *BeaconsServiceTest {
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())
service := logic.NewBeaconsService()
service.Configure(context.Background(), cconf.NewEmptyConfigParams())
references := cref.NewReferencesFromTuples(
context.Background(),
cref.NewDescriptor("beacons", "persistence", "memory", "default", "1.0"), persistence,
cref.NewDescriptor("beacons", "service", "default", "default", "1.0"), service,
)
service.SetReferences(context.Background(), references)
return &BeaconsServiceTest{
BEACON1: BEACON1,
BEACON2: BEACON2,
persistence: persistence,
service: service,
}
}
func (c *BeaconsServiceTest) 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 *BeaconsServiceTest) teardown(t *testing.T) {
err := c.persistence.Close(context.Background())
if err != nil {
t.Error("Failed to close persistence", err)
}
}
func (c *BeaconsServiceTest) testCrudOperations(t *testing.T) {
var beacon1 data1.BeaconV1
// Create the first beacon
beacon, err := c.service.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.service.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.service.GetBeacons(context.Background(), *cquery.NewEmptyFilterParams(), *cquery.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.service.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.service.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.service.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.service.GetBeaconById(context.Background(), beacon1.Id)
assert.Nil(t, err)
assert.Equal(t, data1.BeaconV1{}, beacon)
}
func (c *BeaconsServiceTest) testCalculatePositions(t *testing.T) {
// Create the first beacon
beacon, err := c.service.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.service.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.service.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.service.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 TestBeaconsService(t *testing.T) {
c := newBeaconsServiceTest()
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/test_BeaconsService.py
from pip_services4_data.query import FilterParams, PagingParams
from pip_services4_components.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 TestBeaconsService():
_persistence: BeaconsMemoryPersistence
_service: BeaconsService
@classmethod
def setup_class(cls):
cls._persistence = BeaconsMemoryPersistence()
cls._service = BeaconsService()
references = References.from_tuples(Descriptor('beacons', 'persistence', 'memory', 'default', '1.0'),
cls._persistence,
Descriptor('beacons', 'service', 'default', 'default', '1.0'),
cls._controller)
cls._service.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._service.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._service.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._service.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._service.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._service.delete_beacon_by_id(None, beacon1.id)
# Try to get deleted beacon
beacon = self._service.get_beacon_by_id(None, beacon1.id)
assert beacon is None
def test_calculate_position(self):
# Create the first beacon
beacon1 = self._service.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._service.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._service.calculate_position(None, '1', ['00001'])
assert position is not None
assert "Point" == position["type"]
assert 2 == len(position["coordinates"])
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.