Applicazioni con architetture distribuite in Symfony2
10 ottobre 2014, Symfony Day, Milano
Chi sono?
Matteo Galli
Software Engineer @ Facile.it (Utilities)
...ma anche fotografo e viaggiatore
www.facile.it
matteo.galli@facile.it
twitter.com/thinkindie
instagram.com/thinkindie
Chi siamo?
Facile.it è un comparatore di tariffe
- assicurative
- energetiche (luce e gas)
- telefoniche (ADSL, fisso e mobile)
- mutui, conti bancari e prestiti
I nostri uffici sono a Milano, ma soprattutto...
WE ARE HIRING!
Cosa utilizziamo?
- Stack LAMP / LEMP
- Symfony / Zend
- ElasticSearch
- Node.js
- RabbitMQ
- Redis
- Ember.js / AngularJS / Backbone.js
- Vagrant / Ansible / Puppet
Siamo presenti anche su GitHub github.com/facile-it
Cosa vi raccontiamo oggi
In una parola...
Bunny
Bunny: i requisiti
- Separazione tra frontend e backend
- Multibusiness
- Ricerca globale
- ACL, anche nella ricerca
- Gestione di processi in background
Separazione tra frontend e backend
+
=
<3
Separazione tra frontend e backend
WebService RESTful basato sull'esistenza di risorse (ad es. le entità di Doctrine) di cui si ha accesso alle loro rappresentazioni tramite un identificatore globale (URI).
Separazione tra frontend e backend
GET /api/users/1 HTTP/1.1
Host: ws.bunny.facile.it
Accept: application/vnd.api+json
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
date: Thu, 19 Oct 2014 14:38:16 GMT
{
"id":1,
"timestamp":"2014-06-10T15:42:37+02:00",
"lastUpdateTimestamp":"2014-06-10T15:42:37+02:00",
"username":"BunnyWs",
"nome":"Bunny",
"cognome":"WS",
"email":"bunnyws@facile.it",
"isActive":true
}
Separazione tra frontend e backend
- FOSRestBundle
- FOSOAuthServerBundle
- FOSUserBundle
- JSONAPI (Ember.js Driven)
FOSRestBundle
Semplifica e velocizza lo sviluppo di un WebService RESTful
- gestione dei codici HTTP di risposta
- differenti view handlers per differenti valori di Accept
- URL REST-friendly
FOSRestBundle
/**
* @RouteResource("Compagnia")
*/
class CompagnieController extends BunnyController
{
/**
* @View()
*/
public function cgetAction() {}
/**
* @View()
*/
public function getAction(Request $request) {}
/**
* @View()
*/
public function postAction(Request $request) {}
/**
* @View()
*/
public function patchAction(Request $request) {}
/**
* @View()
*/
public function deleteAction(Request $request) {}
}
Multibusiness aka Bundlize the world
- Un bundle di base per il WS
- Un bundle per ogni business (ADSL, Luce&Gas, Mobile), differenti al più per un numero ristretto di entità o di comandi in background
- Un bundle per ciascuna altra attività cross-business (es.: invio di SMS)
Ricerca Globale or AMA
- Posso rapidamente ricercare un qualsiasi documento contenente un qualsiasi termine
- FOSElasticaBundle: le entità di Doctrine vengono persiste anche su ElasticSearch
SmartReindexing: la modifica di una entità richiede l'aggiornamento di tutti i documenti in cui è contenuta
$inheritanceTree = $this->getInheritanceTree($rootEntity);
$entities = $this->em->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
foreach ($entities as $entity) {
$class = $this->cmf->getMetadataFor($entity);
$associationMappings = $class->getAssociationMappings();
if (empty($associationMappings)) {
continue;
} else {
foreach ($associationMappings as $nestedEntity) {
if (in_array($nestedEntity['targetEntity'], $inheritanceTree)) {
...
}
}
}
}
DoctrineMetadata: ho già una mappa (ricorsiva) delle relazioni tra entità
ACL, anche nella ricerca
ACL = Access Control List
Permettono di definire regole (ACE) capillari per accedere a risorse.
Es.: permessi dei files su piattaforme Unix, regole del firewall.
ACL, anche nella ricerca
La gestione base delle ACL viene affidata al provider di default di Symfony e vengono persistite su MySQL
Le ACE vengono definite in ciascuna entità tramite custom annotations
namespace Facile\Ws\BunnyBundle\Entity;
use Facile\Ws\BunnyBundle\Annotation\EntityAce as ACE;
/**
* @ACE("ROLE", name="ROLE_ADMIN", mask="MASK_MASTER");
* @ACE("ROLE", name="ROLE_USER", mask="MASK_VIEW");
*/
class Compagnia
{
/**
* @ACE("ROLE", name="ROLE_USER", mask="MASK_VIEW")
* @ACE("ROLE", name="ROLE_ADMIN", mask="MASK_MASTER")
*/
protected $nome;
}
(Un comando si occupa poi di inizializzare tutte le entità)
ACL, anche nella ricerca
Utilizzare il provider di default di Symfony per filtrare una collezione di entità non è performante né soddisfacente.
(Non provatelo a casa)
ACL, anche nella ricerca
You know, for search!
Quando una entità viene salvata su ElasticSearch il documento viene diviso in due parti
- meta: contenente le ACL "espanse"
- data: l'entità vera e propria
{
"data" : {
"lastUpdateTimestamp" : "2014-10-08T09:28:45+02:00",
"id" : 5,
"timestamp" : "2014-06-10T15:42:33+02:00",
"venduto" : true,
"nome" : "Facile.it",
"type" : "energia"
},
"meta" : {
"acl" : [
{
"mask" : 1,
"name" : "role_user",
"type" : "role"
},
{
"name" : "role_admin",
"mask" : 64,
"type" : "role"
}
]
}
}
La ricerca di documenti viene eseguita con \Elastica\Query e filtrata con i ruoli ricoperti dall'utente attivo
$elasticaFilterOr = new \Elastica\Filter\BoolOr();
foreach ($this->getUserExpandedRoles() as $role) {
$elasticaFilterRolesName = new \Elastica\Filter\Term();
$elasticaFilterRolesName->setTerm('meta.acl.name', strtolower($role));
$elasticaFilterRolesType = new \Elastica\Filter\Term();
$elasticaFilterRolesType->setTerm('meta.acl.type', 'role');
$elasticaFilterAndRole = new \Elastica\Filter\BoolAnd();
$elasticaFilterAndRole->addFilter($elasticaFilterRolesName);
$elasticaFilterAndRole->addFilter($elasticaFilterRolesType);
$elasticaFilterOr->addFilter($elasticaFilterAndRole);
}
$elasticaQuery = new \Elastica\Query(new \Elastica\Query\MatchAll());
$elasticaQuery->setFilter($elasticaFilterOr);
Utilizzando i filtri (dalla documentazione di ES):
- non viene calcolato lo _score per ogni documento, ma viene applicata una logica booleana
- i risultati di molti filtri possono essere salvati in cache
- restringo la query sui documenti effettivamente ricercabili, ottimizzando la query stessa
Redis è un database di tipo chiave-valore, dove la chiave, come il valore, possono essere una qualsiasi sequenza di bytes.
http://redis.io
Redis è utilizzabile per:
- Caching avanzato
- Rankings in tempo reale
- Contatori
- Pub/Sub
- Code
Bunny utilizza Redis per la cache di
- Metadati di Doctrine
- Query (DQL → SQL)
- Risultati delle query
- Entità
doctrine:
orm:
entity_managers:
bunny:
metadata_cache_driver:
type: redis
host: localhost
port: 6380
query_cache_driver:
type: redis
host: localhost
port: 6380
result_cache_driver:
type: redis
host: localhost
port: 6380
Redis viene utilizzato anche per la gestione di code con Resque tramite php-resque.
Qualsiasi operazione che particolarmente complessa viene demandata quindi ad un processo asincrono o programmato.
Esempi di processi asincroni:
- SmartReindexing
- Generazione di Report
- Caricamento su account FTP esterni
- Caricamenti batch su gestionali di terzi
Domande?
Grazie
https://joind.in/talk/view/12215
Applicazioni Distribuite con Symfony2
By Matteo Galli
Applicazioni Distribuite con Symfony2
- 3,098