De symfony 2013 dr. jenkins y mr. hyde - slides-842359017

61

Transcript of De symfony 2013 dr. jenkins y mr. hyde - slides-842359017

Page 1: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017
Page 2: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017
Page 3: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 4: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

ACTO I - Los personajes

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 5: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Mr. Hyde Bodaclick

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 6: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

• 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

Page 7: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 8: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Dr. Jenkins Core

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 9: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

• 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

Page 10: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 11: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

ACTO II - Envuelvelo en una API "REST"

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 12: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Entidades Sobrenaturales

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 13: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 14: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 15: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 16: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

desymfony 2013 Dr. Jenkins & Mr. Hyde

Un Jenkins feliz

Page 17: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

API Legacy .../api/[public|secured]/legacy/...

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 18: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 19: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 20: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

API Bridge .../api/[public|secured]/...

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 21: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 22: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

// 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

Page 23: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

ACTO III - Dos en uno

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 24: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Frontal login único

App. nueva

App. antigua

Perfil del usuario

OAuth

Login

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 25: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Frontal login único

App. nueva

App. antigua

Perfil del usuario

OAuth + Token WSSE (login)

Login

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 26: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 27: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 28: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Internet Reverse Proxy

Perfil del usuario - Reverse Proxy

bodaclick.com/^((?!my).)*$

bodaclick.com/my

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 29: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 30: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Perfil del usuario - Reverse Proxy

desymfony 2013 Dr. Jenkins & Mr. Hyde

Internet Reverse Proxy

bodaclick.com/^((?!my).)*$

bodaclick.com/my

iframe

Page 31: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

ACTO IV - Sangre, sudor y migraciones

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 32: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

• 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

Page 33: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 34: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Fuerza bruta Migraciones

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 35: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 36: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Divididas Migraciones

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 37: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

MigrateUserCommand RabbitMQ

Consumidor de Mapeos

Datos mapeados

Migración en dos pasos

desymfony 2013 Dr. Jenkins & Mr. Hyde

MySQL MySQL

App. nueva App. antigua

Page 38: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 39: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 40: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Perezosas Migraciones

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 41: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 42: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

...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

Page 43: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Perezosas,

asíncronas e

inversas Migraciones

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 44: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Evento asíncrono comunicado al driver

AsyncEventDispatcher

Controlador Evento propio

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 45: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 46: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Evento asíncrono comunicado al driver

Listener/ Publicador

o Driver

AsyncEventDispatcher

Controlador Evento propio

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 47: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 48: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 49: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 50: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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

Page 51: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 52: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Mixtas Migraciones

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 53: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

Migración perezosa mixta

MongoDb

MySQL

MySQL

Legacy

Internet

Internet

desymfony 2013 Dr. Jenkins & Mr. Hyde

Perfil del

usuario

App. antigua

App. nueva

Page 54: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

//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

Page 55: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

ACTO V - Routing

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 56: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

/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>

Page 57: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

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();

//...

Page 58: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

desymfony 2013 Dr. Jenkins & Mr. Hyde

Page 59: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

@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

Page 60: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

@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

Page 61: De symfony 2013   dr. jenkins y mr. hyde - slides-842359017

¿?

desymfony 2013 Dr. Jenkins & Mr. Hyde