Three tier architecture
Key takeaways
Three-tier architecture | Software application architecture that organizes applications into three logical tiers. |
Inversion of control | Design principle used to invert control in OO programming to achieve loose coupling. |
Factory | A method used to define a separate operation for object creation. |
Locator pattern | A pattern that considers a registry known as the "service locator", which stores the information necessary to perform a certain task. |
Configuration file | A YAML file containing information about the different components and their configurations. |
Introduction
In this tutorial, you will learn how to construct an application using Pip.Services components and a three-tier structure.
We will begin with a brief description of the example that we will be constructing and a list of the necessary pre-requisites.
Then, we will see a detailed description of the three tiers with code examples for each of them. We will continue by explaining how two important concepts are applied in Pip.Services: inversion of control and locator pattern, and how to construct a process container for our program.
Next, we will see how to run our app by selecting a specific database, and how the results obtained from its execution are presented on our browser.
We will finish by showing the complete code of our example and summarizing what was learned.
Brief description of the example
The example in this tutorial consists of an application that sends a message to a browser. The message has the format “Hello {name}!” where name is the random name of a person that was selected from a database.
In order to achieve this, we divide our app into three tiers. The first is the presentation or view layer, which consists of a REST service that will provide information to the browser. The second is the application layer. This tier contains a controller that connects the REST service to the database and extracts a random name from it. The last one is the data or persistence layer, which is created by using a MySQL database. The following table summarizes this and the concepts behind.
Pre-requisites
Before creating this app, we need to install several modules that contain the necessary components. They are:
npm install pip-services3-commons-nodex --save
dotnet add package PipServices3.Commons
go get -u github.com/pip-services3-gox/pip-services3-commons-gox@latest
pip install pip-services3-commons
npm install pip-services3-rpc-nodex --save
dotnet add package PipServices3.Rpc
go get -u github.com/pip-services3-gox/pip-services3-rpc-gox@latest
pip install pip-services3-rpc
npm install pip-services3-mysql-nodex --save
dotnet add package PipServices3.Mysql
go get -u github.com/pip-services3-gox/pip-services3-mysql-gox@latest
pip install pip-services3-mysql
npm install pip-services3-postgres-nodex --save
dotnet add package PipServices3.Postgres
go get -u github.com/pip-services3-gox/pip-services3-postgres-gox@latest
pip install pip-services3-postgres
Data object
In order to use the data obtained from the database, we define a data structure that mirrors the table where the data is stored.
This table contains three columns of type varchar, namely id, type, and name. Thus, our data structure looks like this:
import { IStringIdentifiable } from "pip-services3-commons-nodex";
export class MyFriend implements IStringIdentifiable {
public id: string;
public type: string;
public name: string;
}
[DataContract]
public class MyFriend : IStringIdentifiable
{
[DataMember(Name = "id")]
public string Id { get; set; }
[DataMember(Name = "type")]
public string Type { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
}
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,
}
}
from pip_services3_commons.data import IStringIdentifiable
class MyFriend(IStringIdentifiable):
def __init__(self, id: str, type: str, name: str):
self.id = id
self.type = type
self.name = name
Tier 1: Presentation layer or view
This layer is used to show the result of our app on the browser. It is constructed as a subclass of the RestService class. In it, we set a reference to the controller to create the connection between the two and be able to use the greetings() method. We also define the elements of the URL to the resulting webpage.
import { ConfigParams, Descriptor, IReferences, IStringIdentifiable } from "pip-services3-commons-nodex";
import { RestService } from "pip-services3-rpc-nodex";
export class HelloFriendRestService extends RestService {
protected controller: HelloFriendController;
public constructor() {
super()
this._baseRoute = "/hello_friend";
}
public configure(config: ConfigParams): void {
super.configure(config);
}
public setReferences(references: IReferences): void {
super.setReferences(references);
this.configure = references.getOneRequired(new Descriptor("hello-friend", "controller", "*", "*", "1.0"));
}
private async greeting(req: any, res: any): Promise<void> {
let result = await this.controller.greeting();
await this.sendResult(req, res, result);
}
private async create(req: any, res: any): Promise<void> {
let correlationId = this.getCorrelationId(req);
let friend = new MyFriend();
await this.sendResult(req, res, friend);
}
public register(): void {
this.registerRoute("GET", "/greeting", null, this.greeting);
this.registerRoute("GET", "/greeting_create", null, this.create);
}
}
using System;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using PipServices3.Commons.Config;
using PipServices3.Commons.Refer;
using PipServices3.Commons.Data;
using PipServices3.Rpc.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
public class HelloFriendRestService: RestService
{
protected HelloFriendController controller;
public HelloFriendRestService() : base()
{
_baseRoute = "/hello_friend";
}
public override void Configure(ConfigParams config)
{
base.Configure(config);
}
public override void SetReferences(IReferences references)
{
base.SetReferences(references);
controller = references.GetOneRequired<HelloFriendController>(new Descriptor("hello-friend", "controller", "*", "*", "1.0"));
}
private async Task Greeting(HttpRequest req, HttpResponse res, RouteData routeData)
{
var result = await controller.Greeting();
await SendResultAsync(res, result);
}
private async Task CreateAsync(HttpRequest req, HttpResponse res, RouteData routeData)
{
var correlationId = this.GetCorrelationId(req);
var friend = new MyFriend() { Id = req.Query["id"], Type = req.Query["type"], Name = req.Query["Name"] };
var result = await controller.CreateAsync(correlationId, friend);
await SendResultAsync(res, result);
}
public override void Register()
{
RegisterRoute(method: "GET", route: "/greeting", action: Greeting);
RegisterRoute(method: "GET", route: "/greeting_create", action: CreateAsync);
}
}
import (
"context"
"net/http"
cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
cservices "github.com/pip-services3-gox/pip-services3-rpc-gox/services"
)
type HelloFriendRestService struct {
*cservices.RestService
controller *HelloFriendController
}
func NewHelloFriendRestService() *HelloFriendRestService {
c := &HelloFriendRestService{}
c.RestService = cservices.InheritRestService(c)
c.BaseRoute = "/hello_friend"
return c
}
func (c *HelloFriendRestService) Configure(ctx context.Context, config *cconf.ConfigParams) {
c.RestService.Configure(ctx, config)
}
func (c *HelloFriendRestService) SetReferences(ctx context.Context, references cref.IReferences) {
c.RestService.SetReferences(ctx, references)
res, err := references.GetOneRequired(cref.NewDescriptor("hello-friend", "controller", "*", "*", "1.0"))
if err != nil {
panic(err)
}
c.controller = res.(*HelloFriendController)
}
func (c *HelloFriendRestService) Greeting(res http.ResponseWriter, req *http.Request) {
result, err := c.controller.Greeting(req.Context())
c.SendResult(res, req, result, err)
}
func (c *HelloFriendRestService) 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 *HelloFriendRestService) Register() {
c.RegisterRoute("GET", "/greeting", nil, c.Greeting)
c.RegisterRoute("GET", "/greeting_create", nil, c.Create)
}
import bottle
from pip_services3_commons.data import IStringIdentifiable
from pip_services3_commons.validate import Schema
from pip_services3_rpc.services import RestService
class HelloFriendRestService(RestService):
def __init__(self):
super(HelloFriendRestService, self).__init__()
self._base_route = "/hello_friend"
self._controller: HelloFriendController = None
def configure(self, config):
super().configure(config)
def set_references(self, references):
super(HelloFriendRestService, 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._controller.greeting()
return self.send_result(result)
def create(self):
correlation_id = self._get_correlation_id()
item = MyFriend(
bottle.request.query["id"],
bottle.request.query["type"],
bottle.request.query["name"]
)
result = self._controller.create(correlation_id, item)
return self.send_result(result)
Tier 2: Application layer or controller
The controller allows us to connect the presentation and persistence layers and produce some data transformations.
Thus, it sets a reference to the database. This reference is not to a specific database, but a general persistence component that will allow us to select between different databases at deployment time.
This class also defines the greeting method, which selects a random name from the database and then passes it to the view. It also defines a default name, which will be used if no name is obtained from the database query.
import { IConfigurable, IReferenceable, IReferences } from "pip-services3-commons-nodex";
export class HelloFriendController implements IConfigurable, IReferenceable {
private defaultName: string;
private persistence: HelloFriendPersistence;
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(correlationId: string, item: MyFriend): Promise<MyFriend> {
let res = await this.persistence.create(correlationId, item);
return res;
}
}
using PipServices3.Commons.Config;
using PipServices3.Commons.Refer;
using PipServices3.Commons.Commands;
public class HelloFriendController : IConfigurable, IReferenceable
{
private string defaultName;
private HelloFriendPersistence persistence;
public HelloFriendController()
{
defaultName = "Pip User";
}
public void Configure(ConfigParams config)
{
defaultName = config.GetAsStringWithDefault("default_name", defaultName);
}
public void SetReferences(IReferences references)
{
persistence = references.GetOneRequired<HelloFriendPersistence>(new Descriptor("hello-friend", "persistence", "*", "*", "1.0"));
}
public async Task<string> Greeting()
{
var filter = FilterParams.FromTuples("type", "friend");
var selectedFilter = await persistence.GetOneRandomAsync(null, filter);
var name = selectedFilter!= null ? selectedFilter.Name : null;
return $"Hello, {name} !";
}
public async Task<MyFriend> CreateAsync(string correlationId, MyFriend item)
{
var res = await persistence.CreateAsync(correlationId, item);
return res;
}
}
type HelloFriendController struct {
defaultName string
persistence *HelloFriendPersistence
}
func NewHelloFriendController() *HelloFriendController {
c := &HelloFriendController{}
c.defaultName = "Pip User"
return c
}
func (c *HelloFriendController) Configure(ctx context.Context, config *cconf.ConfigParams) {
c.defaultName = config.GetAsStringWithDefault("default_name", c.defaultName)
}
func (c *HelloFriendController) SetReferences(ctx context.Context, references cref.IReferences) {
res, err := references.GetOneRequired(cref.NewDescriptor("hello-friend", "persistence", "*", "*", "1.0"))
if err != nil {
panic(err)
}
c.persistence = res.(*HelloFriendPersistence)
}
func (c *HelloFriendController) Greeting(ctx context.Context) (string, error) {
filter := cdata.NewFilterParamsFromTuples("type", "friend")
selectedFilter, err := c.persistence.GetOneRandom(ctx, "123", *filter)
if err != nil {
return "", err
}
return "Hello, " + selectedFilter.Name + "!", nil
}
func (c *HelloFriendController) Create(ctx context.Context, correlationId string, item MyFriend) (MyFriend, error) {
return c.persistence.Create(ctx, correlationId, item)
}
from pip_services3_commons.config import IConfigurable
from pip_services3_commons.refer import IReferences, IReferenceable
class HelloFriendController(IConfigurable, IReferenceable):
__defaultName = None
__persistence: 'HelloFriendPersistence' = 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, correlation_id: Optional[str], item: MyFriend) -> MyFriend:
res = self.__persistence.create(correlation_id, item)
return res
Tier 3: Data layer or persistence layer
This layer connects to a database containing a table with names. The class constructor accepts the name of the table to be used, which in this example is called ‘myfriends’.
The class also contains the defineSchema() method, which ensures that if our table doesn’t exist in the database, it is created.
Next, it contains the composeFilter() method, which customizes a filter to the needs of the database, and the getOneRandom() method, which is an override of the parent class.
import { FilterParams } from "pip-services3-commons-nodex";
import { IdentifiableMySqlPersistence } from "pip-services3-mysql-nodex";
export class HelloFriendPersistence extends IdentifiableMySqlPersistence<MyFriend, string> {
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(correlationId: string, filter: FilterParams): Promise<MyFriend> {
return super.getOneRandom(correlationId, this.composeFilter(filter));
}
}
using PipServices3.MySql.Persistence;
using PipServices3.Commons.Data;
public class HelloFriendPersistence: IdentifiableMySqlPersistence<MyFriend, string>
{
public HelloFriendPersistence() :base("myfriends3") { }
protected override void DefineSchema()
{
ClearSchema();
EnsureSchema($"CREATE TABLE IF NOT EXISTS {_tableName} (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)");
}
private static string ComposeFilter(FilterParams filter)
{
filter ??= new FilterParams();
var type = filter.GetAsNullableString("type");
var name = filter.GetAsNullableString("name");
var filterCondition = "";
if (type != null)
filterCondition += "`type`='" + type + "'";
if (name != null)
filterCondition += "`name`='" + name + "'";
return filterCondition;
}
public Task<MyFriend> GetOneRandomAsync(string correlationId, FilterParams filter)
{
return base.GetOneRandomAsync(correlationId, ComposeFilter(filter));
}
}
import (
"context"
cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
mysqlpersist "github.com/pip-services3-gox/pip-services3-mysql-gox/persistence"
)
type HelloFriendPersistence struct {
*mysqlpersist.IdentifiableMySqlPersistence[MyFriend, string]
}
func NewHelloFriendPersistence() *HelloFriendPersistence {
c := &HelloFriendPersistence{}
c.IdentifiableMySqlPersistence = mysqlpersist.InheritIdentifiableMySqlPersistence[MyFriend, string](c, "myfriends3")
return c
}
func (c *HelloFriendPersistence) DefineSchema() {
c.ClearSchema()
c.EnsureSchema("CREATE TABLE `" + c.TableName + "` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)")
}
func (c *HelloFriendPersistence) composeFilter(filter cdata.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 *HelloFriendPersistence) GetOneRandom(ctx context.Context, correlationId string, filter cdata.FilterParams) (item MyFriend, err error) {
return c.MySqlPersistence.GetOneRandom(ctx, correlationId, c.composeFilter(filter))
}
from pip_services3_mysql.persistence import IdentifiableMySqlPersistence
from pip_services3_commons.data import FilterParams
class HelloFriendPersistence(IdentifiableMySqlPersistence):
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, correlation_id: str, filter: FilterParams) -> MyFriend:
return super().get_one_random(correlation_id, self._compose_filter(filter))
Containerization
Now that we have the code for our three tiers, we can put it together in an executable container. This is done in two steps: object creation and binding.
The first is based on the inversion of control principle through the use of factories. The second considers the Locator pattern through an external configuration file with information on the different modules and their properties. The following sections explain them in detail.
Inversion of control: Factories
Pip.Services uses the Inversion of Control principle to create different objects. As such, it employs factories to create instances of classes.
In our example, we create the HelloFriendServiceFactory, which is a subclass of Factory and registers the HelloFriendRestService, HelloFriendController, and HelloFriendPersistence components as classes to be instantiated.
import { Descriptor } from "pip-services3-commons-nodex";
import { Factory } from "pip-services3-components-nodex";
export class HelloFriendServiceFactory extends Factory {
public constructor() {
super();
let HttpServiceDescriptor = new Descriptor("hello-friend", "service", "http", "*", "1.0"); // View
let ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller
let PersistenceDescriptor = new Descriptor("hello-friend", "persistence", "mysql", "*", "1.0"); // Persistence
this.registerAsType(HttpServiceDescriptor, HelloFriendRestService); // View
this.registerAsType(ControllerDescriptor, HelloFriendController); // Controller
this.registerAsType(PersistenceDescriptor, HelloFriendPersistence); // Persistence
}
}
using PipServices3.Commons.Refer;
using PipServices3.Components.Build;
public class HelloFriendServiceFactory: Factory
{
public HelloFriendServiceFactory(): base()
{
var HttpServiceDescriptor = new Descriptor("hello-friend", "service", "http", "*", "1.0"); // View
var ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller
var PersistenceDescriptor = new Descriptor("hello-friend", "persistence", "mysql", "*", "1.0"); // Persistence
RegisterAsType(HttpServiceDescriptor, typeof(HelloFriendRestService)); // View
RegisterAsType(ControllerDescriptor, typeof(HelloFriendController)); // Controller
RegisterAsType(PersistenceDescriptor, typeof(HelloFriendPersistence)); // Persistence
}
}
import (
cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
)
var HttpServiceDescriptor = cref.NewDescriptor("hello-friend", "service", "http", "*", "1.0") // View
var ControllerDescriptor = cref.NewDescriptor("hello-friend", "controller", "default", "*", "1.0") // Controller
var PersistenceDescriptor = cref.NewDescriptor("hello-friend", "persistence", "mysql", "*", "1.0") // Persistence
type HelloFriendServiceFactory struct {
*cbuild.Factory
}
func NewHelloFriendServiceFactory() *HelloFriendServiceFactory {
c := &HelloFriendServiceFactory{}
c.Factory = cbuild.NewFactory()
c.RegisterType(HttpServiceDescriptor, NewHelloFriendRestService) // View
c.RegisterType(ControllerDescriptor, NewHelloFriendController) // Controller
c.RegisterType(PersistenceDescriptor, NewHelloFriendPersistence) // Persistence
return c
}
from pip_services3_commons.refer import Descriptor
from pip_services3_components.build import Factory
class HelloFriendServiceFactory(Factory):
def __init__(self):
super(HelloFriendServiceFactory, self).__init__()
HttpServiceDescriptor = Descriptor('hello-friend', 'service', 'http', '*', '1.0') # View
ControllerDescriptor = Descriptor('hello-friend', 'controller', 'default', '*', '1.0') # Controller
PersistenceDescriptor = Descriptor('hello-friend', 'persistence', 'mysql', '*', '1.0') # Persistence
self.register_as_type(HttpServiceDescriptor, HelloFriendRestService) # View
self.register_as_type(ControllerDescriptor, HelloFriendController) # Controller
self.register_as_type(PersistenceDescriptor, HelloFriendPersistence) # Persistence
Locator pattern: config file
Pip.Services uses the locator pattern to create the bindings between the different objects. To do this, we create a configuration file with information about the different components. Among them, we specify the actual configuration of our MySQL database.
---
# 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"
# Persistnece - MySQL
- descriptor: "hello-friend:persistence:mysql:default:1.0"
connection:
host: 'localhost'
port: '3306'
database: 'pip'
credential:
username: 'root'
password: ''
Process container
Now that our support structure has been created, we add the components to a process container. This container will allow us to run our code as a sole app.
import { ProcessContainer } from "pip-services3-container-nodex";
import { DefaultRpcFactory } from "pip-services3-rpc-nodex";
export class HelloFriendProcess extends ProcessContainer {
public constructor() {
super("hello-friend", "HelloFriend microservice");
this._configPath = "./config.yaml"
this._factories.add(new HelloFriendServiceFactory());
this._factories.add(new DefaultRpcFactory());
}
}
using PipServices3.Container;
using PipServices3.Rpc.Build;
public class HelloFriendProcess: ProcessContainer
{
public HelloFriendProcess(): base("hello-friend", "HelloFriend microservice")
{
_configPath = "../../../config.yaml";
_factories.Add(new HelloFriendServiceFactory());
_factories.Add(new DefaultRpcFactory());
}
}
import (
rpcbuild "github.com/pip-services3-gox/pip-services3-rpc-gox/build"
crun "github.com/pip-services3-gox/pip-services3-container-gox/container"
)
type HelloFriendProcess struct {
*crun.ProcessContainer
}
func NewHelloFriendProcess() *HelloFriendProcess {
c := &HelloFriendProcess{}
c.ProcessContainer = crun.NewProcessContainer("hello-friend", "HelloFriend microservice")
c.AddFactory(NewHelloFriendServiceFactory())
c.AddFactory(rpcbuild.NewDefaultRpcFactory())
return c
}
from pip_services3_container.ProcessContainer import ProcessContainer
from pip_services3_rpc.build import DefaultRpcFactory
class HelloFriendProcess(ProcessContainer):
def __init__(self):
super(HelloFriendProcess, self).__init__('hello-friend', 'HelloFriend microservice')
self._config_path = './config.yaml'
self._factories.add(HelloFriendServiceFactory())
self._factories.add(DefaultRpcFactory())
Running the app
Our final step is to execute the app via the container’s run() command. The following example shows how to do this.
export async function main() {
try {
let proc = new HelloFriendProcess();
proc.run(process.argv);
} catch (ex) {
console.error(ex);
}
}
class Program
{
static void Main(string[] args)
{
try
{
var task = (new HelloFriendProcess()).RunAsync(args);
task.Wait();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
}
}
}
func main() {
proc := NewHelloFriendProcess()
proc.Run(context.Background(), os.Args)
}
if __name__ == '__main__':
runner = HelloFriendProcess()
print("run")
try:
runner.run()
except Exception as ex:
print(ex)
Result
Once our app is running, we can see the results by calling the previously defined link. In our example, the URL is:
http://localhost:8080/hello_friend/greeting
And, if everything went right, we will see something similar to:
Complete code
Below, we can see the complete code of our example.
Code Example
import {
ConfigParams, Descriptor, FilterParams,
IConfigurable, IReferenceable, IReferences,
IStringIdentifiable
} from "pip-services3-commons-nodex";
import { Factory } from "pip-services3-components-nodex";
import { ProcessContainer } from "pip-services3-container-nodex";
import { IdentifiableMySqlPersistence } from "pip-services3-mysql-nodex";
import { DefaultRpcFactory, RestService } from "pip-services3-rpc-nodex";
// Data object
export class MyFriend implements IStringIdentifiable {
public id: string;
public type: string;
public name: string;
}
// Tier 1: View
export class HelloFriendRestService extends RestService {
protected controller: HelloFriendController;
public constructor() {
super()
this._baseRoute = "/hello_friend";
}
public configure(config: ConfigParams): void {
super.configure(config);
}
public setReferences(references: IReferences): void {
super.setReferences(references);
this.controller = references.getOneRequired(new Descriptor("hello-friend", "controller", "*", "*", "1.0"));
}
private async greeting(req: any, res: any): Promise<void> {
let result = await this.controller.greeting();
await this.sendResult(req, res, result);
}
private async create(req: any, res: any): Promise<void> {
let correlationId = this.getCorrelationId(req);
let friend: MyFriend = req.query;
let result = await this.controller.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);
}
}
// Tier 2 : Controller
export class HelloFriendController implements IConfigurable, IReferenceable {
private defaultName: string;
private persistence: HelloFriendPersistence;
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(correlationId: string, item: MyFriend): Promise<MyFriend> {
let res = await this.persistence.create(correlationId, item);
return res;
}
}
// Tier 3 = Persistence
export class HelloFriendPersistence extends IdentifiableMySqlPersistence<MyFriend, string> {
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(correlationId: string, filter: any): Promise<MyFriend> {
return super.getOneRandom(correlationId, this.composeFilter(filter));
}
}
// Inversion of control: Factory
export class HelloFriendServiceFactory extends Factory {
public constructor() {
super();
let HttpServiceDescriptor = new Descriptor("hello-friend", "service", "http", "*", "1.0"); // View
let ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller
let PersistenceDescriptor = new Descriptor("hello-friend", "persistence", "mysql", "*", "1.0"); // Persistence
this.registerAsType(HttpServiceDescriptor, HelloFriendRestService); // View
this.registerAsType(ControllerDescriptor, HelloFriendController); // Controller
this.registerAsType(PersistenceDescriptor, HelloFriendPersistence); // Persistence
}
}
// Containerization
export class HelloFriendProcess extends ProcessContainer {
public constructor() {
super("hello-friend", "HelloFriend microservice");
this._configPath = "./config.yaml"
this._factories.add(new HelloFriendServiceFactory());
this._factories.add(new DefaultRpcFactory());
}
}
// Running the app
export async function main() {
try {
let proc = new HelloFriendProcess();
proc.run(process.argv);
} catch (ex) {
console.error(ex);
}
}
Code Example
using System;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using PipServices3.Commons.Config;
using PipServices3.Commons.Refer;
using PipServices3.Commons.Data;
using PipServices3.Rpc.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using PipServices3.MySql.Persistence;
using PipServices3.Components.Build;
using PipServices3.Container;
using PipServices3.Rpc.Build;
// Data object
[DataContract]
public class MyFriend : IStringIdentifiable
{
[DataMember(Name = "id")]
public string Id { get; set; }
[DataMember(Name = "type")]
public string Type { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
}
// Tier 1: View
public class HelloFriendRestService: RestService
{
protected HelloFriendController controller;
public HelloFriendRestService() : base()
{
_baseRoute = "/hello_friend";
}
public override void Configure(ConfigParams config)
{
base.Configure(config);
}
public override void SetReferences(IReferences references)
{
base.SetReferences(references);
controller = references.GetOneRequired<HelloFriendController>(new Descriptor("hello-friend", "controller", "*", "*", "1.0"));
}
private async Task Greeting(HttpRequest req, HttpResponse res, RouteData routeData)
{
var result = await controller.Greeting();
await SendResultAsync(res, result);
}
private async Task CreateAsync(HttpRequest req, HttpResponse res, RouteData routeData)
{
var correlationId = this.GetCorrelationId(req);
var friend = new MyFriend() { Id = req.Query["id"], Type = req.Query["type"], Name = req.Query["Name"] };
var result = await controller.CreateAsync(correlationId, friend);
await SendResultAsync(res, result);
}
public override void Register()
{
RegisterRoute(method: "GET", route: "/greeting", action: Greeting);
RegisterRoute(method: "GET", route: "/greeting_create", action: CreateAsync);
}
}
// Tier 2 : Controller
public class HelloFriendController : IConfigurable, IReferenceable
{
private string defaultName;
private HelloFriendPersistence persistence;
public HelloFriendController()
{
defaultName = "Pip User";
}
public void Configure(ConfigParams config)
{
defaultName = config.GetAsStringWithDefault("default_name", defaultName);
}
public void SetReferences(IReferences references)
{
persistence = references.GetOneRequired<HelloFriendPersistence>(new Descriptor("hello-friend", "persistence", "*", "*", "1.0"));
}
public async Task<string> Greeting()
{
var filter = FilterParams.FromTuples("type", "friend");
var selectedFilter = await persistence.GetOneRandomAsync(null, filter);
var name = selectedFilter!= null ? selectedFilter.Name : null;
return $"Hello, {name} !";
}
public async Task<MyFriend> CreateAsync(string correlationId, MyFriend item)
{
var res = await persistence.CreateAsync(correlationId, item);
return res;
}
}
// Tier 3 = Persistence
public class HelloFriendPersistence: IdentifiableMySqlPersistence<MyFriend, string>
{
public HelloFriendPersistence() :base("myfriends3") { }
protected override void DefineSchema()
{
ClearSchema();
EnsureSchema($"CREATE TABLE IF NOT EXISTS {_tableName} (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)");
}
private static string ComposeFilter(FilterParams filter)
{
filter ??= new FilterParams();
var type = filter.GetAsNullableString("type");
var name = filter.GetAsNullableString("name");
var filterCondition = "";
if (type != null)
filterCondition += "`type`='" + type + "'";
if (name != null)
filterCondition += "`name`='" + name + "'";
return filterCondition;
}
public Task<MyFriend> GetOneRandomAsync(string correlationId, FilterParams filter)
{
return base.GetOneRandomAsync(correlationId, ComposeFilter(filter));
}
}
// Inversion of control: Factory
public class HelloFriendServiceFactory: Factory
{
public HelloFriendServiceFactory(): base()
{
var HttpServiceDescriptor = new Descriptor("hello-friend", "service", "http", "*", "1.0"); // View
var ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller
var PersistenceDescriptor = new Descriptor("hello-friend", "persistence", "mysql", "*", "1.0"); // Persistence
RegisterAsType(HttpServiceDescriptor, typeof(HelloFriendRestService)); // View
RegisterAsType(ControllerDescriptor, typeof(HelloFriendController)); // Controller
RegisterAsType(PersistenceDescriptor, typeof(HelloFriendPersistence)); // Persistence
}
}
// Containerization
public class HelloFriendProcess: ProcessContainer
{
public HelloFriendProcess(): base("hello-friend", "HelloFriend microservice")
{
_configPath = "../../../config.yaml";
_factories.Add(new HelloFriendServiceFactory());
_factories.Add(new DefaultRpcFactory());
}
}
// Running the app
class Program
{
static void Main(string[] args)
{
try
{
var task = (new HelloFriendProcess()).RunAsync(args);
task.Wait();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
}
}
}
Code Example
package main
import (
"context"
"net/http"
"os"
cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
crun "github.com/pip-services3-gox/pip-services3-container-gox/container"
mysqlpersist "github.com/pip-services3-gox/pip-services3-mysql-gox/persistence"
rpcbuild "github.com/pip-services3-gox/pip-services3-rpc-gox/build"
cservices "github.com/pip-services3-gox/pip-services3-rpc-gox/services"
)
// Data object
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,
}
}
// Tier 1: View
type HelloFriendRestService struct {
*cservices.RestService
controller *HelloFriendController
}
func NewHelloFriendRestService() *HelloFriendRestService {
c := &HelloFriendRestService{}
c.RestService = cservices.InheritRestService(c)
c.BaseRoute = "/hello_friend"
return c
}
func (c *HelloFriendRestService) Configure(ctx context.Context, config *cconf.ConfigParams) {
c.RestService.Configure(ctx, config)
}
func (c *HelloFriendRestService) SetReferences(ctx context.Context, references cref.IReferences) {
c.RestService.SetReferences(ctx, references)
res, err := references.GetOneRequired(cref.NewDescriptor("hello-friend", "controller", "*", "*", "1.0"))
if err != nil {
panic(err)
}
c.controller = res.(*HelloFriendController)
}
func (c *HelloFriendRestService) Greeting(res http.ResponseWriter, req *http.Request) {
result, err := c.controller.Greeting(req.Context())
c.SendResult(res, req, result, err)
}
func (c *HelloFriendRestService) 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 *HelloFriendRestService) Register() {
c.RegisterRoute("GET", "/greeting", nil, c.Greeting)
c.RegisterRoute("GET", "/greeting_create", nil, c.Create)
}
// Tier 2 : Controller
type HelloFriendController struct {
defaultName string
persistence *HelloFriendPersistence
}
func NewHelloFriendController() *HelloFriendController {
c := &HelloFriendController{}
c.defaultName = "Pip User"
return c
}
func (c *HelloFriendController) Configure(ctx context.Context, config *cconf.ConfigParams) {
c.defaultName = config.GetAsStringWithDefault("default_name", c.defaultName)
}
func (c *HelloFriendController) SetReferences(ctx context.Context, references cref.IReferences) {
res, err := references.GetOneRequired(cref.NewDescriptor("hello-friend", "persistence", "*", "*", "1.0"))
if err != nil {
panic(err)
}
c.persistence = res.(*HelloFriendPersistence)
}
func (c *HelloFriendController) Greeting(ctx context.Context) (string, error) {
filter := cdata.NewFilterParamsFromTuples("type", "friend")
selectedFilter, err := c.persistence.GetOneRandom(ctx, "123", *filter)
if err != nil {
return "", err
}
return "Hello, " + selectedFilter.Name + "!", nil
}
func (c *HelloFriendController) Create(ctx context.Context, correlationId string, item MyFriend) (MyFriend, error) {
return c.persistence.Create(ctx, correlationId, item)
}
// Tier 3 = Persistence
type HelloFriendPersistence struct {
*mysqlpersist.IdentifiableMySqlPersistence[MyFriend, string]
}
func NewHelloFriendPersistence() *HelloFriendPersistence {
c := &HelloFriendPersistence{}
c.IdentifiableMySqlPersistence = mysqlpersist.InheritIdentifiableMySqlPersistence[MyFriend, string](c, "myfriends3")
return c
}
func (c *HelloFriendPersistence) DefineSchema() {
c.ClearSchema()
c.EnsureSchema("CREATE TABLE `" + c.TableName + "` (id VARCHAR(32) PRIMARY KEY, `type` VARCHAR(50), `name` TEXT)")
}
func (c *HelloFriendPersistence) composeFilter(filter cdata.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 *HelloFriendPersistence) GetOneRandom(ctx context.Context, correlationId string, filter cdata.FilterParams) (item MyFriend, err error) {
return c.MySqlPersistence.GetOneRandom(ctx, correlationId, c.composeFilter(filter))
}
// Inversion of control: Factory
var HttpServiceDescriptor = cref.NewDescriptor("hello-friend", "service", "http", "*", "1.0") // View
var ControllerDescriptor = cref.NewDescriptor("hello-friend", "controller", "default", "*", "1.0") // Controller
var PersistenceDescriptor = cref.NewDescriptor("hello-friend", "persistence", "mysql", "*", "1.0") // Persistence
type HelloFriendServiceFactory struct {
*cbuild.Factory
}
func NewHelloFriendServiceFactory() *HelloFriendServiceFactory {
c := &HelloFriendServiceFactory{}
c.Factory = cbuild.NewFactory()
c.RegisterType(HttpServiceDescriptor, NewHelloFriendRestService) // View
c.RegisterType(ControllerDescriptor, NewHelloFriendController) // Controller
c.RegisterType(PersistenceDescriptor, NewHelloFriendPersistence) // Persistence
return c
}
// Containerization
type HelloFriendProcess struct {
*crun.ProcessContainer
}
func NewHelloFriendProcess() *HelloFriendProcess {
c := &HelloFriendProcess{}
c.ProcessContainer = crun.NewProcessContainer("hello-friend", "HelloFriend microservice")
c.AddFactory(NewHelloFriendServiceFactory())
c.AddFactory(rpcbuild.NewDefaultRpcFactory())
return c
}
// Running the app
func main() {
proc := NewHelloFriendProcess()
proc.SetConfigPath("./config/config.yml")
proc.Run(context.Background(), os.Args)
}
Code Example
# Data object
from typing import Optional
import bottle
from pip_services3_commons.data import IStringIdentifiable, FilterParams
class MyFriend(IStringIdentifiable):
def __init__(self, id: str, type: str, name: str):
self.id = id
self.type = type
self.name = name
from pip_services3_commons.validate import Schema
from pip_services3_rpc.services import RestService
# Tier 1: View
class HelloFriendRestService(RestService):
def __init__(self):
super(HelloFriendRestService, self).__init__()
self._base_route = "/hello_friend"
self._controller: HelloFriendController = None
def configure(self, config):
super().configure(config)
def set_references(self, references):
super(HelloFriendRestService, 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._controller.greeting()
return self.send_result(result)
def create(self):
correlation_id = self._get_correlation_id()
item = MyFriend(
bottle.request.query["id"],
bottle.request.query["type"],
bottle.request.query["name"]
)
result = self._controller.create(correlation_id, item)
return self.send_result(result)
# Tier 2 : Controller
from pip_services3_commons.config import IConfigurable
from pip_services3_commons.refer import IReferences, IReferenceable
class HelloFriendController(IConfigurable, IReferenceable):
__defaultName = None
__persistence: 'HelloFriendPersistence' = 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, correlation_id: Optional[str], item: MyFriend) -> MyFriend:
res = self.__persistence.create(correlation_id, item)
return res
# Tier 3 = Persistence
from pip_services3_mysql.persistence import IdentifiableMySqlPersistence
class HelloFriendPersistence(IdentifiableMySqlPersistence):
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, correlation_id: str, filter: FilterParams) -> MyFriend:
return super().get_one_random(correlation_id, self._compose_filter(filter))
from pip_services3_commons.refer import Descriptor
from pip_services3_components.build import Factory
# Inversion of control: Factory
class HelloFriendServiceFactory(Factory):
def __init__(self):
super(HelloFriendServiceFactory, self).__init__()
HttpServiceDescriptor = Descriptor('hello-friend', 'service', 'http', '*', '1.0') # View
ControllerDescriptor = Descriptor('hello-friend', 'controller', 'default', '*', '1.0') # Controller
PersistenceDescriptor = Descriptor('hello-friend', 'persistence', 'mysql', '*', '1.0') # Persistence
self.register_as_type(HttpServiceDescriptor, HelloFriendRestService) # View
self.register_as_type(ControllerDescriptor, HelloFriendController) # Controller
self.register_as_type(PersistenceDescriptor, HelloFriendPersistence) # Persistence
# Containerization
from pip_services3_container.ProcessContainer import ProcessContainer
from pip_services3_rpc.build import DefaultRpcFactory
class HelloFriendProcess(ProcessContainer):
def __init__(self):
super(HelloFriendProcess, self).__init__('hello-friend', 'HelloFriend microservice')
self._config_path = './config.yaml'
self._factories.add(HelloFriendServiceFactory())
self._factories.add(DefaultRpcFactory())
# Running the app
if __name__ == '__main__':
runner = HelloFriendProcess()
print("run")
try:
runner.run()
except Exception as ex:
print(ex)
Wrapping up
In this tutorial, we have learned how to create a simple application based on a three-tier architecture. First, we saw how to create a view based on a REST service. Then, we understood how to create a controller that manages the connection between the view and the third layer, namely persistence. Next, we saw how to create a persistence layer that includes a MySQL database. Finally, we executed the application and saw the result on our browser.