Communication between components

How we can use the References class to communicate between different components.

Key takeaways

References Component used to store and retrieve component references.
Get methods Collection of several methods contained in the References class and used to retrieve stored component references. E.g. getOneRequired().
setReferences() Method available from the PrometheusCounters and PrometheusMetricsController classes and used to store component references in a References component.

Introduction

When using Pip.Services, a good programming practice is to use an external entity to handle the communication between different components. In this tutorial, you will learn how to do this by using the References class.

For this, we will first see the main characteristics and methods of the References component. Then, we will explain how this component is used in the example provided in the Prometheus tutorial and how it helps with the communication between different components. To conclude, we will summarize the learned concepts.

The References component

The References component is available via the Commons module. It’s used to store information on registered components and to pass this information to other components when requested.

Pre-requisites

To use this component, we must first import it. This can be done with the following command:

import { References } from "pip-services4-components-node";
Not available
import (
    refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
)
Not available
from pip_services4_components.refer import References
Not available

Creation

In order to create a References object, we need to create a component that can be added to it. Following the Prometheus example, we create a PrometheusMetricsController object. The code below shows how to do this:

import { ConfigParams } from "pip-services4-components-node";
import { PrometheusMetricsController } from "pip-services4-prometheus-node";

let controller = new PrometheusMetricsController();

controller.configure(ConfigParams.fromTuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8080
));
Not available
import (
	"context"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	pcontroller "github.com/pip-services4/pip-services4-go/pip-services4-prometheus-go/controllers"
	pcount "github.com/pip-services4/pip-services4-go/pip-services4-prometheus-go/count"
)


controller := pcontroller.NewPrometheusMetricsController()
service.Configure(context.Background(), cconf.NewConfigParamsFromTuples(
	"connection.protocol", "http",
	"connection.host", "localhost",
	"connection.port", 8080,
))

counters := pcount.NewPrometheusCounters()
counters.Configure(context.Background(), cconf.NewConfigParamsFromTuples(
	"connection.protocol", "http",
	"connection.host", "localhost",
	"connection.port", 8081,
))
Not available
from pip_services4_prometheus.controllers import PrometheusMetricsController
from pip_services4_prometheus.count import PrometheusCounters
from pip_services4_components.config import ConfigParams

controller = PrometheusMetricsController()

controller.configure(ConfigParams.from_tuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8080
))

counters = PrometheusCounters()
counters.configure(ConfigParams.from_tuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8081
))
Not available

There are three main ways to create a References component. The first is to use the fromTuples() method. This static method allows us to add one or more references to our component. In the example below, we create a References component and add the controller previously created.

import { Descriptor, References } from "pip-services4-components-node";

let references = References.fromTuples(
  new Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller
);

Not available
import (
        "context"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
)

references := refer.NewReferencesFromTuples(context.Background(),
	refer.NewDescriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller,
)
Not available
from pip_services4_components.refer import Descriptor

references = References.from_tuples(
    Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller
)
Not available

The second way is to create an instance of the References class and later on add the necessary references to it. The following code shows how to do this:

let references2 = new References();
Not available
references := refer.NewEmptyReferences()
Not available
references2 = References()
Not available

Lastly, we can use the constructor with a tuple that includes a list of references, where odd elements are locators and even elements are component references. The following example shows how to create a References object that contains a reference to the previously created controller.

let references3 = new References([new Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller]);
Not available
references3 := refer.NewReferences(context.Background(), []interface{}{refer.NewDescriptor("pip-services", "context-info", "default", "default", "1.0"), controller})
Not available
references3 = References((Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller))
Not available

Main methods

The References class has several methods that allow us to store and retrieve data from an instance of it. They are:

find()

This method obtains all the component references that match a specific locator. In the example below, we ask for all those references that relate to our controller, and we obtain a reference to this component.

references.find(controller, true);
Not available
references.Find(controller, true)
Not available
references.find(controller, True)
Not available
getAll()

This method retrieves all the stored component references in the form of a list. In our example, it returns a list with a reference to the controller object.

references.getAll();
Not available
references.GetAll()
Not available
references.get_all()
Not available
getAllLocators()

This method obtains the locators corresponding to all the registered component references. In the example below, we get a Descriptor object containing information about the controller object.

references.getAllLocators();
Not available
references.GetAllLocators()
Not available
references.get_all_locators()
Not available
getOneOptional()

This method retrieves an optional component reference that matches a specified locator. In the example below, we obtain a reference to the stored controller object.

references.getOneOptional(controller);
Not available
references.GetOneOptional(controller)
Not available
references.get_one_optional(controller)
Not available
getOneRequired()

This method gets a required component reference that matches a specified locator and throws a ReferenceException when no reference is found. In the following example, we request and obtain the controller component.

references.getOneRequired(controller);
Not available
references.GetOneRequired(controller)
Not available
references.get_one_required(controller)
Not available
getOptional()

This method obtains all component references that match a specified locator. In the example below, we ask for the controller component and obtain a list with it.

references.getOptional(controller);
Not available
references.GetOptional(controller)
Not available
references.get_optional(controller)
Not available
getRequired()

This method gets all component references that match a specified locator. If no reference is found, it throws a ReferenceException. In our example, it returns a reference to the stored controller component.

references.getRequired(controller);
Not available
references.GetRequired(controller)
Not available
references.get_required(controller)
Not available
put()

This method adds a reference to the Reference component. In the example below, we add a reference to the controller object defined earlier.

references2.put(new Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller);
Not available
references2.Put(context.Background(), refer.NewDescriptor("pip-controller", "metrics-controller", "prometheus", "default", "1.0"), controller)
Not available
references2.put(Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller)
Not available
remove()

This method removes a previously added reference that matches a specified locator. If many references match the locator, it removes only the first one. In the example below, it removes the reference to the controller object previously added.

references2.remove(controller);
Not available
references2.Remove(context.Background(), controller)
Not available
references2.remove(controller)
Not available
removeAll()

This method removes all component references that match a specified locator. For example, the code below removes all references to the controller component.

references.removeAll(controller);
Not available
references.RemoveAll(context.Background(), controller)
Not available
references.remove_all(controller)
Not available

Example 1

In order to explain how the References component can help us to create communication channels for different components, we will use the example created in the Prometheus tutorial. There, we created a custom component named MyComponentA, a PrometheusCounters component that was used to create different counters, and a PrometheusMetricsController object that was used to create a webpage containing the counters' information under /metrics.

In the tutorial’s example, these three components are added to the References object via the setReferences() method. When needed, they are called via a get method. The figure below summarizes this structure.

figure 1

In that example, and in order to add the three components to the References object, we use the fromTuples() method. This method accepts one or more Descriptor objects, each containing information about a component, and the respective referenced object. The following code shows how this is done:

let context_info = new ContextInfo();
context_info.name = "Test";
context_info.description = "This is a test container";

let references = References.fromTuples(
    new Descriptor("pip-services", "context-info", "default", "default", "1.0"), context_info,
    new Descriptor("pip-services", "counters", "prometheus", "default", "1.0"), counters,
    new Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller
);

controller.setReferences(references);
counters.setReferences(references);
Not available
import (
	"context"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	ccontext "github.com/pip-services4/pip-services4-go/pip-services4-components-go/context"
)

contextInfo := ccontext.NewContextInfo()
contextInfo.Name = "Test"
contextInfo.Description = "This is a test container"

references := refer.NewReferencesFromTuples(context.Background(),
	refer.NewDescriptor("pip-services", "context-info", "default", "default", "1.0"), contextInfo,
	refer.NewDescriptor("pip-services", "counters", "prometheus", "default", "1.0"), counters,
	refer.NewDescriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller,
)

controller.SetReferences(context.Background(), references)
counters.SetReferences(context.Background(), references)
Not available
from pip_services4_components.refer import Descriptor
from pip_services4_components.context import ContextInfo

context_info = ContextInfo()
context_info.name = 'Test'
context_info.description = 'This is a test container'

references = References.from_tuples(
    Descriptor("pip-services", "context-info", "default", "default", "1.0"), context_info,
    Descriptor("pip-services", "counters", "prometheus", "default", "1.0"), counters,
    Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller
)

controller.set_references(references)
counters.set_references(references)
Not available

Once we have our components in the References object, we can obtain them via any of the get methods available from this class. The code below shows how the MyComponentA class obtains the required counters:

class MyComponentA implements IReferenceable {
    public consoleLog: boolean = true; // console log flag
    private counters: CachedCounters;

    public constructor() {

        if (this.consoleLog)
            console.log("MyComponentA has been created.");
    }
    public setReferences(references: IReferences): void {
        this.counters = references.getOneRequired<CachedCounters>(
            new Descriptor("*", "counters", "*", "*", "*")
        );
    }
}

Not available
type MyComponentA struct {
	counters *pcount.PrometheusCounters

	ConsoleLog bool // console log flag
}

func NewMyComponentA() *MyComponentA {
	c := MyComponentA{
		ConsoleLog: true,
	}
	if c.ConsoleLog {
		fmt.Println("MyComponentA has been created.")
	}
	return &c
}

func (c *MyComponentA) SetReferences(references refer.IReferences) {
	p, err := references.GetOneRequired(
		refer.NewDescriptor("*", "counters", "prometheus", "*", "*"),
	)

	if p != nil && err == nil {
		c.counters = p.(*pcount.PrometheusCounters)
	}
}
Not available
from pip_services4_components.refer import IReferenceable, IReferences

class MyComponentA(IReferenceable):
    console_log: bool = True  # console log flag

    def __init__(self):
        self._counters: PrometheusCounters = None
        if self.console_log:
            print("MyComponentA has been created.")

    # Added references for getting counters
    def set_references(self, references: IReferences):
        self._counters = references.get_one_required(
            Descriptor("*", "counters", "*", "*", "*")
        )
Not available

Similarly, if we check the source code for the PrometheusCounters class, we can see that the class gets the context information via the getOneOptional() method.

/**
	 * Sets references to dependent components.
	 * 
	 * @param references 	references to locate the component dependencies. 
     */
    public setReferences(references: IReferences): void {
        this._logger.setReferences(references);
        this._connectionResolver.setReferences(references);

        let contextInfo = references.getOneOptional<ContextInfo>(
            new Descriptor("pip-services", "context-info", "default", "*", "1.0"));
        if (contextInfo != null && this._source == null) {
            this._source = contextInfo.name;
        }
        if (contextInfo != null && this._instance == null) {
            this._instance = contextInfo.contextId;
        }
    }
Not available
// SetReferences method are sets references to dependent components.
//
//	Parameters:
//		- ctx context.Context	operation context
//		- references  cref.IReferences
//
// references to locate the component dependencies.
func (c *PrometheusCounters) SetReferences(ctx context.Context, references cref.IReferences) {
	c.logger.SetReferences(ctx, references)
	c.connectionResolver.SetReferences(ctx, references)
	ref := references.GetOneOptional(
		cref.NewDescriptor("pip-services", "context-info", "default", "*", "1.0"))
	contextInfo, _ := ref.(*cctx.ContextInfo)
	if contextInfo != nil && c.source == "" {
		c.source = contextInfo.Name
	}
	if contextInfo != nil && c.instance == "" {
		c.instance = contextInfo.ContextId
	}
}
Not available
    def set_references(self, references: IReferences):
        """
        Sets references to dependent components.
        :param references: references to locate the component dependencies.
        """
        self.__logger.set_references(references)
        self.__connection_resolver.set_references(references)

        context_info = references.get_one_optional(Descriptor("pip-services", "context-info", "default", "*", "1.0"))
        if context_info is not None and self.__source is None:
            self.__source = context_info.name
        if context_info is not None and self.__instance is None:
            self.__instance = context_info.context_id
Not available

And the PrometheusMetricsController component obtains the context information via the getOneRequired() method:

 /**
	 * Sets references to dependent components.
	 * 
	 * @param references 	references to locate the component dependencies. 
     */
    public setReferences(references: IReferences): void {
        super.setReferences(references);

        this._cachedCounters = this._dependencyResolver.getOneOptional<PrometheusCounters>("prometheus-counters");
        if (this._cachedCounters == null)
            this._cachedCounters = this._dependencyResolver.getOneOptional<CachedCounters>("cached-counters");

        let contextInfo = references.getOneOptional<ContextInfo>(
            new Descriptor("pip-services", "context-info", "default", "*", "1.0"));

        if (contextInfo != null && (this._source == "" || this._source == undefined)) {
            this._source = contextInfo.name;
        }
        if (contextInfo != null && (this._instance == "" || this._instance == undefined)) {
            this._instance = contextInfo.contextId;
        }
    }
Not available
// SetReferences is sets references to dependent components.
//
//	Parameters:
//		- ctx context.Context	operation context
//		- references cref.IReferences
//
// references to locate the component dependencies.
func (c *PrometheusMetricsController) SetReferences(ctx context.Context, references cref.IReferences) {
	c.RestController.SetReferences(ctx, references)

	resolv := c.DependencyResolver.GetOneOptional("prometheus-counters")
	c.cachedCounters = resolv.(*pcount.PrometheusCounters).CachedCounters
	if c.cachedCounters == nil {
		resolv = c.DependencyResolver.GetOneOptional("cached-counters")
		c.cachedCounters = resolv.(*ccount.CachedCounters)
	}
	ref := references.GetOneOptional(
		cref.NewDescriptor("pip-services", "context-info", "default", "*", "1.0"))
	contextInfo := ref.(*cctx.ContextInfo)

	if contextInfo != nil && c.source == "" {
		c.source = contextInfo.Name
	}
	if contextInfo != nil && c.instance == "" {
		c.instance = contextInfo.ContextId
	}
}
Not available
 def set_references(self, references: IReferences):
        """
        Sets references to dependent components.
        :param references: references to locate the component dependencies.
        """
        super().set_references(references)

        self.__cached_counters = self._dependency_resolver.get_one_required('prometheus-counters')
        if self.__cached_counters is None:
            self.__cached_counters = self._dependency_resolver.get_one_required('cached-counters')

        context_info = references.get_one_required(Descriptor("pip-services", "context-info", "default", "*", "1.0"))

        if context_info is not None and (self.__source == '' or self.__source is None):
            self.__source = context_info.name

        if context_info is not None and (self.__instance == '' or self.__instance is None):
            self.__instance = context_info.context_id
Not available

Example 2

An important advantage of Pip.Services is that once we have a set of references, we can define from general to specific queries.

For example, let’s assume that we have created the following References object:

let references = References.fromTuples(
    new Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller,
    new Descriptor("pip-services", "metrics-controller", "prometheus", "default", "2.0"), controller2,
    new Descriptor("pip-services", "counters", "prometheus", "default", "1.0"), counters,
    new Descriptor("pip-services", "context-info", "default", "*", "1.0"), new ContextInfo("tutorial", "Example context conmponent"),
    new Descriptor("pip-services", "logger", "console", "default", "1.0"), new ConsoleLogger()
);
Not available
references := refer.NewReferencesFromTuples(context.Background(),
	refer.NewDescriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller,
	refer.NewDescriptor("pip-services", "metrics-controller", "prometheus", "default", "2.0"), controller2,
	refer.NewDescriptor("pip-services", "counters", "prometheus", "default", "1.0"), counters,
	refer.NewDescriptor("pip-services", "context-info", "default", "*", "1.0"), cinfo.NewContextInfo(),
	refer.NewDescriptor("pip-services", "logger", "console", "default", "1.0"), clog.NewConsoleLogger(),
)
Not available
references = References.from_tuples(
    Descriptor("pip-services", "metrics-controller", "prometheus", "default", "1.0"), controller,
    Descriptor("pip-services", "metrics-controller", "prometheus", "default", "2.0"), controller2,
    Descriptor("pip-services", "counters", "prometheus", "default", "1.0"), counters,
    Descriptor("pip-services", "context-info", "default", "*", "1.0"),  ContextInfo('tutorial', 'Example context conmponent'),
    Descriptor("pip-services", "logger", "console","default", "1.0"),  ConsoleLogger()
)
Not available

Then, we can generate different types of queries, such as:

a. Get all controller objects

If we want to obtain all objects of a specific type, such as all our controller objects, we can create this query by specifying the name of the type and considering all other fields generic. Generic fields are specified by using a star to indicate “any”. For example, to obtain the two controllers, we can do the following query:

references.find(new Descriptor("*", "metrics-controller", "*", "*", "*"), true);
Not available
references.Find(refer.NewDescriptor("*", "metrics-controller", "*", "*", "*"), true)
Not available
references.find(Descriptor("*", "metrics-controller", "*", "*", "*"), True)
Not available

b. Get a specific controller object

The Reference object also allows us to work with several versions of a component. For example, in our Reference object, we have defined two versions of the controller. Therefore, if we want to choose version 2.0, we just need to create a reference to it. The example below shows how to do this.

references.getOneOptional(new Descriptor("*", "metrics-controller", "*", "*", "2.0"));

Not available
references.GetOneOptional(refer.NewDescriptor("*", "metrics-controller", "*", "*", "2.0"))
Not available
references.get_one_optional(Descriptor("*", "metrics-controller", "*", "*", "2.0"))
Not available

c. Get all components in a group

If we need to obtain all the components in a group, we can specify the group’s name and consider all the other fields generic. Thus, if we want to obtain the five objects stored in the “pip-services” group, we can do:

references.getRequired(new Descriptor("pip-services", "*", "*", "*", "*"));

Not available
references.GetRequired(refer.NewDescriptor("pip-services", "*", "*", "*", "*"))
Not available
references.get_required(Descriptor("pip-services", "*", "*", "*", "*"))
Not available

d. Get a specific component

To get a specific component outside the references object, we just need to specify its name. The following example shows how to obtain the controller component defined earlier.

let reference = references.getOneOptional(controller);

Not available
reference := references.GetOneOptional(controller)
Not available
reference = references.get_one_optional(controller)
Not available

Wrapping up

In this tutorial, we have learned how to use the References class to allow communication between different components. First, we saw how to create an instance of this class and use its different methods. Then, we understood how this component is used in the example defined in the Prometheus tutorial, where it works as a broker that stores information on registered components and passes it to other components when requested. Finally, we learned how to create different types of queries, from generic to specific ones.