Upload
eduardo-gulias
View
1.402
Download
0
Embed Size (px)
Citation preview
Dr. Jenkins y Mr. Hyde
Acto I - Los personajes
Acto II - Envuelvelo en una API "REST"
Acto III - Dos en uno
Acto IV - Sangre, sudor y migraciones
Acto V - URL's y Redis
desymfony 2013 Dr. Jenkins & Mr. Hyde
ACTO I - Los personajes
desymfony 2013 Dr. Jenkins & Mr. Hyde
Mr. Hyde Bodaclick
desymfony 2013 Dr. Jenkins & Mr. Hyde
• PHP 4 Spaghetti western
• Ausencia MVC
• Inicio del desarrollo en 2000
• Reescritura del 90% en 2007
• + de 25 desarrolladores
• 4 Bases de datos
• Más de 1.5M de líneas de código
• Formado por: Directorio, CRM+ERP, Lista de bodas,
Estadisticas, Extranet para clientes, CMS, Área de
contenidos, WebTV, Etc.
Mr. Hyde
desymfony 2013 Dr. Jenkins & Mr. Hyde
desymfony 2013 Dr. Jenkins & Mr. Hyde
Dr. Jenkins Core
desymfony 2013 Dr. Jenkins & Mr. Hyde
• REST
• PHP 5.4
• Symfony 2.1.x
• LAM
• SQLite
• Redis con Twemproxy
• MongoDB
• RabbitMQ
• Jenkins, PHPUnit & Capifony
• Satis
Dr. Jenkins
desymfony 2013 Dr. Jenkins & Mr. Hyde
Joel Spolsky (Stack Overflow co-founder) dijo:
(sobre Netscape)" Bueno, si. Lo hicieron. Lo hicieron al tomar la peor
decisión estratégica que una empresa de software puede hacer:
decidieron re-escribir el código desde 0"
fuente: http://www.joelonsoftware.com/articles/fog0000000069.html
Dan Milsten, fundador de Hub8, en un post en On Startups (publicado
por Dharmesh Shah, inversor de Stack Exchange):
"Prepárate para que este proyecto no termine jamás.Lo primero y
absolutamente crítico que tienes que entender sobre empezar una
reescritura es que va a tomar muchísimo más de lo que esperas. Incluso
después de que quitas el típico optimismo del desarrollador. He aquí
porqué: Migrar datos es lo peor que puedes echarte a la cara, más allá
de cualquier otra cosa.“ fuente: http://onstartups.com/tabid/3339/bid/97052/How-To-Survive-a-Ground-Up-Rewrite-Without-Losing-Your-Sanity.aspx
Re-escribir desde 0, según los expertos
desymfony 2013 Dr. Jenkins & Mr. Hyde
ACTO II - Envuelvelo en una API "REST"
desymfony 2013 Dr. Jenkins & Mr. Hyde
Entidades Sobrenaturales
desymfony 2013 Dr. Jenkins & Mr. Hyde
Las bases de datos
BBDD 1
BBDD 3 BBDD 4
BBDD 2
desymfony 2013 Dr. Jenkins & Mr. Hyde
BBDD 1
BBDD 3 BBDD 4
Relacionadas entre si por claves extranjeras mantenidas por software
//namespace BDK\LegacyDbBundle\Entity;
/**
* @ORM\Table(name="boda.CLIENTE")
* @ORM\Entity
*/
class Cliente
{
//...
/**
* @ORM\ManyToMany(targetEntity="Tags")
* @ORM\JoinTable(name="bodamoll.b_tags",
* joinColumns={@ORM\JoinColumn(name="id_cliente", referencedColumnName="ID")},
* inverseJoinColumns={@ORM\JoinColumn(name="id_tag", referencedColumnName="id")}
* )
*/
private $id_tag;
//...
Hackeando las DQL
desymfony 2013 Dr. Jenkins & Mr. Hyde
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
if ($this->kernel->getEnvironment() != 'test') {
return; }
$classMetadata = $eventArgs->getClassMetadata();
$assoMap = $classMetadata->getAssociationMappings();
foreach ($assoMap as $asso) {
if (isset($asso["joinTable"])) {
$asso["joinTable"]["name"] = str_replace(".", "_", $asso["joinTable"]["name"]);
$classMetadata->setAssociationOverride($asso["fieldName"],$asso);
}
}
$tableName = $classMetadata->getTableName();
$classMetadata->setPrimaryTable(array('name' => str_replace(".", "_", $tableName)));
}
Hackeando las DQL
desymfony 2013 Dr. Jenkins & Mr. Hyde
desymfony 2013 Dr. Jenkins & Mr. Hyde
Un Jenkins feliz
API Legacy .../api/[public|secured]/legacy/...
desymfony 2013 Dr. Jenkins & Mr. Hyde
//namespace BDK\LegacyBundle\Tests\Controller\LegacyController;
//PostPublicUserControllerTest
$user = [
'legacy' => [...],
'core' => [...],
];
$client->request('POST', "/api/public/legacy/novio{$oauthString}", $user, array());
API Legacy - Envío
desymfony 2013 Dr. Jenkins & Mr. Hyde
//namespace BDK\LegacyBundle\Controller;
// class UserPublicController
public function postNovioAction(Request $request)
{
$view = FOSView::create();
$viewData = $this->container
->get('bdk.legacy.rest_manager')
->postUser($request, ProfileType::USER);
return $view->setStatusCode($viewData['status'])
->setData($viewData['data']);
}
API Legacy - Recepción
desymfony 2013 Dr. Jenkins & Mr. Hyde
API Bridge .../api/[public|secured]/...
desymfony 2013 Dr. Jenkins & Mr. Hyde
//Legacy
$user = [
"legacy" => [...],
"core" => [...],
];
//Bridge
$user = ["name" = "", "surname"= "", .... ];
$client->request('POST', "/api/public/users{$this-> oauthString}&profile=user",
$user, array());
API Legacy - Envío
desymfony 2013 Dr. Jenkins & Mr. Hyde
// namespace BDK\LegacyBundle\Controller; // class BridgeUserPublicController public function postUsersAction(Request $request, $profile) { $em = $this->get('doctrine.orm.legacy_entity_manager');
//... $mapper = new CoreArrayToLegacyNovioArrayMapper($em); $coreArray = $request->request->all(); $params = [ 'core' => $coreArray, 'legacy' => $mapper->map($coreArray) ]; $request->request->replace($params); $view = FOSView::create(); $viewData = $this->container->get('bdk.legacy.rest_manager')
->postUser($request, ProfileType::USER); return $view->setStatusCode($viewData['status'])->setData($viewData['data']); }
API Legacy - Recepción
desymfony 2013 Dr. Jenkins & Mr. Hyde
ACTO III - Dos en uno
desymfony 2013 Dr. Jenkins & Mr. Hyde
Frontal login único
App. nueva
App. antigua
Perfil del usuario
OAuth
Login
desymfony 2013 Dr. Jenkins & Mr. Hyde
Frontal login único
App. nueva
App. antigua
Perfil del usuario
OAuth + Token WSSE (login)
Login
desymfony 2013 Dr. Jenkins & Mr. Hyde
Frontal login único
App. nueva
App. antigua
Perfil del usuario
Info del usuario
Acceso al perfil del usuario
OAuth + Token WSSE (login)
Login
desymfony 2013 Dr. Jenkins & Mr. Hyde
Perfil del
usuario
Enlaces a la plataforma antigua
Login
Frontal login único
App. nueva
App. antigua
Perfil del usuario
Info del usuario
OAuth + Token WSSE (login)
Acceso al perfil del usuario desymfony 2013 Dr. Jenkins & Mr. Hyde
Internet Reverse Proxy
Perfil del usuario - Reverse Proxy
bodaclick.com/^((?!my).)*$
bodaclick.com/my
desymfony 2013 Dr. Jenkins & Mr. Hyde
desymfony 2013 Dr. Jenkins & Mr. Hyde
Perfil del usuario - Reverse Proxy
desymfony 2013 Dr. Jenkins & Mr. Hyde
Internet Reverse Proxy
bodaclick.com/^((?!my).)*$
bodaclick.com/my
iframe
ACTO IV - Sangre, sudor y migraciones
desymfony 2013 Dr. Jenkins & Mr. Hyde
• 4 bases de datos
• Datos inconsistentes
• Emails repetidos
• Fechas como 0000-00-00
• Enums
• Tenemos tablas con más de 100 campos
• Campos por defecto a 0000-00-00
• Tablas tanto innodb como MyISAM
• Cotejamientos diferentes (utf8, latin)
• Tablas > 6 GB
API Legacy - Mapeo
desymfony 2013 Dr. Jenkins & Mr. Hyde
desymfony 2013 Dr. Jenkins & Mr. Hyde
Fuerza bruta Migraciones
desymfony 2013 Dr. Jenkins & Mr. Hyde
protected function migrateUser($override, $limit = 0) { //...
foreach ($oldUsers as $oldUser) { try { $userArray = $mapper->getUserCoreArray($oldUser); $userArray = $this->trimUserArray($userArray); $coreUser = $this->persistToCore($userArray, $oldUser, $override); $oldUser->setNewId($coreUser->getUser()->getId()); $this->legacyEm->persist($oldUser); $this->legacyEm->flush(); } catch (\Exception $e) { //... } }
}
Migración por comando único
desymfony 2013 Dr. Jenkins & Mr. Hyde
Divididas Migraciones
desymfony 2013 Dr. Jenkins & Mr. Hyde
MigrateUserCommand RabbitMQ
Consumidor de Mapeos
Datos mapeados
Migración en dos pasos
desymfony 2013 Dr. Jenkins & Mr. Hyde
MySQL MySQL
App. nueva App. antigua
protected function migrateUser($override, $limit = 0) { //...
foreach ($oldUsers as $oldUser) { try { $userArray = $mapper->getUserCoreArray($oldUser); //Usamos RabbitMQ $data['oldUser'] = $oldUser; $data['userArray'] = $userArray; $this->getContainer()->get('old_sound_rabbit_mq.migration_producer')
->publish($serializer($data)); } catch (\Exception $e) { //... } } //...
}
Migración por comando y consumidor
desymfony 2013 Dr. Jenkins & Mr. Hyde
protected function execute(AMQPMessage $migration) {
//...
try {
$coreUser = $this->persistToCore($userArray, $oldUser, $override);
$oldUser->setNewId($coreUser->getUser()->getId());
$this->legacyEm->persist($oldUser);
$this->legacyEm->flush();
} catch (\Exception $e) {
//...
}
//... }
Migración por comando y consumidor
desymfony 2013 Dr. Jenkins & Mr. Hyde
Perezosas Migraciones
desymfony 2013 Dr. Jenkins & Mr. Hyde
class SecurityController extends Controller
{
public function postTokenAction()
{
//...
$username = $request->get('_username');
$password = $request->get('_password');
$preLoginEvent = new PreUserLoginEvent($username,
$password);
$this->get('event_dispatcher')
->dispatch(UserEvents::PRE_LOGIN, $preLoginEvent);
//...
}
}
Migración por evento - Lanzamiento
desymfony 2013 Dr. Jenkins & Mr. Hyde
...Perezosas (Listener) //namespace BDK\LegacyBundle\EventListener;
//class UserLoginCreateUserProfileListener
public function onPreUserLogin(PreUserLoginEvent $event)
{
//...
$coreUser = $this->legacyUserManager->
createCoreUser(
$userArray,
ProfileType::USER,
['Create', 'Default']
);
//...
}
Migración por evento - Listener
desymfony 2013 Dr. Jenkins & Mr. Hyde
Perezosas,
asíncronas e
inversas Migraciones
desymfony 2013 Dr. Jenkins & Mr. Hyde
Evento asíncrono comunicado al driver
AsyncEventDispatcher
Controlador Evento propio
desymfony 2013 Dr. Jenkins & Mr. Hyde
//abstract class AsyncWeddingEvent implements AsyncEventInterface;
//class PostCreatWeddingEvent extends AsyncWeddingEvent;
$event = new PostCreateWeddingEvent();
$event->setWedding($wedding);
$event->setUserProfile($userProfile);
$this->container->get('bdk.async_event_dispatcher')->dispatch($event);
Evento asíncrono lanzado
desymfony 2013 Dr. Jenkins & Mr. Hyde
Evento asíncrono comunicado al driver
Listener/ Publicador
o Driver
AsyncEventDispatcher
Controlador Evento propio
desymfony 2013 Dr. Jenkins & Mr. Hyde
Evento asíncrono para un sistema pub/sub
Sistema Pub/Sub
Listener/ Publicador
o Driver
AsyncEventDispatcher
Controlador Evento propio
desymfony 2013 Dr. Jenkins & Mr. Hyde
//Resources/config/async_drivers.yml
services:
bdk.wedding.async_event_driver_create:
class: BDK\WeddingBundle\Model\EventDispatcher\AsyncDriver\RabbitMQDriver
arguments: [@old_sound_rabbit_mq.wedding_event_producer, @serializer]
calls:
- [setRoutingKey, ['create.wedding.event']]
tags:
- { name: bdk.async_event_dispatcher, event: bdk.async.post_create_wedding }
Configuración del driver
desymfony 2013 Dr. Jenkins & Mr. Hyde
Evento asíncrono para un sistema pub/sub
Sistema Pub/Sub
Subscriptor MySQL MySQL
Listener/ Publicador
o Driver
AsyncEventDispatcher
Controlador Evento propio
desymfony 2013 Dr. Jenkins & Mr. Hyde
App. nueva App. antigua
Evento asíncrono para un sistema pub/sub
Consumidor MySQL MySQL
Listener/ Productor o Driver
AsyncEventDispatcher
Controlador Evento propio
desymfony 2013 Dr. Jenkins & Mr. Hyde
*.wedding.event
RabbitMQ
Topic Exchange
App. nueva App. antigua
//BDK\LegacyBundle\EventListener\Async\WeddingEventListener
class WeddingEventListener implements ConsumerInterface
{
//...
if ($wedding->getProvince()) {
$legacyCountry = $this->legacyEm->getRepository('BDKLegacyDbBundle:Pais')
->findOneByCodPais($wedding->getProvince()->getCountry());
$legacyProvince = $this->legacyEm->getRepository('BDKLegacyDbBundle:Provincia')
->findOneBy(['idPais' => $legacyCountry->getId(), 'provincia' =>
$wedding->getProvince()->getName()]);
$legacyEventUser->setProvinciaId($legacyProvince->getId());
}
//...
}
Consumidor
desymfony 2013 Dr. Jenkins & Mr. Hyde
Mixtas Migraciones
desymfony 2013 Dr. Jenkins & Mr. Hyde
Migración perezosa mixta
MongoDb
MySQL
MySQL
Legacy
Internet
Internet
desymfony 2013 Dr. Jenkins & Mr. Hyde
Perfil del
usuario
App. antigua
App. nueva
//postLoad
//...
$service = $em->getClassMetadata('BDKWeddingBundle:Wedding')
->reflClass->getProperty(service);
$service>setAccessible(true);
$service>setValue(
$wedding,
$this->dm->getReference(
'BDKWeddingBundle:Service',
$wedding->getServiced()
)
);
Cargar datos desde MongoDB
desymfony 2013 Dr. Jenkins & Mr. Hyde
ACTO V - Routing
desymfony 2013 Dr. Jenkins & Mr. Hyde
/var/.../reportajes/45.php
//45.php
$ruta = "/desymfony-2013.html";
//...
Urls físicas
desymfony 2013 Dr. Jenkins & Mr. Hyde
bodaclick.com/desymfony-2013.html
//desymfony-2013.html
$idReportaje = 45;
cargaContenido();
//...
<a href=‘url(“reportaje=45”)’> Texto </a>
Urls via redis
SET www.bodaclick.com:reportaje:45
/desymfony-2013.html
desymfony 2013 Dr. Jenkins & Mr. Hyde
<a href=‘url(“reportaje=45”)’> Texto </a>
bodaclick.com/desymfony-2013.html
//desymfony-2013.html
$idReportaje = 45;
cargaContenido();
//...
desymfony 2013 Dr. Jenkins & Mr. Hyde
@etorras79
etorras
@BodaclickIT
Enrique Torras, como Mr. Hyde
• Ingeniero en Informática
• Desarrollando web desde 2004
• Actualmente dirige el área de
desarrollo en Bodaclick
desymfony 2013 Dr. Jenkins & Mr. Hyde
slideshare.net/etorras
@egulias
egulias
• Desarrollador web desde 2006
• Coqueteando con Symfony (y
otros frameworks) desde 2007
• Miembro de Symfony Madrid
• Actualmente trabajando como
líder de equipo en Bodaclick
@BodaclickIT
Eduardo Gulias, como Dr. Jenkins
slideshare.net/egulias
joind.in/talk/view/8834
desymfony 2013 Dr. Jenkins & Mr. Hyde
¿?
desymfony 2013 Dr. Jenkins & Mr. Hyde