Process container
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.
Similarly, 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-services4-container-node';
import ccont "github.com/pip-services4/pip-services4-go/pip-services4-container-go/container"
from pip_services4_container import ProcessContainer
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, IOpenable, Context, IReferences,
Parameters, IReferenceable, IUnreferenceable, IExecutable
} from "pip-services4-components-node";
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(ctx: Context): Promise<void> {
console.log("MyComponentA has been opened.");
}
public async close(ctx: Context): Promise<void> {
console.log("MyComponentA has been closed.");
}
public async execute(ctx: Context, args: Parameters): Promise<any> {
console.log("Executing");
let result = args;
return result;
}
}
import (
"context"
"fmt"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
cexec "github.com/pip-services4/pip-services4-go/pip-services4-components-go/exec"
crefer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
)
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 cexec.Parameters) (result any, err error) {
fmt.Println("Executing")
result = args
return result, nil
}
from pip_services4_components.refer import IReferenceable, IReferences, IUnreferenceable
from pip_services4_components.run import IOpenable
from pip_services4_components.exec import IExecutable
from pip_services4_components.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, context):
print("MyComponentA has been opened.")
def close(self, context):
print("MyComponentA has been closed.")
def my_task(self, context):
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, context: str, args: str):
print('Executing')
result = args
return result
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 { Descriptor, Factory } from "pip-services4-components-node";
export class MyFactory extends Factory {
public constructor() {
super();
this.registerAsType(new Descriptor("myservice", "MyComponentA", "*", "*", "1.0"), MyComponentA);
}
}
import cbuild "github.com/pip-services4/pip-services4-go/pip-services4-components-go/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
}
from pip_services4_components.build import Factory
class MyFactory(Factory):
def __init__(self):
super(MyFactory, self).__init__()
ComponentADescriptor = Descriptor("mycontroller", "MyComponentA", "*", "*", "1.0")
self.register_as_type(ComponentADescriptor, MyComponentA)
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: mycontroller
description: My controller is 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: "mycontroller: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-services4-container-node";
export class MyProcess extends ProcessContainer {
public constructor() {
super('myservice', 'My service running as a process');
this._configPath = './config.yaml'
this._factories.add(new MyFactory())
}
}
import (
crefer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
ccont "github.com/pip-services4/pip-services4-go/pip-services4-container-go/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
}
from pip_services4_container import ProcessContainer
from pip_services4_components.refer import Descriptor
class MyProcess(ProcessContainer):
def __init__(self):
super(MyProcess, self).__init__('mycontroller', 'My controller running as a process')
self._config_path = './config.yaml'
self._factories.add(MyFactory())
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)
}
}
func main() {
proc := NewMyProcess()
// proc.SetConfigPath("./config/config.yml")
proc.Run(context.Background(), os.Args)
}
if __name__ == '__main__':
runner = MyProcess()
print("run")
try:
runner.run()
except Exception as ex:
print(ex)
As a result, we get the following output on our console:
As we can see from the above results, the container takes care of the lifecycle of the packaged components:
- It obtains information about dependencies from the configuration file.
- Creates the packaged components via their respective factories.
- Configures the configurable components.
- Links the components to the components registered in their set_references() method.
- Opens the openable components.
Once the process is stopped, the container:
- Unlinks the linked components.
- Closes the opened components
- Closes itself.
Final code
Finally, we merge the code from the previous sections into one program:
import {
ConfigParams, Descriptor, Factory, IConfigurable,
IExecutable, IOpenable, IReferenceable, IReferences,
IUnreferenceable, Context, Parameters
} from "pip-services4-components-node";
import { ProcessContainer } from "pip-services4-container-node";
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(ctx: Context): Promise<void> {
console.log("MyComponentA has been opened.");
}
public async close(ctx: Context): Promise<void> {
console.log("MyComponentA has been closed.");
}
public async execute(ctx: Context, args: Parameters): Promise<any> {
console.log("Executing");
let result = args;
return result;
}
}
import (
"context"
"fmt"
"os"
cbuild "github.com/pip-services4/pip-services4-go/pip-services4-components-go/build"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
cexec "github.com/pip-services4/pip-services4-go/pip-services4-components-go/exec"
crefer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
ccont "github.com/pip-services4/pip-services4-go/pip-services4-container-go/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 cexec.Parameters) (result any, err error) {
fmt.Println("Executing")
result = args
return result, nil
}
from pip_services4_components.refer import IReferenceable, IReferences, IUnreferenceable
from pip_services4_components.run import IOpenable
from pip_services4_components.exec import IExecutable
from pip_services4_components.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, context):
print("MyComponentA has been opened.")
def close(self, context):
print("MyComponentA has been closed.")
def my_task(self, context):
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, context: str, args: str):
print('Executing')
result = args
return result
from pip_services4_components.build import Factory
class MyFactory(Factory):
def __init__(self):
super(MyFactory, self).__init__()
ComponentADescriptor = Descriptor("mycontroller", "MyComponentA", "*", "*", "1.0")
self.register_as_type(ComponentADescriptor, MyComponentA)
from pip_services4_container import ProcessContainer
from pip_services4_components.refer import Descriptor
class MyProcess(ProcessContainer):
def __init__(self):
super(MyProcess, self).__init__('mycontroller', 'My controller running as a process')
self._config_path = './configProcessContainer.yaml'
self._factories.add(MyFactory())
if __name__ == '__main__':
runner = MyProcess()
print("run")
try:
runner.run()
except Exception as ex:
print(ex)
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.