Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

68
Moving a ZF1 Application to SF2 - Lessons learned Baldur Rensch

description

Hautelook is a large ecommerce application that is currently running a Zend Framework 1 backend. The next iteration of its API (used by desktop, mobile, as well as iPhone and Android native applications) is done with Symfony 2. This API is following the principles for hypermedia APIs. To that end, Hal+Json is the media-type we chose, and we implemented most of it using the FSC HateoasBundle. Another critical piece of Hal+Json APIs is documentation. To this end we have used NelmioApiDocBundle to automatically generate documentation for the API endpoints. The other critical piece of any application is performance for which we use XHProf with XHGui. In my talk I want to touch on all those aspects, show some of the lessons learned, how we solved some of the problems, and what is still unsolved.

Transcript of Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Page 1: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Moving a ZF1 Application to SF2 -

Lessons learned

Baldur Rensch

Page 2: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Agenda

About me

What is Hautelook

HAL

Before

Switching to Symfony

Lessons Learned

Page 3: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

About me

architecting at Hautelook

contributing on GitHub: @baldurrensch

opinionating on twitter: @brensch

Page 4: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

What is ?Member only shopping site

Private, limited-time sale events

daily email invitation at 8am

Page 5: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Some stats

Alexa traffic rank for US: 847

More than 12 million members (on average 20k new members per day)

Up to 200 orders per minute

Massive traffic spikes (remember, 8am)

[1]

Page 6: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

[1]

Some stats

Alexa traffic rank for US: 847

More than 12 million members (on average 20k new members per day)

Up to 200 orders per minute

Massive traffic spikes (remember, 8am) daily

Page 7: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Our Technologies

Page 8: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Our stack

API

Website Admin Mobile Clients (iOS / Android)

HAL+JSON HAL+JSON HAL+JSON

[13, 14]

Page 9: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

HAL

[3]

Page 10: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Zend for thee!

Page 11: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Page 12: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

Controller

Service

Model

Call the service

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

Page 13: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

Controller

Service

Model

Prepare for response

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

Page 14: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);} View

Controller

Service

Model

Page 15: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Input Validation

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

Page 16: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Call modelpublic function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

Page 17: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

View

Controller

Service

Model

Prepare for response

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

Page 18: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

<?php

class V4_Model_CartItems{ public function itemsForMember($member_id) { $db = Zend_Registry::get('db');

$q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...)EOT;

$result = $db->fetchAll($q, $member_id);

return $result; }}

View

Controller

Service

Model

Run some SQL

Page 19: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

<?php

class V4_Model_CartItems{ public function itemsForMember($member_id) { $db = Zend_Registry::get('db');

$q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...)EOT;

$result = $db->fetchAll($q, $member_id);

return $result; }}

protected function modifyData(Halo_Response $service_response){ $member_id = $this->member_id; $data = $service_response->getData();

$items = $data['items']; unset($data['items']);

$cart = new Hal_Resource("/v4/members/{$member_id}/cart", $data); $cart->setLink(new Hal_Link("/v4/members/{$member_id}/checkout", 'http://hautelook.com/rels/checkout'));

foreach ($items as $item) { $event_id = $item['sku']['event_id']; $color = $item['sku']['color']; $expires = new DateTime($item['expires_at']);

$cart_data = array( 'quantity' => (int) $item['quantity'], (...) );

$sku_data = array( 'event_id' => $event_id, (...) );

if (isset($item['style']['images'][strtolower($color)])) { $images = $item['style']['images'][strtolower($color)]['angles']; } $image_link = $images[0]['zoom'];

$style = new Hal_Resource("/v4/events/{$event_id}/styles/{$item['style_num']}", $style_data);

$r = new Hal_Resource("/v4/members/{$member_id}/cart/{$item['cart_id']}", $cart_data);

$style->setEmbedded('http://hautelook.com/rels/sku', $sku); $style->setLink(new Hal_Link($image_link, 'http://hautelook.com/rels/images/style/zoom')); $r->setEmbedded('http://hautelook.com/rels/styles', $style);

$cart->setEmbedded('http://hautelook.com/rels/cart-items', $r); }

$service_response->success($cart->toArray());}

Controller

Service

Model

Page 20: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

View

class V4_Controller_Cart extends Halo_Rest_ViewController{ public function get() { $service = new V4_Service_Cart;! !! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params);! !! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); }

$this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }

public function resource(array $data) { $this->checkMemberId($data['member_id']);

$input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data );

if (!$input->isValid()) { return $this->response(false, $input); }

$cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], ));

$items[$k]['style'] = $style->getData();

}

$result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, );

return $this->response(true, $result);}

<?php

class V4_Model_CartItems{ public function itemsForMember($member_id) { $db = Zend_Registry::get('db');

$q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...)EOT;

$result = $db->fetchAll($q, $member_id);

return $result; }}

protected function modifyData(Halo_Response $service_response){ $member_id = $this->member_id; $data = $service_response->getData();

$items = $data['items']; unset($data['items']);

$cart = new Hal_Resource("/v4/members/{$member_id}/cart", $data); $cart->setLink(new Hal_Link("/v4/members/{$member_id}/checkout", 'http://hautelook.com/rels/checkout'));

foreach ($items as $item) { $event_id = $item['sku']['event_id']; $color = $item['sku']['color']; $expires = new DateTime($item['expires_at']);

$cart_data = array( 'quantity' => (int) $item['quantity'], (...) );

$sku_data = array( 'event_id' => $event_id, (...) );

if (isset($item['style']['images'][strtolower($color)])) { $images = $item['style']['images'][strtolower($color)]['angles']; } $image_link = $images[0]['zoom'];

$style = new Hal_Resource("/v4/events/{$event_id}/styles/{$item['style_num']}", $style_data);

$r = new Hal_Resource("/v4/members/{$member_id}/cart/{$item['cart_id']}", $cart_data);

$style->setEmbedded('http://hautelook.com/rels/sku', $sku); $style->setLink(new Hal_Link($image_link, 'http://hautelook.com/rels/images/style/zoom')); $r->setEmbedded('http://hautelook.com/rels/styles', $style);

$cart->setEmbedded('http://hautelook.com/rels/cart-items', $r); }

$service_response->success($cart->toArray());}

Controller

Service

Model

Convert array results to HAL+Json,

yuck!

Page 21: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Issues

This is fine when you have 5 end points and simple responses.

Lots of boiler plate code

Zend Framework 1 did not scale very well. We constantly had to overwrite parts of the framework.

Page 22: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Moving from Imperative to Declarative Programming

[4,5]

Imperative Declarative

“In computer science, imperative programming is a programming paradigm that

describes computation in terms of statements that change a program state.”

“In computer science, declarative programming is a programming paradigm that

expresses the logic of a computation without

describing its control flow.”

Page 23: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

[4,5]

Imperative Declarative

“In computer science, imperative programming is a programming paradigm that

describes computation in terms of statements that change a program state.”

“In computer science, declarative programming is a programming paradigm that

expresses the logic of a computation without

describing its control flow.”

Moving from Imperative to Declarative Programming

Page 24: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Let’s use Symfony instead, shall we?

[2]

Page 25: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Advantages:

Symfony allows for way more declarative programming which allows us to write less code.

Allows us to extend way easier. And it’s actually fun.

Community is great.

Page 26: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Bundles we use

Friends of Symphony: RestBundle

Nelmio: ApiDocBundle, SolariumBundle

JMS: SerializerBundle

Football Social Club: HateoasBundle

Hautelook: GearmanBundle

Page 27: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/json'); $response->setETag(md5($response->getContent()));

return $response;}

Controller

Service

Model/View

Page 28: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Routing, Input Validation

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 29: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Documentation

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 30: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Call Service to get data

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 31: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Createresponse

/** * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */public function getCartAction($memberId){ $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member);

$response = $this->get('serializer')->serialize($cart, 'json');

$response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent()));

return $response;}

Page 32: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

public function getCart(Members $member){ $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId());

$cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray);

return $cart;}

Controller

Service

Model/View

Page 33: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/View

Get entities from database

public function getCart(Members $member){ $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId());

$cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray);

return $cart;}

Page 34: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Controller

Service

Model/ViewConvert to view

model

Get entities from database

public function getCart(Members $member){ $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId());

$cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray);

return $cart;}

Page 35: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Controller

Service

Model/View

Page 36: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Link

Controller

Service

Model/View

Page 37: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Embedded

Controller

Service

Model/View

Page 38: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy

There is a RFC for it: RFC-6570

[6, 7, 12]

/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

1 And are the magic that make Hateoas possible

1

Page 39: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

[6, 7, 12]1 And are the magic that make Hateoas possible

1

Page 40: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

$templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ));

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

[6, 7, 12]1 And are the magic that make Hateoas possible

1

Page 41: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

$templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ));

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

It even integrates with the HateoasBundle:

[6, 7, 12]1 And are the magic that make Hateoas possible

1

hautelook_style_image_resizable: pattern: /resizer/{width}x{height}/products/{styleNum}/{size}/{imageId}.jpg defaults: width: "{width}" height: "{height}"

Page 42: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

URI Templates are sexy/demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*}

/** * @Rest\Relation("http://hautelook.com/rels/image/resizable", * href = @Rest\Route("hautelook_style_image_resizable", * parameters = { "styleNum": ".solrDocument.styleNum", "imageId": ".firstImageId" }, * options = { "router": "templated" } * ), * excludeIf = { ".firstImageId": null }, * attributes = { "templated": true } * ) */

$templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ));

There is a RFC for it: RFC-6570

There is a bundle for it™: TemplatedURIBundle

It even integrates with the HateoasBundle:

[6, 7, 12]1 And are the magic that make Hateoas possible

1

Page 43: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Documentation with NelmioAPIDocBundle

Page 44: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Yeay, RESTful :)

Documentation with NelmioAPIDocBundle

Page 45: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

HTML form -makes testing

easy

Documentation with NelmioAPIDocBundle

Page 46: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Documentation with NelmioAPIDocBundle

Page 47: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

[8, 9, 10]

Page 48: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

use JMS\Serializer\Annotation as JMS;use FSC\HateoasBundle\Annotation as Rest;

/** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */class Cart

Why its difficult

[8, 9, 10]

Page 49: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

[8, 9, 10]

Page 50: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

[8, 9, 10]

Page 51: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

[8, 9, 10]

Page 52: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

[8, 9, 10]

Page 53: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

XHGui on top of XHProf

[8, 9, 10]

Page 54: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

XHGui on top of XHProf

Uses a shared database backend

[8, 9, 10]

Page 55: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Measuring performance with declarative programming

Why its difficult

XHProf to the rescue

Hierarchical function-level profiler

PHP Extension

Written by Facebook

XHGui on top of XHProf

Uses a shared database backend

There is a bundle for that™ as well![8, 9, 10]

Page 56: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned
Page 57: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Which functions are most

expensive?

Page 58: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

How often was a function called, how long did it

take?

Page 59: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

WTF?

Page 60: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

Page 61: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

Page 62: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Page 63: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Example: Session storage in multiple storage layers such as: Memcached and Database

Page 64: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Example: Session storage in multiple storage layers such as: Memcached and Database

There is a bundle for that™ now as well:

SessionStorageHandlerChainBundle

[11]

Page 65: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Need more documentation / community around enterprise level Symfony development

Page 66: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Lessons learnedLarge scale Symfony deployments are not that common

A lot of modules that larger applications need don’t exist

Need more documentation / community around enterprise level Symfony development

Our Developers love developing in Symfony

Page 67: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Questions?

Let’s get in touch for feedback, questions, discussion

Leave feedback at: https://joind.in/8676

Connect on Twitter or Github.

Page 68: Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Sources[1] http://www.alexa.com/siteinfo/hautelook.com[2] http://fc08.deviantart.net/fs50/f/2009/280/3/c/And_Then_There_Was_Light_by_GTwerks.jpg[3] http://stateless.co/hal_specification.html[4] https://en.wikipedia.org/wiki/Declarative_programming[5] https://en.wikipedia.org/wiki/Imperative_programming[6] https://tools.ietf.org/html/rfc6570[7] https://github.com/hautelook/TemplatedUriBundle[8] https://github.com/facebook/xhprof[9] https://github.com/preinheimer/xhprof[10] https://github.com/jonaswouters/XhprofBundle[11] https://github.com/hautelook/SessionStorageHandlerChainBundle[12] https://github.com/fxa/uritemplate-js[13] https://play.google.com/store/apps/details?id=com.hautelook.mcom&hl=en[14] https://itunes.apple.com/us/app/hautelook/id390783984?mt=8