More Related Content Similar to How to build customizable multitenant web applications - PHPBNL11 Similar to How to build customizable multitenant web applications - PHPBNL11 (20) More from Stephan Hochdörfer More from Stephan Hochdörfer (20) How to build customizable multitenant web applications - PHPBNL111. 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
2. About me
Stephan Hochdörfer, bitExpert AG
Department Manager Research Labs
enjoying PHP since 1999
S.Hochdoerfer@bitExpert.de
@shochdoerfer
10. Single Tenancy
Tenant 1 Tenant 2 Tenant 3
Application Application Application
Database Database Database
Hardware Hardware Hardware
11. Multi Tenancy
Tenant 1 Tenant 2 Tenant 3
Application
Database
Hardware
13. What should be customizable?
Tenant 1 Tenant 2 Tenant 3
Application
Database
Hardware
14. What should be customizable?
Tenant 1 Tenant 2 Tenant 3
Application
Database
Hardware
22. Application | Frontend
How to customize?
<!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>
23. Application | Frontend
How to customize?
<!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>
24. Application | Frontend
How to customize?
<!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>
28. Application | Backend
Menubar generation
<?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);
}
}
29. Application | Backend
Menubar generation
<?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);
}
}
}
31. Application | Backend
Menubar generation
Module 1 Module 2 Module 3
register at start up
Application core
32. Application | Backend
Menubar generation
Configuration for tenant 1
Module 1 Module 2 Module 3
register at start up
Application core
33. Application | Backend
Menubar generation
Configuration for tenant 2
Module 1 Module 2 Module 3
register at start up
Application core
34. Application | Backend
Optimize workflows
<?php
if('CC' == $paymentType)
{
// handle credit card payment
}
else if('COD' == $paymentType)
{
// handle cash on delivery payment
}
35. Application | Backend
Optimize workflows
<?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...
}
}
37. Application | Backend
Optimize workflows
<?php
$paymentType = 'CC'; // set via request
$payment = PaymentFactory::create($paymentType);
$payment->execute($order);
38. Application | Backend
Optimize workflows
<?php
$paymentType = 'CC'; // set via request
$payment = PaymentFactory::create($paymentType, $tenant);
$payment->execute($order);
40. Application | Backend
Custom logic - Subclassing?
Abstract
Payment
CC
Payment
CCPayment CCPayment
Tenant 1 Tenant 2
43. Application | Backend
Custom logic - Hooks
<?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);
}
46. Application | Backend
Custom logic – Dependency Injection
<?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="MyAppServiceOrder.php">
</bean>
<bean id="Tenant1.Service.Order" class="MyAppServiceOrder.php">
<property name="paymentPreProcessor"
ref="Tentant1.Payment.PaymentValidation" />
</bean>
<bean id="Tenant2.Service.Order" class="MyAppServiceOrder.php">
<property name="paymentPreProcessor"
ref="Tentant2.Payment.StrictValidation" />
<property name="paymentPostProcessor"
ref="Tentant2.Payment.PushOrderToSAP" />
</bean>
</beans>
48. Application | Backend
Custom logic
<?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);
}
49. Application | Backend
Custom logic
<?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);
}
51. Application | Backend
Custom logic – Aspects for the masses!
/**
* @aspect
*/
class CustomPaymentProcessingAspect {
/**
* @around MyAppServiceOrder->processPayment
*/
public function customFilter(AOPJoinPointInterface $joinPoint) {
// @TODO: implement pre-processing logic
// ...
$result = $joinPoint->getAdviceChain()->proceed($joinPoint);
// @TODO: implement post-processing logic
// ...
return $result;
}
}
52. Application | Backend
Custom logic - Result
<?php
$paymentType = 'CC'; // set via request
$payment = PaymentFactory::create($paymentType, $tenant);
$payment->execute($order);
58. Database
Database – Where to store the data?
Database per Tenant?
Schema per Tenant?
Tenant Id per Row?
64. What`s beyond?
Multi Tenancy – Single Instance
Tenant 1 Tenant 2 Tenant 3
Application
Database
Hardware
65. What`s beyond?
Multi Tenancy – Multi Instance
Tenant 1 Tenant 2 Tenant 3
Application
Database
Hardware
66. What`s beyond?
Generative Programming
Configuration
Configuration
1 ... n
Implementation
Implementation Generator Product
components Generator Product
components
Generator
Generator
application
application
67. What`s beyond?
Generative Programming
Configuration
Configuration
Tenant 1
Tenant 1
Implementation
Implementation Generator
components Generator
components
Tenant x
Tenant x
Generator
Generator
application
application
71. What`s beyond?
Generative Programming – Bonus points
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()
}
}
72. What`s beyond?
Generative Programming – Bonus points
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()
);
}
}
}
73. Application | Backend
Generative Programming – Bonus points
Example:
Preprocessor:
Postprocessor:
Output:
74. Application | Backend
Generative Programming – Bonus points
Example:
Preprocessor:
Postprocessor:
Output:
<?php
$paymentType = 'CC'; // set via request
$payment = PaymentFactory::create($paymentType);
$payment->execute($order);
75. Application | Backend
Generative Programming – Bonus points
Example:
Preprocessor: $this->paymentPreProcessor->run($payment, $tenant, $order);
Postprocessor:
Output:
76. Application | Backend
Generative Programming – Bonus points
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);