MongoDB Persistence

by Aleksey Dvoykin

Introduction

In our previous tutorials, we took a look at in-memory and file persistence component implementations. Another frequent choice of persistence is Pip.Service’s MongoDb persistence. This persistence stores data in MongoDB - a popular document-oriented database. The most basic implementation of this component is the MongoDbPersistence class defined in the MongoDb module. It is capable of storing a collection of documents, opening and closing connections, and performing a few simple CRUD operations.

MongoDBPersistence

This is a basic component that stores data items of any type. Some basic operations for creating, getting, and deleting are already included. More advanced CRUD operations over the data items can be implemented in child classes by accessing the collection or model properties. This component also contains methods for opening and closing connections using the credentials provided.

The example below demonstrates a class that implements the MongoDB persistence component for the Beacon data model.


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;
    }

    public async 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);
            });    
        });     
    }
}

class BeaconMongoDbPersistence : MongoDbPersistence<BeaconV1>
{
    public BeaconMongoDbPersistence()
    {
        base("beacons");
    }

    public async BeaconV1 GetByNameAsync(string correlationId, string name)
    {
        var builder = Builders<BeaconV1>.Filter;
        var filter = builder.Eq(x => x.Name, name);
        var result = await _collection.Find(filter).FirstOrDefaultAsync();
        return result;
    }

    public async BeaconV1 setAsync(String correlatonId, BeaconV1 item)
    {
        var filter = Builders<BeaconV1>.Filter.Eq(x => x.Id, item.Id);
        var options = new FindOneAndReplaceOptions<T>
        {
            ReturnDocument = ReturnDocument.After,
            IsUpsert = true
        };
        var result = await _collection.FindOneAndReplaceAsync(filter, item, options);
        return result;
    }
}



package persistence

import (
	"reflect"
	"strings"

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

type BeaconsMongoDbPersistence struct {
	mpersist.IdentifiableMongoDbPersistence
}

func NewBeaconsMongoDbPersistence() *BeaconsMongoDbPersistence {
	proto := reflect.TypeOf(&data1.BeaconV1{})
	c := &BeaconsMongoDbPersistence{}
	c.IdentifiableMongoDbPersistence = *mpersist.InheritIdentifiableMongoDbPersistence(c, proto, "beacons")
	return c
}

func (c *BeaconsMongoDbPersistence) composeFilter(filter *cdata.FilterParams) interface{} {
	if filter == nil {
		filter = cdata.NewEmptyFilterParams()
	}

	criteria := make([]bson.M, 0, 0)

	id := filter.GetAsString("id")
	if id != "" {
		criteria = append(criteria, bson.M{"_id": id})
	}

	siteId := filter.GetAsString("site_id")
	if siteId != "" {
		criteria = append(criteria, bson.M{"site_id": siteId})
	}
	label := filter.GetAsString("label")
	if label != "" {
		criteria = append(criteria, bson.M{"label": label})
	}
	udi := filter.GetAsString("udi")
	if udi != "" {
		criteria = append(criteria, bson.M{"udi": udi})
	}

	udis := filter.GetAsString("udis")
	var arrUdis []string = make([]string, 0, 0)
	if udis != "" {
		arrUdis = strings.Split(udis, ",")
		if len(arrUdis) > 1 {
			criteria = append(criteria, bson.M{"udi": bson.D{{"$in", arrUdis}}})
		}
	}
	if len(criteria) > 0 {
		return bson.D{{"$and", criteria}}
	}
	return bson.M{}
}

func (c *BeaconsMongoDbPersistence) GetPageByFilter(correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (page *data1.BeaconV1DataPage, err error) {
	tempPage, resErr := c.IdentifiableMongoDbPersistence.GetPageByFilter(correlationId, c.composeFilter(filter), paging, nil, nil)
	if resErr != nil {
		return nil, resErr
	}

	// Convert to BeaconV1DataPage
	dataLen := int64(len(tempPage.Data)) // For full release tempPage and delete this by GC
	beaconData := make([]*data1.BeaconV1, dataLen)
	for i, v := range tempPage.Data {
		beaconData[i] = v.(*data1.BeaconV1)
	}
	page = data1.NewBeaconV1DataPage(&dataLen, beaconData)
	return page, nil
}

func (c *BeaconsMongoDbPersistence) GetOneByUdi(correlationId string, udi string) (result *data1.BeaconV1, err error) {
	filter := bson.M{"udi": udi}
	docPointer := c.NewObjectByPrototype()
	foRes := c.Collection.FindOne(c.Connection.Ctx, filter)

	ferr := foRes.Decode(docPointer.Interface())
	if ferr != nil {
		if ferr == mongo.ErrNoDocuments {
			return nil, nil
		}
		return nil, ferr
	}

	// Convert to BeaconV1
	item := c.ConvertToPublic(docPointer)
	if item != nil {
		val, _ := item.(*data1.BeaconV1)
		result = val
	}
	return result, nil
}

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.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);
  }
}

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

And this is how we could use such a class:


let persistence = new BeaconsMongoDbPersistence();
// ...

await persistence.open("test");

let beacon = <BeaconV1>{
    id: '1', 
    site_id: "0001",
    udi: "0002"

};

await persistence.set("test", beacon);
let item = persistence.getOneByUdi("test", "0002");
await persistence.close("test");
console.log(item.udi); // Result: 0002


var persistence = new BeaconMongoDbPersistence();
// ...

await persistence.OpenAsync("test");

var beacon = new BeaconV1
{
    name: "Super Beacon"
};
await persistence.SetAsync("test", beacon);
var item = await persistence.GetByNameAsync("test", "Super Beacon");
await persistence.CloseAsync("test");
Console.Out.WriteLine(item);                   // Result: { name: "Super Beacon" }



persistence := persist.NewBeaconsMongoDbPersistence()
// ...

persistence.Open("test")

beacon := &data1.BeaconV1{
	Id:  "1",
	Udi: "0002",
	SiteId: "0001",
}

persistence.Set("test", beacon)
item, _ := persistence.GetOneByUdi("test", "0002")
persistence.Close("test")
fmt.Println(item.Id)
var persistence = BeaconsMongoDbPersistence();
//...

await persistence.open('test');

var beacon = BeaconV1(id: '1', site_id: '0001', udi: '0002');

await persistence.set('test', beacon);
var item = await persistence.getOneByUdi('test', '0002');
await persistence.close('test');
print(item!.udi); // Result: 0002

persistence = BeaconsMongoDbPersistence()
# ...

persistence.open("test")

beacon = BeaconV1(id="1", site_id="0001", udi="0002")

persistence.set("test", beacon)
item = persistence.get_one_by_udi("test", "1")
persistence.close("test")
print(item) 


Not available

Configuring database connections

As mentioned earlier, the MongoDbPersistence contains methods for opening and closing connections. To connect to the appropriate database and collection, we need to first configure the connection with all necessary parameters. MongoDbPersistence uses the MongoDbConnection class for establishing connections.

The MongoDbConnection class provides MongoDB connectivity using a plain driver. To reduce the number of database connections needed, a connection can be defined and then shared through multiple persistence components.

By default, MongoDbPersistence tries to establish a local connection on MongoDb’s default port - 27017. If the desired MongoDb server is located elsewhere, the persistence should be configured with the corresponding host and port information. Persistence configuration can be performed in a number of ways.

The example below demonstrates how the ConfigParams class can be used for persistence configuration. To learn more about this class, and about microservice configuration in general, be sure to read this.


let persistence = new BeaconsMongoDbPersistence();
// Let's say we need to connect to a local MongoDb, but on a non-standard port - 30000

persistence.configure(ConfigParams.fromTuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test"
))
await persistence.open(null) // While opening, it will try to establish a connection with the locally hosted MongoDb on port 30000


var persistence = new BeaconMongoDbPersistence();
// Let's say we need to connect to a local MongoDb, but on a non-standard port - 30000

persistence.Configure(ConfigParams.FromTuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test"
));
await persistence.openAsync(); 	// While opening, it will try to establish a connection with the locally hosted MongoDb on port 30000


persistence := persist.NewBeaconsMongoDbPersistence()

// Let's say we need to connect to a local MongoDb, but on a non-standard port - 30000
persistence.Configure(cconf.NewConfigParamsFromTuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test",
))

// While opening, it will try to establish a connection with the locally hosted MongoDb on port 30000
persistence.Open("123") 

var persistence = BeaconsMongoDbPersistence();
// Let's say we need to connect to a local MongoDb, but on a non-standard port - 30000

persistence.configure(ConfigParams.fromTuples([
  'connection.host',
  'localhost',
  'connection.port',
  '30000',
  'connection.database',
  'test'
]));

// While opening, it will try to establish a connection with the locally hosted MongoDb on port 30000
await persistence.open(null);
persistence = BeaconsMongoDbPersistence()
# Let's say we need to connect to a local MongoDb, but on a non-standard port - 30000

persistence.configure(ConfigParams.from_tuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test"
))
persistence.open(None) # While opening, it will try to establish a connection with the locally hosted MongoDb on port 30000


Not available

Likewise, a connection can be configured using a configuration file. In this case, there exist two approaches:

  1. configuring multiple persistences using a common MongoDbConnection.
  2. configuring a single persistence with its own, private MongoDbConnection.

To perform configuration using a single MongoDbConnection, one of the following descriptors should be used:

pip-services:connection:mongodb:*:1.0 or pip-services3:connection:mongodb:*:1.0.

To learn more about references, descriptors, and component references, follow this link.
First, add an element with the “pip-services” descriptor to the configuration file.

...
# MongoDb Connection
- descriptor: "pip-services:connection:mongodb:default:1.0"
  connection:
    host: localhost
    port: 30000
...

Next, register the persistence as a component in the microservice’s Factory:


export class BeaconsServiceFactory extends Factory{
    public static MemoryPersistenceDescriptor = new Descriptor('beacons', 'persistence', 'memory', '*', '1.0');
    public static FilePersistenceDescriptor = new Descriptor('beacons', 'persistence', 'file', '*', '1.0');
    public static MongoDbPersistenceDescriptor = new Descriptor('beacons', 'persistence', 'mongodb', '*', '1.0');
    public static ControllerDescriptor = new Descriptor('beacons', 'controller', 'default', '*', '1.0');
    public static HttpServiceV1Descriptor = new Descriptor('beacons', 'service', 'http', '*', '1.0');
    
    constructor(){
        super();

        this.registerAsType(BeaconsServiceFactory.MemoryPersistenceDescriptor, BeaconsMemoryPersistence);
        this.registerAsType(BeaconsServiceFactory.FilePersistenceDescriptor, BeaconsFilePersistence);
        this.registerAsType(BeaconsServiceFactory.MongoDbPersistenceDescriptor, BeaconsMongoDbPersistence);
        this.registerAsType(BeaconsServiceFactory.ControllerDescriptor, BeaconsController);
        this.registerAsType(BeaconsServiceFactory.HttpServiceV1Descriptor, BeaconsHttpServiceV1);
    }
}



public class BeaconsServiceFactory : Factory
{
    public static Descriptor Descriptor = new Descriptor("beacons", "factory", "service", "default", "1.0");
    public static Descriptor MemoryPersistenceDescriptor = new Descriptor("beacons", "persistence", "memory", "*", "1.0");
    public static Descriptor MongoDbPersistenceDescriptor = new Descriptor("beacons", "persistence", "mongodb", "*", "1.0");
    public static Descriptor ControllerDescriptor = new Descriptor("beacons", "controller", "default", "*", "1.0");
    public static Descriptor HttpServiceDescriptor = new Descriptor("beacons", "service", "http", "*", "1.0");
    public BeaconsServiceFactory()
    {
        RegisterAsType(MemoryPersistenceDescriptor, typeof(BeaconsMemoryPersistence));
        RegisterAsType(MongoDbPersistenceDescriptor, typeof(BeaconsMongoDbPersistence));
        RegisterAsType(ControllerDescriptor, typeof(BeaconsController));
        RegisterAsType(HttpServiceDescriptor, typeof(BeaconsHttpServiceV1));
    }
}

import (
	logic "github.com/pip-services-samples/service-beacons-go/logic"
	persist "github.com/pip-services-samples/service-beacons-go/persistence"
	services1 "github.com/pip-services-samples/service-beacons-go/services/version1"
	cref "github.com/pip-services3-go/pip-services3-commons-go/refer"
	cbuild "github.com/pip-services3-go/pip-services3-components-go/build"
)

type BeaconsServiceFactory struct {
	cbuild.Factory
}

func NewBeaconsServiceFactory() *BeaconsServiceFactory {
	c := &BeaconsServiceFactory{
		Factory: *cbuild.NewFactory(),
	}

	memoryPersistenceDescriptor := cref.NewDescriptor("beacons", "persistence", "memory", "*", "1.0")
	filePersistenceDescriptor := cref.NewDescriptor("beacons", "persistence", "file", "*", "1.0")
	mongoDbPersistenceDescriptor := cref.NewDescriptor("beacons", "persistence", "mongodb", "*", "1.0")
	controllerDescriptor := cref.NewDescriptor("beacons", "controller", "default", "*", "1.0")
	httpServiceV1Descriptor := cref.NewDescriptor("beacons", "service", "http", "*", "1.0")

	c.RegisterType(memoryPersistenceDescriptor, persist.NewBeaconsMemoryPersistence)
	c.RegisterType(filePersistenceDescriptor, persist.NewBeaconsFilePersistence)
	c.RegisterType(mongoDbPersistenceDescriptor, persist.NewBeaconsMongoDbPersistence)
	c.RegisterType(controllerDescriptor, logic.NewBeaconsController)
	c.RegisterType(httpServiceV1Descriptor, services1.NewBeaconsHttpServiceV1)

	return c
}

import 'package:pip_services3_components/pip_services3_components.dart';
import 'package:pip_services3_commons/pip_services3_commons.dart';

import '../persistence/BeaconsMemoryPersistence.dart';
import '../persistence/BeaconsFilePersistence.dart';
import '../persistence/BeaconsMongoDbPersistence.dart';
import '../logic/BeaconsController.dart';
import '../services/version1/BeaconsCommandableHttpServiceV1.dart';

class BeaconsServiceFactory extends Factory {
  static final MemoryPersistenceDescriptor =
      Descriptor('beacons', 'persistence', 'memory', '*', '1.0');
  static final FilePersistenceDescriptor =
      Descriptor('beacons', 'persistence', 'file', '*', '1.0');
  static final MongoDbPersistenceDescriptor =
      Descriptor('beacons', 'persistence', 'mongodb', '*', '1.0');
  static final ControllerDescriptor =
      Descriptor('beacons', 'controller', 'default', '*', '1.0');
  static final CommandableHttpServiceV1Descriptor =
      Descriptor('beacons', 'service', 'commandable-http', '*', '1.0');
  
  BeaconsServiceFactory() : super() {
    registerAsType(BeaconsServiceFactory.MemoryPersistenceDescriptor,
        BeaconsMemoryPersistence);
    registerAsType(BeaconsServiceFactory.FilePersistenceDescriptor,
        BeaconsFilePersistence);
    registerAsType(BeaconsServiceFactory.MongoDbPersistenceDescriptor,
        BeaconsMongoDbPersistence);
    registerAsType(
        BeaconsServiceFactory.ControllerDescriptor, BeaconsController);
    registerAsType(BeaconsServiceFactory.CommandableHttpServiceV1Descriptor,
        BeaconsCommandableHttpServiceV1);
    
  }
}

 class BeaconsServiceFactory(Factory):

    MemoryPersistenceDescriptor = Descriptor('beacons', 'persistence', 'memory', '*', '1.0')
    FilePersistenceDescriptor = Descriptor('beacons', 'persistence', 'file', '*', '1.0')
    MongoDbPersistenceDescriptor = Descriptor('beacons', 'persistence', 'mongodb', '*', '1.0')
    ControllerDescriptor = Descriptor('beacons', 'controller', 'default', '*', '1.0')
    HttpServiceV1Descriptor = Descriptor('beacons', 'service', 'http', '*', '1.0')

    def __init__(self):
        super(BeaconsServiceFactory, self).__init__()

        self.register_as_type(BeaconsServiceFactory.MemoryPersistenceDescriptor, BeaconsMemoryPersistence)
        self.register_as_type(BeaconsServiceFactory.FilePersistenceDescriptor, BeaconsFilePersistence)
        self.register_as_type(BeaconsServiceFactory.MongoDbPersistenceDescriptor, BeaconsMongoDbPersistence)
        self.register_as_type(BeaconsServiceFactory.ControllerDescriptor, BeaconsController)
        self.register_as_type(BeaconsServiceFactory.HttpServiceV1Descriptor, BeaconsHttpServiceV1)



Not available

And add the DefaultMongoDbFactory to the microservice’s ProcessContainer:


export class BeaconsProcess extends ProcessContainer{
    public constructor(){
        super('beacons', 'Beacons microservice');

        this.addFactory(new BeaconsServiceFactory());
        this.addFactory(new DefaultRpcFactory());
        this.addFactory(new DefaultSwaggerFactory());
    }
}



public class BeaconsProcess : ProcessContainer
{
    public BeaconsProcess()
        : base("beacons", "Beacons microservice")
    {
        _factories.Add(new DefaultMongoDbFactory());
        _factories.Add(new BeaconsFactory());
    }
}




import (
	factory "github.com/pip-services-samples/service-beacons-go/build"
	cproc "github.com/pip-services3-go/pip-services3-container-go/container"
	rbuild "github.com/pip-services3-go/pip-services3-rpc-go/build"
	sbuild "github.com/pip-services3-go/pip-services3-swagger-go/build"
)

type BeaconsProcess struct {
	cproc.ProcessContainer
}

func NewBeaconsProcess() *BeaconsProcess {
	c := &BeaconsProcess{
		ProcessContainer: *cproc.NewProcessContainer("beacons", "Beacons microservice"),
	}

	c.AddFactory(factory.NewBeaconsServiceFactory())
	c.AddFactory(rbuild.NewDefaultRpcFactory())
	c.AddFactory(sbuild.NewDefaultSwaggerFactory())

	return c
}


import 'package:pip_services3_container/pip_services3_container.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';

import '../build/BeaconsServiceFactory.dart';

class BeaconsProcess extends ProcessContainer {
  BeaconsProcess() : super('beacons', 'Beacons microservice') {
    factories.add(BeaconsServiceFactory());
    factories.add(DefaultRpcFactory());
  }
}

 class BeaconsProcess(ProcessContainer):
    def __init__(self):
        super(BeaconsProcess, self).__init__('beacons', 'Beacons microservice')

        self._factories.add(BeaconsServiceFactory())
        self._factories.add(DefaultRpcFactory())
        self._factories.add(DefaultSwaggerFactory())


Not available

If we’re configuring just a single connection to the Beacons MongoDB persistence, the connection configuration should use the “beacons” descriptor:

...
# MongoDb persistence
- descriptor: "beacons:persistence:mongodb:default:1.0"
  connection:
    host: localhost
    port: 30000
...

Identifiable data objects and IdentifiableMongoDBPersistence

The implementation we will be working with going forward is called the IdentifiableMongoDbPersistence. It stores and processes data objects that have a unique ID field and implement the IIdentifiable interface defined in the Commons module.


export interface IIdentifiable<K> {
	/** The unique object identifier of type K. */
	id: K;
}

public interface IIdentifiable<T>
{
    T Id { get; set; }
}

// Golang does not have an IIdentifiable interface.
abstract class IIdentifiable<K> {
  K? id;
}


class IIdentifiable(ABC):
    id: Any


Not available

IdentifiableMongoDbPersistence implements a number of CRUD operations that are based on working with the model’s id in a predefined manner. In addition, it provides methods for getting paginated results and listing data using detailed filter, sort, and even projection parameters.


export class IdentifiableMongoDbPersistence<T extends IIdentifiable<K>, K> extends MongoDbPersistence<T>
    implements IWriter<T, K>, IGetter<T, K>, ISetter<T> {

    public constructor(collection: string);

    protected async convertFromPublicPartial(value: any): any;

    public async getListByIds(correlationId: string, ids: K[]): Promise<T[]>;

    public async getOneByUdi(correlationId: string, id: K): Promise<T>;

    public async create(correlationId: string, item: T): Promise<T>;

    public async set(correlationId: string, item: T): Promise<T>;

    public async update(correlationId: string, item: T): Promise<T>;

    public async updatePartially(correlationId: string, id: K, data: AnyValueMap): Promise<T> ;

    public async deleteById(correlationId: string, id: K): Promise<T> ;

    public async deleteByIds(correlationId: string, ids: K[]): Promise<void>;
}


public class IdentifiableMongoDbPersistence<T, K> : MongoDbPersistence<T>, IWriter<T, K>, IGetter<T, K>, ISetter<T>
    where T : IIdentifiable<K>
    where K : class
{

    public virtual async Task<DataPage<object>> GetPageByFilterAndProjectionAsync(string correlationId, FilterDefinition<T> filterDefinition, PagingParams paging = null, SortDefinition<T>
    sortDefinition = null, ProjectionParams projection = null);

    public virtual async Task<List<T>> GetListByIdsAsync(string correlationId, K[] ids);

    public virtual async Task<T> GetOneByIdAsync(string correlationId, K id);

    public virtual async Task<object> GetOneByIdAsync(string correlationId, K id, ProjectionParams projection);

    public override async Task<T> CreateAsync(string correlationId, T item);

    public virtual async Task<T> SetAsync(string correlationId, T item);

    public virtual async Task<T> UpdateAsync(string correlationId, T item);

    public virtual async Task<T> ModifyAsync(string correlationId, FilterDefinition<T> filterDefinition, UpdateDefinition<T> updateDefinition);

    public virtual async Task<T> ModifyByIdAsync(string correlationId, K id, UpdateDefinition<T> updateDefinition);

    public virtual async Task<T> DeleteByIdAsync(string correlationId, K id);

    public virtual async Task DeleteByIdsAsync(string correlationId, K[] ids);

    #region Overridable Compose Methods
    protected virtual FilterDefinition<T> ComposeFilter(FilterParams filterParams);

    protected virtual UpdateDefinition<T> ComposeUpdate(AnyValueMap updateMap);

    protected virtual SortDefinition<T> ComposeSort(SortParams sortParams);

    protected virtual ProjectionDefinition<T> CreateProjectionDefinition(ProjectionParams projection, ProjectionDefinitionBuilder<T> projectionBuilder);
}



type IdentifiableMongoDbPersistence struct {
	MongoDbPersistence
}

func InheritIdentifiableMongoDbPersistence(overrides IMongoDbPersistenceOverrides, proto reflect.Type, collection string) *IdentifiableMongoDbPersistence {
    ...
}

func (c *IdentifiableMongoDbPersistence) Configure(config *cconf.ConfigParams) {
    ...
}

func (c *IdentifiableMongoDbPersistence) GetListByIds(correlationId string, ids []interface{}) (items []interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) GetOneById(correlationId string, id interface{}) (item interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) Create(correlationId string, item interface{}) (result interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) Set(correlationId string, item interface{}) (result interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) Update(correlationId string, item interface{}) (result interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) UpdatePartially(correlationId string, id interface{}, data *cdata.AnyValueMap) (item interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) DeleteById(correlationId string, id interface{}) (item interface{}, err error) {
    ...
}

func (c *IdentifiableMongoDbPersistence) DeleteByIds(correlationId string, ids []interface{}) error {
    ...
}

class IdentifiableMongoDbPersistence<T extends IIdentifiable<K>, K>
    extends MongoDbPersistence<T>
    implements IWriter<T, K>, IGetter<T, K>, ISetter<T> {

    IdentifiableMongoDbPersistence(String? collection);

    Future<List<T>> getListByIds(String? correlationId, List<K> ids) async

    @override
    Future<T?> create(String? correlationId, T? item) async async

    @override
    Future<T?> set(String? correlationId, T? item) async

    @override
    Future<T?> update(String? correlationId, T? item) async

    Future<T?> updatePartially(String? correlationId, K? id, AnyValueMap? data) async

    @override
    Future<T?> deleteById(String? correlationId, K? id) async

    Future deleteByIds(String? correlationId, List<K> ids) async
}


class IdentifiableMongoDbPersistence(MongoDbPersistence):

    def __init__(self, collection: str = None):
        ...

    def _convert_from_public_partial(self, value: Any) -> Any:
        ...

    def get_list_by_ids(self, correlation_id: Optional[str], ids: List[Any]) -> List[T]:
        ...

    def get_one_by_udi(self, correlation_id: Optional[str], id: Any) -> T:
        ...

    def create(self, correlation_id: Optional[str], item: T) -> T:
        ...

    def set(self, correlation_id: Optional[str], item: T) -> T:
        ...

    def update(self, correlation_id: Optional[str], item: T) -> Optional[T]:
        ...

    def update_partially(self, correlation_id: Optional[str], id: Any, data: AnyValueMap) -> T:
        ...

    def delete_by_id(self, correlation_id: Optional[str], id: Any) -> T:
        ...

    def delete_by_ids(self, correlation_id: Optional[str], ids: List[Any]):
        ...


Not available

We can build upon the IdentifiableMongoDbPersistence by overriding its ComposeFilter method:


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 async getPageByFilter(correlationId: string, filter: FilterParams,
        paging: PagingParams): Promise<DataPage<BeaconV1>> {
        return await super.getPageByFilter(correlationId, this.composeFilter(filter), paging, null, null);
    }



class BeaconsMongoDbPersistence : IdentifiableMongoDbPersistence<BeaconV1, string>
{
    public constructor()
    {
        base("beacons");
    }

    protected override FilterDefinition<BeaconV1> ComposeFilter(FilterParams filter)
    {
        filterParams = filterParams ?? new FilterParams();
        var builder = Builders<BeaconV1>.Filter;
        var filter = builder.Empty;
        String name = filter.getAsNullableString("name");
        if (name != null)
            filter &= builder.Eq(b => b.Name, name);
        filter &= builder.Eq(b => b.Name, name);
        return filter;
    }

    public GetAsync(String correlationId, FilterParams filter, PagingParams paging)
    {
        return await GetPageByFilterAsync(correlationId, ComposeFilter(filter), paging, null, null);
    }

}

import (
	"reflect"
	"strings"

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

type BeaconsMongoDbPersistence struct {
	mpersist.IdentifiableMongoDbPersistence
}

func NewBeaconsMongoDbPersistence() *BeaconsMongoDbPersistence {
	proto := reflect.TypeOf(&data1.BeaconV1{})
	c := &BeaconsMongoDbPersistence{}
	c.IdentifiableMongoDbPersistence = *mpersist.InheritIdentifiableMongoDbPersistence(c, proto, "beacons")
	return c
}

func (c *BeaconsMongoDbPersistence) composeFilter(filter *cdata.FilterParams) interface{} {
	if filter == nil {
		filter = cdata.NewEmptyFilterParams()
	}

	criteria := make([]bson.M, 0, 0)

	id := filter.GetAsString("id")
	if id != "" {
		criteria = append(criteria, bson.M{"_id": id})
	}

	siteId := filter.GetAsString("site_id")
	if siteId != "" {
		criteria = append(criteria, bson.M{"site_id": siteId})
	}
	label := filter.GetAsString("label")
	if label != "" {
		criteria = append(criteria, bson.M{"label": label})
	}
	udi := filter.GetAsString("udi")
	if udi != "" {
		criteria = append(criteria, bson.M{"udi": udi})
	}

	udis := filter.GetAsString("udis")
	var arrUdis []string = make([]string, 0, 0)
	if udis != "" {
		arrUdis = strings.Split(udis, ",")
		if len(arrUdis) > 1 {
			criteria = append(criteria, bson.M{"udi": bson.D{{"$in", arrUdis}}})
		}
	}
	if len(criteria) > 0 {
		return bson.D{{"$and", criteria}}
	}
	return bson.M{}
}

func (c *BeaconsMongoDbPersistence) GetPageByFilter(correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (page *data1.BeaconV1DataPage, err error) {
	tempPage, resErr := c.IdentifiableMongoDbPersistence.GetPageByFilter(correlationId, c.composeFilter(filter), paging, nil, nil)
	if resErr != nil {
		return nil, resErr
	}

	// Convert to BeaconV1DataPage
	dataLen := int64(len(tempPage.Data)) // For full release tempPage and delete this by GC
	beaconData := make([]*data1.BeaconV1, dataLen)
	for i, v := range tempPage.Data {
		beaconData[i] = v.(*data1.BeaconV1)
	}
	page = data1.NewBeaconV1DataPage(&dataLen, beaconData)
	return page, nil
}

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.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);
  }
}

 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(self, correlation_id: str, filter: FilterParams, paging: PagingParams) -> T
        return get_page_by_filter(correlation_id, self.__compose_filter(filter), paging, None, None)



Not available

In most scenarios, child classes only need to override the get_page_by_filter(), get_list_by_filter(), or delete_by_filter() operations using a custom filter function (like the compose_filter function in the example above). All of the other operations can be used straight out of the box. Developers can implement custom methods by directly accessing the data objects, which are stored in the _collection property. See the MongoDb module’s API documentation for more details.

Filtering

Persistence components in the Pip.Services Toolkit use a number of data patterns. IdentifiableMongoDbPersistence, for example, supports Filtering. This pattern allows clients to use a FilterParams object to describe a subset of data using key-value pairs. These FilterParams can then be used for retrieving data in accordance with the specified search criteria (see the Commons module).

let filter = FilterParams.fromTuples(
    "name", 'ABC'
 )
let page = await persistence.getPageByFilter("123", filter, null)


var filter = FilterParams.FromTuples(
    "name", "ABC"
);
var result = await persistence.GetPageByFilterAsync(null, filter, null);

filter := cdata.NewFilterParamsFromTuples(
	"name", "ABC",
)
result, _ := persistence.GetPageByFilter("123", filter, nil)

// Get all beacons
var filter = FilterParams.fromTuples(['name', 'ABC']);

var page = await _persistence.getPageByFilter('123', filter, null);

filter = FilterParams.from_tuples(
    'name', 'ABC'
)
result = persistence.get_page_filter(None, filter, None)

Not available

In the persistence component, the developer is responsible for parsing FilterParams and passing a filter function to the persistence’s methods of the base class.


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;
}


protected override FilterDefinition<BeaconV1> ComposeFilter(FilterParams filter)
{
    filterParams = filterParams ?? new FilterParams();
    var builder = Builders<BeaconV1>.Filter;
    var filter = builder.Empty;
    String name = filter.getAsNullableString("name");
    if (name != null)
        filter &= builder.Eq(b => b.Name, name);
    return filter;
}

func (c *BeaconsMongoDbPersistence) composeFilter(filter *cdata.FilterParams) interface{} {
	if filter == nil {
		filter = cdata.NewEmptyFilterParams()
	}

	criteria := make([]bson.M, 0, 0)

	id := filter.GetAsString("id")
	if id != "" {
		criteria = append(criteria, bson.M{"_id": id})
	}

	siteId := filter.GetAsString("site_id")
	if siteId != "" {
		criteria = append(criteria, bson.M{"site_id": siteId})
	}
	label := filter.GetAsString("label")
	if label != "" {
		criteria = append(criteria, bson.M{"label": label})
	}
	udi := filter.GetAsString("udi")
	if udi != "" {
		criteria = append(criteria, bson.M{"udi": udi})
	}

	udis := filter.GetAsString("udis")
	var arrUdis []string = make([]string, 0, 0)
	if udis != "" {
		arrUdis = strings.Split(udis, ",")
		if len(arrUdis) > 1 {
			criteria = append(criteria, bson.M{"udi": bson.D{{"$in", arrUdis}}})
		}
	}
	if len(criteria) > 0 {
		return bson.D{{"$and", criteria}}
	}
	return bson.M{}
}

  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.split(',');
    }
    if (udis is List) {
      criteria.add({
        'udi': {r'$in': udis}
      });
    }

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

 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



Not available

Paging

Another common data pattern is Paging. It is used to retrieve large datasets in chunks, through multiple calls to the storage. A client can ask for the results to be paged by specifying a set of PagingParams, which include the starting position and the number of objects to return. Clients can also request the total number of items in the dataset using PagingParams, but this parameter is optional. A DataPage object with a subset of the data will be returned as the result.

//skip = 25, take = 50, total = False
let paging = new PagingParams(25, 50, false);
let result = await persistence.getPageByFilter(null, null, paging);

// skip = 25, take = 50, total = False
var paging = new PagingParams(25, 50, false);
var result = await persistence.GetPageByFilterAsync(null, null, paging);

// skip = 25, take = 50, total = False
paging := cdata.NewPagingParams(25, 50, false)
result := persistence.GetPageByFilter("123", nil, paging)
//skip = 25, take = 50, total = False
var paging = PagingParams(25, 50, false);
var result = await _persistence.getPageByFilter(null, null, paging);

# skip = 25, take = 50, total = False
paging = PagingParams(25, 50, False)
result = persistence.get_page_by_filter(None, None, paging)


Not available

Custom Persistence Methods

As mentioned above, developers can also implement custom persistence methods. The _collection property can be used to access data objects from within such methods. Below is an example of a custom get_one_by_udi persistence method.

public async 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);
        });    
    });     
}


public async Task<BeaconV1> GetOneByNameAsync(string correlationId, string name)
{
    var builder = Builders<BeaconV1>.Filter;
    var filter = builder.Eq(x => x.Name, name);
    var result = await _collection.Find(filter).FirstOrDefaultAsync();
    if (result != null)
        _logger.Trace(correlationId, "Retrieved from {0} with name = {1}", _collectionName, name);
    else
        _logger.Trace(correlationId, "Nothing found from {0} with name = {1}", _collectionName, name);
    return result;
}

func (c *BeaconsMongoDbPersistence) GetOneById(correlationId string, id string) (item *data1.BeaconV1, err error) {
	result, err := c.IdentifiableMongoDbPersistence.GetOneById(correlationId, id)

	// Convert to BeaconV1
	if result != nil {
		val, _ := result.(*data1.BeaconV1)
		item = val
	}
	return item, err
}
  @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);
  }

 def get_one_by_udi(self, correlation_id: Optional[str], udi: Any) -> T:

    item = self._collection.find_one({'udi': udi})

    if item is None:
        self._logger.trace(correlation_id, "Nothing found from %s with udi = %s", self._collection_name, udi)
    else:
        self._logger.trace(correlation_id, "Retrieved from %s with udi = %s", self._collection_name, udi)

    item = self._convert_to_public(item)

    return item


Not available

When we put everything together, we end up with the following component:

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 async getPageByFilter(correlationId: string, filter: FilterParams,
        paging: PagingParams): Promise<DataPage<BeaconV1>> {
            DataPage.
        return super.getPageByFilter(correlationId, this.composeFilter(filter), paging, null, null);
    }

    public async 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);
            });    
        });     
    }
}

class BeaconsMongoDbPersistence : IdentifiableMongoDbPersistence<BeaconV1, string>
{
    public constructor()
    {
        base("beacons");
    }

    protected override FilterDefinition<BeaconV1> ComposeFilter(FilterParams filter)
    {
        filterParams = filterParams ?? new FilterParams();
        var builder = Builders<BeaconV1>.Filter;
        var filter = builder.Empty;
        String name = filter.getAsNullableString("name");
        if (name != null)
            filter &= builder.Eq(b => b.Name, name);
        return filter;
    }

    public GetAsync(String correlationId, FilterParams filter, PagingParams paging)
    {
        return await GetPageByFilterAsync(correlationId, ComposeFilter(filter), paging, null, null);
    }
    public async Task<BeaconV1> GetOneByNameAsync(string correlationId, string name)
    {
        var builder = Builders<BeaconV1>.Filter;
        var filter = builder.Eq(x => x.Name, name);
        var result = await _collection.Find(filter).FirstOrDefaultAsync();
        if (result != null)
            _logger.Trace(correlationId, "Retrieved from {0} with name = {1}", _collectionName, name);
        else
            _logger.Trace(correlationId, "Nothing found from {0} with name = {1}", _collectionName, name);
        return result;
    }
}


import (
	"reflect"
	"strings"

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

type BeaconsMongoDbPersistence struct {
	mpersist.IdentifiableMongoDbPersistence
}

func NewBeaconsMongoDbPersistence() *BeaconsMongoDbPersistence {
	proto := reflect.TypeOf(&data1.BeaconV1{})
	c := &BeaconsMongoDbPersistence{}
	c.IdentifiableMongoDbPersistence = *mpersist.InheritIdentifiableMongoDbPersistence(c, proto, "beacons")
	return c
}

func (c *BeaconsMongoDbPersistence) composeFilter(filter *cdata.FilterParams) interface{} {
	if filter == nil {
		filter = cdata.NewEmptyFilterParams()
	}

	criteria := make([]bson.M, 0, 0)

	id := filter.GetAsString("id")
	if id != "" {
		criteria = append(criteria, bson.M{"_id": id})
	}

	siteId := filter.GetAsString("site_id")
	if siteId != "" {
		criteria = append(criteria, bson.M{"site_id": siteId})
	}
	label := filter.GetAsString("label")
	if label != "" {
		criteria = append(criteria, bson.M{"label": label})
	}
	udi := filter.GetAsString("udi")
	if udi != "" {
		criteria = append(criteria, bson.M{"udi": udi})
	}

	udis := filter.GetAsString("udis")
	var arrUdis []string = make([]string, 0, 0)
	if udis != "" {
		arrUdis = strings.Split(udis, ",")
		if len(arrUdis) > 1 {
			criteria = append(criteria, bson.M{"udi": bson.D{{"$in", arrUdis}}})
		}
	}
	if len(criteria) > 0 {
		return bson.D{{"$and", criteria}}
	}
	return bson.M{}
}

func (c *BeaconsMongoDbPersistence) GetPageByFilter(correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (page *data1.BeaconV1DataPage, err error) {
	tempPage, resErr := c.IdentifiableMongoDbPersistence.GetPageByFilter(correlationId, c.composeFilter(filter), paging, nil, nil)
	if resErr != nil {
		return nil, resErr
	}

	// Convert to BeaconV1DataPage
	dataLen := int64(len(tempPage.Data)) // For full release tempPage and delete this by GC
	beaconData := make([]*data1.BeaconV1, dataLen)
	for i, v := range tempPage.Data {
		beaconData[i] = v.(*data1.BeaconV1)
	}
	page = data1.NewBeaconV1DataPage(&dataLen, beaconData)
	return page, nil
}

func (c *BeaconsMongoDbPersistence) GetOneByUdi(correlationId string, udi string) (result *data1.BeaconV1, err error) {
	filter := bson.M{"udi": udi}
	docPointer := c.NewObjectByPrototype()
	foRes := c.Collection.FindOne(c.Connection.Ctx, filter)

	ferr := foRes.Decode(docPointer.Interface())
	if ferr != nil {
		if ferr == mongo.ErrNoDocuments {
			return nil, nil
		}
		return nil, ferr
	}

	// Convert to BeaconV1
	item := c.ConvertToPublic(docPointer)
	if item != nil {
		val, _ := item.(*data1.BeaconV1)
		result = val
	}
	return result, nil
}

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.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 (udi.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);
  }
}

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: str, filter: FilterParams, paging: PagingParams) -> T
        return get_page_by_filter(correlation_id, self.__compose_filter(filter), paging, None, None)

    def get_one_by_udi(self, correlation_id: Optional[str], udi: Any) -> T:

        item = self._collection.find_one({'udi': udi})

        if item is None:
            self._logger.trace(correlation_id, "Nothing found from %s with udi = %s", self._collection_name, udi)
        else:
            self._logger.trace(correlation_id, "Retrieved from %s with udi = %s", self._collection_name, udi)

        item = self._convert_to_public(item)

        return item


Not available

The following example demonstrates how we can use our newly created persistence for writing and reading Beacon objects to a MongoDB:

let persistence = new BeaconsMongoDbPersistence();

persistence.Configure(cconf.NewConfigParamsFromTuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test",
))

await persistence.open(null);

let beacon = <BeaconV1>{
    id: '1', 
    site_id: "0001",
    udi: "0002"
};

await persistence.set("test", beacon)
let item = await persistence.getOneByUdi("test", "0002")
console.log(item.udi) // Result: 0002
let page = await persistence.getPageByFilter("test", FilterParams.fromTuples("udi", "0002"), null)

console.log(page.data.length) // Result: 1
console.log(page.data[0].udi)   // Result: 0002
await persistence.close("test")

var persistence = new BeaconMongoDbPersistence();

persistence.Configure(ConfigParams.FromTuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test"
));

await persistence.OpenAsync();

var beacon = new BeaconV1
{
    Id: "1", 
    SiteId: "0001", 
    Udi: "0002"
};

await persistence.SetAsync("test", beacon);
var item = await persistence.GetByNameAsync("test", "0002");
Console.Out.WriteLine(item.Udi);    // Result: 0002

var itemsPage = await persistence.GetPageByFilterAsync("test", FilterParams.FromTuples(
	"udi", "0002"
), null);

Console.Out.WriteLine(itemsPage.Data.Count); // Result: 1
Console.Out.WriteLine(itemsPage.Data[0]); // Result: 0002

await persistence.CloseAsync("test");


persistence := persist.NewBeaconsMongoDbPersistence()

persistence.Configure(cconf.NewConfigParamsFromTuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test",
))
	
persistence.Open("123")
beacon := &data1.BeaconV1{Id: "1", SiteId: "0001", Udi: "0002"}

persistence.Set("test", beacon)
item, _ := persistence.GetOneByUdi("test", "0002")

fmt.Println(item.Udi) // Result: 0002

itemsPage, _ := persistence.GetPageByFilter("test", cdata.NewFilterParamsFromTuples("udi", "0002"), nil)

fmt.Println(len(itemsPage.Data))   // Result: 1
fmt.Println(itemsPage.Data[0].Udi) // Result: 0002
persistence.Close("test")
var persistence = BeaconsMongoDbPersistence();

persistence.configure(ConfigParams.fromTuples([
  'connection.host',
  'localhost',
  'connection.port',
  '30000',
  'connection.database',
  'test'
]));

await persistence.open('test');

var beacon = BeaconV1(id: '1', site_id: '0001', udi: '0002');

await persistence.set('test', beacon);
var item = await persistence.getOneByUdi('test', '0002');
print(item!.udi); // Result: 0002

var page = await persistence.getPageByFilter(
    'test', FilterParams.fromTuples(['udi', '0002']), null);

print(page.data.length); // Result: 1
print(page.data[0].udi); // Result: 0002

await persistence.close('test');


persistence = BeaconsMongoDbPersistence()

persistence.configure(ConfigParams.from_tuples(
	"connection.host", "localhost",
	"connection.port", "30000",
	"connection.database", "test"
))

persistence.open(None)
beacon = BeaconV1(id="1", site_id="0001", udi="0002")

persistence.set("test", beacon)
item = persistence.get_one_by_udi("test", "0002")

print(item.udi)   # Result: 0002

items_page = persistence.get_page_by_filter("test", FilterParams.from_tuples("udi", "0002"), None)

print(len(items_page.data)) # Result: 1
print(items_page.data[0].udi)   # Result: 0002
persistence.close("test")


Not available