Commandable gRPC

How to create a commandable gRPC client and service.

Key takeaways

CommandSet Component that contains a set of commands and events supported by a commandable object.
CommandableGrpcService Service that receives commands via the gRPC protocol.
CommandableGrpcClient Client that calls a commandable gRPC service.

Introduction

This tutorial will help you understand how to create a commandable gRPC client and service. First, we will learn the basics of these components. Then, we will create an example where a commandable gRPC client communicates with a commandable gRPC service. After the example, we will summarize the concepts learned.

Commandable gRPC basics

gRPC is an open-source RPC framework based on HTTP/2 and originally created by Google. Pip.Services implements it in the gRPC module. The two main components in this module are CommandableGrpcService and CommandableGrprcClient.

The CommandableGrpcService class describes a service that receives commands via the gRPC protocol. These commands are then linked to commands defined in a CommandSet component.

The CommandableGrpcClient class is used to create clients that call a CommandbleGrpcService.

Commandable pattern

The example in this tutorial is structured according to the Commandable pattern. This pattern considers a CommandSet component, where all commands are registered. It also uses a controller that links to this command set and defines the specific aspects of each command.

The main advantage that this pattern offers is allowing for the use of a defined command set by commandable components using different communication methods – such as gRPC, HTTP, Azure, etc. - where the specifics for each case are declared in the controller and the common aspects in the CommandSet class.

figure 1

Example

To learn how to create a commandable gRPC client and service, we will build an example where a service uses a command set containing CRUD operations that are applied to data objects.

Project structure

To organize this example, we use the following directory structure: First, we have a directory named “clients” that contains all the files related to the client. Second, our service is organized into three layers namely, data, business logic and service layers.

The data layer is represented in the “data” directory and contains the files that define the data structure used in this example. The business logic layer contains the controller and command set, and the corresponding files are stored in the “logic” directory. And, the “services” directory contains the commandable gRPC service.

Three additional files in this project are the container used to wrap the application and stored in the “containers” directory; the “main” program, which is the application starter; and the client_creator, which is used to create an instance of the client and call the CRUD methods.

figure 2

Pre-requisites

In order to create the CommandableGrpcService, we need to import this class first. The following code shows how to do this:

import { CommandableGrpcService } from "pip-services3-grpc-nodex";
using PipServices3.Grpc.Services;
import (
    grpcsrv "github.com/pip-services3-gox/pip-services3-grpc-gox/services"
)
import 'package:pip_services3_grpc/pip_services3_grpc.dart';
from pip_services3_grpc.services.CommandableGrpcService import CommandableGrpcService
Not available

Similarly, we need to import the CommandableGrpcClient:

import { CommandableGrpcClient } from "pip-services3-grpc-nodex";
using PipServices3.Grpc.Clients;
import (
    grpcclnt "github.com/pip-services3-gox/pip-services3-grpc-gox/clients"
)
import 'package:pip_services3_grpc/pip_services3_grpc.dart';
from pip_services3_grpc.clients.CommandableGrpcClient import CommandableGrpcClient
Not available

Data structure

The next thing that we need to do is to create a class representing the data to be used in the example. The code below shows the MyData class, which defines the data structure of an identifiable data object containing a key and a content field. Additionally, we define a toString() method, which transforms the data object into a dictionary.

import { IStringIdentifiable } from "pip-services3-commons-nodex";


export class MyData implements IStringIdentifiable {
    public id: string;
    public key: string;
    public content: string;
}
using Newtonsoft.Json;
using PipServices3.Commons.Data;


public class MyData : IStringIdentifiable
    {
        public MyData()
        {
        }

        public MyData(string id, string key, string content)
        {
            Id = id;
            Key = key;
            Content = content;
        }

        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("key")]
        public string Key { get; set; }

        [JsonProperty("content")]
        public string Content { get; set; }
    }
type MyData struct {
	Id      string `json:"id"`
	Key     string `json:"key"`
	Content string `json:"content"`
}

func NewMyData(id string, key string, content string) *MyData {
	return &MyData{
		Id:      id,
		Key:     key,
		Content: content,
	}
}
import 'package:pip_services3_commons/pip_services3_commons.dart';


class MyData implements IStringIdentifiable, ICloneable {
  @override
  String? id;
  String? key;
  String? content;

  MyData();

  MyData.from(this.id, this.key, this.content);

  Map<String, dynamic> toJson() {
    return <String, dynamic>{'id': id, 'key': key, 'content': content};
  }

  void fromJson(Map<String, dynamic> json) {
    id = json['id'];
    key = json['key'];
    content = json['content'];
  }

  @override
  MyData clone() {
    return MyData.from(id, key, content);
  }
}
from pip_services3_commons.data.IStringIdentifiable import IStringIdentifiable


class MyData(IStringIdentifiable):

    def __init__(self, id: str = None, key: str = None, content: str = None):
        self.id = id
        self.key = key
        self.content = content

    def clone(self):
        return MyData(self.id, self.key, self.content)

    @staticmethod
    def to_dict(my_data: 'MyData') -> dict:
        if my_data is not None:
            return {'id': my_data.id, 'key': my_data.key, 'content': my_data.content}
Not available

We also need to create a schema for this data class, which will be used by the CommandSet component:

import { ObjectSchema, TypeCode } from "pip-services3-commons-nodex";


export class MyDataSchema extends ObjectSchema {
    public constructor() {
        super();
        this.withOptionalProperty("id", TypeCode.String)
        this.withRequiredProperty("key", TypeCode.String)
        this.withOptionalProperty("content", TypeCode.String)
    }
}
using PipServices3.Commons.Validate;


public class MyDataSchema: ObjectSchema
{
    public MyDataSchema() : base()
    {
        WithOptionalProperty("id", TypeCode.String);
        WithRequiredProperty("key", TypeCode.String);
        WithOptionalProperty("content", TypeCode.String);
    }
}
import (
	cconv "github.com/pip-services3-gox/pip-services3-commons-gox/convert"
	cvalid "github.com/pip-services3-gox/pip-services3-commons-gox/validate"
)



type MyDataSchema struct {
	*cvalid.ObjectSchema
}

func NewMyDataSchema() *MyDataSchema {
	c := &MyDataSchema{}
	c.ObjectSchema = cvalid.NewObjectSchema()
	c.WithOptionalProperty("id", cconv.String)
	c.WithRequiredProperty("key", cconv.String)
	c.WithOptionalProperty("content", cconv.String)

	return c
}
class MyDataSchema extends ObjectSchema {
  MyDataSchema() : super() {
    withOptionalProperty('id', TypeCode.String);
    withRequiredProperty('key', TypeCode.String);
    withOptionalProperty('content', TypeCode.String);
  }
}
from pip_services3_commons.convert.TypeCode import TypeCode
from pip_services3_commons.validate.ObjectSchema import ObjectSchema


class MyDataSchema(ObjectSchema):

    def __init__(self):
        super().__init__()
        self.with_optional_property("id", TypeCode.String)
        self.with_required_property("key", TypeCode.String)
        self.with_optional_property("content", TypeCode.String)
Not available

And a factory, which will be used by the container:

import { Descriptor } from "pip-services3-commons-nodex";
import { Factory } from "pip-services3-components-nodex";

import { MyDataController, MyDataCommandableGrpcService } from "my-package";


export class DefaultMyDataFactory extends Factory {
    public static FactoryDescriptor = new Descriptor("service-mydata", "factory", "default", "default", "1.0");
    public static ControllerDescriptor = new Descriptor("service-mydata", "controller", "default", "*", "1.0");
    public static CommandableGrpcServiceDescriptor = new Descriptor("service-mydata", "service", "commandable-grpc", "*", "1.0");

    public constructor() {
        super();
        this.registerAsType(DefaultMyDataFactory.ControllerDescriptor, MyDataController)
        this.registerAsType(DefaultMyDataFactory.CommandableGrpcServiceDescriptor, MyDataCommandableGrpcService)
    }
}
using PipServices3.Commons.Refer;
using PipServices3.Components.Build;

public class DefaultMyDataFactory: Factory
{
    public static Descriptor FactoryDescriptor = new Descriptor("service-mydata", "factory", "default", "default", "1.0");
    public static Descriptor ControllerDescriptor = new Descriptor("service-mydata", "controller", "default", "*", "1.0");
    public static Descriptor CommandableGrpcServiceDescriptor = new Descriptor("service-mydata", "service", "commandable-grpc", "*", "1.0");

    public DefaultMyDataFactory(): base()
    {
        RegisterAsType(DefaultMyDataFactory.ControllerDescriptor, typeof(MyDataController));
        RegisterAsType(DefaultMyDataFactory.CommandableGrpcServiceDescriptor, typeof(MyDataCommandableGrpcService));
    }
}

import (
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
)


var FactoryDescriptor = cref.NewDescriptor("service-mydata", "factory", "default", "default", "1.0")
var ControllerDescriptor = cref.NewDescriptor("service-mydata", "controller", "default", "*", "1.0")
var CommandableGrpcServiceDescriptor = cref.NewDescriptor("service-mydata", "service", "commandable-grpc", "*", "1.0")

type DefaultMyDataFactory struct {
	*cbuild.Factory
}

func NewDefaultMyDataFactory() *DefaultMyDataFactory {
	c := DefaultMyDataFactory{
		Factory: cbuild.NewFactory(),
	}

	c.RegisterType(ControllerDescriptor, NewMyDataController)
	c.RegisterType(CommandableGrpcServiceDescriptor, NewMyDataCommandableGrpcService)

	return &c
}
import 'package:pip_services3_components/pip_services3_components.dart';

class DefaultMyDataFactory extends Factory {
  static Descriptor FactoryDescriptor =
      Descriptor('service-mydata', 'factory', 'default', 'default', '1.0');
  static Descriptor ControllerDescriptor =
      Descriptor('service-mydata', 'controller', 'default', '*', '1.0');
  static Descriptor CommandableGrpcServiceDescriptor =
      Descriptor('service-mydata', 'service', 'commandable-grpc', '*', '1.0');

  DefaultMyDataFactory() : super() {
    registerAsType(DefaultMyDataFactory.ControllerDescriptor, MyDataController);
    registerAsType(DefaultMyDataFactory.CommandableGrpcServiceDescriptor,
        MyDataCommandableGrpcService);
  }
}

from pip_services3_commons.refer.Descriptor import Descriptor
from pip_services3_components.build.Factory import Factory

from logic.MyDataController import MyDataController
from services.MyDataCommandableGrpcService import MyDataCommandableGrpcService


class DefaultMyDataFactory(Factory):
    FactoryDescriptor = Descriptor("service-mydata", "factory", "default", "default", "1.0")
    ControllerDescriptor = Descriptor("service-mydata", "controller", "default", "*", "1.0")
    CommandableGrpcServiceDescriptor = Descriptor("service-mydata", "service", "commandable-grpc", "*", "1.0")

    def __init__(self):
        """
        Create a new instance of the factory.
        """
        super().__init__()
        self.register_as_type(DefaultMyDataFactory.ControllerDescriptor, MyDataController)
        self.register_as_type(DefaultMyDataFactory.CommandableGrpcServiceDescriptor, MyDataCommandableGrpcService)
Not available

Command set

Now, we need to create a command set component. We do this by extending the CommandSet class and defining our CRUD commands.

import { 
    Command, CommandSet, DataPage, FilterParams, FilterParamsSchema, 
    ICommand, ObjectSchema, PagingParams, PagingParamsSchema, TypeCode
} from "pip-services3-commons-nodex";

import { IMyDataController, MyDataSchema, MyData } from "my-package";

export class MyDataCommandSet extends CommandSet {
    private _controller: IMyDataController;

    public constructor(controller: IMyDataController) {
        super();
        this._controller = controller;

        this.addCommand(this._makePageByFilterCommand());
        this.addCommand(this._makeGetOneByIdCommand());
        this.addCommand(this._makeCreateCommand());
        this.addCommand(this._makeUpdateCommand());
        this.addCommand(this._makeDeleteByIdCommand());
    }

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

    private _makeGetOneByIdCommand(): ICommand {
        return new Command(
            'get_my_data_by_id',
            new ObjectSchema()
                .withOptionalProperty('my_data_id', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let id = args.getAsString("my_data_id")
                return await this._controller.getOneById(correlationId, id);
            }
        );
    }

    private _makeCreateCommand(): ICommand {
        return new Command(
            'create_my_data',
            new ObjectSchema()
                .withOptionalProperty('my_data', new MyDataSchema()),
            async (correlationId: string, args: Parameters) => {
                let entity: MyData = args.get("my_data");
                return await this._controller.create(correlationId, entity);
            }
        );
    }

    private _makeUpdateCommand(): ICommand {
        return new Command(
            'update_my_data',
            new ObjectSchema()
                .withOptionalProperty('my_data', new MyDataSchema()),
            async (correlationId: string, args: Parameters) => {
                let entity: MyData = args.get("my_data");
                return await this._controller.update(correlationId, entity);
            }
        );
    }

    private _makeDeleteByIdCommand(): ICommand {
        return new Command(
            'delete_my_data',
            new ObjectSchema()
                .withOptionalProperty('my_data_id', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let id = args.getAsString("my_data_id")
                return await this._controller.deleteById(correlationId, id);
            }
        );
    }
}

using PipServices3.Commons.Commands;
using PipServices3.Commons.Data;
using PipServices3.Commons.Refer;
using PipServices3.Commons.Run;
using PipServices3.Commons.Validate;

using TypeCode = PipServices3.Commons.Convert.TypeCode;

public class MyDataCommandSet: CommandSet
{
    private IMyDataController _controller;

    public MyDataCommandSet(IMyDataController controller)
    {
        this._controller = controller;

        this.AddCommand(this.MakePageByFilterCommand());
        this.AddCommand(this.MakeGetOneByIdCommand());
        this.AddCommand(this.MakeCreateCommand());
        this.AddCommand(this.MakeUpdateCommand());
        this.AddCommand(this.MakeDeleteByIdCommand());
    }

    private ICommand MakePageByFilterCommand()
    {
        return new Command(
            "get_my_datas",
            new ObjectSchema()
                .WithOptionalProperty("filter", new FilterParamsSchema())
                .WithOptionalProperty("paging", new PagingParamsSchema()),
            async (correlationId, args) =>
            {
                var filter = FilterParams.FromValue(args.Get("filter"));
                var paging = PagingParams.FromValue(args.Get("paging"));

                return await _controller.GetPageByFilterAsync(correlationId, filter, paging);
            });
    }

    private ICommand MakeGetOneByIdCommand()
    {
        return new Command(
            "get_my_data_by_id",
            new ObjectSchema()
                .WithRequiredProperty("my_data_id", TypeCode.String),
            async (correlationId, args) =>
            {
                var mydataId = args.GetAsString("my_data_id");
                return await _controller.GetOneByIdAsync(correlationId, mydataId);
            });
    }

    private ICommand MakeCreateCommand()
    {
        return new Command(
            "create_my_data",
            new ObjectSchema()
                .WithRequiredProperty("my_data", new MyDataSchema()),
            async (correlationId, args) =>
            {
                var mydata = ExtractMyData(args);
                return await _controller.CreateAsync(correlationId, mydata);
            });
    }

    private ICommand MakeUpdateCommand()
    {
        return new Command(
            "update_my_data",
            new ObjectSchema()
                .WithRequiredProperty("my_data", new MyDataSchema()),
            async (correlationId, args) =>
            {
                var mydata = ExtractMyData(args);
                return await _controller.UpdateAsync(correlationId, mydata);
            });
    }

    private ICommand MakeDeleteByIdCommand()
    {
        return new Command(
            "delete_my_data",
            new ObjectSchema()
                .WithRequiredProperty("my_data_id", TypeCode.String),
            async (correlationId, args) =>
            {
                var mydataId = args.GetAsString("my_data_id");

                return await _controller.DeleteByIdAsync(correlationId, mydataId);
            });
    }

    private static MyData ExtractMyData(Parameters args)
    {
        var map = args.GetAsMap("my_data");

        var id = map.GetAsStringWithDefault("id", string.Empty);
        var key = map.GetAsStringWithDefault("key", string.Empty);
        var content = map.GetAsStringWithDefault("content", string.Empty);

        var mydata = new MyData(id, key, content);
        return mydata;
    }
}



import (
	"context"
	"encoding/json"

	ccomand "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cconv "github.com/pip-services3-gox/pip-services3-commons-gox/convert"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	crun "github.com/pip-services3-gox/pip-services3-commons-gox/run"
	cvalid "github.com/pip-services3-gox/pip-services3-commons-gox/validate"
	cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
	grpcsrv "github.com/pip-services3-gox/pip-services3-grpc-gox/services"
)

type MyDataCommandSet struct {
	*ccomand.CommandSet
	controller IMyDataController
}

func NewMyDataCommandSet(controller IMyDataController) *MyDataCommandSet {
	dcs := MyDataCommandSet{}
	dcs.CommandSet = ccomand.NewCommandSet()

	dcs.controller = controller

	dcs.AddCommand(dcs.makePageByFilterCommand())
	dcs.AddCommand(dcs.makeGetOneByIdCommand())
	dcs.AddCommand(dcs.makeCreateCommand())
	dcs.AddCommand(dcs.makeUpdateCommand())
	dcs.AddCommand(dcs.makeDeleteByIdCommand())
	return &dcs
}

func (c *MyDataCommandSet) makePageByFilterCommand() ccomand.ICommand {
	return ccomand.NewCommand(
		"get_my_datas",
		cvalid.NewObjectSchema().WithOptionalProperty("filter", cvalid.NewFilterParamsSchema()).WithOptionalProperty("paging", cvalid.NewPagingParamsSchema()),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			var filter *cdata.FilterParams
			var paging *cdata.PagingParams

			if _val, ok := args.Get("filter"); ok {
				filter = cdata.NewFilterParamsFromValue(_val)
			}
			if _val, ok := args.Get("paging"); ok {
				paging = cdata.NewPagingParamsFromValue(_val)
			}

			return c.controller.GetPageByFilter(correlationId, filter, paging)
		},
	)
}

func (c *MyDataCommandSet) makeGetOneByIdCommand() ccomand.ICommand {
	return ccomand.NewCommand(
		"get_my_data_by_id",
		cvalid.NewObjectSchema().WithRequiredProperty("my_data_id", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			id := args.GetAsString("my_data_id")
			return c.controller.GetOneById(correlationId, id)
		},
	)
}

func (c *MyDataCommandSet) makeCreateCommand() ccomand.ICommand {
	return ccomand.NewCommand(
		"create_my_data",
		cvalid.NewObjectSchema().WithRequiredProperty("my_data", NewMyDataSchema()),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			var entity MyData

			if _val, ok := args.Get("my_data"); ok {
				val, _ := json.Marshal(_val)
				json.Unmarshal(val, &entity)
			}

			return c.controller.Create(correlationId, entity)
		},
	)
}

func (c *MyDataCommandSet) makeUpdateCommand() ccomand.ICommand {
	return ccomand.NewCommand(
		"update_my_data",
		cvalid.NewObjectSchema().WithRequiredProperty("my_data", NewMyDataSchema()),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			var entity MyData

			if _val, ok := args.Get("my_data"); ok {
				val, _ := json.Marshal(_val)
				json.Unmarshal(val, &entity)
			}
			return c.controller.Update(correlationId, entity)
		},
	)
}

func (c *MyDataCommandSet) makeDeleteByIdCommand() ccomand.ICommand {
	return ccomand.NewCommand(
		"delete_my_data",
		cvalid.NewObjectSchema().WithRequiredProperty("my_data_id", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result any, err error) {
			id := args.GetAsString("my_data_id")
			return c.controller.DeleteById(correlationId, id)
		},
	)
}


class MyDataCommandSet extends CommandSet {
  IMyDataController _controller;

  MyDataCommandSet(IMyDataController controller) : _controller = controller {
    addCommand(_makePageByFilterCommand());
    addCommand(_makeGetOneByIdCommand());
    addCommand(_makeCreateCommand());
    addCommand(_makeUpdateCommand());
    addCommand(_makeDeleteByIdCommand());
  }

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

  ICommand _makeGetOneByIdCommand() {
    return Command('get_my_data_by_id',
        ObjectSchema(true).withRequiredProperty('my_data_id', TypeCode.String),
        (String? correlationId, Parameters args) {
      var id = args.getAsString('my_data_id');
      return _controller.GetOneByIdAsync(correlationId, id);
    });
  }

  ICommand _makeCreateCommand() {
    return Command('create_my_data',
        ObjectSchema(true).withRequiredProperty('my_data', MyDataSchema()),
        (String? correlationId, Parameters args) {
      var entity = MyData();
      entity.fromJson(args.get('my_data'))
      return _controller.CreateAsync(correlationId, entity);
    });
  }

  ICommand _makeUpdateCommand() {
    return Command('update_my_data',
        ObjectSchema(true).withRequiredProperty('my_data', MyDataSchema()),
        (String? correlationId, Parameters args) {
      var entity = MyData();
      entity.fromJson(args.get('my_data'));
      return _controller.UpdateAsync(correlationId, entity);
    });
  }

  ICommand _makeDeleteByIdCommand() {
    return Command('delete_my_data',
        ObjectSchema(true).withRequiredProperty('my_data_id', TypeCode.String),
        (String? correlationId, Parameters args) {
      var id = args.getAsString('my_data_id');
      return _controller.DeleteByIdAsync(correlationId, id);
    });
  }
}
from pip_services3_commons.commands.Command import Command
from pip_services3_commons.commands.CommandSet import CommandSet
from pip_services3_commons.commands.ICommand import ICommand
from pip_services3_commons.convert.TypeCode import TypeCode
from pip_services3_commons.data.FilterParams import FilterParams
from pip_services3_commons.data.PagingParams import PagingParams
from pip_services3_commons.data.DataPage import DataPage
from pip_services3_commons.validate.FilterParamsSchema import FilterParamsSchema
from pip_services3_commons.validate.ObjectSchema import ObjectSchema
from pip_services3_commons.validate.PagingParamsSchema import PagingParamsSchema

from data.MyData import MyData
from data.MyDataSchema import MyDataSchema
from logic import IMyDataController


class MyDataCommandSet(CommandSet):

    def __init__(self, controller: IMyDataController):
        super().__init__()
        self.__controller = controller

        self.add_command(self._make_page_by_filter_command())
        self.add_command(self._make_get_one_by_id_command())
        self.add_command(self._make_create_command())
        self.add_command(self._make_update_command())
        self.add_command(self._make_delete_by_id_command())

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

            response = self.__controller.get_page_by_filter(correlation_id, filter, paging)

            items = []
            for item in response.data:
                items.append(MyData.to_dict(item))

            return DataPage(items, len(items))

        return Command(
            'get_my_datas',
            ObjectSchema().with_optional_property(
                'filter', FilterParamsSchema()).with_optional_property(
                'paging', PagingParamsSchema()),
            handler
        )

    def _make_get_one_by_id_command(self):
        def handler(correlation_id, args):
            id = args.get_as_string("my_data_id")
            result = self.__controller.get_one_by_id(correlation_id, id)
            return None if not result.id else MyData.to_dict(result)

        return Command(
            "get_my_data_by_id",
            ObjectSchema().with_required_property('my_data_id', TypeCode.String),
            handler
        )

    def _make_create_command(self):
        def handler(correlation_id, args):
            entity = args.get("my_data")
            result = self.__controller.create(correlation_id, MyData(**entity))
            return MyData.to_dict(result)

        return Command(
            "create_my_data",
            ObjectSchema().with_required_property('my_data', MyDataSchema()),
            handler
        )

    def _make_update_command(self):
        def handler(correlation_id, args):
            entity = args.get("my_data")
            result = self.__controller.update(correlation_id, MyData(**entity))
            return MyData.to_dict(result)

        return Command(
            "update_my_data",
            ObjectSchema().with_required_property('my_data', MyDataSchema()),
            handler
        )

    def _make_delete_by_id_command(self):
        def handler(correlation_id, args):
            id = args.get_as_string("my_data_id")
            result = self.__controller.delete_by_id(correlation_id, id)
            return MyData.to_dict(result)

        return Command(
            "delete_my_data",
            ObjectSchema().with_required_property('my_data_id', TypeCode.String),
            handler
        )

Not available

Controller

Next, we create a controller to manage the logic of our example. This controller extends an interface where we declare the CRUD methods used. It also links to our command set from where it obtains the collection of commands used and to the gRPC service that receives data from the client. For each of the commands defined in the CommandSet, it defines a method with the operations that are particular to the considered commandable class. The code below shows the controller and its interface:

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

import { MyData } from "my-package";


export interface IMyDataController {
    getPageByFilter(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<MyData>>;
    getOneById(correlationId: string, id: string): Promise<MyData>;
    create(correlationId: string, entity: MyData): Promise<MyData>;
    update(correlationId: string, entity: MyData): Promise<MyData>;
    deleteById(correlationId: string, id: string): Promise<MyData>;
}
using PipServices3.Commons.Data;

public interface IMyDataController
{
    Task<DataPage<MyData>> GetPageByFilterAsync(string correlationId, FilterParams filter, PagingParams paging);
    Task<MyData> GetOneByIdAsync(string correlationId, string id);
    Task<MyData> CreateAsync(string correlationId, MyData entity);
    Task<MyData> UpdateAsync(string correlationId, MyData entity);
    Task<MyData> DeleteByIdAsync(string correlationId, string id);
}

import (
    cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
)


type IMyDataController interface {
	GetPageByFilter(correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (result *cdata.DataPage[MyData], err error)
	GetOneById(correlationId string, id string) (result *MyData, err error)
	Create(correlationId string, entity MyData) (result *MyData, err error)
	Update(correlationId string, entity MyData) (result *MyData, err error)
	DeleteById(correlationId string, id string) (result *MyData, err error)
}

abstract class IMyDataController {
  Future<DataPage<MyData>> GetPageByFilterAsync(
      String? correlationId, FilterParams filter, PagingParams paging);
  Future<MyData?> GetOneByIdAsync(String? correlationId, String id);
  Future<MyData> CreateAsync(String? correlationId, MyData entity);
  Future<MyData?> UpdateAsync(String? correlationId, MyData entity);
  Future<MyData?> DeleteByIdAsync(String? correlationId, String id);
}

from abc import ABC
from typing import Optional

from pip_services3_commons.data import FilterParams, PagingParams, DataPage

from data.MyData import MyData


class IMyDataController(ABC):
    def get_page_by_filter(self, correlation_id: Optional[str], filter: FilterParams, paging: PagingParams) -> DataPage:
        pass

    def get_one_by_id(self, correlation_id: Optional[str], id: str) -> MyData:
        pass

    def create(self, correlation_id: Optional[str], entity: MyData) -> MyData:
        pass

    def update(self, correlation_id: Optional[str], entity: MyData) -> MyData:
        pass

    def delete_by_id(self, correlation_id: Optional[str], id: str) -> MyData:
        pass
Not available
import { 
    CommandSet, DataPage, FilterParams, 
    ICommandable, PagingParams, IdGenerator
} from "pip-services3-commons-nodex";

import { MyData, IMyDataController, MyDataCommandSet } from "my-package";

export class MyDataController implements IMyDataController, ICommandable {
    private _entities: MyData[] = [];
    private _commandSet: CommandSet; 

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

    public async getPageByFilter(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<MyData>> {
        filter = filter != null ? filter : new FilterParams();
        let key: string = filter.getAsNullableString("key");

        paging = paging != null ? paging : new PagingParams();
        let skip: number = paging.getSkip(0);
        let take: number = paging.getTake(100);

        let result: MyData[] = [];
        for (let i = 0; i < this._entities.length; i++) {
            let entity: MyData = this._entities[i];
            if (key != null && key != entity.key)
                continue;

            skip--;
            if (skip >= 0) continue;

            take--;
            if (take < 0) break;

            result.push(entity);
        }

        return new DataPage<MyData>(result);
    }
    
    public async getOneById(correlationId: string, id: string): Promise<MyData> {
        for (let i = 0; i < this._entities.length; i++) {
            let entity: MyData = this._entities[i];
            if (id == entity.id) {
                return entity;
            }
        }
        return null;
    }

    public async create(correlationId: string, entity: MyData): Promise<MyData> {
        if (entity.id == null || entity.id == "")
            entity.id = IdGenerator.nextLong();
            
        this._entities.push(entity);
        return entity;
    }

    public async update(correlationId: string, newEntity: MyData): Promise<MyData> {
        for (let index = 0; index < this._entities.length; index++) {
            let entity: MyData = this._entities[index];
            if (entity.id == newEntity.id) {
                this._entities[index] = newEntity;
                return newEntity;
            }
        }
        return null;
    }

    public async deleteById(correlationId: string, id: string): Promise<MyData> {
        for (let index = 0; index < this._entities.length; index++) {
            let entity: MyData = this._entities[index];
            if (entity.id == id) {
                this._entities.splice(index, 1);
                return entity;
            }
        }
        return null;
    }
}

using PipServices3.Commons.Commands;
using PipServices3.Commons.Data;
using PipServices3.Commons.Refer;

public class MyDataController : IMyDataController, ICommandable
{
    private IList<MyData> _entities = new List<MyData>();
    private CommandSet _commandSet;

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

    public async Task<DataPage<MyData>> GetPageByFilterAsync(string correlationId, FilterParams filter, PagingParams paging)
    {
        filter = filter != null ? filter : new FilterParams();
        var key = filter.GetAsNullableString("key");

        paging = paging != null ? paging : new PagingParams();
        var skip = paging.GetSkip(0);
        var take = paging.GetTake(100);

        List<MyData> result = new List<MyData>();
        for (var i = 0; i < this._entities.Count; i++)
        {
            var entity = this._entities[i];
            if (key != null && key != entity.Key)
                continue;

            skip--;
            if (skip >= 0) continue;

            take--;
            if (take < 0) break;

            result.Add(entity);
        }

        return new DataPage<MyData>(result);
    }

    public async Task<MyData> GetOneByIdAsync(string correlationId, string id)
    {
        for (var i = 0; i < this._entities.Count; i++)
        {
            var entity = this._entities[i];
            if (id == entity.Id)
                return entity;
        }
        return null;
    }

    public async Task<MyData> CreateAsync(string correlationId, MyData entity)
    {
        if (entity.Id == null || entity.Id == "")
            entity.Id = IdGenerator.NextLong();
        _entities.Add(entity);
        return entity;
    }

    public async Task<MyData> DeleteByIdAsync(string correlationId, string id)
    {
        for (var index = 0; index < this._entities.Count; index++)
        {
            var entity = this._entities[index];
            if (entity.Id == id)
            {
                this._entities.RemoveAt(index);
                return entity;
            }
        }
        return null;
    }


    public async Task<MyData> UpdateAsync(string correlationId, MyData newEntity)
    {
        for (var index = 0; index < this._entities.Count; index++)
        {
            var entity = this._entities[index];
            if (entity.Id == newEntity.Id)
            {
                this._entities[index] = newEntity;
                return newEntity;
            }
        }
        return null;
    }
}

import (
	ccomand "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
)

type MyDataController struct {
	commandSet *MyDataCommandSet
	entities   []MyData
}

func NewMyDataController() *MyDataController {
	dc := MyDataController{}
	dc.entities = make([]MyData, 0)
	return &dc
}

func (c *MyDataController) GetCommandSet() *ccomand.CommandSet {
	if c.commandSet == nil {
		c.commandSet = NewMyDataCommandSet(c)
	}
	return c.commandSet.CommandSet
}

func (c *MyDataController) GetPageByFilter(correlationId string, filter *cdata.FilterParams,
	paging *cdata.PagingParams) (items *cdata.DataPage[MyData], err error) {

	if filter == nil {
		filter = cdata.NewEmptyFilterParams()
	}
	var key string = filter.GetAsString("key")

	if paging == nil {
		paging = cdata.NewEmptyPagingParams()
	}
	var skip int64 = paging.GetSkip(0)
	var take int64 = paging.GetTake(100)

	var result []MyData
	for i := 0; i < len(c.entities); i++ {
		var entity MyData = c.entities[i]
		if key != "" && key != entity.Key {
			continue
		}

		skip--
		if skip >= 0 {
			continue
		}

		take--
		if take < 0 {
			break
		}

		result = append(result, entity)
	}
	var total int64 = (int64)(len(result))
	return cdata.NewDataPage[MyData](result, int(total)), nil
}

func (c *MyDataController) GetOneById(correlationId string, id string) (result *MyData, err error) {
	for i := 0; i < len(c.entities); i++ {
		var entity MyData = c.entities[i]
		if id == entity.Id {
			return &entity, nil
		}
	}
	return nil, nil
}

func (c *MyDataController) Create(correlationId string, entity MyData) (result *MyData, err error) {
	if entity.Id == "" {
		entity.Id = cdata.IdGenerator.NextLong()
	}

	c.entities = append(c.entities, entity)
	return &entity, nil
}

func (c *MyDataController) Update(correlationId string, newEntity MyData) (result *MyData, err error) {
	for index := 0; index < len(c.entities); index++ {
		var entity MyData = c.entities[index]
		if entity.Id == newEntity.Id {
			c.entities[index] = newEntity
			return &newEntity, nil

		}
	}
	return nil, nil
}

func (c *MyDataController) DeleteById(correlationId string, id string) (result *MyData, err error) {
	var entity MyData

	for i := 0; i < len(c.entities); {
		entity = c.entities[i]
		if entity.Id == id {
			if i == len(c.entities)-1 {
				c.entities = c.entities[:i]
			} else {
				c.entities = append(c.entities[:i], c.entities[i+1:]...)
			}
			return &entity, nil
		} else {
			i++
		}
	}
	return nil, nil
}

class MyDataController implements IMyDataController, ICommandable {
  final List<MyData> _entities = List.empty();
  CommandSet? _commandSet;

  @override
  Future<DataPage<MyData>> GetPageByFilterAsync(
      String? correlationId, FilterParams filter, PagingParams paging) async {
    filter = filter ?? FilterParams();
    var key = filter.getAsNullableString('key');

    paging = paging ?? PagingParams();
    var skip = paging.getSkip(0);
    var take = paging.getTake(100);

    var result = <MyData>[];
    for (var i = 0; i < _entities.length; i++) {
      var entity = _entities[i];
      if (key != null && key != entity.key) {
        continue;
      }

      skip--;
      if (skip >= 0) continue;

      take--;
      if (take < 0) break;

      result.add(entity);
    }

    return DataPage<MyData>(result, 0);
  }

  @override
  CommandSet getCommandSet() {
    _commandSet ??= MyDataCommandSet(this);
    return _commandSet!;
  }

  @override
  Future<MyData> CreateAsync(String? correlationId, MyData entity) async {
    if (entity.id == null || entity.id == '') {
      entity.id = IdGenerator.nextLong();
    }
    _entities.add(entity);
    return entity;
  }

  @override
  Future<MyData?> DeleteByIdAsync(String? correlationId, String id) async {
    for (var index = 0; index < _entities.length; index++) {
      var entity = _entities[index];
      if (entity.id == id) {
        _entities.removeAt(index);
        return entity;
      }
    }
    return null;
  }

  @override
  Future<MyData?> GetOneByIdAsync(String? correlationId, String id) async {
    for (var i = 0; i < _entities.length; i++) {
      var entity = _entities[i];
      if (id == entity.id) return entity;
    }
    return null;
  }

  @override
  Future<MyData?> UpdateAsync(String? correlationId, MyData newEntity) async {
    for (var index = 0; index < _entities.length; index++) {
      var entity = _entities[index];
      if (entity.id == newEntity.id) {
        _entities[index] = newEntity;
        return newEntity;
      }
    }
    return null;
  }
}

import threading
from typing import List, Optional

from pip_services3_commons.commands import ICommandable, CommandSet
from pip_services3_commons.data import FilterParams, PagingParams, DataPage, IdGenerator

from data.MyData import MyData
from logic.IMyDataController import IMyDataController
from logic.MyDataCommandSet import MyDataCommandSet


class MyDataController(IMyDataController, ICommandable):

    def __init__(self):
        self._lock = threading.Lock()
        self.__entities: List[MyData] = []
        self.__command_set: MyDataCommandSet = None

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

    def get_page_by_filter(self, correlation_id: Optional[str], filter: FilterParams, paging: PagingParams) -> DataPage:
        filter = filter if filter is not None else FilterParams()
        key = filter.get_as_nullable_string("key")

        paging = paging if paging is not None else PagingParams()
        skip = paging.get_skip(0)
        take = paging.get_take(100)

        result = DataPage([], None)

        for item in self.__entities:
            if key is not None and key != item.key:
                continue

            skip -= 1
            if skip >= 0:
                continue

            take -= 1
            if take < 0:
                break

            result.data.append(item)

        result.total = len(result.data)

        return result

    def get_one_by_id(self, correlation_id: Optional[str], id: str) -> Optional[MyData]:
        with self._lock:
            for item in self.__entities:
                if item.id == id:
                    return item

        return MyData()

    def create(self, correlation_id: Optional[str], item: MyData) -> MyData:
        with self._lock:
            if item.id == '' or item.id is None:
                item.id = IdGenerator.next_long()

            self.__entities.append(item)

        return item

    def update(self, correlation_id: Optional[str], new_item: MyData) -> Optional[MyData]:
        with self._lock:
            for index in range(len(self.__entities)):
                item = self.__entities[index]
                if item.id == new_item.id:
                    self.__entities[index] = new_item
                    return new_item

        return MyData()

    def delete_by_id(self, correlation_id: Optional[str], id: str) -> Optional[MyData]:
        with self._lock:
            for index in range(len(self.__entities)):
                item = self.__entities[index]
                if item.id == id:
                    del self.__entities[index]
                    return item

        return MyData()

Not available

Service

Next, we define the service that provides an endpoint to our application. The following code shows what this service should look like:

import { Descriptor } from "pip-services3-commons-nodex";
import { CommandableGrpcService } from "pip-services3-grpc-nodex";


export class MyDataCommandableGrpcService extends CommandableGrpcService {
    public constructor() {
        super('mydata');
        this._dependencyResolver.put('controller', new Descriptor('service-mydata', 'controller', '*', '*', '*'))
    }
}
using PipServices3.Grpc.Services;
using PipServices3.Commons.Refer;


public class MyDataCommandableGrpcService: CommandableGrpcService
{
    public MyDataCommandableGrpcService() : base("mydata")
    {
        this._dependencyResolver.Put("controller", new Descriptor("service-mydata", "controller", "*", "*", "*"));
    }
}
import (
    grpcsrv "github.com/pip-services3-gox/pip-services3-grpc-gox/services"
    cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
)


type MyDataCommandableGrpcService struct {
	*grpcsrv.CommandableGrpcService
}

func NewMyDataCommandableGrpcService() *MyDataCommandableGrpcService {
	c := &MyDataCommandableGrpcService{}
	c.CommandableGrpcService = grpcsrv.InheritCommandableGrpcService(c, "mydata")
	c.DependencyResolver.Put(context.Background(), "controller", cref.NewDescriptor("service-mydata", "controller", "default", "*", "*"))
	return c
}
class MyDataCommandableGrpcService extends CommandableGrpcService {
  MyDataCommandableGrpcService(): super('mydata') {
    dependencyResolver.put('controller', Descriptor('service-mydata', 'controller', '*', '*', '*'));
  }
}
from pip_services3_commons.refer import Descriptor
from pip_services3_grpc.services.CommandableGrpcService import CommandableGrpcService


class MyDataCommandableGrpcService(CommandableGrpcService):

    def __init__(self):
        super().__init__('mydata')
        self._dependency_resolver.put('controller', Descriptor('service-mydata', 'controller', '*', '*', '*'))
Not available

Container

To run our service, we define a container that calls the data factory previously defined and the DefaultGrpcFactory component. These classes will create our data objects and gRPC service respectively.

using PipServices3.Container;
using PipServices3.Grpc.Build;

public class MyDataProcess: ProcessContainer
{
    public MyDataProcess() : base("my_data", "simple my data microservice")
    {
        _factories.Add(new DefaultMyDataFactory());
        _factories.Add(new DefaultGrpcFactory());
    }
}
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var task = (new MyDataProcess()).RunAsync(args);
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex);
        }
    }
}

import (
    ccont "github.com/pip-services3-gox/pip-services3-container-gox/container"
    grpcbuild "github.com/pip-services3-gox/pip-services3-grpc-gox/build"
)

type MyDataProcess struct {
	*ccont.ProcessContainer
}

func NewMyDataProcess() *MyDataProcess {
	c := &MyDataProcess{}
	c.ProcessContainer = ccont.NewProcessContainer("my_data", "simple my data microservice")
	c.AddFactory(NewDefaultMyDataFactory())
	c.AddFactory(grpcbuild.NewDefaultGrpcFactory())

	return c
}
import 'package:pip_services3_container/pip_services3_container.dart';


class MyDataProcess extends ProcessContainer
{
    MyDataProcess() : super('my_data', 'simple my data microservice')
    {
        factories.add(DefaultMyDataFactory());
        factories.add(DefaultGrpcFactory());
    }
}
from pip_services3_container import ProcessContainer
from pip_services3_grpc.build import DefaultGrpcFactory

from build.DefaultMyDataFactory import DefaultMyDataFactory


class MyDataProcess(ProcessContainer):
    def __init__(self):
        super().__init__("my_data", "simple my data microservice")
        self._factories.add(DefaultMyDataFactory())
        self._factories.add(DefaultGrpcFactory())
Not available

Configuration

Our next step is to create a config file that contains information about our components and can be used by our container to find them. In this example, we don’t specify the _config_path variable in the container but we use its default value ("./config/config.yml"). The code below shows the content of this file:

---
# Container descriptor
- descriptor: "pip-services:context-info:default:default:1.0"
  name: "mydata"
  description: "MyData microservice"

# Console logger
- descriptor: "pip-services:logger:console:default:1.0"
  level: "trace"

# Controller
- descriptor: "service-mydata:controller:default:default:1.0"

# Common GRPC endpoint
- descriptor: "pip-services:endpoint:grpc:default:1.0"
  connection:
    protocol: "http"
    host: "0.0.0.0"
    port: 8090

# Commandable GRPC endpoint version 1.0
- descriptor: "service-mydata:service:commandable-grpc:default:1.0"

Proto files

When using the commandable gRPC classes, we don’t need to worry about the proto files. This is because these classes rely on a universal proto file defined in the gRPC module that is automatically called by them.

Client

After defining our service, we need to create a client that calls its methods. For this, we create an interface that declares the used CRUD methods. Then, we construct a class that calls this interface and defines the inherited methods. The code below shows both programs:

import { FilterParams, PagingParams, DataPage } from "pip-services3-commons-nodex";
import { MyData } from "my-package";

export interface IMyDataClient {
    getMyDatas(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<MyData>>;
    getMyDataById(correlationId: string, id: string): Promise<MyData>;
    createMyData(correlationId: string, entity: MyData): Promise<MyData>;
    updateMyData(correlationId: string, entity: MyData): Promise<MyData>;
    deleteMyData(correlationId: string, id: string): Promise<MyData>;
}
using PipServices3.Commons.Data;

public interface IMyDataClient
{
    Task<DataPage<MyData>> GetMyDatasAsync(string correlationId, FilterParams filter, PagingParams paging);
    Task<MyData> GetMyDataByIdAsync(string correlationId, string id);
    Task<MyData> CreateMyDataAsync(string correlationId, MyData entity);
    Task<MyData> UpdateMyDataAsync(string correlationId, MyData entity);
    Task<MyData> DeleteMyDataAsync(string correlationId, string id);
}
import (
	"context"

	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
)

type IMyDataClient interface {
	GetMyDatas(ctx context.Context, correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (result *cdata.DataPage[MyData], err error)
	GetMyDataById(ctx context.Context, correlationId string, dummyId string) (result *MyData, err error)
	CreateMyData(ctx context.Context, correlationId string, dummy MyData) (result *MyData, err error)
	UpdateMyData(ctx context.Context, correlationId string, dummy MyData) (result *MyData, err error)
	DeleteMyData(ctx context.Context, correlationId string, dummyId string) (result *MyData, err error)
}
abstract class IMyDataClient {
    Future<DataPage<MyData>?> getMyDatas(String? correlationId, FilterParams? filter, PagingParams? paging);
    Future<MyData?> getMyDataById(String? correlationId, String? id);
    Future<MyData> createMyData(String? correlationId, MyData entity);
    Future<MyData> updateMyData(String? correlationId, MyData entity);
    Future<MyData> deleteMyData(String? correlationId, String id);
}
from abc import ABC
from typing import Optional
from pip_services3_commons.data import FilterParams, PagingParams, DataPage
from data.MyData import MyData

class IMyDataClient(ABC):

    def get_my_datas(self, correlation_id: Optional[str], filter: Optional[FilterParams], paging: Optional[PagingParams]) -> DataPage:
        pass

    def get_my_data_by_id(self, correlation_id: Optional[str], my_data_id: str) -> MyData:
        pass

    def create_my_data(self, correlation_id: Optional[str], my_data: MyData) -> MyData:
        pass

    def update_my_data(self, correlation_id: Optional[str], my_data: MyData) -> MyData:
        pass

    def delete_my_data(self, correlation_id: Optional[str], my_data_id: str) -> MyData:
        pass
Not available
import { DataPage, Descriptor, FilterParams, PagingParams } from "pip-services3-commons-nodex";
import { CommandableGrpcClient } from "pip-services3-grpc-nodex";
import { MyData, IMyDataClient } from "my-package";


export class MyCommandableGrpcClient extends CommandableGrpcClient implements IMyDataClient {
    public constructor() {
        super('mydata');
    }

    public async getMyDatas(correlationId: string, filter: FilterParams, paging: PagingParams): Promise<DataPage<MyData>> {
        return await this.callCommand('get_my_datas', correlationId, { filter: filter, paging: paging });
    }

    public async getMyDataById(correlationId: string, id: string): Promise<MyData> {
        return await this.callCommand('get_my_data_by_id', correlationId, { my_data_id: id });
    }

    public async createMyData(correlationId: string, entity: MyData): Promise<MyData> {
        return await this.callCommand('create_my_data', correlationId, { my_data: entity });
    }

    public async updateMyData(correlationId: string, entity: MyData): Promise<MyData> {
        return this.callCommand('update_my_data', correlationId, { my_data: entity })
    }
    
    public async deleteMyData(correlationId: string, id: string): Promise<MyData> {
        return this.callCommand('delete_my_data', correlationId, { my_data_id: id })
    }

}
using PipServices3.Commons.Commands;
using PipServices3.Commons.Data;
using PipServices3.Commons.Refer;
using PipServices3.Grpc.Clients;


public class MyCommandableGrpcClient : CommandableGrpcClient, IMyDataClient
{
    public MyCommandableGrpcClient() : base("mydata") { }

    public Task<DataPage<MyData>> GetMyDatasAsync(string correlationId, FilterParams filter, PagingParams paging)
    {
        filter = filter ?? new FilterParams();
        paging = paging ?? new PagingParams();

        var requestEntity = new
        {
            correlationId=correlationId,
            filter=filter,
            paging= paging,
        };

        return CallCommandAsync<DataPage<MyData>>("get_my_datas", correlationId, requestEntity);
    }

    public Task<MyData> GetMyDataByIdAsync(string correlationId, string id)
    {
        var requestEntity = new
        {
            correlationId = correlationId,
            my_data_id=id
        };

        return CallCommandAsync<MyData>("get_my_data_by_id", correlationId, requestEntity);
    }

    public Task<MyData> CreateMyDataAsync(string correlationId, MyData entity)
    {
        var requestEntity = new
        {
            correlationId = correlationId,
            my_data =entity
        };

        return CallCommandAsync<MyData>("create_my_data", correlationId, requestEntity);
    }

    public Task<MyData> DeleteMyDataAsync(string correlationId, string id)
    {
        var requestEntity = new
        {
            correlationId = correlationId,
            my_data_id =id
        };

        return CallCommandAsync<MyData>("delete_my_data", correlationId, requestEntity);
    }
   

    public Task<MyData> UpdateMyDataAsync(string correlationId, MyData entity)
    {
        var requestEntity = new
        {
            correlationId= correlationId,
            my_data=entity
        };

        return CallCommandAsync<MyData>("update_my_data", correlationId, requestEntity);
    }
}

import (
	"context"

	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	grpcclients "github.com/pip-services3-gox/pip-services3-grpc-gox/clients"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
)


type MyDataCommandableGrpcClient struct {
	*grpcclnt.CommandableGrpcClient
}

func NewMyDataCommandableGrpcClient() *MyDataCommandableGrpcClient {
	dcgc := MyDataCommandableGrpcClient{}
	dcgc.CommandableGrpcClient = grpcclnt.NewCommandableGrpcClient("mydata")
	return &dcgc
}

func (c *MyDataCommandableGrpcClient) GetMyDatas(ctx context.Context, correlationId string, filter *cdata.FilterParams, paging *cdata.PagingParams) (result *cdata.DataPage[MyData], err error) {

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

	response, calErr := c.CallCommand(ctx, "get_my_datas", correlationId, cdata.NewAnyValueMapFromValue(params.Value()))
	if calErr != nil {
		return nil, calErr
	}

	return grpcclnt.HandleHttpResponse[*cdata.DataPage[MyData]](response, correlationId)
}

func (c *MyDataCommandableGrpcClient) GetMyDataById(ctx context.Context, correlationId string, mydataId string) (result *MyData, err error) {

	params := cdata.NewEmptyAnyValueMap()
	params.Put("my_data_id", mydataId)

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

	return grpcclnt.HandleHttpResponse[*MyData](response, correlationId)
}

func (c *MyDataCommandableGrpcClient) CreateMyData(ctx context.Context, correlationId string, mydata MyData) (result *MyData, err error) {

	params := cdata.NewEmptyAnyValueMap()
	params.Put("my_data", mydata)

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

	return grpcclnt.HandleHttpResponse[*MyData](response, correlationId)
}

func (c *MyDataCommandableGrpcClient) UpdateMyData(ctx context.Context, correlationId string, mydata MyData) (result *MyData, err error) {

	params := cdata.NewEmptyAnyValueMap()
	params.Put("my_data", mydata)

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

	return grpcclnt.HandleHttpResponse[*MyData](response, correlationId)
}

func (c *MyDataCommandableGrpcClient) DeleteMyData(ctx context.Context, correlationId string, mydataId string) (result *MyData, err error) {

	params := cdata.NewEmptyAnyValueMap()
	params.Put("my_data_id", mydataId)

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

	return grpcclnt.HandleHttpResponse[*MyData](response, correlationId)
}

class MyCommandableGrpcClient extends CommandableGrpcClient
    implements IMyDataClient {
  MyCommandableGrpcClient() : super('mydata');

  @override
  Future<DataPage<MyData>?> getMyDatas(
      String? correlationId, FilterParams? filter, PagingParams? paging) async {
    var response = await callCommand(
        'get_my_datas', correlationId, {'filter': filter, 'paging': paging});
    if (response == null) {
      return null;
    }
    return DataPage<MyData>.fromJson(response, (item) => MyData.fromJson(item));
  }

  @override
  Future<MyData> createMyData(String? correlationId, MyData entity) async {
    var response =
        await callCommand('create_my_data', correlationId, {'my_data': entity});
    return MyData.fromJson(response);
  }

  @override
  Future<MyData> deleteMyData(String? correlationId, String id) async {
    var response =
        await callCommand('delete_my_data', correlationId, {'my_data_id': id});
    return MyData.fromJson(response);
  }

  @override
  Future<MyData?> getMyDataById(String? correlationId, String? id) async {
    var response = await callCommand(
        'get_my_data_by_id', correlationId, {'my_data_id': id});

    if (response == null) {
      return null;
    }
    return MyData.fromJson(response);
  }

  @override
  Future<MyData> updateMyData(String? correlationId, MyData entity) async {
    var response =
        await callCommand('update_my_data', correlationId, {'my_data': entity});

    return MyData.fromJson(response);
  }
}

from typing import Optional

from pip_services3_commons.data.DataPage import DataPage
from pip_services3_commons.data.FilterParams import FilterParams
from pip_services3_commons.data.PagingParams import PagingParams

from pip_services3_grpc.clients.CommandableGrpcClient import CommandableGrpcClient

from data.MyData import MyData
from .IMyDataClient import IMyDataClient

class MyCommandableGrpcClient(CommandableGrpcClient, IMyDataClient):

    def __init__(self):
        super().__init__('mydata')

    def get_my_datas(self, correlation_id: Optional[str], filter: Optional[FilterParams], paging: Optional[PagingParams]) -> DataPage:
        return self.call_command('get_my_datas', correlation_id, {'filter': filter, 'paging': paging})

    def get_my_data_by_id(self, correlation_id: Optional[str], my_data_id) -> MyData:
        return self.call_command('get_my_data_by_id', correlation_id, {'my_data_id': my_data_id})

    def create_my_data(self, correlation_id: Optional[str], my_data) -> MyData:
        return self.call_command('create_my_data', correlation_id, {'my_data': my_data})

    def update_my_data(self, correlation_id: Optional[str], my_data) -> MyData:
        return self.call_command('update_my_data', correlation_id, {'my_data': my_data})

    def delete_my_data(self, correlation_id: Optional[str], my_data_id: str) -> MyData:
        return self.call_command('delete_my_data', correlation_id, {'my_data_id': my_data_id})

Not available

Running the application

Now, we start the service. To do this, we run the following code:

export async function main() { 
    try {
        let proc = new MyDataProcess();
        proc.run(process.argv);
    } catch (ex) {
        console.error(ex);
    }
}
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var task = (new MyDataProcess()).RunAsync(args);
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex);
        }
    }
}

import (
    "os"
    "context"
)

func main() {
	proc := NewMyDataProcess()
	proc.SetConfigPath("./config/config.yml")
	proc.Run(context.Background(), os.Args)
}
void main(List<String> argument) {
  try {
    var proc = MyDataProcess();
    proc.run(argument);
  } catch (ex) {
    print(ex);
  }
}
import sys

from containers.MyDataProcess import MyDataProcess

if __name__ == '__main__':
    runner = MyDataProcess()
    try:
        runner.run()
    except Exception as ex:
        sys.stderr.write(str(ex) + '\n')
Not available

Which, after execution, produces the following output:

figure 3

Once our service is running, we run a program that creates an instance of the client and instances of the MyData class, and calls the CRUD operations available from the service. The following code shows how to do this:

import { ConfigParams, References } from "pip-services3-commons-nodex";
import { MyData, MyCommandableGrpcClient, IMyDataClient } from "my-package";

export async function main() {
    const assert = require('assert');

    let correlationId = 'example';

    // create client
    let grpcConfig = ConfigParams.fromTuples(
        'connection.protocol', 'http',
        'connection.host', 'localhost',
        'connection.port', 8090
    );

    let grpcClient = new MyCommandableGrpcClient();
    grpcClient.configure(grpcConfig);
    grpcClient.setReferences(new References());
    await grpcClient.open(correlationId);

    // simple data
    let data1: MyData = {id: '1', key: '0005', content: 'any content 1'};
    let data2: MyData = {id: '2', key: '0010', content: 'any content 2'};

    // using the client
    let res = await grpcClient.createMyData(correlationId, data1);
    assert(res.id == data1.id);

    res = await grpcClient.createMyData(correlationId, data2);
    assert(res.id == data2.id);

    let resPage = await grpcClient.getMyDatas(correlationId, null, null);
    assert(resPage.data.length == 2); 

    res = await grpcClient.deleteMyData(correlationId, data2.id);
    assert(res.id == data2.id);

    res = await grpcClient.getMyDataById(correlationId, data2.id);
    assert(res == null);
}

using PipServices3.Commons.Refer;
using PipServices3.Commons.Config;

var correlationId = "example";

// create client
var grpcConfig = ConfigParams.FromTuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8090
);

var grpcClient = new MyCommandableGrpcClient();
grpcClient.Configure(grpcConfig);
grpcClient.SetReferences(new References());
await grpcClient.OpenAsync(correlationId);

// simple data
var data1 = new MyData("1", "0005", "any content 1");
var data2 = new MyData("2", "0010", "any content 2");

// using the client
var res = await grpcClient.CreateMyDataAsync(correlationId, data1);
Debug.Assert(res.Id == data1.Id);

res = await grpcClient.CreateMyDataAsync(correlationId, data2);
Debug.Assert(res.Id == data2.Id);

var resPage = await grpcClient.GetMyDatasAsync(correlationId, null, null);
Debug.Assert(resPage.Data.Count == 2);

res = await grpcClient.DeleteMyDataAsync(correlationId, data2.Id);
Debug.Assert(res.Id == data2.Id);

res = await grpcClient.GetMyDataByIdAsync(correlationId, data2.Id);
Debug.Assert(res == null);


import (
	"context"
	"testing"
	pack "tst/pack"

	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	"github.com/stretchr/testify/assert"
)

func TestRun(t *testing.T) {
	ctx := context.Background()
	correlationId := "123"
	// create client
	grpcConfig := cconf.NewConfigParamsFromTuples(
		"connection.protocol", "http",
		"connection.host", "localhost",
		"connection.port", 8090,
	)

	grpcClient := pack.NewMyDataCommandableGrpcClient()
	grpcClient.Configure(ctx, grpcConfig)
	grpcClient.SetReferences(ctx, cref.NewReferences(ctx, make([]any, 0)))
	err := grpcClient.Open(ctx, correlationId)
	assert.Nil(t, err)
	// simple data
	data1 := pack.MyData{Id: "1", Key: "0005", Content: "any content 1"}
	data2 := pack.MyData{Id: "2", Key: "0010", Content: "any content 2"}

	// using the client
	res, err := grpcClient.CreateMyData(ctx, correlationId, data1)
	assert.Nil(t, err)
	assert.Equal(t, res.Id, data1.Id)

	res, err = grpcClient.CreateMyData(ctx, correlationId, data2)
	assert.Nil(t, err)
	assert.Equal(t, res.Id, data2.Id)

	resPage, err := grpcClient.GetMyDatas(ctx, correlationId, nil, nil)
	assert.Nil(t, err)
	assert.Equal(t, len(resPage.Data), 2)

	res, err = grpcClient.DeleteMyData(ctx, correlationId, data2.Id)
	assert.Nil(t, err)
	assert.Equal(t, res.Id, data2.Id)

	res, err = grpcClient.GetMyDataById(ctx, correlationId, data2.Id)
	assert.Nil(t, err)
	assert.Nil(t, res)
}


void main() async {
  var correlationId = 'example';

  // create client
  var grpcConfig = ConfigParams.fromTuples([
    'connection.protocol',
    'http',
    'connection.host',
    'localhost',
    'connection.port',
    8090
  ]);

  var grpcClient = MyCommandableGrpcClient();
  grpcClient.configure(grpcConfig);
  grpcClient.setReferences(References());
  await grpcClient.open(correlationId);

  // simple data
  var data1 = MyData.from('1', '0005', 'any content 1');
  var data2 = MyData.from('2', '0010', 'any content 2');

  // using the client
  MyData? res = await grpcClient.createMyData(correlationId, data1);
  assert(res.id == data1.id);

  res = await grpcClient.createMyData(correlationId, data2);
  assert(res.id == data2.id);

  var resPage = await grpcClient.getMyDatas(correlationId, null, null);
  assert(resPage?.data.length == 2);

  res = await grpcClient.deleteMyData(correlationId, data2.id!);
  assert(res.id == data2.id);

  res = await grpcClient.getMyDataById(correlationId, data2.id);
  assert(res == null);
}

from pip_services3_commons.config import ConfigParams
from pip_services3_commons.refer import References

from clients.IMyDataClient import IMyDataClient
from clients.MyCommandableGrpcClient import MyCommandableGrpcClient
from data.MyData import MyData

correlation_id = 'example'

# create client
grpc_config = ConfigParams.from_tuples(
    'connection.protocol', 'http',
    'connection.host', 'localhost',
    'connection.port', 8090
)

grpcClient = MyCommandableGrpcClient()
grpcClient.configure(grpc_config)
grpcClient.set_references(References())
grpcClient.open(correlation_id)

# simple data
data1: MyData = MyData('1', '0005', 'any content 1')
data2: MyData = MyData('2', '0010', 'any content 2')

# using the client
res = grpcClient.create_my_data(correlation_id, data1)
assert res.id == data1.id

res = grpcClient.create_my_data(correlation_id, data2)
assert res.id == data2.id

res = grpcClient.get_my_datas(correlation_id, None, None)
assert len(res.data) == 2

res = grpcClient.delete_my_data(correlation_id, data2.id)
assert res.id == data2.id

res = grpcClient.get_my_data_by_id(correlation_id, data2.id)
assert res is None

Not available

Which, after running will produce the following output from our service:

figure 4

Wrapping up

In this tutorial, we have learned how to create a simple system that includes a command set, together with a service and a client that communicate via the gRPC protocol. In order to do this, we created a system that contains a CommandSet, a CommandableGrpcService and a CommandableGrpcClient. Then, we encapsulated our service in a container and created a program that calls the different CRUD methods available from the command set.