• Like
  • Save
SOA with Symfony2 @ ConFoo 2014 in Montreal (CA)
Upcoming SlideShare
Loading in...5
×
 

SOA with Symfony2 @ ConFoo 2014 in Montreal (CA)

on

  • 5,872 views

Symfony2 is one of the de-facto standards for developing enterprise-ready applications in PHP: being a very structured & decoupled framework, it becomes very handy and suitable for building Service ...

Symfony2 is one of the de-facto standards for developing enterprise-ready applications in PHP: being a very structured & decoupled framework, it becomes very handy and suitable for building Service Oriented architectures, which require loose coupling and a clean and tested structure: we will see hot to create a Service Oriented Architecture in Symfony2, taking advantage of messaging systems like RabbitMQ, HTTP APIs and Sf2's internals.

Statistics

Views

Total Views
5,872
Views on SlideShare
4,312
Embed Views
1,560

Actions

Likes
15
Downloads
52
Comments
0

8 Embeds 1,560

http://odino.org 1468
https://twitter.com 37
http://know.iteonline.co.za 27
http://127.0.0.1 11
http://feedly.com 11
http://digg.com 3
http://www.slideee.com 2
http://www.linkedin.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    SOA with Symfony2 @ ConFoo 2014 in Montreal (CA) SOA with Symfony2 @ ConFoo 2014 in Montreal (CA) Presentation Transcript

    • SOA with Symfony2 Alessandro Nadalin - Montreal, February 2014
    • This talk is for those...
    • Stuck with the legacy
    • dealing with CRONs
    • in the need of a solid foundation
    • rely on web services
    • need a pluggable software architecture
    • who love @fabpot
    • SOA
    • 1. SO WHAT? (A)
    • A software design based on discrete software components, "services", that collectively provide the functionalities of the larger software application
    • You typically start with the infamous PHP app, hopefully built with Symfony2 which does everything on its own
    • Then you realize that to provide a chat system to your users PHP might not be the best...
    • And soon you also decide, to improve performances, that your frontend should have its own in-memory persistence, to be faster and you put it into another service
    • Then, as always...
    • SCALE.
    • And eventually, your lead architect will come up and tell you that your Java-based chat sucks and should be replaced with...
    • NODEJS
    • In human-understandable words, SOA is a software design which embraces splitting a monolithic, totalitarian software architecture into smaller pieces, thus making them independent, loosely coupled and more maintainable
    • Ok, but in the real world?
    • 2. Your (Symfony2) app is just a piece of the puzzle
    • How does communication (usually) happen?
    • WEBSERVICES
    • Services can request data to other services, usually through WSs
    • POX
    • https://github.com/kriswallsmith/buzz https://github.com/fabpot/Goutte https://github.com/guzzle/guzzle
    • SOAP
    • PHP and SOAP in 2014 http://www.whitewashing.de/2014/01/31/soap_and_php_in_2014.html
    • HTTP
    • REST
    • FosREST https://github.com/FriendsOfSymfony/FOSRestBundle
    • # app/config/routing.yml users: type: rest resource: MyBundleUsersController
    • GET /users
    • class UsersController { public function getUsersAction() { return $this->get(‘storage’)->getUsers(); } }
    • class UsersController { public function getUsersAction() { return $this->get(‘storage’)->getUsers(); } }
    • class UsersController { public function getUsersAction() { return $this->get(‘storage’)->getUsers(); } }
    • View?
    • SERIALIZE ALL THE THINGS!
    • JMSSerializer Bundle: https://github.com/schmittjoh/JMSSerializerBundle
    • /** * @ExclusionPolicy("all") */ class Customer implements UserInterface, EquatableInterface { /** * @Expose * @SerializedName("addresses") * @Groups({"Customer:depth:1"}) */ protected $addresses; /** * @Expose * @Groups({ * "Customer:list", * "Customer:detail", * }) */ private $email;
    • /** * @ExclusionPolicy("all") */ class Customer implements UserInterface, EquatableInterface { /** * @Expose * @SerializedName("addresses") * @Groups({"Customer:depth:1"}) */ protected $addresses; /** * @Expose * @Groups({ * "Customer:list", * "Customer:detail", * }) */ private $email;
    • /** * @ExclusionPolicy("all") */ class Customer implements UserInterface, EquatableInterface { /** * @Expose * @SerializedName("addresses") * @Groups({"Customer:depth:1"}) */ protected $addresses; /** * @Expose * @Groups({ * "Customer:list", * "Customer:detail", * }) */ private $email;
    • /** * @ExclusionPolicy("all") */ class Customer implements UserInterface, EquatableInterface { /** * @Expose * @SerializedName("addresses") * @Groups({"Customer:depth:1"}) */ protected $addresses; /** * @Expose * @Groups({ * "Customer:list", * "Customer:detail", * }) */ private $email;
    • /** * @ExclusionPolicy("all") */ class Customer implements UserInterface, EquatableInterface { /** * @Expose * @SerializedName("addresses") * @Groups({"Customer:depth:1"}) */ protected $addresses; /** * @Expose * @Groups({ * "Customer:list", * "Customer:detail", * }) */ private $email;
    • /** * @ExclusionPolicy("all") */ class Customer implements UserInterface, EquatableInterface { /** * @Expose * @SerializedName("addresses") * @Groups({"Customer:depth:1"}) */ protected $addresses; /** * @Expose * @Groups({ * "Customer:list", * "Customer:detail", * }) */ private $email;
    • EVENTS
    • services notify the architecture that an event has happened
    • asynchronous messaging queues
    • RabbitMQ https://github.com/videlalvaro/rabbitmqbundle
    • old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false producers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} consumers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} queue_options: {name: 'userreg'} callback: mailer
    • old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false producers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} consumers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} queue_options: {name: 'userreg'} callback: mailer
    • old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false producers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} consumers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} queue_options: {name: 'userreg'} callback: mailer
    • old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false producers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} consumers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} queue_options: {name: 'userreg'} callback: mailer
    • old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false producers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} consumers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} queue_options: {name: 'userreg'} callback: mailer
    • mailer: class: "MyMailer" arguments: [..., ...]
    • $this->get('old_sound_rabbit_mq.user_registration_producer') ->publish(serialize($msg));
    • php app/console rabbitmq:consumer user_registration
    • old_sound_rabbit_mq: connections: default: host: 'localhost' port: 5672 user: 'guest' password: 'guest' vhost: '/' lazy: false producers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} consumers: user_registration: connection: default exchange_options: {name: 'userreg', type: direct} queue_options: {name: 'userreg'} callback: mailer
    • class Mailer { … public function execute(AMQPMessage $message) { // do stuff } }
    • class Mailer { … public function execute(AMQPMessage $message) { // do stuff } }
    • 2. Free data
    • CONSIDER ELIMINATING FK CONSTRAINTS
    • A service might need to handle data with another DBMS, so FKs are virtually impossible
    • ABSTRACT THE DATA
    • You might think in "rows" but the architecture thinks in "resources"
    • $this->get(‘doctrine’) ->getRepository(‘My:Entity’) ->findActiveOnes()
    • $this->get(‘doctrine’) ->getRepository(‘My:Entity’) ->findActiveOnes()
    • $this->get(‘storage’) ->getRepository(‘My:Entity’) ->findActiveOnes()
    • class RedisStorage { public function getRepository($name) { $this->hash = $name; return $this; } public function findActiveOnes() { $results = $this->redis->hget($this->hash); return array_filter($results, function($r){ return $r[‘active’] == true; }); } }
    • class RedisStorage { public function getRepository($name) { $this->hash = $name; return $this; } public function findActiveOnes() { $results = $this->redis->hget($this->hash); return array_filter($results, function($r){ return $r[‘active’] == true; }); } }
    • class RedisStorage { public function getRepository($name) { $this->hash = $name; return $this; } public function findActiveOnes() { $results = $this->redis->hget($this->hash); return array_filter($results, function($r){ return $r[‘active’] == true; }); } }
    • class RedisStorage { public function getRepository($name) { $this->hash = $name; return $this; } public function findActiveOnes() { $results = $this->redis->hget($this->hash); return array_filter($results, function($r){ return $r[‘active’] == true; }); } }
    • class RedisStorage { public function getRepository($name) { $this->hash = $name; return $this; } public function findActiveOnes() { $results = $this->redis->hget($this->hash); return array_filter($results, function($r){ return $r[‘active’] == true; }); } }
    • class RedisStorage { public function getRepository($name) { $this->hash = $name; return $this; } public function findActiveOnes() { $results = $this->redis->hget($this->hash); return array_filter($results, function($r){ return $r[‘active’] == true; }); } }
    • REPOSITORIES NEED INTERFACES
    • ENTITIES NEED INTERFACES
    • forget managers, you need collections
    • implements StockStorageInterface
    • use StockStorageInterface as Storage; class RedisStorage implements Storage { ...
    • use StockStorageInterface as Storage; class RedisStorage implements Storage { ...
    • use StockStorageInterface as Storage; class StockStorage implements Storage { ...
    • collections as a service
    • stock: class: “MyNamespaceStockStorage” arguments: … … $this->get(‘stock’)->findActiveOnes();
    • stock: class: “MyNamespaceStockStorage” arguments: … … $this->get(‘stock’)->findActiveOnes();
    • stock: class: “MyNamespaceStockStorage” arguments: … … $this->get(‘stock’)->findActiveOnes();
    • No more FKs and the ability of JOINing to retrieve some related data
    • But you choose what perfectly fits each service: your transactions over a RDBMS and your community over a graph DB
    • So complicated!
    • Have fun returning serialized collections over HTTPS in ~50ms with Doctrine!
    • 3. Standardize
    • EVERY DEVELOPER NEEDS THE ENTIRE ARCHITECTURE ON HIS MACHINE
    • The architecture needs to be installed in ~1 hour
    • Setting up VMs is an hassle and they are so slow!
    • go #vagrant
    • But Vagrant is still suboptimal: provisioning and system resources are still a pain!
    • 4. Identity
    • Centralized authentication = identity service
    • OAuth
    • OpenID
    • JWS
    • JSON WEB SIGNATURE
    • JSON WEB TOKEN
    • JSON WEB SIGNATURE
    • JAVASCRIPT OBJECT SIGNING & ENCRYPTION
    • JOSE http://www.thread-safe.com/2012/03/json-object-signing-and-encryption-jose.html
    • 1. The user enters the credentials once in your frontend 2. The JS app will forward them to your Auth webservice JS APP AUTH SERVICE 3. The Auth webservice will then generate the encrypted JWS and set a cookie with its value JS APP 4. The JS app can now just execute calls using that cookie
    • 1. The user enters the credentials once in your frontend JS APP
    • 2. The JS app will forward them to your Auth webservice JS APP AUTH SERVICE
    • AUTH SERVICE 3. The Auth webservice will then generate the encrypted JWS and set a cookie with its value
    • AUTH SERVICE JS APP 4. The JS app can now just execute calls using that cookie
    • 1. The user enters the credentials once in your frontend 2. The JS app will forward them to your Auth webservice JS APP AUTH SERVICE 3. The Auth webservice will then generate the encrypted JWS and set a cookie with its value JS APP 4. The JS app can now just execute calls using that cookie
    • setcookie($name, $jws,$ttl, $path, $domain, true);
    • setcookie($name, $jws,$ttl, $path, $domain, true); HTTPS
    • JWS in PHP?
    • namshi/jose
    • use NamshiJOSEJWS; $jws = new JWS('RS256'); $jws->setPayload(array( 'uid' => $user->getid(), )); $privateKey = openssl_get_privatekey("file://path/to/private. key"); $jws->sign($privateKey); setcookie('identity', $jws->getTokenString()); use NamshiJOSEJWS; $jws = JWS::load($_COOKIE['identity']); $public_key = openssl_pkey_get_public("/path/to/public.key"); if ($jws->verify($public_key)) { echo "EUREKA!; }
    • use NamshiJOSEJWS; $jws = new JWS('RS256'); $jws->setPayload(array( 'uid' => $user->getid(), )); $privateKey = openssl_get_privatekey("file://path/to/private. key"); $jws->sign($privateKey); setcookie('identity', $jws->getTokenString()); use NamshiJOSEJWS; $jws = JWS::load($_COOKIE['identity']); $public_key = openssl_pkey_get_public("/path/to/public.key"); if ($jws->verify($public_key)) { echo "EUREKA!; }
    • use NamshiJOSEJWS; $jws = new JWS('RS256'); $jws->setPayload(array( 'uid' => $user->getid(), )); $privateKey = openssl_get_privatekey("file://path/to/private. key"); $jws->sign($privateKey); setcookie('identity', $jws->getTokenString()); use NamshiJOSEJWS; $jws = JWS::load($_COOKIE['identity']); $public_key = openssl_pkey_get_public("/path/to/public.key"); if ($jws->verify($public_key)) { echo "EUREKA!; }
    • use NamshiJOSEJWS; $jws = new JWS('RS256'); $jws->setPayload(array( 'uid' => $user->getid(), )); $privateKey = openssl_get_privatekey("file://path/to/private. key"); $jws->sign($privateKey); setcookie('identity', $jws->getTokenString(), ...); use NamshiJOSEJWS; $jws = JWS::load($_COOKIE['identity']); $public_key = openssl_pkey_get_public("/path/to/public.key"); if ($jws->verify($public_key)) { echo "EUREKA!; }
    • use NamshiJOSEJWS; $jws = new JWS('RS256'); $jws->setPayload(array( 'uid' => $user->getid(), )); $privateKey = openssl_get_privatekey("file://path/to/private. key"); $jws->sign($privateKey); setcookie('identity', $jws->getTokenString()); use NamshiJOSEJWS; $jws = JWS::load($_COOKIE['identity']); $public_key = openssl_pkey_get_public("/path/to/public.key"); if ($jws->verify($public_key)) { echo "EUREKA!; }
    • use NamshiJOSEJWS; $jws = new JWS('RS256'); $jws->setPayload(array( 'uid' => $user->getid(), )); $privateKey = openssl_get_privatekey("file://path/to/private. key"); $jws->sign($privateKey); setcookie('identity', $jws->getTokenString()); use NamshiJOSEJWS; $jws = JWS::load($_COOKIE['identity']); $public_key = openssl_pkey_get_public("/path/to/public.key"); if ($jws->verify($public_key)) { echo "EUREKA!; }
    • ...what about Symfony2?
    • use SymfonyComponentSecurity...AuthenticationProviderInterface; class JwsProvider implements AuthenticationProviderInterface { ... public function authenticate(TokenInterface $token) { $key = openssl_pkey_get_public($this->publicKeyPath); $jws = $token->getJws(); if ($key && $jws->isValid($key)) { $token->setUser(User::fromArray($jws->getPayload())); return $token; } throw new AuthenticationException('authentication failed.'); } ... }
    • use SymfonyComponentSecurity...AuthenticationProviderInterface; class JwsProvider implements AuthenticationProviderInterface { ... public function authenticate(TokenInterface $token) { $key = openssl_pkey_get_public($this->publicKeyPath); $jws = $token->getJws(); if ($key && $jws->isValid($key)) { $token->setUser(User::fromArray($jws->getPayload())); return $token; } throw new AuthenticationException('authentication failed.'); } ... }
    • use SymfonyComponentSecurity...AuthenticationProviderInterface; class JwsProvider implements AuthenticationProviderInterface { ... public function authenticate(TokenInterface $token) { $key = openssl_pkey_get_public($this->publicKeyPath); $jws = $token->getJws(); if ($key && $jws->isValid($key)) { $token->setUser(User::fromArray($jws->getPayload())); return $token; } throw new AuthenticationException('authentication failed.'); } ... }
    • use SymfonyComponentSecurity...AuthenticationProviderInterface; class JwsProvider implements AuthenticationProviderInterface { ... public function authenticate(TokenInterface $token) { $key = openssl_pkey_get_public($this->publicKeyPath); $jws = $token->getJws(); if ($key && $jws->isValid($key)) { $token->setUser(User::fromArray($jws->getPayload())); return $token; } throw new AuthenticationException('authentication failed.'); } ... }
    • use SymfonyComponentSecurity...AuthenticationProviderInterface; class JwsProvider implements AuthenticationProviderInterface { ... public function authenticate(TokenInterface $token) { $key = openssl_pkey_get_public($this->publicKeyPath); $jws = $token->getJws(); if ($key && $jws->isValid($key)) { $token->setUser(User::fromArray($jws->getPayload())); return $token; } throw new AuthenticationException('authentication failed.'); } ... }
    • I can't simply use the HTTP basic authentication, it was so convenient!
    • ...and flawed. Modern apps, modern tech.
    • 4. Embrace messaging
    • Don't wait, notify instead
    • Different services can intercept an even, separately
    • If one is down, the others keep working
    • Who cares about milliseconds for notifications?
    • The human body is the bottleneck
    • Email?
    • SMS?
    • Be reliable
    • “Daemons are great”
    • “Daemons are great” - No PHP developer ever
    • SUPERVISOR http://supervisord.org/
    • SUPERVISE http://cr.yp.to/daemontools/supervise.html
    • use python ;-)
    • It doesn’t matter...
    • ‫اﻟﺤﺮوف اﻟﻌﺮﺑﯿﺔ ‪if you talk‬‬
    • Rabbit makes everyone talk the same language
    • chat sync daemons Batch processing frontend ERP agony transcoding telcom
    • But I Symfony2
    • Tech monogamy is so ‘90 “given a hammer, everything becomes a nail”
    • One size doesn’t fit all
    • “But look at Google, they basically use python for everything”
    • “...and C”
    • “...and C++”
    • “...and Java”
    • “...and JavaScript”
    • “...and Go”
    • But hey, you say...
    • they really dislike supporting multiple platforms
    • “...and Dart”
    • But hey, you say...
    • they really are not into supporting the secondary platforms
    • “...and AngularJS”
    • 5. Not always sunday
    • Monitor in real time
    • Native support for Symfony2
    • Logs are first-class citizens
    • https://github.com/Seldaek/monolog
    • Sharp, as much as possible
    • I LIED A LOOOOOT
    • Symfony isn’t even the main point of this SOA talk
    • You can build SOAs with anything
    • ...or can you?
    • http://odino.org/why-we-choose-symfony2-over-any-other-php-framework/
    • By being decoupled and HTTP-centric Symfony2 has turned into an ideal application framework that can take (part of) the stage in a SOA
    • Full-stack is dead
    • PHP developers are dead
    • LONG LIVE API ENGINEERS!
    • All in all...
    • SOA is complex
    • like Symfony2
    • A puzzle with more pieces
    • like Symfony2
    • More things to keep in mind
    • like Symfony2
    • COMPLEX IS NOT COMPLICATED
    • Loose coupling
    • every service is independent, not forced to the constraints of a monolithic block
    • you have the freedom of changing or replacing services without the hassle of touching an entire system
    • State-of-the-art defense against outages
    • Fault tolerance
    • if one of the services has an outage, the rest of the architecture still works
    • if a service, listening for messages, is down, the publisher doesn't get stuck
    • Cleaner architecture
    • SoC happens at architectural, not application, level and you can perform large-scale refactorings without the fear of destroying the entire system
    • ...yawn...
    • Alessandro Nadalin
    • Alessandro Nadalin @_odino_
    • Alessandro Nadalin @_odino_ Namshi | Rocket Internet
    • Alessandro Nadalin @_odino_ Namshi | Rocket Internet VP Technology
    • Alessandro Nadalin @_odino_ Namshi | Rocket Internet VP Technology odino.org
    • Thanks! Alessandro Nadalin @_odino_ Namshi | Rocket Internet VP Technology odino.org
    • By the way
    • Wanna join?
    • We are looking for talented nerds!
    • We are looking for talented nerds! frontend engineer
    • We are looking for talented nerds! frontend engineer data engineer
    • We are looking for talented nerds! lead frontend engineer data engineer
    • Thanks! Alessandro Nadalin @_odino_ Namshi | Rocket Internet VP Technology odino.org
    • Image credits http://www.flickr.com/photos/randystiefer/6998037429/sizes/h/in/photostream/ http://www.flickr.com/photos/55432818@N02/5500963965/ http://www.flickr.com/photos/pamhule/4503305775/ http://www.flickr.com/photos/wili/1427890704/ http://www.flickr.com/photos/nickpiggott/5212959770/sizes/l/in/photostream/ http://www.flickr.com/photos/nomad9491/2549965427/sizes/l/in/photostream/ http://www.flickr.com/photos/amyvdh/95764607/sizes/l/in/photostream/ http://www.flickr.com/photos/matthoult/4524176654/ http://www.flickr.com/photos/kittyeden/2416355396/sizes/l/in/photostream/ http://www.flickr.com/photos/jpverkamp/3078094381/ http://www.flickr.com/photos/madpoet_one/5554416836/ http://www.flickr.com/photos/87792096@N00/2732978107/ http://www.flickr.com/photos/petriv/4787037035/ http://www.flickr.com/photos/51035796522@N01/111091247/sizes/l/in/photostream/ http://www.flickr.com/photos/m-i-k-e/6366787693/sizes/l/in/photostream/ http://www.flickr.com/photos/39065466@N04/9111005211/ http://www.flickr.com/photos/marchorowitz/5449945176/sizes/l/in/photolist-9iAoQ1-8s4ueH-bCWef9-bCWdPh-e48XUmbu67nh-a7xaEr-8wLiNh-9aYU1k-9F4VUN-dYqzr1-9vosHb-8BtFuw-8P3h2e-9tqc6M-82qpt4-7UgkBJ-dgSnfS-aJiubZ-9Xji2U-9UVpkC7BSh7Y-8GE54k-91GHtB-8VMHJ2-8wiwvo-aCmPCg-925Tg8-bcBv9T-dGUseY/ http://www.flickr.com/photos/blegg/745322703/sizes/l/in/photostream/ http://www.flickr.com/photos/centralasian/4649550142/sizes/l/in/photostream/ http://www.flickr.com/photos/pennstatelive/4947279459/sizes/l/in/photostream/ http://www.flickr.com/photos/tjblackwell/7819341478/ http://www.flickr.com/photos/brainbitch/6066375386/ http://www.flickr.com/photos/nnova/4215594009/ http://www.flickr.com/photos/publicenergy/2246574379/ http://www.flickr.com/photos/andrewteman/4592833017/sizes/o/in/photostream/ http://www.flickr.com/photos/beautifulrevelry/8548004964/sizes/o/in/photostream/ http://www.flickr.com/photos/denaldo/5066810104/sizes/l/in/photostream/ http://www.flickr.com/photos/picturewendy/8365723674/sizes/l/in/photostream/ http://www.flickr.com/photos/danielygo/6644679037/sizes/l/in/photostream/ http://www.flickr.com/photos/ross/7614352/sizes/l/in/photostream/ http://www.flickr.com/photos/75932013@N02/6874087329/sizes/l/in/photostream/ http://crucifixjel.deviantart.com/art/300-Wallpaper-03-66516887 https://www.flickr.com/photos/acidsaturation/6635987033/sizes/l/