Deployment time configuration

How to set up and change your configurations at deployment time.

Key takeaways

Factories Allow us to code class instantiation without having to specify all the configuration parameters at coding time.
YAML configuration file Allows us to configure components and provide one or more alternatives at deployment time.
Environment variables Allow us to choose between different component alternatives at deployment time.

Introduction

In this tutorial, you will learn how to configure an application based on Pip.Services components at deployment time. For this, we will use the example already developed in Three tier architecture and expand it to consider two different databases. Then, we will see how to select one of these databases at deployment time.

Basic concepts

Two of the main concepts behind Pip.Services are inversion of control and the locator pattern, which are used to manage object creation and object binding.

Inversion of control is applied through the use of factories to create objects. The advantage here is that we can specify our class in a generic way, for example, by indicating that it is a persistence unit, but without having to specify connection parameters and other aspects.

The locator pattern is implemented via the use of a YAML configuration file. This feature allows us to make configuration choices at deployment time. For example, we can change connection parameters, such as IP addresses or database names, and we can provide more than one option for a type of component.

The sections and example that follow explain in detail how this is done .

Brief description of the example

In this tutorial, we will expand on the example presented in the tutorial Three-tier architecture and available through this website.

That example comprised an application that selects a random name from a database and shows a message saying “Hello {random name}!” on a browser. The database used was MySQL.

Our code expansion will consist of including a second database (PostgreSQL) containing a similar table and adding the capacity to choose between the two at deployment time via environmental variables.

Changes to our example

In this example, we consider two databases, one MySQL and another PostgreSQL and we select one of them at deployment time for the application.

For this, we create an interface that defines a common naming and permits to shift between both databases.

We also create a class per database.

Common interface

One of the principles behind Pip.Services is symmetric implementation. This means that different libraries use similar method names and signatures. As a result, we can create an interface that defines the CRUD methods implemented by one or more databases. This will allow us to have a common central point from which we can define the operations that we need.

import { FilterParams } from "pip-services4-data-node";

export interface IMyDataPersistence {
    getOneRandom(ctx: Context, filter: FilterParams): Promise<MyFriend>;
    create(ctx: Context, item: MyFriend): Promise<MyFriend>;
}

import (
	"context"

	cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
)

type IMyDataPersistence[T any] interface {
	GetOneRandom(ctx context.Context, filter cquery.FilterParams) (item T, err error)
	Create(ctx context.Context, item T) (result T, err error)
}

from abc import ABC
from typing import Optional
from pip_services4_data.query import FilterParams

class IMyDataPersistence(ABC):

    # CRUD operations
    def get_one_random(self, trace_id: Optional[str], filter: FilterParams) -> MyFriend:
        raise NotImplemented()
    
    def create(self, trace_id: Optional[str], item: MyFriend) -> MyFriend:
        raise NotImplemented()
Not available

Controller

Our controller is similar to the one defined in the tutorial “Three tier architecture”. The only difference is that we define the _persistence variable as of type IMyDataPersistence. This will allow us to refer to our database independently of the specific cases.

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


export class HelloFriendController implements IConfigurable, IReferenceable {
    private defaultName: string;
    private persistence: IMyDataPersistence;

    public constructor() {
        this.defaultName = "Pip User";
    }

    public configure(config: ConfigParams): void {
        this.defaultName = config.getAsStringWithDefault("default_name", this.defaultName);
    }

    public setReferences(references: IReferences): void {
        this.persistence = references.getOneRequired(new Descriptor("hello-friend", "persistence", "*", "*", "1.0"));
    }

    public async greeting(): Promise<string> {
        let filter = FilterParams.fromTuples("type", "friend");
        let selectedFilter = await this.persistence.getOneRandom(null, filter);
        let name = selectedFilter != null ? selectedFilter.name : null;

        return `Hello, ${name} !`;
    }

    public async create(ctx: Context, item: MyFriend): Promise<MyFriend>  {
        let res = await this.persistence.create(ctx, item);
        return res;
    }
}

import (
	"context"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
)

type MyFriend struct {
	Id   string `bson:"_id" json:"id"`
	Type string `bson:"type" json:"type"`
	Name string `bson:"name" json:"name"`
}

type HelloFriendService struct {
	defaultName string
	persistence IMyDataPersistence[MyFriend]
}

func NewHelloFriendService() *HelloFriendService {
	c := &HelloFriendService{
		defaultName: "Pip User",
	}

	return c
}

func (c *HelloFriendService) Configure(ctx context.Context, config *cconf.ConfigParams) {
	c.defaultName = config.GetAsStringWithDefault("default_name", c.defaultName)
}

func (c *HelloFriendService) SetReferences(ctx context.Context, references cref.IReferences) {
	res, descrErr := references.GetOneRequired(cref.NewDescriptor("hello-friend", "persistence", "*", "*", "1.0"))
	if descrErr != nil {
		panic(descrErr)
	}

	c.persistence = res.(IMyDataPersistence[MyFriend])
}

func (c *HelloFriendService) Greeting(ctx context.Context) (string, error) {
	filter := cquery.NewFilterParamsFromTuples("type", "friend")
	selectedFilter, err := c.persistence.GetOneRandom(ctx, *filter)
	if err != nil {
		return "", err
	}

	return "Hello, " + selectedFilter.Name + " !", nil
}

func (c *HelloFriendService) Create(ctx context.Context, item MyFriend) (result MyFriend, err error) {
	return c.persistence.Create(ctx, item)
}

from pip_services4_components.config import IConfigurable
from pip_services4_components.refer import IReferences, IReferenceable
from pip_services4_http.controller import RestController

class HelloFriendRestController(RestController):

    def __init__(self):
        super(HelloFriendRestController, self).__init__()

        self._base_route = "/hello_friend"
        self._service: IMyDataPersistence = None

    def configure(self, config):
        super().configure(config)

    def set_references(self, references):
        super(HelloFriendRestController, self).set_references(references)
        self._service = references.get_one_required(Descriptor('hello-friend', 'controller', '*', '*', '1.0'))

    def register(self):
        self.register_route(method="GET", route="/greeting", schema=Schema(), handler=self.greeting)
        self.register_route(method="GET", route="/greeting_create", schema=Schema(), handler=self.create)

    def greeting(self):
        result = self._service.greeting()
        return self.send_result(result)

    def create(self):
        trace_id = self._get_trace_id()
        item = MyFriend(
            bottle.request.query["id"],
            bottle.request.query["type"],
            bottle.request.query["name"]
        )
        result = self._service.create(trace_id, item)

        return self.send_result(result)
Not available

MySQL

This class adds the common interface as a parent. As the getOneRandom() method is protected, we override it and add the composeFilter() method to create a filter that considers the specifics of MySQL.

import { Context } from "pip-services4-components-node"
import { FilterParams } from "pip-services4-data-node";
import { IdentifiableMySqlPersistence } from "pip-services4-mysql-node";

export class HelloFriendPersistence1 extends IdentifiableMySqlPersistence<MyFriend, string> implements IMyDataPersistence {
    public constructor() {
        super("myfriends3");
    }

    protected defineSchema(): void {
        this.clearSchema();
        this.ensureSchema('CREATE TABLE IF NOT EXISTS `' + this._tableName + '` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)');
    }

    private composeFilter(filter: FilterParams): string {
        filter ??= new FilterParams();
        let type = filter.getAsNullableString("type");
        let name = filter.getAsNullableString("name");

        let filterCondition = "";
        if (type != null)
            filterCondition += "type='" + type + "'";
        if (name != null)
            filterCondition += "name='" + name + "'";

        return filterCondition;
    }

    public getOneRandom(ctx: Context, filter: FilterParams): Promise<MyFriend> {
        return super.getOneRandom(ctx, this.composeFilter(filter));
    }
}

import mysqlpersist "github.com/pip-services4/pip-services4-go/pip-services4-mysql-go/persistence"

type HelloFriendPersistence1 struct {
	*mysqlpersist.IdentifiableMySqlPersistence[MyFriend, string]
}

func NewHelloFriendPersistence1() *HelloFriendPersistence1 {
	c := &HelloFriendPersistence1{}
	c.IdentifiableMySqlPersistence = mysqlpersist.InheritIdentifiableMySqlPersistence[MyFriend, string](c, "myfriends3")
	return c
}

func (c *HelloFriendPersistence1) DefineSchema() {
	c.ClearSchema()
	c.EnsureSchema("CREATE TABLE `" + c.TableName + "` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)")
}

func (c *HelloFriendPersistence1) composeFilter(filter cquery.FilterParams) string {
	typee, typeOk := filter.GetAsNullableString("type")
	name, nameOk := filter.GetAsNullableString("name")

	filterObj := ""
	if typeOk && typee != "" {
		filterObj += "`type`='" + typee + "'"
	}
	if nameOk && name != "" {
		filterObj += "`name`='" + name + "'"
	}

	return filterObj
}

func (c *HelloFriendPersistence1) GetOneRandom(ctx context.Context, filter cquery.FilterParams) (item MyFriend, err error) {
	return c.MySqlPersistence.GetOneRandom(ctx, c.composeFilter(filter))
}

from pip_services4_mysql.persistence import IdentifiableMySqlPersistence


class HelloFriendPersistence1(IdentifiableMySqlPersistence, IMyDataPersistence):

    def __init__(self):
        super(HelloFriendPersistence, self).__init__('myfriends3')

    def _define_schema(self):
        self._clear_schema()
        self._ensure_schema(
            'CREATE TABLE IF NOT EXISTS `' + self._table_name + '` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)')
    
    def _compose_filter(self, filter: FilterParams):
        filter = filter or FilterParams()
        type = filter.get_as_nullable_string('type')
        name = filter.get_as_nullable_string('name')

        filter_condition = ''
        if type is not None:
            filter_condition += "`type`='" + type + "'"
        if name is not None:
            filter_condition += "`name`='" + name + "'"

        return filter_condition
    
    def get_one_random(self, trace_id: Optional[str], filter: FilterParams) -> MyFriend:
        return super().get_one_random(trace_id, self._compose_filter(filter)) 
Not available

PostgreSQL

As we are working with two databases, we need to create a class similar to the one we created for MySQL. In this case, it inherits from IdentifiablePostgrePersistence and our common interface IMyDataPersistence.

Similar to our MySQL class, we also override the getOneRandom() method and add a composeFilter() method to adapt our filter to the specifics of PostgreSQL.

import { Context } from "pip-services4-components-node"
import { FilterParams } from "pip-services4-data-node";
import { IdentifiablePostgresPersistence } from "pip-services4-postgres-node";

export class HelloFriendPersistence2 extends IdentifiablePostgresPersistence<MyFriend, string> implements IMyDataPersistence {
    public constructor() {
        super("myfriends3");
    }

    protected defineSchema(): void {
        this.clearSchema();
        this.ensureSchema('CREATE TABLE IF NOT EXISTS ' + this._tableName + ' (id TEXT PRIMARY KEY, type TEXT, name TEXT)');
    }

    private composeFilter(filter: FilterParams): string {
        filter ??= new FilterParams();
        let type = filter.getAsNullableString("type");
        let content = filter.getAsNullableString("content");

        let filterCondition = "";
        if (type != null)
            filterCondition += "type='" + type + "'";
        if (content != null)
            filterCondition += "content='" + content + "'";

        return filterCondition;
    }

    public getOneRandom(ctx: Context, filter: FilterParams): Promise<MyFriend> {
        return super.getOneRandom(ctx, this.composeFilter(filter));
    }
}

type HelloFriendPersistence2 struct {
	*postgrespersist.IdentifiablePostgresPersistence[MyFriend, string]
}

func NewHelloFriendPersistence2() *HelloFriendPersistence2 {
	c := &HelloFriendPersistence2{}
	c.IdentifiablePostgresPersistence = postgrespersist.InheritIdentifiablePostgresPersistence[MyFriend, string](c, "myfriends3")
	return c
}

func (c *HelloFriendPersistence2) DefineSchema() {
	c.ClearSchema()
	c.EnsureSchema("CREATE TABLE IF NOT EXISTS " + c.QuotedTableName() + " (\"id\" TEXT PRIMARY KEY, \"type\" TEXT, \"name\" TEXT)")
}

func (c *HelloFriendPersistence2) composeFilter(filter cquery.FilterParams) string {
	typee, typeOk := filter.GetAsNullableString("type")
	name, nameOk := filter.GetAsNullableString("name")

	filterObj := ""
	if typeOk && typee != "" {
		filterObj += "`type`='" + typee + "'"
	}
	if nameOk && name != "" {
		filterObj += "`name`='" + name + "'"
	}

	return filterObj
}

func (c *HelloFriendPersistence2) GetOneRandom(ctx context.Context, filter cquery.FilterParams) (item MyFriend, err error) {
	return c.PostgresPersistence.GetOneRandom(ctx, c.composeFilter(filter))
}

from pip_services4_postgres.persistence import IdentifiablePostgresPersistence

class HelloFriendPersistence2(IdentifiablePostgresPersistence, IMyDataPersistence):

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

    def _define_schema(self):
        self._clear_schema()
        self._ensure_schema('CREATE TABLE IF NOT EXISTS' + self._table_name + ' (id TEXT PRIMARY KEY, type TEXT, name TEXT)')

    def _compose_filter(self, filter: FilterParams):
        filter = filter or FilterParams()
        key = filter.get_as_nullable_string('key')
        content = filter.get_as_nullable_string('content')

        filter_condition = ''
        if key is not None:
            filter_condition += "key='" + key + "'"
        if content is not None:
            filter_condition += "content='" + content + "'"

        return filter_condition

    def get_one_random(self, trace_id: Optional[str], filter: FilterParams) -> MyFriend:
        return super().get_one_random(trace_id, self._compose_filter(filter))
Not available

Configuration file

In order to be able to select between both databases, we need to include both configurations in our config file. In addition, we need to include a conditional IF statement that will check on the environment variable and select the correct database.

Our file will look something like this:

---
# Container context
- descriptor: "pip-services:context-info:default:default:1.0"
  name: "hello-friend"
  description: "HelloFriend microservice"

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

# Performance counter that post values to log
- descriptor: "pip-services:counters:log:default:1.0"

# Controller
- descriptor: "hello-friend:controller:default:default:1.0"
  default_name: "Friend"

# Shared HTTP Endpoint
- descriptor: "pip-services:endpoint:http:default:1.0"
  connection:
    protocol: http
    host: 0.0.0.0
    port: 8080

# HTTP Service V1
- descriptor: "hello-friend:service:http:default:1.0"

# Heartbeat service
- descriptor: "pip-services:heartbeat-service:http:default:1.0"

# Status service
- descriptor: "pip-services:status-service:http:default:1.0"


{{#if MYSQL_ENABLED}}
# Persistence - MySQL
- descriptor: "hello-friend:persistence:mysql:default:1.0"
  connection:
    host: 'localhost'
    port: '3306'
    database: 'pip'
  credential:
    username: 'root'
    password: ''
{{/if}}

{{#if POSTGRES_ENABLED}}
# Persistence - PostgreSQL
- descriptor: "hello-friend:persistence:postgres:default:1.0"
  connection:
    host: 'localhost'
    port: '5432'
    database: 'pip'
  credential:
    username: 'postgres'
    password: 'admin'
{{/if}}

Final code

Now that we have completed all the necessary additions and modifications in our program, we can join all the parts and get the final application. The code is:

import { Context, Descriptor, Factory, ConfigParams, IReferences, IConfigurable, IReferenceable, IContext } from "pip-services4-components-node"
import { ProcessContainer } from "pip-services4-container-node";
import { FilterParams,IStringIdentifiable } from "pip-services4-data-node";
import { DefaultHttpFactory, RestController } from "pip-services4-http-node";
import { IdentifiablePostgresPersistence } from "pip-services4-postgres-node";
import { IdentifiableMySqlPersistence } from "pip-services4-mysql-node";

export class MyFriend implements IStringIdentifiable {
    public id: string;
    public type: string;
    public name: string;
}

export class HelloFriendRestController extends RestController {
    protected service: HelloFriendService;

    public constructor() {
        super()
        this._baseRoute = "/hello_friend";
    }

    public configure(config: ConfigParams): void {
        super.configure(config);
    }

    public setReferences(references: IReferences): void {
        super.setReferences(references);
        this.service = references.getOneRequired(new Descriptor("hello-friend", "service", "*", "*", "1.0"));
    }

    private async greeting(req: any, res: any): Promise<void> {
        let result = await this.service.greeting();
        await this.sendResult(req, res, result);
    }

    private async create(req: any, res: any): Promise<void> {
        let correlationId = this.getTraceId(req);
        let friend: MyFriend = req.query;
        let result = await this.service.create(correlationId, friend);

        await this.sendResult(req, res, result);
    }

    public register(): void {
        this.registerRoute("GET", "/greeting", null, this.greeting);
        this.registerRoute("GET", "/greeting_create", null, this.create);
    }
}



export class HelloFriendService implements IConfigurable, IReferenceable {
    private defaultName: string;
    private persistence: IMyDataPersistence;

    public constructor() {
        this.defaultName = "Pip User";
    }

    public configure(config: ConfigParams): void {
        this.defaultName = config.getAsStringWithDefault("default_name", this.defaultName);
    }

    public setReferences(references: IReferences): void {
        this.persistence = references.getOneRequired(new Descriptor("hello-friend", "persistence", "*", "*", "1.0"));
    }

    public async greeting(): Promise<string> {
        let filter = FilterParams.fromTuples("type", "friend");
        let selectedFilter = await this.persistence.getOneRandom(null, filter);
        let name = selectedFilter != null ? selectedFilter.name : null;

        return `Hello, ${name} !`;
    }

    public async create(ctx: Context, item: MyFriend): Promise<MyFriend>  {
        let res = await this.persistence.create(ctx, item);
        return res;
    }
}

  
  
export interface IMyDataPersistence {
    getOneRandom(ctx: Context, filter: FilterParams): Promise<MyFriend>;
    create(ctx: Context, item: MyFriend): Promise<MyFriend>;
}
    


export class HelloFriendPersistence1 extends IdentifiableMySqlPersistence<MyFriend, string> implements IMyDataPersistence {
    public constructor() {
        super("myfriends3");
    }
    get(key: string) {
        throw new Error("Method not implemented.");
    }

    protected defineSchema(): void {
        this.clearSchema();
        this.ensureSchema('CREATE TABLE IF NOT EXISTS `' + this._tableName + '` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)');
    }

    private composeFilter(filter: FilterParams): string {
        filter ??= new FilterParams();
        let type = filter.getAsNullableString("type");
        let name = filter.getAsNullableString("name");

        let filterCondition = "";
        if (type != null)
            filterCondition += "type='" + type + "'";
        if (name != null)
            filterCondition += "name='" + name + "'";

        return filterCondition;
    }

    public getOneRandom(ctx: Context, filter: FilterParams): Promise<MyFriend> {
        return super.getOneRandom(ctx, this.composeFilter(filter));
    }
}


   
export class HelloFriendPersistence2 extends IdentifiablePostgresPersistence<MyFriend, string> implements IMyDataPersistence {
    public constructor() {
        super("myfriends3");
    }

    protected defineSchema(): void {
        this.clearSchema();
        this.ensureSchema('CREATE TABLE IF NOT EXISTS ' + this._tableName + ' (id TEXT PRIMARY KEY, type TEXT, name TEXT)');
    }

    private composeFilter(filter: FilterParams): string {
        filter ??= new FilterParams();
        let type = filter.getAsNullableString("type");
        let content = filter.getAsNullableString("content");

        let filterCondition = "";
        if (type != null)
            filterCondition += "type='" + type + "'";
        if (content != null)
            filterCondition += "content='" + content + "'";

        return filterCondition;
    }

    public getOneRandom(ctx: Context, filter: FilterParams): Promise<MyFriend> {
        return super.getOneRandom(ctx, this.composeFilter(filter));
    }
}



export class HelloFriendControllerFactory extends Factory {
    public constructor() {
        super();
        let HttpControllerDescriptor = new Descriptor("hello-friend", "controller", "http", "*", "1.0");      // View
        let ServiceDescriptor = new Descriptor("hello-friend", "service", "default", "*", "1.0"); // service
        let PersistenceDescriptor1 = new Descriptor("hello-friend", "persistence", "mysql", "*", "1.0"); // Persistence
        let PersistenceDescriptor2 = new Descriptor("hello-friend", "persistence", "postgres", "*", "1.0"); // Persistence

        this.registerAsType(HttpControllerDescriptor, HelloFriendRestController); // View
        this.registerAsType(ServiceDescriptor, HelloFriendService);  // Service
        this.registerAsType(PersistenceDescriptor1, HelloFriendPersistence1); // Persistence
        this.registerAsType(PersistenceDescriptor2, HelloFriendPersistence2); // Persistence
    }
}



export class HelloFriendProcess extends ProcessContainer {
    public constructor() {
        super("hello-friend", "HelloFriend microservice");
        this._configPath = "./config.yaml"
        this._factories.add(new HelloFriendControllerFactory());
        this._factories.add(new DefaultHttpFactory());
    }
}



export async function main() { 
    try {
        // Step 1 - Database selection
        // process.env['MYSQL_ENABLED'] = 'true';
        process.env['POSTGRES_ENABLED'] = 'true';

        // Step 2 - The run() command
        let proc = new HelloFriendProcess();
        proc.run(process.argv);
    } catch (ex) {
        console.error(ex);
    }
}


package main

import (
	"context"
	"net/http"
	"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"
	cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	crun "github.com/pip-services4/pip-services4-go/pip-services4-container-go/container"
	cquery "github.com/pip-services4/pip-services4-go/pip-services4-data-go/query"
	httpbuild "github.com/pip-services4/pip-services4-go/pip-services4-http-go/build"
	ccontrollers "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
	mysqlpersist "github.com/pip-services4/pip-services4-go/pip-services4-mysql-go/persistence"
	postgrespersist "github.com/pip-services4/pip-services4-go/pip-services4-postgres-go/persistence"
)

type MyFriend struct {
	Id   string `bson:"_id" json:"id"`
	Type string `bson:"type" json:"type"`
	Name string `bson:"name" json:"name"`
}

func (d *MyFriend) SetId(id string) {
	d.Id = id
}

func (d MyFriend) GetId() string {
	return d.Id
}

func (d MyFriend) Clone() MyFriend {
	return MyFriend{
		Id:   d.Id,
		Type: d.Type,
		Name: d.Name,
	}
}

type HelloFriendRestController struct {
	*ccontrollers.RestController
	controller *HelloFriendService
}

func NewHelloFriendRestController() *HelloFriendRestController {
	c := &HelloFriendRestController{}
	c.RestController = ccontrollers.InheritRestController(c)
	c.BaseRoute = "/hello_friend"
	return c
}

func (c *HelloFriendRestController) Configure(ctx context.Context, config *cconf.ConfigParams) {
	c.RestController.Configure(ctx, config)
}

func (c *HelloFriendRestController) SetReferences(ctx context.Context, references cref.IReferences) {
	c.RestController.SetReferences(ctx, references)
	res, err := references.GetOneRequired(cref.NewDescriptor("hello-friend", "controller", "*", "*", "1.0"))
	if err != nil {
		panic(err)
	}

	c.controller = res.(*HelloFriendService)
}

func (c *HelloFriendRestController) Greeting(res http.ResponseWriter, req *http.Request) {
	result, err := c.controller.Greeting(req.Context())
	c.SendResult(res, req, result, err)
}

func (c *HelloFriendRestController) Create(res http.ResponseWriter, req *http.Request) {
	friend := MyFriend{Id: "0", Type: "New type", Name: "New name"}
	c.SendResult(res, req, friend, nil)
}

func (c *HelloFriendRestController) Register() {
	c.RegisterRoute("GET", "/greeting", nil, c.Greeting)
	c.RegisterRoute("GET", "/greeting_create", nil, c.Create)
}

type HelloFriendService struct {
	defaultName string
	persistence IMyDataPersistence[MyFriend]
}

func NewHelloFriendService() *HelloFriendService {
	c := &HelloFriendService{
		defaultName: "Pip User",
	}

	return c
}

func (c *HelloFriendService) Configure(ctx context.Context, config *cconf.ConfigParams) {
	c.defaultName = config.GetAsStringWithDefault("default_name", c.defaultName)
}

func (c *HelloFriendService) SetReferences(ctx context.Context, references cref.IReferences) {
	res, descrErr := references.GetOneRequired(cref.NewDescriptor("hello-friend", "persistence", "*", "*", "1.0"))
	if descrErr != nil {
		panic(descrErr)
	}

	c.persistence = res.(IMyDataPersistence[MyFriend])
}

func (c *HelloFriendService) Greeting(ctx context.Context) (string, error) {
	filter := cquery.NewFilterParamsFromTuples("type", "friend")
	selectedFilter, err := c.persistence.GetOneRandom(ctx, "123", *filter)
	if err != nil {
		return "", err
	}

	return "Hello, " + selectedFilter.Name + " !", nil
}

func (c *HelloFriendService) Create(ctx context.Context, correlationId string, item MyFriend) (result MyFriend, err error) {
	return c.persistence.Create(ctx, correlationId, item)
}

type IMyDataPersistence[T any] interface {
	GetOneRandom(ctx context.Context, correlationId string, filter cquery.FilterParams) (item T, err error)
	Create(ctx context.Context, correlationId string, item T) (result T, err error)
}

type HelloFriendPersistence1 struct {
	*mysqlpersist.IdentifiableMySqlPersistence[MyFriend, string]
}

func NewHelloFriendPersistence1() *HelloFriendPersistence1 {
	c := &HelloFriendPersistence1{}
	c.IdentifiableMySqlPersistence = mysqlpersist.InheritIdentifiableMySqlPersistence[MyFriend, string](c, "myfriends3")
	return c
}

func (c *HelloFriendPersistence1) DefineSchema() {
	c.ClearSchema()
	c.EnsureSchema("CREATE TABLE `" + c.TableName + "` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)")
}

func (c *HelloFriendPersistence1) composeFilter(filter cquery.FilterParams) string {
	typee, typeOk := filter.GetAsNullableString("type")
	name, nameOk := filter.GetAsNullableString("name")

	filterObj := ""
	if typeOk && typee != "" {
		filterObj += "`type`='" + typee + "'"
	}
	if nameOk && name != "" {
		filterObj += "`name`='" + name + "'"
	}

	return filterObj
}

func (c *HelloFriendPersistence1) GetOneRandom(ctx context.Context, filter cquery.FilterParams) (item MyFriend, err error) {
	return c.MySqlPersistence.GetOneRandom(ctx, c.composeFilter(filter))
}

type HelloFriendPersistence2 struct {
	*postgrespersist.IdentifiablePostgresPersistence[MyFriend, string]
}

func NewHelloFriendPersistence2() *HelloFriendPersistence2 {
	c := &HelloFriendPersistence2{}
	c.IdentifiablePostgresPersistence = postgrespersist.InheritIdentifiablePostgresPersistence[MyFriend, string](c, "myfriends3")
	return c
}

func (c *HelloFriendPersistence2) DefineSchema() {
	c.ClearSchema()
	c.EnsureSchema("CREATE TABLE IF NOT EXISTS " + c.QuotedTableName() + " (\"id\" TEXT PRIMARY KEY, \"type\" TEXT, \"name\" TEXT)")
}

func (c *HelloFriendPersistence2) composeFilter(filter cquery.FilterParams) string {
	typee, typeOk := filter.GetAsNullableString("type")
	name, nameOk := filter.GetAsNullableString("name")

	filterObj := ""
	if typeOk && typee != "" {
		filterObj += "`type`='" + typee + "'"
	}
	if nameOk && name != "" {
		filterObj += "`name`='" + name + "'"
	}

	return filterObj
}

func (c *HelloFriendPersistence2) GetOneRandom(ctx context.Context, filter cquery.FilterParams) (item MyFriend, err error) {
	return c.PostgresPersistence.GetOneRandom(ctx, c.composeFilter(filter))
}

var HttpControllerDescriptor = cref.NewDescriptor("hello-friend", "controller", "http", "*", "1.0")    // Controller
var ServiceDescriptor = cref.NewDescriptor("hello-friend", "service", "default", "*", "1.0")           // Service
var PersistenceDescriptor1 = cref.NewDescriptor("hello-friend", "persistence", "mysql", "*", "1.0")    // Persistence1
var PersistenceDescriptor2 = cref.NewDescriptor("hello-friend", "persistence", "postgres", "*", "1.0") // Persistence2

type HelloFriendServiceFactory struct {
	*cbuild.Factory
}

func NewHelloFriendServiceFactory() *HelloFriendServiceFactory {
	c := &HelloFriendServiceFactory{}
	c.Factory = cbuild.NewFactory()

	c.RegisterType(HttpControllerDescriptor, NewHelloFriendRestController) // Controller
	c.RegisterType(ServiceDescriptor, NewHelloFriendService)               // Service
	c.RegisterType(PersistenceDescriptor1, NewHelloFriendPersistence1)     // Persistence1
	c.RegisterType(PersistenceDescriptor2, NewHelloFriendPersistence2)     // Persistence2

	return c
}

// Containerization
type HelloFriendProcess struct {
	*crun.ProcessContainer
}

func NewHelloFriendProcess() *HelloFriendProcess {
	c := &HelloFriendProcess{}
	c.ProcessContainer = crun.NewProcessContainer("hello-friend", "HelloFriend microservice")
	c.SetConfigPath("./config.yaml")
	c.AddFactory(NewHelloFriendServiceFactory())
	c.AddFactory(httpbuild.NewDefaultHttpFactory())

	return c
}

// Running the app
func main() {

	// Step 1 - Database selection
	// os.Setenv("MYSQL_ENABLED", "true")
	os.Setenv("POSTGRES_ENABLED", "true")

	// Step 2 - The run() command
	proc := NewHelloFriendProcess()
	proc.Run(context.Background(), os.Args)
}

from pip_services4_data.data import IStringIdentifiable


class MyFriend(IStringIdentifiable):
    def __init__(self, id: str, type: str, name: str):
        self.id = id
        self.type = type
        self.name = name



import bottle
from pip_services4_data.validate import Schema
from pip_services4_http.controller import RestController


class HelloFriendRestController(RestController):

    def __init__(self):
        super(HelloFriendRestController, self).__init__()

        self._base_route = "/hello_friend"
        self._service: HelloFriendService = None

    def configure(self, config):
        super().configure(config)

    def set_references(self, references):
        super(HelloFriendRestController, self).set_references(references)
        self._controller = references.get_one_required(Descriptor('hello-friend', 'controller', '*', '*', '1.0'))

    def register(self):
        self.register_route(method="GET", route="/greeting", schema=Schema(), handler=self.greeting)
        self.register_route(method="GET", route="/greeting_create", schema=Schema(), handler=self.create)

    def greeting(self):
        result = self._service.greeting()
        return self.send_result(result)

    def create(self):
        trace_id = self._get_trace_id()
        item = MyFriend(
            bottle.request.query["id"],
            bottle.request.query["type"],
            bottle.request.query["name"]
        )
        result = self._service.create(trace_id, item)

        return self.send_result(result)



from pip_services4_rpc.commands import ICommandable
from pip_services4_components.config import IConfigurable
from pip_services4_components.refer import IReferences, IReferenceable


class HelloFriendService(IConfigurable, IReferenceable):
    __defaultName = None
    __persistence: IMyDataPersistence = None

    def __init__(self):
        self.__defaultName = "Pip User"

    def configure(self, config):
        self.__defaultName = config.get_as_string_with_default("default_name", self.__defaultName)

    def set_references(self, references: IReferences):
        self.__persistence = references.get_one_required(Descriptor("hello-friend", "persistence", "*", "*", "1.0"))

    def greeting(self):
        filter_param = FilterParams.from_tuples("type", "friend")
        selected_friend = self.__persistence.get_one_random(None, filter_param)
        name2 = selected_friend.name

        return f"Hello, {name2} !"

    def create(self, trace_id: Optional[str], item: MyFriend) -> MyFriend:
        res = self.__persistence.create(trace_id, item)
        return res

    
    
from abc import ABC
from typing import Optional
from pip_services4_data.query import FilterParams


class IMyDataPersistence(ABC):

    # CRUD operations
    def get_one_random(self, trace_id: Optional[str], filter: FilterParams) -> MyFriend:
        raise NotImplemented()

    def create(self, trace_id: Optional[str], item: MyFriend) -> MyFriend:
        raise NotImplemented()
        


from pip_services4_mysql.persistence import IdentifiableMySqlPersistence


class HelloFriendPersistence1(IdentifiableMySqlPersistence, IMyDataPersistence):

    def __init__(self):
        super(HelloFriendPersistence1, self).__init__('myfriends3')

    def _define_schema(self):
        self._clear_schema()
        self._ensure_schema(
            'CREATE TABLE IF NOT EXISTS `' + self._table_name + '` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)')

    def _compose_filter(self, filter: FilterParams):
        filter = filter or FilterParams()
        type = filter.get_as_nullable_string('type')
        name = filter.get_as_nullable_string('name')

        filter_condition = ''
        if type is not None:
            filter_condition += "`type`='" + type + "'"
        if name is not None:
            filter_condition += "`name`='" + name + "'"

        return filter_condition

    def get_one_random(self, trace_id: str, filter: FilterParams) -> MyFriend:
        return super().get_one_random(trace_id, self._compose_filter(filter))


       
from pip_services4_postgres.persistence import IdentifiablePostgresPersistence


class HelloFriendPersistence2(IdentifiablePostgresPersistence, IMyDataPersistence):

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

    def _define_schema(self):
        self._clear_schema()
        self._ensure_schema('CREATE TABLE IF NOT EXISTS ' + self._table_name + ' (id TEXT PRIMARY KEY, type TEXT, name TEXT)')

    def _compose_filter(self, filter: FilterParams):
        filter = filter or FilterParams()
        key = filter.get_as_nullable_string('key')
        content = filter.get_as_nullable_string('content')

        filter_condition = ''
        if key is not None:
            filter_condition += "key='" + key + "'"
        if content is not None:
            filter_condition += "content='" + content + "'"

        return filter_condition

    def get_one_random(self, trace_id: Optional[str], filter: FilterParams) -> MyFriend:
        return super().get_one_random(trace_id, self._compose_filter(filter))



from pip_services4_components.refer import Descriptor
from pip_services4_components.build import Factory


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

        HttpControllerDescriptor = Descriptor('hello-friend', 'controller', 'http', '*', '1.0')  # Controller
        ServiceDescriptor = Descriptor('hello-friend', 'service', 'default', '*', '1.0')  # Service
        PersistenceDescriptor1 = Descriptor('hello-friend', 'persistence', 'mysql', '*', '1.0')  # Persistence
        PersistenceDescriptor2 = Descriptor('hello-friend', 'persistence', 'postgres', '*', '1.0')  # Persistence

        self.register_as_type(HttpControllerDescriptor, HelloFriendRestController)  # View
        self.register_as_type(ServiceDescriptor, HelloFriendService)  # Service
        self.register_as_type(PersistenceDescriptor1, HelloFriendPersistence1)  # Persistence
        self.register_as_type(PersistenceDescriptor2, HelloFriendPersistence2)  # Persistence


       
from pip_services4_container.container import ProcessContainer
from pip_services4_http.build import DefaultRpcFactory


class HelloFriendProcess(ProcessContainer):

    def __init__(self):
        super(HelloFriendProcess, self).__init__('hello-friend', 'HelloFriend microservice')
        self._config_path = './configDTC.yaml'
        self._factories.add(HelloFriendControllerFactory())
        self._factories.add(DefaultRpcFactory())

# Step 1 - Database selection
import os

os.environ["MYSQL_ENABLED"] = "True"
# os.environ["POSTGRES_ENABLED"] = "True"

# Step 2 - The run() command
if __name__ == '__main__':
    runner = HelloFriendProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)
Not available

Running our app

The fact that we can choose between two different databases brings an extra step to our process, which is, selecting the operating database.

This is done by assigning a value to the environmental variable representing the selected database. We can do this by setting our variable from Windows or through code. We can then deselect this database by assigning an empty string or by not assigning a value to its environment variable. The following example shows how to select our MySQL database and run the application.

export async function main() { 
    try {
        // Step 1 - Database selection
        // process.env['MYSQL_ENABLED'] = 'true';
        process.env['POSTGRES_ENABLED'] = 'true';

        // Step 2 - The run() command
        let proc = new HelloFriendProcess();
        proc.run(process.argv);
    } catch (ex) {
        console.error(ex);
    }
}

func main() {
	// Step 1 - Database selection
	// os.Setenv("MYSQL_ENABLED", "true")
	os.Setenv("POSTGRES_ENABLED", "true")

	// Step 2 - The run() command
	proc := NewHelloFriendProcess()
	proc.Run(context.Background(), os.Args)
}


# Step 1 - Database selection
import os

os.environ["MYSQL_ENABLED"] = "True"
#os.environ["POSTGRES_ENABLED"] = "True"

# Step 2 - The run() command
if __name__ == '__main__':
    runner = HelloFriendProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)
Not available

Results

If we selected the MySQL database, we will see the following message:

figure 1

Alternatively, if we selected the PostgreSQL database, we will see the following message:

figure 2

Wrapping up

In this tutorial, we have examined how to create a web application that allows us to select between two different databases at deployment time. Although this example is simple, it can be extended to consider many other scenarios, as the principles and concepts employed in its construction are the same.