Step 4. Implementing persistence components

In this step, we’ll be creating components for persisting the data model we defined in the previous step. In our projects, we always create at least two persistences: one for storing data in-memory (used for testing), and another for storing data in an external database (used in production). With the Beacons example, we’ll be doing the same.

Let’s start by navigating to the src directory and creating a persistence directory inside it. This directory is going to contain all of the files that relate to this step of the tutorial.

The first thing we are going to do is define what functionality our persistent storage should have. We can define these in a form of an interface and name it IBeaconsPersistence

/src/persistence/IBeaconsPersistence.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 '../data/version1/BeaconV1';

export interface IBeaconsPersistence {
    getPageByFilter(correlationId: string, filter: FilterParams,
        paging: PagingParams): Promise<DataPage<BeaconV1>>;
    
    getOneById(correlationId: string, id: string): Promise<BeaconV1>;
    
    getOneByUdi(correlationId: string, udi: string): Promise<BeaconV1>;

    create(correlationId: string, item: BeaconV1): Promise<BeaconV1>;
    
    update(correlationId: string, item: BeaconV1): Promise<BeaconV1>;

    deleteById(correlationId: string, id: string): Promise<BeaconV1>;
}

src/interface/persistence/IBeaconsPersistence.cs

namespace Beacons.Persistence
{
    public interface IBeaconsPersistence
    {
        Task<DataPage<BeaconV1>> GetPageByFilterAsync(string correlationId, FilterParams filter, PagingParams paging);
        Task<BeaconV1> GetOneByIdAsync(string correlationId, string id);
        Task<BeaconV1> GetOneByUdiAsync(string correlationId, string udi);
        Task<BeaconV1> CreateAsync(string correlationId, BeaconV1 item);
        Task<BeaconV1> UpdateAsync(string correlationId, BeaconV1 item);
        Task<BeaconV1> DeleteByIdAsync(string correlationId, string id);
    }
}

/persistence/IBeaconsPersistence.go

package persistence

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 IBeaconsPersistence interface {
	GetPageByFilter(ctx context.Context, correlationId string, filter cdata.FilterParams, paging cdata.PagingParams) (cdata.DataPage[data1.BeaconV1], error)

	GetOneById(ctx context.Context, correlationId string, id string) (data1.BeaconV1, error)

	GetOneByUdi(ctx context.Context, correlationId string, udi string) (data1.BeaconV1, error)

	Create(ctx context.Context, correlationId string, item data1.BeaconV1) (data1.BeaconV1, error)

	Update(ctx context.Context, correlationId string, item data1.BeaconV1) (data1.BeaconV1, error)

	DeleteById(ctx context.Context, correlationId string, id string) (data1.BeaconV1, error)
}


/lib/persistence/IBeaconsPersistence.dart

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

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

  Future<BeaconV1> getOneById(String correlationId, String id);

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

  Future<BeaconV1> create(String correlationId, BeaconV1 item);

  Future<BeaconV1> update(String correlationId, BeaconV1 item);

  Future<BeaconV1> deleteById(String correlationId, String id);
}


/src/persistence/IBeaconsPersistence.py

from typing import Optional

from pip_services3_commons.data import PagingParams, FilterParams, DataPage

from src.data.version1 import BeaconV1


class IBeaconsPersistence:

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

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

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

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

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

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

The first persistence to implement this interface will be the memory persistence, which we will name BeaconsMemoryPersistence. This class will need to extend the IdentifiableMemoryPersistence class from the pip-services3-data module, and have a few additional functions added to it. One of these functions will be used to create filters for the getPageByFilter method that we’re going to override from the parent class. This function will be called composeFilter, as it’s going to allow us to filter data in accordance with the received filtering parameters. The overriding getPageByFilter method then simply calls the parent’s method, passing the composeFilter function as a filter parameter. The second function that we will need to implement is the getOneByUdi method, whose purpose will be to retrieve a beacon by its udi.

The resulting code for this class is listed below:

/src/persistence/BeaconsMemoryPersistence.ts

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

import { IdentifiableMemoryPersistence } from 'pip-services3-data-nodex';

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

export class BeaconsMemoryPersistence
    extends IdentifiableMemoryPersistence<BeaconV1, string>
    implements IBeaconsPersistence {

    constructor() {
        super();

        this._maxPageSize = 1000;
    }

    private composeFilter(filter: FilterParams): any {
        filter = filter || new FilterParams();

        let id = filter.getAsNullableString('id');
        let siteId = filter.getAsNullableString('site_id');
        let label = filter.getAsNullableString('label');
        let udi = filter.getAsNullableString('udi');
        let udis = filter.getAsObject('udis');
        if (typeof udis === "string") {
            udis = udis.split(',');
        }
        if (!Array.isArray(udis)) {
            udis = null;
        }

        return (item) => {
            if (id != null && item.id != id)
                return false;
            if (siteId != null && item.site_id != siteId)
                return false;
            if (label != null && item.label != label)
                return false;
            if (udi != null && item.udi != udi)
                return false;
            if (udis != null && udis.indexOf(item.udi) < 0)
                return false;
            return true;
        };
    }

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

    public async getOneByUdi(correlationId: string, udi: string): Promise<BeaconV1> {        
        let item = this._items.find((item) => item.udi == udi);

        if (item != null) this._logger.trace(correlationId, "Found beacon by %s", udi);
        else this._logger.trace(correlationId, "Cannot find beacon by %s", udi);

       return item;
    }

}

src/interface/persistence/BeaconsMemoryPersistence.cs

namespace Beacons.Persistence
{
    public class BeaconsMemoryPersistence : IdentifiableMemoryPersistence<BeaconV1, string>, IBeaconsPersistence
    {
        public BeaconsMemoryPersistence()
        {
            _maxPageSize = 1000;
        }

        private List<Func<BeaconV1, bool>> ComposeFilter(FilterParams filter)
        {
            filter = filter ?? new FilterParams();

            var id = filter.GetAsNullableString("id");
            var siteId = filter.GetAsNullableString("site_id");
            var label = filter.GetAsNullableString("label");
            var udi = filter.GetAsNullableString("udi");

            var udis = filter.GetAsNullableString("udis");
            var udiList = udis != null ? udis.Split(',') : null;

            return new List<Func<BeaconV1, bool>>() {
                (item) =>
                {
                    if (id != null && item.Id != id)
                        return false;
                    if (siteId != null && item.SiteId != siteId)
                        return false;
                    if (label != null && item.Label != label)
                        return false;
                    if (udi != null && item.Udi != udi)
                        return false;
                    if (udiList != null && !udiList.Contains(item.Udi))
                        return false;
                    return true;
                }
            };
        }

        public Task<DataPage<BeaconV1>> GetPageByFilterAsync(string correlationId, FilterParams filter, PagingParams paging)
        {
            return base.GetPageByFilterAsync(correlationId, ComposeFilter(filter), paging);
        }

        public async Task<BeaconV1> GetOneByUdiAsync(string correlationId, string udi)
        {
            BeaconV1 item = null;

            lock (_lock)
            {
                item = _items.Find((beacon) => { return beacon.Udi == udi; });
            }

            if (item != null) _logger.Trace(correlationId, "Found beacon by {0}", udi);
            else _logger.Trace(correlationId, "Cannot find beacon by {0}", udi);

            return await Task.FromResult(item);
        }
    }
}

/persistence/BeaconsMemoryPersistence.go

package persistence

import (
	"context"
	"strings"

	data1 "github.com/pip-services-samples/service-beacons-gox/data/version1"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	cpersist "github.com/pip-services3-gox/pip-services3-data-gox/persistence"
)

type BeaconsMemoryPersistence struct {
	*cpersist.IdentifiableMemoryPersistence[data1.BeaconV1, string]
}

func NewBeaconsMemoryPersistence() *BeaconsMemoryPersistence {
	c := &BeaconsMemoryPersistence{
		IdentifiableMemoryPersistence: cpersist.NewIdentifiableMemoryPersistence[data1.BeaconV1, string](),
	}
	c.IdentifiableMemoryPersistence.MaxPageSize = 1000
	return c
}

func (c *BeaconsMemoryPersistence) composeFilter(filter cdata.FilterParams) func(beacon data1.BeaconV1) bool {

	id := filter.GetAsString("id")
	siteId := filter.GetAsString("site_id")
	label := filter.GetAsString("label")
	udi := filter.GetAsString("udi")

	var udiValues []string
	if _udis, ok := filter.GetAsObject("udis"); ok {
		if _val, ok := _udis.([]string); ok {
			udiValues = _val
		}
		if _val, ok := _udis.(string); ok {
			udiValues = strings.Split(_val, ",")
		}

	}

	return func(beacon data1.BeaconV1) bool {
		if id != "" && beacon.Id != id {
			return false
		}
		if siteId != "" && beacon.SiteId != siteId {
			return false
		}
		if label != "" && beacon.Label != label {
			return false
		}
		if udi != "" && beacon.Udi != udi {
			return false
		}
		if len(udiValues) > 0 && !ContainsStr(udiValues, beacon.Udi) {
			return false
		}
		return true
	}
}

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

	return c.IdentifiableMemoryPersistence.
		GetPageByFilter(ctx, correlationId, c.composeFilter(filter), paging, nil, nil)
}

func (c *BeaconsMemoryPersistence) GetOneByUdi(ctx context.Context, correlationId string, udi string) (data1.BeaconV1, error) {
	var item *data1.BeaconV1
	for _, beacon := range c.IdentifiableMemoryPersistence.Items {
		if beacon.Udi == udi {
			_item := beacon.Clone()
			item = &_item
			break
		}
	}

	if item != nil {
		c.IdentifiableMemoryPersistence.Logger.Trace(ctx, correlationId, "Found beacon by %s", udi)
	} else {
		c.IdentifiableMemoryPersistence.Logger.Trace(ctx, correlationId, "Cannot find beacon by %s", udi)
	}

	return *item, nil
}

func ContainsStr(arr []string, substr string) bool {
	for _, _str := range arr {
		if _str == substr {
			return true
		}
	}
	return false
}

/lib/persistence/BeaconsMemoryPersistence.dart

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

class BeaconsMemoryPersistence
    extends IdentifiableMemoryPersistence<BeaconV1, String>
    implements IBeaconsPersistence {
  BeaconsMemoryPersistence() : super() {
    maxPageSize = 1000;
  }

  Function composeFilter(FilterParams filter) {
    filter = filter ?? FilterParams();

    var id = filter.getAsNullableString('id');
    var siteId = filter.getAsNullableString('site_id');
    var label = filter.getAsNullableString('label');
    var udi = filter.getAsNullableString('udi');
    var labelLike = filter.getAsNullableString('label_like');
    var udis = filter.getAsObject('udis');
    if (udis != null && udis is String) {
      udis = (udis as String).split(',');
    }
    if (udis != null && !(udis is List)) {
      udis = null;
    }

    return (item) {
      if (id != null && item.id != id) {
        return false;
      }
      if (siteId != null && item.site_id != siteId) {
        return false;
      }
      if (label != null && item.label != label) {
        return false;
      }

      if (labelLike != null) {
        var regexp = RegExp(r'^' + labelLike, caseSensitive: false);
        if (regexp.allMatches(item.label).isEmpty) {
          return false;
        }
      }

      if (udi != null && item.udi != udi) {
        return false;
      }
      if (udis != null && (udis as List).indexOf(item.udi) < 0) {
        return false;
      }
      return true;
    };
  }

  @override
  Future<DataPage<BeaconV1>> getPageByFilter(
      String correlationId, FilterParams filter, PagingParams paging) {
    return super
        .getPageByFilterEx(correlationId, composeFilter(filter), paging, null);
  }

  @override
  Future<BeaconV1> getOneByUdi(String correlationId, String udi) async {
    var item = items.firstWhere((item) => item.udi == udi);

    if (item != null) {
      logger.trace(correlationId, 'Found beacon by %s', [udi]);
    } else {
      logger.trace(correlationId, 'Cannot find beacon by %s', [udi]);
    }

    return item;
  }
}

/src/persistence/BeaconsMemoryPersistence.py

from typing import Optional, Any, Callable

from pip_services3_commons.data import FilterParams, DataPage, PagingParams
from pip_services3_data.persistence import IdentifiableMemoryPersistence

from .IBeaconsPersistence import IBeaconsPersistence
from ..data.version1 import BeaconV1


class BeaconsMemoryPersistence(IdentifiableMemoryPersistence, IBeaconsPersistence):

    def __init__(self):
        super(BeaconsMemoryPersistence, self).__init__()
        self._max_page_size = 1000

    def __compose_filter(self, filter: FilterParams) -> Callable:
        filter = filter if filter is not None else FilterParams()

        id = filter.get_as_nullable_string("id")
        site_id = filter.get_as_nullable_string("site_id")
        label = filter.get_as_nullable_string("label")
        udi = filter.get_as_nullable_string("udi")
        udis = filter.get_as_object("udis")
        if udis is not None and len(udis) > 0:
            udis = udis.split(",")

        def filter_beacons(item):
            if id is not None and item.id != id:
                return False
            if site_id is not None and item.site_id != site_id:
                return False
            if label is not None and item.label != label:
                return False
            if udi is not None and item.udi != udi:
                return False
            if udis is not None and item.udi not in udis:
                return False
            return True

        return filter_beacons

    def get_page_by_filter(self, correlation_id: Optional[str], filter: FilterParams, paging: PagingParams,
                           sort: Any = None, select: Any = None) -> DataPage:

        return super(BeaconsMemoryPersistence, self).get_page_by_filter(correlation_id,
                                                                        self.__compose_filter(filter), paging=paging)

    def get_one_by_udi(self, correlation_id: Optional[str], udi: str) -> BeaconV1:
        if udi is None:
            return None
        for item in self._items:
            if udi == item.udi:
                return item

Not available

And that’s pretty much it for the memory persistence.

Now let’s move on to something a bit more sophisticated - a MongoDB persistence. Here we’re also going to use an already existing base class, IdentifiableMongoDbPersistence, from the pip-services3-mongodb module, and write a few functions, the most important of which will be composeFilter. This time around, its implementation is going to contain syntax for creating database requests. The resulting code for this class is listed below:

/src/persistence/BeaconsMongoDbPersistence.ts

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

import { IdentifiableMongoDbPersistence } from 'pip-services3-mongodb-nodex';

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

export class BeaconsMongoDbPersistence
    extends IdentifiableMongoDbPersistence<BeaconV1, string>
    implements IBeaconsPersistence {

    constructor() {
        super('beacons');
        this._maxPageSize = 1000;
    }

    private composeFilter(filter: FilterParams): any {
        filter = filter || new FilterParams();

        let criteria = [];

        let id = filter.getAsNullableString('id');
        if (id != null) {
            criteria.push({ _id: id });
        }

        let siteId = filter.getAsNullableString('site_id');
        if (siteId != null) {
            criteria.push({ site_id: siteId });
        }

        let label = filter.getAsNullableString('label');
        if (label != null) {
            criteria.push({ label: label });
        }

        let udi = filter.getAsNullableString('udi');
        if (udi != null) {
            criteria.push({ udi: udi });
        }

        let udis = filter.getAsObject('udis');
        if (typeof udis === "string") {
            udis = udis.split(',');
        }
        if (Array.isArray(udis)) {
            criteria.push({ udi: { $in: udis } });
        }

        return criteria.length > 0 ? { $and: criteria } : null;
    }

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

    public getOneByUdi(correlationId: string, udi: string): Promise<BeaconV1> {
        let criteria = {
            udi: udi
        };

        return new Promise((resolve, reject) => {
            this._collection.findOne(criteria, (err, item) => {
                if (err != null) {
                    reject(err);
                    return;
                }

                if (item != null) this._logger.trace(correlationId, "Found beacon by %s", udi);
                else this._logger.trace(correlationId, "Cannot find beacon by %s", udi);
                
                item = this.convertToPublic(item);
                resolve(item);
            });    
        });     
    }
}

src/interface/persistence/BeaconsMongoDbPersistence.cs

namespace Beacons.Persistence
{
    public class BeaconsMongoDbPersistence : IdentifiableMongoDbPersistence<BeaconV1, string>, IBeaconsPersistence
    {
        public BeaconsMongoDbPersistence()
            : base("beacons")
        { }

        private new FilterDefinition<BeaconV1> ComposeFilter(FilterParams filterParams)
        {
            filterParams = filterParams ?? new FilterParams();

            var builder = Builders<BeaconV1>.Filter;
            var filter = builder.Empty;

            var id = filterParams.GetAsNullableString("id");
            if (!string.IsNullOrEmpty(id))
                filter &= builder.Eq(b => b.Id, id);
            
            var siteId = filterParams.GetAsNullableString("site_id");
            if (!string.IsNullOrEmpty(siteId))
                filter &= builder.Eq(b => b.SiteId, siteId);
            
            var label = filterParams.GetAsNullableString("label");
            if (!string.IsNullOrEmpty(label))
                filter &= builder.Eq(b => b.Label, label);
            
            var udi = filterParams.GetAsNullableString("udi");
            if (!string.IsNullOrEmpty(udi))
                filter &= builder.Eq(b => b.Udi, udi);

            var udis = filterParams.GetAsNullableString("udis");
            var udiList = !string.IsNullOrEmpty(udis) ? udis.Split(',') : null;
            if (udiList != null)
                filter &= builder.In(b => b.Udi, udiList);

            return filter;
        }

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

        public async Task<BeaconV1> GetOneByUdiAsync(string correlationId, string udi)
        {
            var builder = Builders<BeaconV1>.Filter;
            var filter = builder.Eq(x => x.Udi, udi);
            var result = await _collection.Find(filter).FirstOrDefaultAsync();

            if (result != null)
                _logger.Trace(correlationId, "Retrieved from {0} with udi = {1}", _collectionName, udi);
            else
                _logger.Trace(correlationId, "Nothing found from {0} with udi = {1}", _collectionName, udi);

            return result;

        }
    }
}

/persistence/BeaconsMongoDbPersistence.go

package persistence

import (
	"context"
	"strings"

	data1 "github.com/pip-services-samples/service-beacons-gox/data/version1"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	cmongo "github.com/pip-services3-gox/pip-services3-mongodb-gox/persistence"
	"go.mongodb.org/mongo-driver/bson"
)

type BeaconsMongoPersistence struct {
	*cmongo.IdentifiableMongoDbPersistence[data1.BeaconV1, string]
}

func NewBeaconsMongoPersistence() *BeaconsMongoPersistence {
	c := &BeaconsMongoPersistence{}
	c.IdentifiableMongoDbPersistence = cmongo.InheritIdentifiableMongoDbPersistence[data1.BeaconV1, string](c, "beacons")
	return c
}

func (c *BeaconsMongoPersistence) GetPageByFilter(ctx context.Context, correlationId string, filter cdata.FilterParams, paging cdata.PagingParams) (cdata.DataPage[data1.BeaconV1], error) {
	filterObj := bson.M{}
	if id, ok := filter.GetAsNullableString("id"); ok && id != "" {
		filterObj["_id"] = id
	}
	if siteId, ok := filter.GetAsNullableString("site_id"); ok && siteId != "" {
		filterObj["site_id"] = siteId
	}
	if typeId, ok := filter.GetAsNullableString("type"); ok && typeId != "" {
		filterObj["type"] = typeId
	}
	if udi, ok := filter.GetAsNullableString("udi"); ok && udi != "" {
		filterObj["udi"] = udi
	}
	if label, ok := filter.GetAsNullableString("label"); ok && label != "" {
		filterObj["label"] = label
	}
	if udis, ok := filter.GetAsObject("udis"); ok {
		var udisM bson.M
		switch _udis := udis.(type) {
		case []string:
			if len(_udis) > 0 {
				udisM = bson.M{"$in": _udis}
			}
		case string:
			if _udisArr := strings.Split(_udis, ","); len(_udisArr) > 0 {
				udisM = bson.M{"$in": _udisArr}
			}
		}
		if udisM != nil {
			filterObj["udi"] = udisM
		}
	}

	return c.IdentifiableMongoDbPersistence.GetPageByFilter(ctx, correlationId,
		filterObj, paging,
		nil, nil,
	)
}

func (c *BeaconsMongoPersistence) GetOneByUdi(ctx context.Context, correlationId string, udi string) (data1.BeaconV1, error) {

	paging := *cdata.NewPagingParams(0, 1, false)
	page, err := c.IdentifiableMongoDbPersistence.GetPageByFilter(ctx, correlationId,
		bson.M{"udi": udi}, paging,
		nil, nil,
	)
	if err != nil {
		return data1.BeaconV1{}, err
	}
	if page.HasData() {
		return page.Data[0], nil
	}
	return data1.BeaconV1{}, nil
}

/lib/persistence/BeaconsMongoDbPersistence.dart

import 'dart:async';
import 'package:mongo_dart_query/mongo_dart_query.dart' as mngquery;
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_mongodb/pip_services3_mongodb.dart';

import '../data/version1/BeaconV1.dart';
import './IBeaconsPersistence.dart';

class BeaconsMongoDbPersistence
    extends IdentifiableMongoDbPersistence<BeaconV1, String>
    implements IBeaconsPersistence {
  BeaconsMongoDbPersistence() : super('beacons') {
    maxPageSize = 1000;
  }

  dynamic composeFilter(FilterParams filter) {
    filter = filter ?? FilterParams();

    var criteria = [];

    var id = filter.getAsNullableString('id');
    if (id != null) {
      criteria.add({'_id': id});
    }

    var siteId = filter.getAsNullableString('site_id');
    if (siteId != null) {
      criteria.add({'site_id': siteId});
    }

    var label = filter.getAsNullableString('label');
    if (label != null) {
      criteria.add({'label': label});
    }

    var udi = filter.getAsNullableString('udi');
    if (udi != null) {
      criteria.add({'udi': udi});
    }

    var labelLike = filter.getAsNullableString('label_like');
    if (labelLike != null) {
      var regexp = RegExp(r'^' + labelLike, caseSensitive: false);
      criteria.add({r'$regex': regexp.pattern});
    }

    var udis = filter.getAsObject('udis');
    if (udis is String) {
      udis = (udis as String).split(',');
    }
    if (udis is List) {
      criteria.add({
        'udi': {r'$in': udis}
      });
    }

    return criteria.isNotEmpty ? {r'$and': criteria} : null;
  }

  @override
  Future<DataPage<BeaconV1>> getPageByFilter(
      String correlationId, FilterParams filter, PagingParams paging) async {
    return super
        .getPageByFilterEx(correlationId, composeFilter(filter), paging, null);
  }

  @override
  Future<BeaconV1> getOneByUdi(String correlationId, String udi) async {
    var filter = {'udi': udi};
    var query = mngquery.SelectorBuilder();
    var selector = <String, dynamic>{};
    if (filter != null && filter.isNotEmpty) {
      selector[r'$query'] = filter;
    }
    query.raw(selector);

    var item = await collection.findOne(filter);

    if (item == null) {
      logger.trace(correlationId, 'Nothing found from %s with id = %s',
          [collectionName, udi]);
      return null;
    }
    logger.trace(
        correlationId, 'Retrieved from %s with id = %s', [collectionName, udi]);
    return convertToPublic(item);
  }
}

/src/persistence/BeaconsMongoDbPersistence.py

from typing import Any, Optional

from pip_services3_commons.data import FilterParams, PagingParams, DataPage
from pip_services3_mongodb.persistence import IdentifiableMongoDbPersistence

from .IBeaconsPersistence import IBeaconsPersistence
from ..data.version1 import BeaconV1


class BeaconsMongoDbPersistence(IdentifiableMongoDbPersistence, IBeaconsPersistence):

    def __init__(self):
        super(BeaconsMongoDbPersistence, self).__init__("beacons")
        self._max_page_size = 1000

    def compose_filter(self, filter: FilterParams) -> Any:
        filter = filter if filter is not None else FilterParams()
        criteria = []

        id = filter.get_as_nullable_string("id")
        if id is not None:
            criteria.append({"id": id})
        site_id = filter.get_as_nullable_string("site_id")
        if site_id is not None:
            criteria.append({"site_id": site_id})
        label = filter.get_as_nullable_string("label")
        if label is not None:
            criteria.append({"label": label})
        udi = filter.get_as_nullable_string("udi")
        if udi is not None:
            criteria.append({"udi": udi})
        udis = filter.get_as_object("udis")
        if udis is not None and len(udis) > 0:
            udis = udis.split(",")
            criteria.append({"udi": {"$in": udis}})
        return {"$and": criteria} if len(criteria) > 0 else None

    def get_page_by_filter(self, correlation_id: Optional[str], filter: FilterParams, paging: PagingParams,
                           sort: Any = None, select: Any = None) -> DataPage:
        filter = filter if filter is not None else FilterParams()
        return super(BeaconsMongoDbPersistence, self).get_page_by_filter(correlation_id, self.compose_filter(filter),
                                                                         paging, None, None)

    def get_one_by_udi(self, correlation_id: Optional[str], udi: str) -> BeaconV1:
        if udi is None:
            return None
        item = self._collection.find_one({'udi': udi})
        item = self._convert_to_public(item)

        if item is None:
            self._logger.trace(correlation_id, "Found beacon by %s", udi)
        else:
            self._logger.trace(correlation_id, "Cannot find beacon by %s", udi)

        return item
Not available

Let’s take a quick look at what’s in this code. A basic set of CRUD operations are already implemented in the data module. There’s minimal code that needs to be written by us as developers for this class: just a filter function, and non-standard methods for searching by a specific data field. The rest of the methods that we defined in our interface are already implemented in the parent class.

To make sure that the code does just what we expect it to do, let’s add some tests. We’ll be placing the files with our tests in the test directory and organizing them into subdirectories, whose names will reflect the components they are testing.

Thanks to the modular structure of microservices, each component is easily testable with the help of simple mock tests. We’ll start with creating a class that contains a set of testable commands and checks the results we receive with the help of standard testing libraries. This class will be accepting any persistence that implements our IBeaconsPersistence interface as a parameter. This way we can use the same set of commands to test both of our persistence implementations. This set of commands should contain standard CRUD operations, which are implemented in the parent class, as well as the methods we’ve added in the child classes.

/test/persistence/BeaconsPersistenceFixture.ts

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

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 { IBeaconsPersistence } from '../../src/persistence/IBeaconsPersistence';

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
};
const BEACON3: BeaconV1 = {
    id: '3',
    udi: '00003',
    type: BeaconTypeV1.AltBeacon,
    site_id: '2',
    label: 'TestBeacon3',
    center: { type: 'Point', coordinates: [ 10, 10 ] },
    radius: 50
};

export class BeaconsPersistenceFixture {
    private _persistence: IBeaconsPersistence;

    public constructor(persistence: IBeaconsPersistence) {
        assert.isNotNull(persistence);
        this._persistence = persistence;
    }

    private async testCreateBeacons() {
        // Create the first beacon
        let beacon = await this._persistence.create(
            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 this._persistence.create(
            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);

        // Create the third beacon
        beacon = await this._persistence.create(
            null,
            BEACON3
        );
        assert.isObject(beacon);
        assert.equal(BEACON3.udi, beacon.udi);
        assert.equal(BEACON3.site_id, beacon.site_id);
        assert.equal(BEACON3.type, beacon.type);
        assert.equal(BEACON3.label, beacon.label);
        assert.isNotNull(beacon.center);
    }

    public async testCrudOperations() {
        // Create items
        await this.testCreateBeacons();

        // Get all beacons
        let page = await this._persistence.getPageByFilter(
            null,
            new FilterParams(),
            new PagingParams()
        );
        assert.isObject(page);
        assert.lengthOf(page.data, 3);

        let beacon1 = page.data[0];

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

        let beacon = await this._persistence.update(
            null,
            beacon1
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);
        assert.equal('ABC', beacon.label);

        // Get beacon by udi
        beacon = await this._persistence.getOneByUdi(
            null, 
            beacon1.udi,
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);

        // Delete the beacon
        beacon = await this._persistence.deleteById(
            null,
            beacon1.id
        );
        assert.isObject(beacon);
        assert.equal(beacon1.id, beacon.id);

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

    public async testGetWithFilters() {
        // Create items
        await this.testCreateBeacons();

        // Filter by id
        let page = await this._persistence.getPageByFilter(
            null,
            FilterParams.fromTuples(
                'id', '1'
            ),
            new PagingParams()
        );
        assert.lengthOf(page.data, 1);

        // Filter by udi
        page = await this._persistence.getPageByFilter(
            null,
            FilterParams.fromTuples(
                'udi', '00002'
            ),
            new PagingParams()
        );
        assert.lengthOf(page.data, 1);

        // Filter by udis
        page = await this._persistence.getPageByFilter(
            null,
            FilterParams.fromTuples(
                'udis', '00001,00003'
            ),
            new PagingParams()
        );
        assert.lengthOf(page.data, 2);

        // Filter by site_id
        page = await this._persistence.getPageByFilter(
            null,
            FilterParams.fromTuples(
                'site_id', '1'
            ),
            new PagingParams()
        );
        assert.lengthOf(page.data, 2);
    }
}

/test/service.test/persistence/BeaconsPersistenceFixture.cs

namespace Beacons.Persistence
{
    public class BeaconsPersistenceFixture
    {
        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 BeaconV1 BEACON3 = new BeaconV1
        {
            Id = "3",
            Udi = "00003",
            Type = BeaconTypeV1.AltBeacon,
            SiteId = "2",
            Label = "TestBeacon3",
            Center = new CenterObjectV1 { Type = "Point", Coordinates = new double[] { 10, 10 } },
            Radius = 50
        };

        private IBeaconsPersistence _persistence;

        public BeaconsPersistenceFixture(IBeaconsPersistence persistence)
        {
            _persistence = persistence;
        }

        private async Task TestCreateBeaconsAsync()
        {
            // Create the first beacon
            var beacon = await _persistence.CreateAsync(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 _persistence.CreateAsync(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);

            // Create the third beacon
            beacon = await _persistence.CreateAsync(null, BEACON3);

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

        public async Task TestCrudOperationsAsync()
        {
            // Create items
            await TestCreateBeaconsAsync();

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

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

            var beacon1 = page.Data[0];

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

            var beacon = await _persistence.UpdateAsync(null, beacon1);

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

            // Get beacon by udi
            beacon = await _persistence.GetOneByUdiAsync(null, beacon1.Udi);

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

            // Delete the beacon
            beacon = await _persistence.DeleteByIdAsync(null, beacon1.Id);

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

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

            Assert.Null(beacon);
        }

        public async Task TestGetWithFiltersAsync()
        {
            // Create items
            await TestCreateBeaconsAsync();

            // Filter by id
            var page = await _persistence.GetPageByFilterAsync(
                null,
                FilterParams.FromTuples(
                    "id", "1"
                ),
                new PagingParams()
            );

            Assert.Single(page.Data);

            // Filter by udi
            page = await _persistence.GetPageByFilterAsync(
                null,
                FilterParams.FromTuples(
                    "udi", "00002"
                ),
                new PagingParams()
            );

            Assert.Single(page.Data);

            // Filter by udis
            page = await _persistence.GetPageByFilterAsync(
                null,
                FilterParams.FromTuples(
                    "udis", "00001,00003"
                ),
                new PagingParams()
            );

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

            // Filter by site_id
            page = await _persistence.GetPageByFilterAsync(
                null,
                FilterParams.FromTuples(
                    "site_id", "1"
                ),
                new PagingParams()
            );

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

/test/persistence/BeaconsPersistenceFixture.go

package test_persistence

import (
	"context"
	"testing"

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

type BeaconsPersistenceFixture struct {
	BEACON1     *data1.BeaconV1
	BEACON2     *data1.BeaconV1
	BEACON3     *data1.BeaconV1
	persistence persist.IBeaconsPersistence
}

func NewBeaconsPersistenceFixture(persistence persist.IBeaconsPersistence) *BeaconsPersistenceFixture {
	c := BeaconsPersistenceFixture{}

	c.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,
	}

	c.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,
	}

	c.BEACON3 = &data1.BeaconV1{
		Id:     "3",
		Udi:    "00003",
		Type:   data1.AltBeacon,
		SiteId: "2",
		Label:  "TestBeacon3",
		Center: data1.GeoPointV1{Type: "Point", Coordinates: []float32{10.0, 10.0}},
		Radius: 50,
	}

	c.persistence = persistence
	return &c
}

func (c *BeaconsPersistenceFixture) testCreateBeacons(t *testing.T) {
	// Create the first beacon
	beacon, err := c.persistence.Create(context.Background(), "", *c.BEACON1)
	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.persistence.Create(context.Background(), "", *c.BEACON2)
	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)

	// Create the third beacon
	beacon, err = c.persistence.Create(context.Background(), "", *c.BEACON3)
	assert.Nil(t, err)
	assert.NotEqual(t, data1.BeaconV1{}, beacon)
	assert.Equal(t, c.BEACON3.Udi, beacon.Udi)
	assert.Equal(t, c.BEACON3.SiteId, beacon.SiteId)
	assert.Equal(t, c.BEACON3.Type, beacon.Type)
	assert.Equal(t, c.BEACON3.Label, beacon.Label)
	assert.NotNil(t, beacon.Center)
}

func (c *BeaconsPersistenceFixture) TestCrudOperations(t *testing.T) {
	var beacon1 data1.BeaconV1

	// Create items
	c.testCreateBeacons(t)

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

	// Update the beacon
	beacon1.Label = "ABC"
	beacon, err := c.persistence.Update(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.persistence.GetOneByUdi(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.persistence.DeleteById(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.persistence.GetOneById(context.Background(), "", beacon1.Id)
	assert.Nil(t, err)
	assert.Equal(t, data1.BeaconV1{}, beacon)
}

func (c *BeaconsPersistenceFixture) TestGetWithFilters(t *testing.T) {
	// Create items
	c.testCreateBeacons(t)

	// Get all beacons
	page, err := c.persistence.GetPageByFilter(context.Background(), "",
		*cdata.NewEmptyFilterParams(), *cdata.NewEmptyPagingParams())
	assert.Nil(t, err)
	assert.NotNil(t, page)
	assert.True(t, page.HasData())

	beacon := page.Data[0]

	filter := *cdata.NewFilterParamsFromTuples(
		"id", beacon.Id,
	)
	// Filter by id
	page, err = c.persistence.GetPageByFilter(context.Background(), "",
		filter,
		*cdata.NewEmptyPagingParams())
	assert.Nil(t, err)
	assert.True(t, page.HasData())
	assert.Len(t, page.Data, 1)

	// Filter by udi
	filter = *cdata.NewFilterParamsFromTuples(
		"udi", "00002",
	)
	page, err = c.persistence.GetPageByFilter(
		context.Background(),
		"",
		filter,
		*cdata.NewEmptyPagingParams())
	assert.Nil(t, err)
	assert.True(t, page.HasData())
	assert.Len(t, page.Data, 1)

	// Filter by udis
	filter = *cdata.NewFilterParamsFromTuples(
		"udis", "00001,00003",
	)
	page, err = c.persistence.GetPageByFilter(
		context.Background(),
		"",
		filter,
		*cdata.NewEmptyPagingParams())

	assert.Nil(t, err)
	assert.True(t, page.HasData())
	assert.Len(t, page.Data, 2)

	// Filter by site_id
	filter = *cdata.NewFilterParamsFromTuples(
		"site_id", "1",
	)
	page, err = c.persistence.GetPageByFilter(
		context.Background(),
		"",
		filter,
		*cdata.NewEmptyPagingParams())

	assert.Nil(t, err)
	assert.Len(t, page.Data, 2)
}

/test/persistence/BeaconsPersistenceFixture.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);
final BEACON3 = BeaconV1(
    id: '3',
    udi: '00003',
    type: BeaconTypeV1.altBeacon,
    site_id: '2',
    label: 'TestBeacon3',
    center: {
      'type': 'Point',
      'coordinates': [10.0, 10.0]
    },
    radius: 50.0);

class BeaconsPersistenceFixture {
  IBeaconsPersistence _persistence;

  BeaconsPersistenceFixture(IBeaconsPersistence persistence) {
    expect(persistence, isNotNull);
    _persistence = persistence;
  }

  void _testCreateBeacons() async {
    // Create the first beacon
    var beacon = await _persistence.create(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 _persistence.create(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);

    // Create the third beacon
    beacon = await _persistence.create(null, BEACON3);
    expect(beacon, isNotNull);
    expect(BEACON3.udi, beacon.udi);
    expect(BEACON3.site_id, beacon.site_id);
    expect(BEACON3.type, beacon.type);
    expect(BEACON3.label, beacon.label);
    expect(beacon.center, isNotNull);
  }

  void testCrudOperations() async {
    BeaconV1 beacon1;

    // Create items
    await _testCreateBeacons();

    // Get all beacons
    var page = await _persistence.getPageByFilter(
        null, FilterParams(), PagingParams());
    expect(page, isNotNull);
    expect(page.data.length, 3);

    beacon1 = page.data[0];

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

    var beacon = await _persistence.update(null, beacon1);
    expect(beacon, isNotNull);
    expect(beacon1.id, beacon.id);
    expect('ABC', beacon.label);

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

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

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

  void testGetWithFilters() async {
    // Create items

    await _testCreateBeacons();

    // Filter by id
    var page = await _persistence.getPageByFilter(
        null, FilterParams.fromTuples(['id', '1']), PagingParams());
    expect(page.data.length, 1);

    // Filter by udi
    page = await _persistence.getPageByFilter(
        null, FilterParams.fromTuples(['udi', '00002']), PagingParams());
    expect(page.data.length, 1);

    // Filter by udis
    page = await _persistence.getPageByFilter(
        null, FilterParams.fromTuples(['udis', '00001,00003']), PagingParams());
    expect(page.data.length, 2);

    // Filter by site_id
    page = await _persistence.getPageByFilter(
        null, FilterParams.fromTuples(['site_id', '1']), PagingParams());
    expect(page.data.length, 2);
  }
}

/test/persistence/BeaconsPersistenceFixture.py

from pip_services3_commons.data import PagingParams, FilterParams

from src.data.version1 import BeaconV1, BeaconTypeV1
from src.persistence import IBeaconsPersistence

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 BeaconsPersistenceFixture():
    _persistence: IBeaconsPersistence = None

    def __init__(self, persistence: IBeaconsPersistence):
        self._persistence = persistence

    def test_create_beacons(self):
        #Create the first beacon
        beacon1 = self._persistence.create(None, BEACON1)

        assert beacon1 != 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 != None

        #Create the second beacon
        beacon2 = self._persistence.create(None, BEACON2)

        assert beacon2 != 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 != None

        #Create the third beacon
        beacon3 = self._persistence.create(None, BEACON3)

        assert beacon3 != None
        assert beacon3.id == BEACON3.id
        assert beacon3.site_id == BEACON3.site_id
        assert beacon3.udi == BEACON3.udi
        assert beacon3.type == BEACON3.type
        assert beacon3.label == BEACON3.label
        assert beacon3.center != None

    def test_crud_operations(self):
        #Create 3 beacons
        self.test_create_beacons()

        #Get all beacons
        page = self._persistence.get_page_by_filter(None, FilterParams(), PagingParams())
        assert page != None
        assert len(page.data) == 3

        beacon1 = page.data[0]

        #Update the beacon
        beacon1['label'] = "ABC"
        beacon = self._persistence.update(None, beacon1)
        assert beacon != None
        assert beacon1.id == beacon.id
        assert "ABC" == beacon.label

        #Get beacon by udi
        beacon = self._persistence.get_one_by_udi(None, beacon1.udi)
        assert beacon != None
        assert beacon.id == beacon1.id

        #Delete beacon
        self._persistence.delete_by_id(None, beacon1.id)

        #Try to get deleted beacon
        beacon = self._persistence.get_one_by_id(None, beacon1.id)
        assert beacon == None

    def test_get_with_filter(self):
        #Create 3 beacons
        self.test_create_beacons()

        #Filter by id
        page = self._persistence.get_page_by_filter(None, FilterParams.from_tuples("id", "1"), PagingParams())
        assert page != None
        assert len(page.data) == 1

        #Filter by udi
        page = self._persistence.get_page_by_filter(None, FilterParams.from_tuples("udi", "00002"), PagingParams())
        assert page != None
        assert len(page.data) == 1

        #Filter by udis
        page = self._persistence.get_page_by_filter(None, FilterParams.from_tuples("udis", '00001,00003'), PagingParams())
        assert page != None
        assert len(page.data) == 2

        #Filter by udi
        page = self._persistence.get_page_by_filter(None, FilterParams.from_tuples("site_id", "1"), PagingParams())
        assert page != None
        assert len(page.data) == 2
Not available

Now that we have a set of tests, we can dive into the testing itself. To do this, we’ll create files for testing each of our persistences and run them.

/test/persistence/BeaconsMemoryPersistence.test.ts

import { ConfigParams } from 'pip-services3-commons-nodex';

import { BeaconsMemoryPersistence } from '../../src/persistence/BeaconsMemoryPersistence';
import { BeaconsPersistenceFixture } from './BeaconsPersistenceFixture';

suite('BeaconsMemoryPersistence', () => {
    let persistence: BeaconsMemoryPersistence;
    let fixture: BeaconsPersistenceFixture;

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

        fixture = new BeaconsPersistenceFixture(persistence);

        await persistence.open(null);
    });

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

    test('CRUD Operations', async () => {
        await fixture.testCrudOperations();
    });

    test('Get with Filters', async () => {
        await fixture.testGetWithFilters();
    });

});

/test/service.test/persistence/BeaconMemoryPersistenceTest.cs

namespace Beacons.Persistence
{
    public class MemoryBeaconsPersistenceTest: IDisposable
    {
        public BeaconsMemoryPersistence _persistence;
        public BeaconsPersistenceFixture _fixture;

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

            _fixture = new BeaconsPersistenceFixture(_persistence);

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

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

        [Fact]
        public async Task TestCrudOperationsAsync()
        {
            await _fixture.TestCrudOperationsAsync();
        }

        [Fact]
        public async Task TestGetWithFiltersAsync()
        {
            await _fixture.TestGetWithFiltersAsync();
        }
    }
}

/test/persistence/BeaconsMemoryPersistence_test.go

package test_persistence

import (
	"context"
	"testing"

	persist "github.com/pip-services-samples/service-beacons-gox/persistence"
	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
)

type BeaconsMemoryPersistenceTest struct {
	persistence *persist.BeaconsMemoryPersistence
	fixture     *BeaconsPersistenceFixture
}

func newBeaconsMemoryPersistenceTest() *BeaconsMemoryPersistenceTest {
	persistence := persist.NewBeaconsMemoryPersistence()
	persistence.Configure(context.Background(), cconf.NewEmptyConfigParams())

	fixture := NewBeaconsPersistenceFixture(persistence)

	return &BeaconsMemoryPersistenceTest{
		persistence: persistence,
		fixture:     fixture,
	}
}

func (c *BeaconsMemoryPersistenceTest) 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 *BeaconsMemoryPersistenceTest) teardown(t *testing.T) {
	err := c.persistence.Close(context.Background(), "")
	if err != nil {
		t.Error("Failed to close persistence", err)
	}
}

func TestBeaconsMemoryPersistence(t *testing.T) {
	c := newBeaconsMemoryPersistenceTest()
	if c == nil {
		return
	}

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

	c.setup(t)
	t.Run("Get With Filters", c.fixture.TestGetWithFilters)
	c.teardown(t)
}

/test/persistence/BeaconsMemoryPersistence_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';
import './BeaconsPersistenceFixture.dart';

void main() {
  group('BeaconsMemoryPersistence', () {
    BeaconsMemoryPersistence persistence;
    BeaconsPersistenceFixture fixture;

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

      fixture = BeaconsPersistenceFixture(persistence);

      await persistence.open(null);
    });

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

    test('CRUD Operations', () async {
      await fixture.testCrudOperations();
    });

    test('Get with Filters', () async {
      await fixture.testGetWithFilters();
    });
  });
}

/test/persistence/test_BeaconMemoryPersistence.py

from src.persistence.BeaconsMemoryPersistence import BeaconsMemoryPersistence
from .BeaconsPersistenceFixture import BeaconsPersistenceFixture


class TestBeaconMemoryPersistence():
    persistence: BeaconsMemoryPersistence
    fixture: BeaconsPersistenceFixture

    @classmethod
    def setup_class(cls):
        cls.persistence = BeaconsMemoryPersistence()
        cls.fixture = BeaconsPersistenceFixture(cls.persistence)

    def setup_method(self, method):
        self.persistence.clear(None)

    def test_crud_operations(self):
        self.fixture.test_crud_operations()

    def test_get_with_filter(self):
        self.fixture.test_get_with_filter()

Not available

To run these tests, run the command npm test from a terminal at the root of the project.

“But where exactly is the data going to be stored when we get the service actually up and running?" you may ask. Jumping ahead, we’ll tell you that the config.yml configuration file takes care of that. It contains configurations for all of the service’s components, such as: which logger to use, where performance counter output should be, what database to connect to and using what parameters, etc. We’ll discuss this in more detail later on in this tutorial.

Now that we can persist our data, let’s move on to Step 5. Implementing a controller.

Step 5. Implementing a controller.