Process container

How to create a ProcessContainer.

Key takeaways

ProcessContainer Container that can be used to run contained code as a system process.
Custom component Custom component created for the tutorial's example.
Factory Component used to create the contained components.
Configuration file File used by the container to locate components.

Introduction

This tutorial will help you understand how to create a ProcessContainer that manages the life cycle of a custom component. First, we will see a brief description of the component and how to import it. Then, we will create a custom component, a factory to create it, and a process container. Finally, we will run the container and summarize all the learned concepts.

The ProcessContainer container

Docker defines a container as a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another.

Pip.Services offers the ProcessContainer, which allows us to run our contained code as a system process.

In this tutorial, we will create a ProcessContainer component that packages a custom component, run it, and check the results.

Pre-requisites

In order to use the ProcessContainer component, we need to import it first. This can be done with the following command:

import { ProcessContainer } from "pip-services3-container-nodex";
using PipServices3.Container;
import ccont "github.com/pip-services3-gox/pip-services3-container-gox/container"
import 'package:pip_services3_container/pip_services3_container.dart';
from pip_services3_container import ProcessContainer
Not available

Creating a component

Now, we create a component that will operate in a ProcessContainer. In our example, this is a mock component, that only prints a message once an operation is called. Moreover, to see how the container manages the lifecycle of this component, we want this class to be openable and configurable. An example of what this class looks like is:

import { 
    ConfigParams, IConfigurable, IExecutable, 
    IOpenable, IReferenceable, IReferences, 
    IUnreferenceable, Parameters 
} from "pip-services3-commons-nodex";


export class MyComponentA implements IReferenceable, IUnreferenceable, IConfigurable, IOpenable, IExecutable {

    private _open = false;

    public constructor() {
        console.log("MyComponentA has been created.");
    }

    public configure(config: ConfigParams): void {
        console.log("MyComponentA has been configured.");
    }

    public setReferences(references: IReferences): void {
        console.log("MyComponentA's references have been defined.");
    }

    public unsetReferences(): void {
        console.log("References cleared");
    }

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

    public async open(correlationId: string): Promise<void> {
        console.log("MyComponentA has been opened.");
    }

    public async close(correlationId: string): Promise<void> {
        console.log("MyComponentA has been closed.");
    }

    public async execute(correlationId: string, args: Parameters): Promise<any> {
        console.log("Executing");
        let result = args;
        return result;
    }
}
using PipServices3.Commons.Refer;
using PipServices3.Commons.Run;
using PipServices3.Commons.Config;


public class MyComponentA: IReferenceable, IUnreferenceable, IConfigurable, IOpenable, IExecutable
{
    private bool _open = false;

    public MyComponentA()
    {
        Console.WriteLine("MyComponentA has been created.");
    }

    public void Configure(ConfigParams config)
    {
        Console.WriteLine("MyComponentA has been configured.");
    }

    public void SetReferences(IReferences references)
    {
        Console.WriteLine("MyComponentA's references have been defined.");
    }

    public void UnsetReferences()
    {
        Console.WriteLine("References cleared");
    }

    public bool IsOpen()
    {
        return this._open;
    }

    public async Task OpenAsync(string correlationId)
    {
        Console.WriteLine("MyComponentA has been opened.");
    }

    public async Task CloseAsync(string correlationId)
    {
        Console.WriteLine("MyComponentA has been closed.");
    }

    public async Task<object> ExecuteAsync(string correlationId, Parameters args)
    {
        Console.WriteLine("Executing");
        var result = args;
        return result;
    }
}

import (
	"context"
	"fmt"

	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	crefer "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	crun "github.com/pip-services3-gox/pip-services3-commons-gox/run"
)

type MyComponentA struct {
	opened bool
}

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

func (c *MyComponentA) Configure(ctx context.Context, config *cconf.ConfigParams) {
	fmt.Println("MyComponentA has been configured.")
}

func (c *MyComponentA) SetReferences(ctx context.Context, references crefer.IReferences) {
	fmt.Println("MyComponentA's references have been defined.")
}

func (c *MyComponentA) UnsetReferences() {
	fmt.Println("References cleared")
}

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

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

func (c *MyComponentA) Close(ctx context.Context, correlationId string) error {
	fmt.Println("MyComponentA has been closed.")
	return nil
}

func (c *MyComponentA) Execute(ctx context.Context, correlationId string, args crun.Parameters) (result any, err error) {
	fmt.Println("Executing")
	result = args
	return result, nil
}
import 'package:pip_services3_commons/pip_services3_commons.dart';


class MyComponentA
    implements
        IReferenceable,
        IUnreferenceable,
        IConfigurable,
        IOpenable,
        IExecutable {
  bool _open = false;

  MyComponentA() {
    print('MyComponentA has been created.');
  }

  @override
  void configure(ConfigParams config) {
    print('MyComponentA has been configured.');
  }

  @override
  void setReferences(IReferences references) {
    print("MyComponentA's references have been defined.");
  }

  @override
  void unsetReferences() {
    print('References cleared');
  }

  @override
  bool isOpen() {
    return this._open;
  }

  @override
  Future open(String? correlationId) async {
    print('MyComponentA has been opened.');
  }

  @override
  Future close(String? correlationId) async {
    print('MyComponentA has been closed.');
  }

  @override
  Future execute(String? correlationId, Parameters args) async {
    print('Executing');
    var result = args;
    return result;
  }
}
from pip_services3_commons.refer import IReferenceable, IReferences, IUnreferenceable
from pip_services3_commons.run import IOpenable
from pip_services3_commons.run import IExecutable
from pip_services3_commons.config import IConfigurable

class MyComponentA(IReferenceable, IUnreferenceable, IConfigurable, IOpenable, IExecutable):
    
    def __init__(self):
        print("MyComponentA has been created.")
            
    def configure(self, config):
        print("MyComponentA has been configured.")
       
    def is_open(self):
        return self._open

    def open(self, correlation_id):
        print("MyComponentA has been opened.")
    def close(self, correlation_id):
        print("MyComponentA has been closed.")
        
    def my_task(self, correlation_id):
        print("Doing my business task")
        dummy_variable = "dummy value"
    
    def set_references(self, references):
        print("MyComponentA's references have been defined.")    
        
    def unset_references(self):
        print("References cleared")
        
    def execute(self, correlation_id: str, args: str): 
        print('Executing')
        result = args
        return result
Not available

Creating a factory

The next step is to create a factory for our component. For this, we create a class that extends the Factory class and registers our previously defined custom component. The following code shows how to do this:

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

export class MyFactory extends Factory {
    public constructor() {
        super();

        this.registerAsType(new Descriptor("myservice", "MyComponentA", "*", "*", "1.0"), MyComponentA);
    }
}
using PipServices3.Components.Build;

public class MyFactory : Factory
{
    public MyFactory(): base()
    {
        RegisterAsType(new Descriptor("myservice", "MyComponentA", "*", "*", "1.0"), typeof(MyComponentA));
    }
}

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

type MyFactory struct {
	*cbuild.Factory
}

func NewMyFactory() *MyFactory {
	c := &MyFactory{}
	c.Factory = cbuild.NewFactory()
	c.RegisterType(crefer.NewDescriptor("myservice", "MyComponentA", "*", "*", "1.0"), NewMyComponentA)
	return c
}
import 'package:pip_services3_components/pip_services3_components.dart';

class MyFactory extends Factory {
  MyFactory() : super() {
    registerAsType(
        Descriptor('myservice', 'MyComponentA', '*', '*', '1.0'), MyComponentA);
  }
}
from pip_services3_components.build import Factory 

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

        ComponentADescriptor = Descriptor("myservice", "MyComponentA", "*", "*", "1.0")
        self.register_as_type(ComponentADescriptor, MyComponentA)
Not available

Configuration file

Once our container and factory are ready, we create a configuration file that will be used by the ProcessContainer to locate the component. As the ProcessContainer class offers the possibility of using a logger, we also add a reference to it. In this manner, we will be able to see the logs on our console. The following code explains how to do this:

---
# Context information
- descriptor: "pip-services:context-info:default:default:1.0"
  name: myservice
  description: My service running in a process container

# Console logger
- descriptor: "pip-services:logger:console:default:1.0"
  level: {{LOG_LEVEL}}{{^LOG_LEVEL}}info{{/LOG_LEVEL}}

# Performance counters that posts values to log
- descriptor: "pip-services:counters:log:default:1.0"
  
# My component
- descriptor: "myservice:MyComponentA:default:*:1.0"

Creating a ProcessContainer

The final step is to create the ProcessContainer. To do this, we create a class that extends the ProcessContainer component. In it, we add our previously created factory and specify the path to our configuration file (The default path is ./config/config.yml). The following code shows how to do this:

import { ProcessContainer } from "pip-services3-container-nodex";
import { Descriptor } from "pip-services3-commons-nodex";


export class MyProcess extends ProcessContainer {
    public constructor() {
        super('myservice', 'My service running as a process');
        this._configPath = './config.yaml'
        this._factories.add(new MyFactory())
    }
}
using PipServices3.Container;
using PipServices3.Commons.Refer;


public class MyProcess: ProcessContainer
{
    public MyProcess(): base("myservice", "My service running as a process")
    {
        _configPath = "./config.yaml";
        _factories.Add(new MyFactory());
    }
}

import (
    crefer "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
    ccont "github.com/pip-services3-gox/pip-services3-container-gox/container"
)


type MyProcess struct {
	*ccont.ProcessContainer
}

func NewMyProcess() *MyProcess {
	c := &MyProcess{}
	c.ProcessContainer = ccont.NewProcessContainer("myservice", "My service running as a process")
	c.SetConfigPath("./config.yaml")
	c.AddFactory(NewMyFactory())
	return c
}
import 'package:pip_services3_container/pip_services3_container.dart';
import 'package:pip_services3_commons/pip_services3_commons.dart';


class MyProcess extends ProcessContainer {
  MyProcess() : super('myservice', 'My service running as a process') {
    configPath = './config.yaml';
    factories.add(MyFactory());
  }
}
from pip_services3_container import ProcessContainer
from pip_services3_commons.refer import Descriptor

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(MyFactory())
Not available

Running the container

Now that our code is ready, we can run our container. This is done via its run() method. For example, in the code below, we create an instance of our container and call this method.

export async function main() {
    let runner = new MyProcess();
    try {
        runner.run(process.argv);
    } catch (err) {
        console.error(err)
    }
}
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var task = (new MyProcess()).RunAsync(args);
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex);
        }
    }
}
func main() {
	proc := NewMyProcess()
    // proc.SetConfigPath("./config/config.yml")
	proc.Run(context.Background(), os.Args)
}
void main(List<String> argument) {
  try {
    var proc = MyProcess();
    proc.run(argument);
  } catch (ex) {
    print(ex);
  }
}
if __name__ == '__main__':
    runner = MyProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)

Not available

As a result, we get the following output on our console:

figure 1

As we can see from the above results, the container takes care of the lifecycle of the packaged components:

  1. It obtains information about dependencies from the configuration file.
  2. Creates the packaged components via their respective factories.
  3. Configures the configurable components.
  4. Links the components to the components registered in their set_references() method.
  5. Opens the openable components.

Once the process is stopped, the container:

  1. Unlinks the linked components.
  2. Closes the opened components
  3. Closes itself.

Final code

Finally, we merge the code from the previous sections into one program:

import { 
    ConfigParams, Descriptor, IConfigurable, IExecutable, 
    IOpenable, IReferenceable, IReferences, 
    IUnreferenceable, Parameters 
} from "pip-services3-commons-nodex";

import { Factory } from "pip-services3-components-nodex";
import { ProcessContainer } from "pip-services3-container-nodex";

export async function main() {
    let runner = new MyProcess();
    try {
        runner.run(process.argv);
    } catch (err) {
        console.error(err)
    }
}

export class MyProcess extends ProcessContainer {
    public constructor() {
        super('myservice', 'My service running as a process');
        this._configPath = './config.yaml'
        this._factories.add(new MyFactory())
    }
}

export class MyFactory extends Factory {
    public constructor() {
        super();

        this.registerAsType(new Descriptor("myservice", "MyComponentA", "*", "*", "1.0"), MyComponentA);
    }
}

export class MyComponentA implements IReferenceable, IUnreferenceable, IConfigurable, IOpenable, IExecutable {

    private _open = false;

    public constructor() {
        console.log("MyComponentA has been created.");
    }

    public configure(config: ConfigParams): void {
        console.log("MyComponentA has been configured.");
    }

    public setReferences(references: IReferences): void {
        console.log("MyComponentA's references have been defined.");
    }

    public unsetReferences(): void {
        console.log("References cleared");
    }

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

    public async open(correlationId: string): Promise<void> {
        console.log("MyComponentA has been opened.");
    }

    public async close(correlationId: string): Promise<void> {
        console.log("MyComponentA has been closed.");
    }

    public async execute(correlationId: string, args: Parameters): Promise<any> {
        console.log("Executing");
        let result = args;
        return result;
    }
}
        
using System;
using System.Threading.Tasks;
using PipServices3.Commons.Refer;
using PipServices3.Commons.Run;
using PipServices3.Commons.Config;
using PipServices3.Components.Build;
using PipServices3.Container;

namespace ExampleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var task = (new MyProcess()).RunAsync(args);
                task.Wait();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }
    }

    public class MyProcess: ProcessContainer
    {
        public MyProcess(): base("myservice", "My service running as a process")
        {
            _configPath = "../../../config.yaml";
            _factories.Add(new MyFactory());
        }
    }

    public class MyFactory : Factory
    {
        public MyFactory(): base()
        {
            RegisterAsType(new Descriptor("myservice", "MyComponentA", "*", "*", "1.0"), typeof(MyComponentA));
        }
    }

    public class MyComponentA: IReferenceable, IUnreferenceable, IConfigurable, IOpenable, IExecutable
    {
        private bool _open = false;

        public MyComponentA()
        {
            Console.WriteLine("MyComponentA has been created.");
        }

        public void Configure(ConfigParams config)
        {
            Console.WriteLine("MyComponentA has been configured.");
        }

        public void SetReferences(IReferences references)
        {
            Console.WriteLine("MyComponentA's references have been defined.");
        }

        public void UnsetReferences()
        {
            Console.WriteLine("References cleared");
        }

        public bool IsOpen()
        {
            return this._open;
        }

        public async Task OpenAsync(string correlationId)
        {
            Console.WriteLine("MyComponentA has been opened.");
        }

        public async Task CloseAsync(string correlationId)
        {
            Console.WriteLine("MyComponentA has been closed.");
        }

        public async Task<object> ExecuteAsync(string correlationId, Parameters args)
        {
            Console.WriteLine("Executing");
            var result = args;
            return result;
        }
    }
}
        
import (
	"context"
	"fmt"
	"os"

	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	crefer "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	crun "github.com/pip-services3-gox/pip-services3-commons-gox/run"
	cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
	ccont "github.com/pip-services3-gox/pip-services3-container-gox/container"
)

func main() {
	proc := NewMyProcess()
	proc.SetConfigPath("./config/config.yml")
	proc.Run(context.Background(), os.Args)
}

type MyProcess struct {
	*ccont.ProcessContainer
}

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

type MyFactory struct {
	*cbuild.Factory
}

func NewMyFactory() *MyFactory {
	c := &MyFactory{}
	c.Factory = cbuild.NewFactory()
	c.RegisterType(crefer.NewDescriptor("myservice", "MyComponentA", "*", "*", "1.0"), NewMyComponentA)
	return c
}

type MyComponentA struct {
	opened bool
}

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

func (c *MyComponentA) Configure(ctx context.Context, config *cconf.ConfigParams) {
	fmt.Println("MyComponentA has been configured.")
}

func (c *MyComponentA) SetReferences(ctx context.Context, references crefer.IReferences) {
	fmt.Println("MyComponentA's references have been defined.")
}

func (c *MyComponentA) UnsetReferences() {
	fmt.Println("References cleared")
}

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

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

func (c *MyComponentA) Close(ctx context.Context, correlationId string) error {
	fmt.Println("MyComponentA has been closed.")
	return nil
}

func (c *MyComponentA) Execute(ctx context.Context, correlationId string, args crun.Parameters) (result any, err error) {
	fmt.Println("Executing")
	result = args
	return result, nil
}

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

void main(List<String> argument) {
  try {
    var proc = MyProcess();
    proc.run(argument);
  } catch (ex) {
    print(ex);
  }
}

class MyProcess extends ProcessContainer {
  MyProcess() : super('myservice', 'My service running as a process') {
    configPath = './config.yaml';
    factories.add(MyFactory());
  }
}

class MyFactory extends Factory {
  MyFactory() : super() {
    registerAsType(
        Descriptor('myservice', 'MyComponentA', '*', '*', '1.0'), MyComponentA);
  }
}

class MyComponentA
    implements
        IReferenceable,
        IUnreferenceable,
        IConfigurable,
        IOpenable,
        IExecutable {
  bool _open = false;

  MyComponentA() {
    print('MyComponentA has been created.');
  }

  @override
  void configure(ConfigParams config) {
    print('MyComponentA has been configured.');
  }

  @override
  void setReferences(IReferences references) {
    print("MyComponentA's references have been defined.");
  }

  @override
  void unsetReferences() {
    print('References cleared');
  }

  @override
  bool isOpen() {
    return this._open;
  }

  @override
  Future open(String? correlationId) async {
    print('MyComponentA has been opened.');
  }

  @override
  Future close(String? correlationId) async {
    print('MyComponentA has been closed.');
  }

  @override
  Future execute(String? correlationId, Parameters args) async {
    print('Executing');
    var result = args;
    return result;
  }
}
        
from pip_services3_commons.refer import IReferenceable, IReferences, IUnreferenceable
from pip_services3_commons.run import IOpenable
from pip_services3_commons.run import IExecutable
from pip_services3_commons.config import IConfigurable

class MyComponentA(IReferenceable, IUnreferenceable, IConfigurable, IOpenable, IExecutable):
    
    def __init__(self):
        print("MyComponentA has been created.")
            
    def configure(self, config):
        print("MyComponentA has been configured.")
       
    def is_open(self):
        return self._open

    def open(self, correlation_id):
        print("MyComponentA has been opened.")
    def close(self, correlation_id):
        print("MyComponentA has been closed.")
        
    def my_task(self, correlation_id):
        print("Doing my business task")
        dummy_variable = "dummy value"
    
    def set_references(self, references):
        print("MyComponentA's references have been defined.")    
        
    def unset_references(self):
        print("References cleared")
    
    def __del__(self):
        print("Component destroyed")
        
    def execute(self, correlation_id: str, args: str): 
        print('Executing')
        result = args
        return result
    
    
from pip_services3_components.build import Factory 

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

        ComponentADescriptor = Descriptor("myservice", "MyComponentA", "*", "*", "1.0")
        self.register_as_type(ComponentADescriptor, MyComponentA)
        
from pip_services3_container import ProcessContainer
from pip_services3_commons.refer import Descriptor

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(MyFactory())
        
if __name__ == '__main__':
    runner = MyProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)
        
Not available

Wrapping up

In this tutorial, we have learned how to create a ProcessContainer. This component allows us to run our code as a system process.

First, we created a custom component that is openable and configurable. Then, we created a factory to create this component via the container and a configuration file used by the container to locate the different components. Finally, we created the container, run it, and obtained an output on our console showing the creation, configuration and opening of our component.

The example in this tutorial used a custom component. The same procedure applies to the containerization of any other component or set of components.