symfony http kernel for refactoring legacy apps: the ez publish case study - pug milano 2013

40
Case study: eZ Publish Symfony2 http kernel for legacy apps Gaetano Giunta | PUG Milano | Maggio 2013

Upload: gaetano-giunta

Post on 06-May-2015

3.264 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Case study: eZ Publish

Symfony2 http kernel for legacy apps

Gaetano Giunta | PUG Milano | Maggio 2013

Page 2: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

The drivers

Page 3: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Existing codebase is 10 years old

High maintenance cost

Started with no unit tests

Layers and roles not properly defined / documented

OOP before php had

Private/protected/static

Closures

Namespaces

Late static binding

And much more

Not built for an Ajax and REST world

5/18/2013 [email protected] Slide 3

Why change? Everyone loves NEW!

Page 4: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Existing codebase is 10 years old

Widely deployed

Well debugged

Pitfalls have probably been uncovered by now

Proven to scale

Well known:

Documentation improved over years

Tutorials, forums, blogs, aggregators

Active community of practitioners

Official training courses

5/18/2013 [email protected] Slide 4

Why change? Do not forget drawbacks

Page 5: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Focus on our core business Experience Management

Content Management

NOT Framework maintenance

Durable Architecture

API stability

Battle tested / not (only) the latest trend

Scalability

Lively Community!

5/18/2013 [email protected] Slide 5

Picking a framework for a platform rebuild

Page 6: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

• Simple Integration with existing API

• HMVC (Hierarchical Model View Controller) stack

• Decoupled Components

• Dependency Injection

• Good Template Engine

• Extensible, Open, Reliable ;-)

5/18/2013 [email protected] Slide 6

Prerequisites

Page 7: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

• Home brew

• Zeta Components

• Zend Framework 2

• Symfony 2 (Full Stack!)

5/18/2013 [email protected] Slide 7

Candidates

Page 8: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

5/18/2013 [email protected] Slide 8

And the winner is… Title of presentation is a hint, really...

Page 9: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

The challenge

Page 10: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Product Management SCRUM Story:

«As an existing user, I don’t want to be pissed off by a new #@!$% version!»

5/18/2013 [email protected] Slide 10

Backwards compatibility (life sucks)

Page 11: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Product Management SCRUM Story:

«As an existing user, I don’t want to be pissed off by a new #@!$% version!»

• 100% Data Compatible (same DB scheme)

• Possibility to include legacy templates in the new ones

• Routing fallback

• Load legacy content templates with legacy rules

• Settings

• Access Symfony services from legacy modules

5/18/2013 [email protected] Slide 11

Backwards compatibility: the objectives

Page 12: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Product Management SCRUM Story:

«As an existing user, I don’t want to be pissed off by a new #@!$% version!»

• 100% Data Compatible (same DB scheme)

• Possibility to include legacy templates in the new ones

• Routing fallback

• Load legacy content templates with legacy rules

• Settings

• Access Symfony services from legacy modules

5/18/2013 [email protected] Slide 12

Backwards compatibility: the objectives

Page 13: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

A new architecture

Page 14: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Product Management SCRUM Story:

«As an existing user, I don’t want to be pissed off by a new #@!$% version!»

• 100% Data Compatible (same DB scheme)

• Possibility to include legacy templates in the new ones

• Routing fallback

• Load legacy content templates with legacy rules

• Settings

• Access Symfony services from legacy modules

Challenge Accepted

5/18/2013 [email protected] Slide 14

BC: the challenge

Page 15: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

5/18/2013 [email protected] Slide 15

Dual-core architecture

Page 16: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Legacy version still works perfectly standalone

5/18/2013 [email protected] Slide 16

BC: icing on the cake

Page 17: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Isn’t this what you have been waiting for?

The HTTP Kernel

Page 18: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Request => process() => Response

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$input = $request->get('name', 'World'); // allows a default value

$response = new Response('Hello ' . htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));

$response->send(); // takes care of http headers

The HTTPFoundation Component eases mundane tasks

5/18/2013 [email protected] Slide 18

Use the HTTP, Luke A very, very simple frontend controller

Page 19: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

The HTTPKernel component “formalizes the process of starting with a request

and creating the appropriate response”

interface HttpKernelInterface

{

const MASTER_REQUEST = 1;

const SUB_REQUEST = 2;

public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);

}

Returns a Response instance

The «H» in HMVC: subrequests are baked in from the beginning

Looks simple so far, isn’t it?

5/18/2013 [email protected] Slide 19

The heart of the application

Page 20: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

The HttpKernel defines a complex flexible workflow

The controller to execute is found via a ControllerResolver

«Framework» work is done via an event system / event listeners

5/18/2013 [email protected] Slide 20

Adding the magic

Page 21: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

$request = Request::createFromGlobals();

$dispatcher = new EventDispatcher();

// ... add some event listeners, eg: routing, security checking

// create the controller resolver

$resolver = new MyControllerResolver();

// instantiate the kernel

$kernel = new HttpKernel( $dispatcher, $resolver );

$response = $kernel->handle( $request );

$response->send();

$kernel->terminate( $request, $response );

5/18/2013 [email protected] Slide 21

Building a frontend controller

Page 22: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Any class implementing the ControllerResolverInterface can be used

interface ControllerResolverInterface

{

// must return a callable

public function getController(Request $request);

// returns an array of arguments for the controller

public function getArguments(Request $request, $controller);

}

...

5/18/2013 [email protected] Slide 22

Finding the Controller

Page 23: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

The Event Dispatcher Component can be used

use Symfony\Component\EventDispatcher\Event;

$dispatcher->addListener('foo.action', $callable);

Depending on returned value, workflow might be altered (see docs online)

Dispatched events:

Name Name as constant Argument passed to the listener

kernel.request KernelEvents::REQUEST GetResponseEvent

kernel.controller KernelEvents::CONTROLLER FilterControllerEvent

kernel.view KernelEvents::VIEW GetResponseForControllerResultEvent

kernel.response KernelEvents::RESPONSE FilterResponseEvent

kernel.terminate KernelEvents::TERMINATE PostResponseEvent

kernel.exception KernelEvents::EXCEPTION GetResponseForExceptionEvent

5/18/2013 [email protected] Slide 23

Adding event listeners

Page 24: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Taming the beast

Page 25: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

New Core: a standard Simfony app («ezpublish» = «app»)

«Legacy Stack» isolated in a dedicated directory

5/18/2013 [email protected] Slide 25

Refactoring: directory layout

Page 26: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

New Core: Sf Bundles

5/18/2013 [email protected] Slide 26

Refactoring: bundles

Page 27: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

use Symfony\Component\HttpFoundation\Request;

require_once __DIR__ . '/../ezpublish/autoload.php'; // set up class autoloading

require_once __DIR__ . '/../ezpublish/EzPublishKernel.php';

$kernel = new EzPublishKernel( 'dev', true ); // extends the Sf Kernel class

$kernel->loadClassCache(); // a method from parent class

$request = Request::createFromGlobals();

$response = $kernel->handle( $request );

$response->send();

$kernel->terminate( $request, $response );

The Kernel class wraps the HTTPKernel

It adds a Service Container

It allows to register bundles via registerBundles()

5/18/2013 [email protected] Slide 27

The final frontend controller Using Symfony Full Stack

Page 28: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Sandbox legacy code in a closure

Index.php had to be refactored (from 1100 lines to 20)

Logic moved to a php class

Separated environment setup from execution and teardown

runCallback() sets up the global legacy environment

5/18/2013 [email protected] Slide 28

Refactoring: bridging Legacy code

Page 29: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Routing

Page 30: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

eZPublish 4 uses a custom MVC implementation

Frontend controller: index.php

Bootstraps configuration system, logging, “siteaccess”

Controllers are “plain php” files, properly declared

Url syntax: http:// site / module / controller / parameters

Parameters use a custom format instead of the query string

Virtual aliases can be added on top

For all content nodes, a nice alias is always generated by the system

Good for SEO

Technical debt

No DIC anywhere (registry pattern used)

No nested controllers

No provision for REST / AJAX

Implemented ad-hoc in many plugins (code/functionality duplication)

Policies are tied to controllers, not to the underlying content model

5/18/2013 [email protected] Slide 30

Routing

Page 31: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

5/18/2013 [email protected] Slide 31

Routing: seamless integration

Page 32: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

The ChainRouter from the Sf CMF project is used

Routes for new controllers can be declared in different ways

In a configuration file

app/config/routing.yml

Mybundle/Resources/config/routing.yml (loaded from main routing file)

Via annotations (phpdoc comments)

needs the SensioFrameworkExtraBundle bundle

Command line to dump them

php app/console router:debug

Maximum flexibility for parameters: required/optionsl, default values,

validation, restrict http method, extra support for locale and format, ...

5/18/2013 [email protected] Slide 32

Routing: how it works

Page 33: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Caching

Page 34: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

eZ Publish 4 has a complicated advanced caching system

For viewing content, cache is generated on access, invalidated on editing

TTL = infinite

When editing a content, cache is also invalidated for all related contents

Extra invalidation rules can be configured

Can be set up to be pregenerated at editing time (tradeoff: editing speed)

Cache keys include policies of current user, query string, custom session data

“Cache-blocks” can also be added anywhere in the templates

Expiry rules can be set on each block, TTL-based or content-editing based

Breaks mvc principle

Most powerful AND misunderstood feature in the CMS

5/18/2013 [email protected] Slide 34

eZ4 Caching: basics

Page 35: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

eZ has a built-in “full-page cache” (stores html on disk)

Currently deprecated, in favour of using a caching reverse Proxy

Performances same if not better

Delegate maintenance of part of the stack (Varnish, Squid)

Holy grail of caching: high TTL and support for PURGE command

1. When RP requests page from server, he gets a high TTL => cache page forever

2. When page changes, server tells to RP to purge that url from cache

Best reduction in number of requests to server while always showing fresh data

Downside: extremely hard to cache pages for connected users

ESI support as well

Hard to make efficient, as eZ can not regenerate an ESI block without full page

context

5/18/2013 [email protected] Slide 35

eZ4 Caching: integration with Reverse Proxies

Page 36: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

HTTP Expiration and Validation are used

By setting caching headers on response object

Integrates with a Gateway Cache (a.k.a Reverse Proxy)

Native (built-in, php)

$kernel = new Kernel('prod', false);

$kernel = new HTTPCache($kernel);

External (Varnish, Squid, ...)

Native support for ESI

Using {{ render_esi() }} in twig

5/18/2013 [email protected] Slide 36

Symfony Caching: basics

Page 37: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

REST

Page 38: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

eZ4 had an incomplete REST API

Only functionality available: reading content

Based on Zeta Components MVC component

A new API has been implemented

Full reading and writing of content is possible

All “dictionary” data is also available

Content-type for response can be JSON or XML (with an XSD!)

Fully restful

Usage of all HTTP verbs (and then some: PATCH)

Respect http headers of request (eg: “Accept”)

HATEOAS: use urls as resource ids

No separate request handling framework needed: pure Symfony routing

Bonus points: a client for the REST API, implements the same interfaces exposed by the local PHP API – network transparency!!!

5/18/2013 [email protected] Slide 40

REST API

Page 39: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

More info

Page 40: Symfony HTTP Kernel for refactoring legacy apps: the eZ Publish case study - Pug Milano 2013

Tutorials:

http://fabien.potencier.org/article/50/create-your-own-framework-on-top-of-the-

symfony2-components-part-1

http://symfony.com/doc/current/components/http_kernel/introduction.html

Sf2 book – jolly good looking docs:

http://symfony.com/doc/current/book/index.html

eZ Publish:

Community: http://share.ez.no

Source code: https://github.com/ezsystems

API docs: http://pubsvn.ez.no/preview.html

Contact me: @gggeek, [email protected]

5/18/2013 [email protected] Slide 42

The usual suspects