How to build customizable multitenant web applications - PHPBNL11

Preview:

DESCRIPTION

 

Citation preview

How to build customizable multitenant web applications

Stephan Hochdörfer, bitExpert AG

"Building an application so customizable it's the last

application you'll ever need to build"

Harrie Verveer

About me

Stephan Hochdörfer, bitExpert AG

Department Manager Research Labs

enjoying PHP since 1999

S.Hochdoerfer@bitExpert.de

@shochdoerfer

Single Tenancy

Developer vs. Businessman

Single Tenancy – more customers

Single Tenancy – even more customers

Where will this lead to?

Maintenance nightmare!

Single Tenancy

Tenant 1

Application

Database

Hardware

Single Tenancy

Tenant 2

Application

Database

Hardware

Tenant 1

Application

Database

Hardware

Tenant 3

Application

Database

Hardware

Multi Tenancy

Tenant 2Tenant 1

Application

Database

Hardware

Tenant 3

What should be customizable?

What should be customizable?

Tenant 2Tenant 1

Application

Database

Hardware

Tenant 3

What should be customizable?

Tenant 2Tenant 1

Application

Database

Hardware

Tenant 3

How to skin an application?

Frontend | Branding

How to skin an application?

Application | Frontend

Remember:It`s a web application!

How to skin an application?

Application | Frontend

HTML

How to skin an application?

Application | Frontend

HTML + CSS

Application | Frontend

Application | Frontend

Application | Frontend

How to customize?

Application | Frontend

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title>My App</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="css/styles/myapp.css" /></head><body></body></html>

How to customize?

Application | Frontend

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title>My App</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="css/styles/<?php echo$tenant ?>.css" /></head><body></body></html>

How to customize?

Application | Frontend

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title>My App</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="css/styles/myapp.css" /> <link rel="stylesheet" type="text/css" href="css/styles/<?php echo $tenant ?>.css" /></head><body></body></html>

Feature driven CSS

Application | Frontend

Wait, there`s more...

Feature driven CSS

Application | Frontend

display: none

Application | Frontend

Next level...

Menubar generation

Application | Backend

<?php

if($user->hasEnabled(Module::ORDERMANAGEMENT)){ if($user->canAccess(OrderManagement::LIST_ORDERS)) {

$this->renderLink(OrderManagement::LIST_ORDERS); }

if($user->canAccess(OrderManagement::ADD_ORDER)) {

$this->renderLink(OrderManagement::ADD_ORDER); }

if($user->canAccess(OrderManagement::CANCEL_ORDER)) {

$this->renderLink(OrderManagement::CANCEL_ORDER); }}

Menubar generation

Application | Backend

<?php

if($tenant->hasModule(Module::ORDERMANAGEMENT){ if($user->hasEnabled(Module::ORDERMANAGEMENT)) {

if($user->canAccess(OrderManagement::LIST_ORDERS)){ $this->renderLink(OrderManagement::LIST_ORDERS);}

if($user->canAccess(OrderManagement::ADD_ORDER)){ $this->renderLink(OrderManagement::ADD_ORDER);}

}}

Menubar generation

Application | Backend

Modularize!

Menubar generation

Application | Backend

Module 2Module 1

Application core

Module 3

register at start up

Menubar generation

Application | Backend

Module 2Module 1

Application core

Module 3

register at start up

Configuration for tenant 1

Menubar generation

Application | Backend

Module 2Module 1

Application core

Module 3

register at start up

Configuration for tenant 2

Optimize workflows

Application | Backend

<?php

if('CC' == $paymentType){ // handle credit card payment}else if('COD' == $paymentType){ // handle cash on delivery payment}

Optimize workflows

Application | Backend

<?php

if('CC' == $paymentType){ // handle credit card payment for some tenants! if(in_array($tenant->getName(), array('tenant1', 'tenant2')) {

// insert logic here... }}else if('COD' == $paymentType){ // handle cash on delivery payment for some tenants! if(in_array($tenant->getName(), array('tenant3')) {

// insert logic here... }}

Optimize workflows

Application | Backend

Decouple functionality!

Optimize workflows

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType);

$payment->execute($order);

Optimize workflows

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType, $tenant);

$payment->execute($order);

Optimize workflows

Application | Backend

How to add custom logic?

Custom logic - Subclassing?

Application | Backend

AbstractPayment

CCPayment

CCPaymentTenant 1

CCPaymentTenant 2

Custom logic

Application | Backend

Any alternatives?

Custom logic

Application | Backend

Let`s add hooks...

Custom logic - Hooks

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType, $tenant);

if($this->paymentPreProcessor instanceof IPaymentPreProcessor) { $this->paymentPreProcessor->run($payment, $tenant, $order);}

$payment->execute($order);

if($this->paymentPostProcessor instanceof IPaymentPostProcessor) { $this->paymentPostProcessor->run($payment, $tenant, $order);}

Custom logic

Application | Backend

How to set the dependencies?

Custom logic

Application | Backend

Inject the dependencies!

Custom logic – Dependency Injection

Application | Backend

<?xml version="1.0" encoding="UTF-8" ?><beans xmlns="http://www.bitexpert.de/schema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.bitexpert.de/schema/

http://www.bitexpert.de/schema/bitFramework-beans.xsd">

<bean id="MyApp.Service.Order" class="MyApp\Service\Order.php"></bean>

<bean id="Tenant1.Service.Order" class="MyApp\Service\Order.php"><property name="paymentPreProcessor"

ref="Tentant1.Payment.PaymentValidation" /></bean>

<bean id="Tenant2.Service.Order" class="MyApp\Service\Order.php"><property name="paymentPreProcessor"

ref="Tentant2.Payment.StrictValidation" /><property name="paymentPostProcessor"

ref="Tentant2.Payment.PushOrderToSAP" /></bean>

</beans>

Custom logic

Application | Backend

Any improvements?

Custom logic

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType, $tenant);

if($this->paymentPreProcessor instanceof IPaymentPreProcessor) { $this->paymentPreProcessor->run($payment, $tenant, $order);}

$payment->execute($order);

if($this->paymentPostProcessor instanceof IPaymentPostProcessor) { $this->paymentPostProcessor->run($payment, $tenant, $order);}

Custom logic

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType, $tenant);

if($this->paymentPreProcessor instanceof IPaymentPreProcessor) { $this->paymentPreProcessor->run($payment, $tenant, $order);}

$payment->execute($order);

if($this->paymentPostProcessor instanceof IPaymentPostProcessor) { $this->paymentPostProcessor->run($payment, $tenant, $order);}

Custom logic

Application | Backend

Aspect-oriented programming

Custom logic – Aspects for the masses!

Application | Backend

/** * @aspect */class CustomPaymentProcessingAspect {

/** * @around MyApp\Service\Order->processPayment */public function customFilter(\AOP\JoinPointInterface $joinPoint) {

// @TODO: implement pre-processing logic// ...

$result = $joinPoint->getAdviceChain()->proceed($joinPoint);

// @TODO: implement post-processing logic// ...

return $result;}

}

Custom logic - Result

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType, $tenant);

$payment->execute($order);

Application | Backend

Next level...

Database – Where to store the data?

Database

Database – Where to store the data?

Database

We need to store data for a tenant!

Database – Where to store the data?

Database

Database per Tenant?

Database – Where to store the data?

Database

Database per Tenant?

Schema per Tenant?

Database – Where to store the data?

Database

Database per Tenant?

Schema per Tenant?

Tenant Id per Row?

Database – How to access the data?

Database

ORM dynamic statements

vs.

What`s beyond?

Generalize you should!

Multi Tenancy

What`s beyond?

Software system family

No single solution!

What`s beyond?

A factory for mass production!

What`s beyond?

Multi Tenancy – Single Instance

Tenant 2Tenant 1

Application

Database

Hardware

Tenant 3

What`s beyond?

Multi Tenancy – Multi Instance

Tenant 2Tenant 1

Application

Database

Hardware

Tenant 3

What`s beyond?

Generative Programming

ConfigurationConfiguration

Implementationcomponents

Implementationcomponents

Generatorapplication

Generatorapplication

ProductProductGenerator

Generator

1 ... n

What`s beyond?

Generative Programming

ConfigurationConfiguration

Implementationcomponents

Implementationcomponents

Generatorapplication

Generatorapplication

Tenant 1Tenant 1

GeneratorGenerator

Tenant xTenant x

What`s beyond?

Generative Programming - Goal

What`s beyond?

Create an optimized application!

Generative Programming - Goal

What`s beyond?

Create an optimized applicationfor one tenant!

Generative Programming – Bonus points

What`s beyond?

Reduce application complexity

Generative Programming – Bonus points

What`s beyond?

FileFrm FILEOrderService_php5 { private String PreProcessor = ""; private String PostProcessor = "";

public FILEOrderService_php5() {setFilename("Order.php5");setRelativePath("/classes/MyApp/Service");

}

private void assign() {BEGINCONTENT()<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType, $tenant);

<!{PreProcessor}!>$payment->execute($order);<!{PostProcessor}!>ENDCONTENT() }}

Generative Programming – Bonus points

What`s beyond?

FileFrm FILEOrderService_php5 { [...]

private void configure() {if(this.getConfiguration().hasFeature('PreProcessor')) { PreProcessor = this.getPreProcessorContent(

this.getConfiguration.getTenant() );}

if(this.getConfiguration().hasFeature('PostProcessor')) { PostProcessor = this.getPostProcessorContent(

this.getConfiguration.getTenant() );}

}}

Generative Programming – Bonus points

Application | Backend

Example:Preprocessor:Postprocessor:

Output:

Generative Programming – Bonus points

Application | Backend

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType);

$payment->execute($order);

Example:Preprocessor:Postprocessor:

Output:

Generative Programming – Bonus points

Application | Backend

Example:Preprocessor: $this->paymentPreProcessor->run($payment, $tenant, $order);

Postprocessor:

Output:

Generative Programming – Bonus points

Application | Backend

Example:Preprocessor: $this->paymentPreProcessor->run($payment, $tenant, $order);

Postprocessor:

Output:

<?php

$paymentType = 'CC'; // set via request$payment = PaymentFactory::create($paymentType);

$this->paymentPreProcessor->run($payment, $tenant, $order);

$payment->execute($order);

Generative Programming – Bonus points

What`s beyond?

Reduce maintenance support

Generative Programming – Bonus points

What`s beyond?

FeatureImplementation

component

Generative Programming – Bonus points

What`s beyond?

Feature

Implementationcomponent

Generative Programming – Bonus points

What`s beyond?

Feature

Implementationcomponent

Customer

Generative Programming – The book

What`s beyond?

http://joind.in/2497

Flickr Creditshttp://www.flickr.com/photos/andresrueda/3452940751/

http://www.flickr.com/photos/andresrueda/3455410635/

Recommended