Creating a component

How to create a component and assemble a service from it.

Key takeaways

Component creation How to create a component and define its behavior throughout its lifecycle.
Containerization How to add a component to a container and execute it.

Introduction

In this tutorial, we will learn how to create a component and how to assemble a service from it. We will start with a short description of a component’s lifecycle and then we will create a component by defining step-by-step all the elements that compose its lifecycle. Finally, we will assemble a service from it through a container, run it, and see the results.

The lifecycle of a component

A component lifecycle is implemented using interfaces. In this manner, an existing class can be turned into a Pip.Services component (code augmentation). The following diagram summarizes the main states of a component and their respective interfaces

Console logger messages

Creating our component

Following the sequence presented in the previous figure, we will create a component and explain each step of its lifecycle.

Step 1 – Creating the component

A component can be seen as a class that has a default constructor. Thus, we will begin by creating a class with a constructor, such as:

class MyComponentA {
    private _param1: string = "ABC";
    private _param2: number = 123;
    private _open: boolean = false;
    private _status: string;

    // Creates a new instance of the component.
    public constructor(){
        this._status = "Created";
        console.log("MyComponentA has been created.");
    }
}
Not available
type MyComponentA struct {
	param1 string
	param2 int
	open   bool

	status string
}

func NewMyComponentA() *MyComponentA {
	fmt.Println("MyComponentA has been created.")

	return &MyComponentA{
		param1: "ABC",
		param2: 123,
		open:   false,
		status: "Created",
	}
}
Not available
class MyComponentA():
    _param1 = 'ABC'
    _param2 = 123
    _open = False
    _status = None
    
    def __init__(self):
        """
        Creates a new instance of the component.
        """
        self._status = "Created"
        print("MyComponentA has been created.")  
Not available

Step 2 – Configuration

Now that we have a component, we will add configuration capabilities to it. For this, we need to use the Iconfigurable interface and define the configure method. In this method, we will receive a ConfigParams object (key-value map) with the values of the parameters and assign them to our variables.

import { IConfigurable, ConfigParams } from "pip-services4-components-node";

class MyComponentA implements IConfigurable {
  private _param1: string = "ABC";
  private _param2: number = 123;
  private _open: boolean = false;
  private _status: string;

  // Creates a new instance of the component.
  public constructor(){
      this._status = "Created";
      console.log("MyComponentA has been created.");
  }

  public configure(config: ConfigParams): void {
      this._param1 = config.getAsStringWithDefault("param1", "ABC");
      this._param2 = config.getAsIntegerWithDefault("param2", 123);
      this._status = "Configured";

      console.log("MyComponentA has been configured.");
  }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
)

type MyComponentA struct {
	cconf.IConfigurable

	param1 string
	param2 int
	open   bool

	status string
}

// Creates a new instance of the component.
func NewMyComponentA() *MyComponentA {
	fmt.Println("MyComponentA has been created.")

	return &MyComponentA{
		param1: "ABC",
		param2: 123,
		open:   false,
		status: "Created",
	}
}

func (c *MyComponentA) Configure(ctx context.Context, config *cconf.ConfigParams) {
	c.param1 = config.GetAsStringWithDefault("param1", "ABC")
	c.param2 = config.GetAsIntegerWithDefault("param2", 123)
	c.status = "Configured"
	fmt.Println("MyComponentA has been configured.")
}
Not available
from pip_services4_components.config import IConfigurable, ConfigParams

class MyComponentA(IConfigurable):
    _param1 = 'ABC'
    _param2 = 123
    _open = False
    _status = None
    
    def __init__(self):
        """
        Creates a new instance of the component.
        """
        self._status = "Created"
        print("MyComponentA has been created.")
            
    def configure(self, config):
        self._param1 = config.get_as_string_with_default("param1", 'ABC')
        self._param2 = config.get_as_integer_with_default("param2", 123)
        self._status = "Configured"
        print("MyComponentA has been configured.")

Not available

Step 3 – Referencing

After configuring our component, we want to link MyComponentA to another component called MyComponentB. To do this, we need to use the IReferenceable interface. This interface contains the set_references method, which allows us to define references to dependent components. As a component’s locator, we will use a Descriptor object with the connection parameters. Our code will be like this:

import { 
  IConfigurable, ConfigParams, IReferences, 
  IReferenceable, Descriptor 
} from "pip-services4-components-node";

class MyComponentB {
  private _param1: string = "ABC2";
  private _param2: number = 456;
  private _open: boolean = false;
  private _status: string;

  // Creates a new instance of the component.
  public constructor() {
      this._status = "Created";
      console.log("MyComponentB has been created.");
  }
}

class MyComponentA implements IConfigurable, IReferenceable {
  private _param1: string = "ABC";
  private _param2: number = 123;
  private _open: boolean = false;
  private _status: string;
  private _anotherComponent: MyComponentB;

  // Creates a new instance of the component.
  public constructor(){
      this._status = "Created";
      console.log("MyComponentA has been created.");
  }

  public setReferences(references: IReferences): void {
      this._anotherComponent = references.getOneRequired<MyComponentB>(
          new Descriptor("myservice", "mycomponent-b", "*", "*", "1.0")
      );

      this._status = "Configured";
      console.log("MyComponentA's references have been defined.");
  }

  public configure(config: ConfigParams): void {
      this._param1 = config.getAsStringWithDefault("param1", "ABC");
      this._param2 = config.getAsIntegerWithDefault("param2", 123);
      this._status = "Configured";

      console.log("MyComponentA has been configured.");
  }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
)

type MyComponentB struct {
	param1 string
	param2 int
	open   bool

	status string
}

type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}

	status string
}

// Creates a new instance of the component.
func NewMyComponentB() *MyComponentB {
	fmt.Println("MyComponentB has been created.")

	return &MyComponentB{
		param1: "ABC2",
		param2: 456,
		open:   false,
		status: "Created",
	}
}

// Creates a new instance of the component.
func NewMyComponentA() *MyComponentA {
	fmt.Println("MyComponentA has been created.")

	return &MyComponentA{
		param1: "ABC",
		param2: 123,
		open:   false,
		status: "Created",
	}
}

func (c *MyComponentA) Configure(ctx context.Context, config *cconf.ConfigParams) {
	c.param1 = config.GetAsStringWithDefault("param1", "ABC")
	c.param2 = config.GetAsIntegerWithDefault("param2", 123)
	c.status = "Configured"
	fmt.Println("MyComponentA has been configured.")
}

func (c *MyComponentA) SetReferences(ctx context.Context, references refer.IReferences) {
	component, err := references.GetOneRequired(
		refer.NewDescriptor("myservice", "mycomponent-b", "*", "*", "1.0"),
	)
	if err == nil {
		c.anotherComponent = component.(*MyComponentB)
		c.status = "Configured"
		fmt.Println("MyComponentA's references have been defined.")
	}

}
Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor

class MyComponentB():
    _param1 = 'ABC2'
    _param2 = 456
    _opened = False
    _status = None

    def __init__(self):
        """
        Creates a new instance of the component.
        """
        self._status = "Created"
        print("MyComponentB has been created.")

class MyComponentA(IReferenceable, IConfigurable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    
    def __init__(self):
        """
        Creates a new instance of the component.
        """
        self._status = "Created"
        print("MyComponentA has been created.")
            
    def configure(self, config):
        self._param1 = config.get_as_string_with_default("param1", 'ABC')
        self._param2 = config.get_as_integer_with_default("param2", 123)
        self._status = "Configured"
        print("MyComponentA has been configured.")
    def set_references(self, references):
        self._another_component = references.get_one_required(
            Descriptor("myservice", "mycomponent-b", "*", "*", "1.0")
        )
        self._status = "Configured"
        print("MyComponentA's references have been defined.")


Not available

Step 4 – Opening

To help define those components that require opening, Pip.Services offers the IOpenable interface. This interface is part of the Commons module and offers two methods: open and isOpen. In our example, we will use the first one to create the code that will open the component, and the second one to verify whether the component is open or not.

Moreover, as the opening of the component marks the start of its usage, we will add an optional component called context. This component is used to trace the execution of the component through the call chain. It can be a string, such as “123”.

Now, our code expands to this:

import { 
  IConfigurable, ConfigParams, IReferences, 
  IReferenceable, Descriptor, IOpenable, Context, IContext
} from "pip-services4-components-node";

class MyComponentB {
  // ...
}


class MyComponentA implements IConfigurable, IReferenceable, IOpenable {
  private _param1: string = "ABC";
  private _param2: number = 123;
  private _open: boolean = false;
  private _status: string;
  private _anotherComponent: MyComponentB;

  // Creates a new instance of the component.
  public constructor(){
      this._status = "Created";
      console.log("MyComponentA has been created.");
  }

  public isOpen(): boolean {
      return this._open;
  }

  public async open(ctx: Context): Promise<void> {
    return new Promise<void>((resolve) => {
      this._open = true;
      this._status = "Open";
      console.log("MyComponentA has been opened.");
      resolve();
  });
  }

  public setReferences(references: IReferences): void {
      this._anotherComponent = references.getOneRequired<MyComponentB>(
          new Descriptor("myservice", "mycomponent-b", "*", "*", "1.0")
      );

      this._status = "Configured";
      console.log("MyComponentA's references have been defined.");
  }

  public configure(config: ConfigParams): void {
      this._param1 = config.getAsStringWithDefault("param1", "ABC");
      this._param2 = config.getAsIntegerWithDefault("param2", 123);
      this._status = "Configured";

      console.log("MyComponentA has been configured.");
  }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	run "github.com/pip-services4/pip-services4-go/pip-services4-components-go/run"
)

type MyComponentB struct {
	// ...
}

type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable
	run.IOpenable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}

	status string
}

func (c *MyComponentA) IsOpen() bool {
	return c.open
}

func (c *MyComponentA) Open(ctx context.Context, correlationId string) error {
	c.open = true
	c.status = "Open"
	fmt.Println("MyComponentA has been opened.")
	return nil
}

Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor
from pip_services4_components.run import IOpenable

class MyComponentB():
     _param1 = 'ABC2'      
    # ...
       

class MyComponentA(IReferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    
    # ...

    def is_open(self):
        return self._open

    def open(self, context):
        self._open = True
        self._status = "Open"
        print("MyComponentA has been opened.")

Not available

Step 5 – Execution

We will now define a function that will be used to perform business tasks, and we will call it my_task. In our example, the business task will consist of printing a message and defining a dummy variable.

import { 
  IConfigurable, IReferenceable, IOpenable
} from "pip-services4-components-node";

class MyComponentB {
  // ...
}
   
  
class MyComponentA implements IConfigurable, IReferenceable, IOpenable {
  private _param1: string = "ABC";
  private _param2: number = 123;
  private _open: boolean = false;
  private _status: string;
  private _anotherComponent: MyComponentB;
  
  public dummyVariable: string;

  // ...

  public myTask(correlationId: string): void {
      console.log("Doing my business task");
      this.dummyVariable = "dummy value";
  }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	run "github.com/pip-services4/pip-services4-go/pip-services4-components-go/run"
)

type MyComponentB struct {
	// ...
}

type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable
	run.IOpenable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}
	dummyVariable    string

	status string
}

// ...

func (c *MyComponentA) MyTask(ctx context.Context, correlationId string) {
	fmt.Println("Doing my business task")
	c.dummyVariable = "dummy value"
}
Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor
from pip_services4_components.run import IOpenable

class MyComponentB():
    _param1 = 'ABC2'        
    # ...
    
class MyComponentA(IReferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    dummy_variable = ""
    
    # ...

    def my_task(self, context):
        print("Doing my business task")
        dummy_variable = "dummy value"

Not available

Step 6 – Closing

As any open component needs to be closed to ensure that the resources used are being freed for other processes, we now need to define our close method. To do this, we use the IClosable interface.

Here, we must note that the IClosable interface was already called by the IOpenable interface in step 4. Thus, we don’t need to explicitly call it now. As our component already has access to this interface, we will define the close method for our class. The code below shows a simplified version of this method.

import { 
  IConfigurable, IReferenceable, IOpenable, Context
} from "pip-services4-components-node";

class MyComponentB {
  // ...
}
   
  
class MyComponentA implements IConfigurable, IReferenceable, IOpenable {
  private _param1: string = "ABC";
  private _param2: number = 123;
  private _open: boolean = false;
  private _status: string;
  private _anotherComponent: MyComponentB;
  
  public dummyVariable: string;

  // ...

  public close(ctx: Context): Promise<void> {
    return new Promise<void>((resolve) => {  
      this._open = false;
      this._status = "Closed";
      console.log("MyComponentA has been closed.");

      resolve();
    });
  }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	run "github.com/pip-services4/pip-services4-go/pip-services4-components-go/run"
)

type MyComponentB struct {
	// ...
}

type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable
	run.IOpenable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}
	dummyVariable    string

	status string
}

// ...

func (c *MyComponentA) Close(ctx context.Context, correlationId string) error {
	c.open = false
	c.status = "Closed"
	fmt.Println("MyComponentA has been closed.")

	return nil
}
Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor
from pip_services4_components.run import IOpenable

class MyComponentB():
    _param1 = 'ABC2'             
    # ...
    
class MyComponentA(IReferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    
    # ...
    
    def close(self, context):
        self._open = False
        self._status = "Closed"
        print("MyComponentA has been closed.")        

Not available

Step 7 – Un-referencing

Once our component has been closed, we need to clear the component’s previously defined references. Pip.Services provides the IUnreferenceable interface, which defines the unset_references method. In our example this method will be coded as:

import { 
    IConfigurable, IReferenceable, IOpenable, Context, IUnreferenceable
  } from "pip-services4-components-node";
  
  class MyComponentB {
    // ...
}
       
    
class MyComponentA implements IConfigurable, IReferenceable, IOpenable, IUnreferenceable {
    private _param1: string = "ABC";
    private _param2: number = 123;
    private _open: boolean = false;
    private _status: string;
    private _anotherComponent: MyComponentB;
    
    public dummyVariable: string;

    // ...

    public unsetReferences(): void {
        this._anotherComponent = null;
        this._status = "Un-referenced";
        console.log("References cleared");
    }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	run "github.com/pip-services4/pip-services4-go/pip-services4-components-go/run"
)

type MyComponentB struct {
	// ...
}

type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable
	refer.IUnreferenceable
	run.IOpenable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}
	dummyVariable    string

	status string
}

// ...

// Unsets (clears) previously set references to dependent components.
func (c *MyComponentA) UnsetReferences(ctx context.Context) {
	c.anotherComponent = nil
	c.status = "Un-referenced"
	fmt.Println("References cleared")

}
Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor, IUnreferenceable
from pip_services4_components.run import IOpenable

class MyComponentB():
    _param1 = 'ABC2'        
    # ...
    
class MyComponentA(IReferenceable, IUnreferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    
    # ...
    
    def unset_references(self):
        """
        Unsets (clears) previously set references to dependent components.
        """
        self._another_component = None
        self._status = "Un-referenced"
        print("References cleared")

Not available

Step 8 – Destruction

Finally, to complete the process, we need to dispose of the component. For this, we will use a class destructor or other instruments provided by the languages. Our code will look something like this:

import { 
    IConfigurable, IReferenceable, IOpenable, Context, IUnreferenceable
  } from "pip-services4-components-node";
  
  class MyComponentB {
    // ...
}
       

class MyComponentA implements IConfigurable, IReferenceable, IOpenable, IUnreferenceable {
    private _param1: string = "ABC";
    private _param2: number = 123;
    private _open: boolean = false;
    private _status: string;
    private _anotherComponent: MyComponentB;
    
    public dummyVariable: string;

    // ...

}


try {
    var component = new MyComponentA();
    // ...

    await component.close(null);
} finally {
    console.log("Component destroyed");
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	run "github.com/pip-services4/pip-services4-go/pip-services4-components-go/run"
)

type MyComponentB struct {
    // ...
}
  
type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable
	refer.IUnreferenceable
	run.IOpenable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}
	dummyVariable    string

	status string
}

// ...

component := NewMyComponentA()

defer func() {
	if !component.IsOpen() {
		fmt.Println("Component destroyed")
	}
}()

// ...

component.Close(context.Background(), nil)
Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor, IUnreferenceable
from pip_services4_components.run import IOpenable, ICleanable

class MyComponentB():
    _param1 = 'ABC2'       
    # ...
    
class MyComponentA(IReferenceable, IUnreferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    
    # ...
    
    def __del__(self):
        print("Component destroyed")
        
        
Not available

Important note

In this example, we have created a component that accepts configuration parameters, links to other components, opens and executes a process, closes, deletes the links to other components, and destroys itself.

However, the Pip.Services toolkit provides many other components that can be used to add extra functionality, such as connectivity to other services, observability, and other types of persistence.

Final code

The complete code for our example is:

import { 
    IConfigurable, IReferenceable, IOpenable, Context, IUnreferenceable, IReferences, ConfigParams, Descriptor
  } from "pip-services4-components-node";
  
  class MyComponentB implements IConfigurable, IReferenceable, IOpenable, IUnreferenceable {
    private _param1: string = "ABC2";
    private _param2: number = 456;
    private _open: boolean = false;
    private _status: string;

    // Creates a new instance of the component.
    public constructor() {
        this._status = "Created";
        console.log("MyComponentB has been created.");
    }

    public configure(config: ConfigParams): void {
        // pass
    }

    public setReferences(references: IReferences): void {
        // pass
    }

    public isOpen(): boolean {
        // pass
        return true;
    }

    public open(correlationId: string): Promise<void> {
        // pass
        return;
    }

    public close(correlationId: string): Promise<void> {
        // pass
        return;
    }

    public unsetReferences(): void {
        // pass
        return;
    }
}


class MyComponentA implements IConfigurable, IReferenceable, IOpenable, IUnreferenceable {
    private _param1: string = "ABC";
    private _param2: number = 123;
    private _open: boolean = false;
    private _status: string;
    private _anotherComponent: MyComponentB;
    
    public dummyVariable: string;

    // Creates a new instance of the component.
    public constructor(){
        this._status = "Created";
        console.log("MyComponentA has been created.");
    }

    public isOpen(): boolean {
        return this._open;
    }

    public open(correlationId: string): Promise<void> {
        this._open = true;
        this._status = "Open";
        console.log("MyComponentA has been opened.");
        
        return;
    }

    public close(correlationId: string): Promise<void> {
        this._open = false;
        this._status = "Closed";
        console.log("MyComponentA has been closed.");

        return;
    }

    public myTask(correlationId: string): void {
        console.log("Doing my business task");
        this.dummyVariable = "dummy value";
    }

    public setReferences(references: IReferences): void {
        this._anotherComponent = references.getOneRequired<MyComponentB>(
            new Descriptor("myservice", "mycomponent-b", "*", "*", "1.0")
        );

        this._status = "Configured";
        console.log("MyComponentA's references have been defined.");
    }

    public unsetReferences(): void {
        this._anotherComponent = null;
        this._status = "Un-referenced";
        console.log("References cleared");
    }

    public configure(config: ConfigParams): void {
        this._param1 = config.getAsStringWithDefault("param1", "ABC");
        this._param2 = config.getAsIntegerWithDefault("param2", 123);
        this._status = "Configured";

        console.log("MyComponentA has been configured.");
    }
}
Not available
import (
	"context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	run "github.com/pip-services4/pip-services4-go/pip-services4-components-go/run"
)

type MyComponentB struct {
	param1 string
	param2 int
	open   bool

	status string
}

// Creates a new instance of the component.
func NewMyComponentB() *MyComponentB {
	fmt.Println("MyComponentB has been created.")

	return &MyComponentB{
		param1: "ABC2",
		param2: 456,
		open:   false,
		status: "Created",
	}
}

func (c *MyComponentB) Configure(ctx context.Context, config *cconf.ConfigParams) {
	// pass
}

func (c *MyComponentB) SetReferences(ctx context.Context, references refer.IReferences) {
	// pass
}

func (c *MyComponentB) IsOpen() bool {
	// pass
	return true
}

func (c *MyComponentB) Open(ctx context.Context, correlationId string) error {
	// pass
	return nil
}

func (c *MyComponentB) Close(ctx context.Context, correlationId string) error {
	// pass
	return nil
}

// Unsets (clears) previously set references to dependent components.
func (c *MyComponentB) UnsetReferences(ctx context.Context) {
	// pass
}

func (c *MyComponentB) MyTask(ctx context.Context, correlationId string) {
	// pass
}

type MyComponentA struct {
	cconf.IConfigurable
	refer.IReferenceable
	refer.IUnreferenceable
	run.IOpenable

	param1 string
	param2 int
	open   bool

	anotherComponent interface{}
	dummyVariable    string

	status string
}

// Creates a new instance of the component.
func NewMyComponentA() *MyComponentA {
	fmt.Println("MyComponentA has been created.")

	return &MyComponentA{
		param1: "ABC",
		param2: 123,
		open:   false,
		status: "Created",
	}
}

func (c *MyComponentA) Configure(ctx context.Context, config *cconf.ConfigParams) {
	c.param1 = config.GetAsStringWithDefault("param1", "ABC")
	c.param2 = config.GetAsIntegerWithDefault("param2", 123)
	c.status = "Configured"
	fmt.Println("MyComponentA has been configured.")
}

func (c *MyComponentA) SetReferences(ctx context.Context, references refer.IReferences) {
	component, err := references.GetOneRequired(
		refer.NewDescriptor("myservice", "mycomponent-b", "*", "*", "1.0"),
	)
	if err == nil {
		c.anotherComponent = component.(*MyComponentB)
		c.status = "Configured"
		fmt.Println("MyComponentA's references have been defined.")
	}

}

func (c *MyComponentA) IsOpen() bool {
	return c.open
}

func (c *MyComponentA) Open(ctx context.Context, correlationId string) error {
	c.open = true
	c.status = "Open"
	fmt.Println("MyComponentA has been opened.")

	return nil
}

func (c *MyComponentA) Close(ctx context.Context, correlationId string) error {
	c.open = false
	c.status = "Closed"
	fmt.Println("MyComponentA has been closed.")

	return nil
}

// Unsets (clears) previously set references to dependent components.
func (c *MyComponentA) UnsetReferences(ctx context.Context) {
	c.anotherComponent = nil
	c.status = "Un-referenced"
	fmt.Println("References cleared")

}

func (c *MyComponentA) MyTask(ctx context.Context, correlationId string) {
	fmt.Println("Doing my business task")
	c.dummyVariable = "dummy value"
}

Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import IReferenceable, IReferences, Descriptor, IUnreferenceable
from pip_services4_components.run import IOpenable

class MyComponentB(IReferenceable, IUnreferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC2'
    _param2 = 456
    _opened = False

    def __init__(self):
        """
        Creates a new instance of the component.
        """
        self._status = "Created"
        print("MyComponentB has been created.")
        
    def configure(self, config):
        self._param1 = config.get_as_string_with_default("param1", self._param1)
        self._param2 = config.get_as_integer_with_default("param2", self._param2)
        print("MyComponentB has been configured.")
        
    def set_references(self, references):
        pass
        
    def is_open(self):
        pass

    def open(self, context):
        pass

    def close(self, context):
        pass
        
    def my_task(self, context):
        pass

    def unset_references(self):
        pass
    
    def __del__(self):
        pass



class MyComponentA(IReferenceable, IUnreferenceable, IConfigurable, IOpenable):
    _param1 = 'ABC'
    _param2 = 123
    _another_component: MyComponentB
    _open = False
    _status = None
    
    def __init__(self):
        """
        Creates a new instance of the component.
        """
        self._status = "Created"
        print("MyComponentA has been created.")
            
    def configure(self, config):
        self._param1 = config.get_as_string_with_default("param1", 'ABC')
        self._param2 = config.get_as_integer_with_default("param2", 123)
        self._status = "Configured"
        print("MyComponentA has been configured.")

    def set_references(self, references):
        self._another_component = references.get_one_required(
            Descriptor("myservice", "mycomponent-b", "*", "*", "1.0")
        )
        self._status = "Configured"
        print("MyComponentA's references have been defined.")
        
    def is_open(self):
        return self._open

    def open(self, context):
        self._open = True
        self._status = "Open"
        print("MyComponentA has been opened.")

    def close(self, context):
        self._opened = False
        self._status = "Closed"
        print("MyComponentA has been closed.")
        
    def my_task(self, context):
        print("Doing my business task")
        dummy_variable = "dummy value"

    def unset_references(self):
        """
        Unsets (clears) previously set references to dependent components.
        """
        self._another_component = None
        self._status = "Un-referenced"
        print("References cleared")
    
    def __del__(self):
        print("Component destroyed")

Not available

Executing our code

Now, we can execute our code step-by-step. Our program will look something like this:

import { ConfigParams, Descriptor, References } from "pip-services4-components-node";
  
async function main() {
    try {
        // Step 1 - Creation
        const myComponentA = new MyComponentA();
        const myComponentB = new MyComponentB();

        // Step 2 - Configure the component
        myComponentA.configure(ConfigParams.fromTuples('param1', 'XYZ', 'param2', '987'));

        // Step 3 - Referencing
        // Set references to the component
        myComponentA.setReferences(References.fromTuples(
            new Descriptor('myservice', 'mycomponent-b', 'default', 'default', '1.0'),
            myComponentB
        ));

        // Step 4 - Opening
        await myComponentA.open('123');

        // Step 5 - Execution
        myComponentA.myTask('123');

        // Step 6 - Closing
        await myComponentA.close('123');

        // Step 7 - Un-referencing
        myComponentA.unsetReferences();
    } finally {
        // Step 8 - Destruction
        console.log('Component destroyed');
    }
}
Not available
import (
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	refer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
)

func main() {
	// Step 1 - Creation
	myComponentA := NewMyComponentA()
	myComponentB := NewMyComponentB()

	// Step 2 - Configure the component
	myComponentA.Configure(ctx context.Context, cconf.NewConfigParamsFromTuples(
		"param1", "XYZ",
		"param2", "987",
	))

	// Step 3 - Referencing
	// Set references to the component
	myComponentA.SetReferences(ctx context.Context, refer.NewReferencesFromTuples(
		refer.NewDescriptor("myservice", "mycomponent-b", "default", "default", "1.0"), myComponentB,
	))

	// Step 4 - Openning
	myComponentA.Open(ctx context.Context, "123")

	// Step 5 - Execution
	myComponentA.MyTask(ctx context.Context, "123")

	// Step 6 - Closing
	myComponentA.Close(ctx context.Context, "123")

	// Step 7 - Un-referencing
	myComponentA.UnsetReferences(ctx context.Context)

	// Step 8 - Destruction
	defer func() {
		if !NewMyComponentA().IsOpen() {
			fmt.Println("Component destroyed")
		}
	}()
}

Not available
from pip_services4_components.config import IConfigurable, ConfigParams
from pip_services4_components.refer import References, Descriptor

# Step 1 - Creation
my_component_A = MyComponentA()
my_component_B = MyComponentB()

# Step 2 - Configure the component
my_component_A.configure(ConfigParams.from_tuples(
    'param1', 'XYZ',
    'param2', '987'
))

# Step 3 - Referencing
# Set references to the component
my_component_A.set_references(References.from_tuples(
    Descriptor("myservice", "mycomponent-b", "default", "default", "1.0"), my_component_B
))

# Step 4 - Openning
my_component_A.open("123")

# Step 5 - Execution
my_component_A.my_task("123")

# Step 6 - Closing
my_component_A.close("123")

# Step 7 - Un-referencing
my_component_A.unset_references()

# Step 8 - Destruction
my_component_A.__del__()

Not available

Which, after running, results in the following output:

Console logger messages

Assembling a service from our component

At present, we have a component that is capable of connecting to another component and can execute some actions defined by us. This component is ready for use. However, running it step-by-step can be laborious and inefficient.

To solve this problem, we can use a container. Pip.Services offers the ProcessContainer, which is an Inversion of control (IoC) container that runs as a system process.

As this container uses a factory to create the contained components, we will create one via the Factory class. Once again, we will use Descriptor objects to locate each component, and we will use the method register_as_type to register the component in our factory. This method requires the locator and the component’s type. Our updated code is:

import { Factory } from "pip-services4-components-node";

class MyClassFactory extends Factory {
    public constructor() {
        super();

        var ComponentADescriptor = new Descriptor("myservice", "mycomponentA", "default", "*", "1.0");
        var ComponentBDescriptor = new Descriptor("myservice", "mycomponent-b", "default", "*", "1.0");

        this.registerAsType(ComponentADescriptor, MyComponentA);
        this.registerAsType(ComponentBDescriptor, MyComponentB);
    }
}


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

// Creating a factory

type MyClassFactory struct {
	*build.Factory
}

func NewMyClassFactory() *MyClassFactory {
	c := MyClassFactory{}
	c.Factory = build.NewFactory()

	ComponentADescriptor := refer.NewDescriptor("myservice", "mycomponentA", "default", "*", "1.0")
	ComponentBDescriptor := refer.NewDescriptor("myservice", "mycomponent-b", "default", "*", "1.0")

	c.RegisterType(ComponentADescriptor, NewMyComponentA)
	c.RegisterType(ComponentBDescriptor, NewMyComponentB)

	return &c
}
Not available
# Creating a factory

from pip_services4_components.build import Factory 

class MyClassFactory(Factory):
    def __init__(self):
        super(MyClassFactory, self).__init__()

        ComponentADescriptor = Descriptor("myservice", "mycomponentA", "default", "*", "1.0")
        ComponentBDescriptor = Descriptor("myservice", "mycomponent-b", "default", "*", "1.0")

        self.register_as_type(ComponentADescriptor, MyComponentA)
        self.register_as_type(ComponentBDescriptor, MyComponentB)

Not available

Once our factory is ready, we can proceed to create our container. First, we will create a class named MyProcess as a subclass of ProcessContainer. Inside this class, we will state the path to our configuration file, and add our previously created factory.

import { ProcessContainer } from "pip-services4-container-node";

// Creating a process container
class MyProcess extends ProcessContainer {
    public constructor() {
        super("myservice", "My service running as a process");

        this._configPath = './config.yaml';
        this._factories.add(new MyClassFactory());
    }
}
Not available
import (
	container "github.com/pip-services4/pip-services4-go/pip-services4-container-go/container"
)

type MyProcess struct {
	*container.ProcessContainer
}

func NewMyProcess() *MyProcess {
	c := &MyProcess{
		ProcessContainer: container.NewProcessContainer("myservice", "My service running as a process"),
	}
	c.SetConfigPath("./config.yaml")
	c.AddFactory(NewMyClassFactory())

	return c
}
Not available
# Creating a process container

from pip_services4_container.container import ProcessContainer

class MyProcess(ProcessContainer):
    def __init__(self):
        super(MyProcess, self).__init__('myservice', 'My service running as a process')
        self._config_path = 'config.yaml'
        self._factories.add(MyClassFactory())


Not available

Our configuration file must declare our component’s descriptor and the values we want to assign to our parameters. We will use YAML syntax for this purpose. Below is an example of this type of declaration.

Console logger messages

Using our component

We have a service, and to use it, we just need to create an instance of our container and call the run method.

let MyProcess = require('./obj/component').MyProcess;

try {
    let proc = new MyProcess();
    proc.run(process.argv);
} catch (ex) {
    console.error(ex);
}


Not available
func main() {
	runner := NewMyProcess()
	runner.Run(os.Environ())
}
Not available
if __name__ == '__main__':
    runner = MyProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)


Not available

After running our service, we should see the following output, which confirms that components A and B have been created and linked:

Console logger messages

As our component is complete and fully functional, this step marks the end of our task.

Wrapping up

In this tutorial, we have created a component, defined all the necessary methods for managing its lifecycle, and assembled a service from it. We also saw that containers offer a more efficient way to run components.

More complex components will follow a similar structure, but with added functionality. For example, we can add things like different forms of persistence, connectivity to other services, observability, caching, and more. You can find an example of this in the Data Microservice tutorial.