Deployment time configuration
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()
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)
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))
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))
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)
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)
Results
If we selected the MySQL database, we will see the following message:
Alternatively, if we selected the PostgreSQL database, we will see the following message:
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.