PHP → Node.js

Ein Reisebericht von PHP nach JavaScript

Vortrag von Hannes Moser / @eliias

Der Reiseveranstalter

Hannes Moser

  • Gelernter Designer/Animationsmensch
  • 10+ Jahre als Entwickler
    • Online Games
    • Interaktive Applikationen / Agenturen
    • Backend / Operations
    • Web Applikationen
    • Selbstständiger Softwareentwickler Die Netzarchitekten
  • Lehrverpflichtung an der FH-Salzburg (MMT)
  • Startup Gründer cnuddl.com

Reiseübersicht

Ausgangslage

  • 2 Projekte für 2 Startups
  • Ähnliche Anforderungen
  • 2 verschiedene Ökosysteme
  • Es folgt der Reisebericht!

Reiseziele

Wo möchten wir hin?

  • Rapid prototyping
  • Agile Entwicklung
  • Everything is a service
  • Continous Deployment
  • Reactive Applications → Reactive Manifesto

Fortbewegungsmittel

Der Wunsch

“Das richtige Werkzeug für den richtigen Zweck”

Die Wahrheit

  • Unterschiedliches Niveau (Entwickler)
  • Hoher Zeitdruck → Keine Zeit für Ausbildung!
  • Angst vor Neuem (Motivation/Scheitern/Status)
  • Wartbarkeit → Wer kann das übernehmen?

Webservice/API mit PHP

tl;dr

Webservice/API mit PHP

Aufgabe

  • Wunsch des Chefs: Ökosystem PHP + ZF
  • ORM → Doctrine
  • Framwork → ZF/ZF2, Symfony2, …
  • Skalierbarkeit → Cluster / Cloud
  • Versionierung / CI -> ANT + Jenkins
  • Flott → Mobile First

Webservice/API mit PHP

Herausforderung

  • (Granulares) Rechtesystem
  • Flexibilität (Erweiterbarkeit → Funktionen)
  • Verteilte Entwicklung (3 Länder, 4 Städte)
  • Tests → Regression und Unit: PHPUnit + Fixtures
  • Entwicklungszeit: ~ 3 Mannmonate für Release 1.0

Webservice/API mit PHP

Probleme während der Entwicklung — Tools

  • Doctrine → Hybernate in PHP
    • Adaptionsprobleme (existierende MySQL DB)
    • Keine Transformationen
    • Keine Serialisierung vorgesehen!
    • Es gibt keine (ernstzunehmende) Alternative!
  • JMS Serializer
    • Langsam & Memory Leaks
    • für Symfony entwickelt
  • ZF → deprecated!, ZF2 → WTF!
  • 16 unterdimensionierte VM's mit Load Balancing auf 1 x Hetzner Root Server

Webservice/API mit PHP

Probleme während der Entwicklung — Allgemein

  • Rechtesystem wurde immer komplexer → ACL's auf Propertyebene
  • Tests → Großer Zeitaufwand durch granulare Versionierung
  • Erweiterbarkeit: Nicht flexibel genug!
  • Chef hatte Angst vor Message Queues
  • Niveauunterschiede im Team: Java/PHP Menschen mit Bankenhintergrund als Systemarchitekt
    • Viele Files erzeugen/doppeln
    • Unfähig Doku zu lesen
    • 2 zusätzliche Abstraktionsschichten einführen
    • Operations an sich reißen
    • Meetings in komplizierter Sprache führen
    • 80% der Zeit mit Spezifikation verbringen
    • FAIL

Webservice/API mit PHP

Resultat

  • Hoher zeitlicher/finanzieller Aufwand
  • Hoher Anteil an Infrastrukturcode
  • Während des Projektes 50% des Teams verloren
  • Ökosystem muss wahrscheinlich getauscht werden
  • Hardware kompensiert mangelnde Performance

Webservice/API mit PHP

Resultat

  • API ist RESTful — manchmal
  • Versionierung wurde rückwirkend “vereinfacht”
  • 5 geforkte OSS Libraries (gravierende Mängel)
  • Entwickler schreiben keine Tests
  • Workarounds: “Side API's, direkte SQL Reports, …”
  • Hoher Operations Aufwand
  • War bereits der 2. Versuch!!!

Webservice/API

Alternative Fortbewegungsmittel

Die Auswahl ist groß

  • JVM/Spring
  • JVM/Play
  • JVM/Grails
  • Ruby/Rails
  • Ruby/Sinatra
  • Perl/Mojolicious
  • V8/Node.js/Express

Warum Node.js?

Bootstrap PHP Project

Apache, .htaccess Files, VirtualHosts, Modules, PHP Extensions

php.ini!

Resultat: Alle Entwickler arbeiten auf einem Staging System, dass nicht mehr verändert werden kann, da die letzten 3 Projektjahre darauf herumlungern.

Warum Node.js

Bootstrap Node.js Project

Konfiguration auf Projektebene


        apt-get install nodejs
        apt-get install npm
                        

        {
            "name": "hello-world",
            "description": "hello world test app",
            "version": "0.0.1",
            "dependencies": {
                "express": "3.x"
            }
        }
                        

        var express = require( 'express' ),
            app = express();

        app.get( '/', function( req, res ){
            res.send( 'hello world' );
        });

        app.listen( 3000 );
                        

Warum Node.js

ZF2 Routes


        // One at a time:
        $route = Literal::factory(array(
            'route' => '/foo',
            'defaults' => array(
                'controller' => 'foo-index',
                'action'     => 'index',
            ),
        ));
        $router->addRoute('foo', $route);
                        

Wir haben bei der Design Patterns Vorlesung ganz toll aufgepasst!

Warum Node.js

Express Routes


        // One at a time:
        app.get( '/foo', function() { ... }
                        

Warum Node.js

PHP Modules vs. PEAR Packages vs. Namespaces


    <?php
    include '...';
    include_once '...';
    require '...';
    require_once '...';
    __autoload();
    spl_autoload_register();
    Zend_Loader::init();
                        

    require 'app.class.php';
    new App();
    // vs.
    new Project_App();
    // vs.
    new \Project\App();
                        

Reichlich Auswahl!

Warum Node.js

Common-JS

Dependencies


    var app = require( 'app' );
                        
Module im Projekt.

    var userCtrl = require( './controller/user' );
                        

Warum Node.js

PHP Async


    $client = new Zend_Http_Client( 'http://example.org' );
    $response = $client->request( 'GET' );
                        
Geht nicht!

Warum Node.js

Non Blocking IO → Event Loop


    http.get( 'http://example.org', function( res ) { ... } );
                        
Der GET Request blockt die Ausführung des Codes in keinster Weise. In der Zwischenzeit kann ich das hier machen:

    res.send( 'Bin fertig' );
                        
In der Callback Function kann ich auch später etwas tun, auf das mein Client nicht unbedingt warten muss. z.B. eine neue Notitication in meiner Message Queue anlegen, Bilder und Videos transkodieren, etc.

Warum Node.js

PHP + File Uploads

Erzeugt immer eine temporäre Datei die verschoben werden KANN (nicht muss) → Aufräumen nicht vergessen!


    <?php
        if (move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
            echo "Datei erfolgreich hochgeladen";
        } else {
            echo "Problem beim Speichern der Datei.";
        }
                        

Warum Node.js

Node.js Streams

Mit meinem Stream kann ich machen was ich will!


    app.post( '/files', function ( req, res ) {

        var form = ...;
        form.on( 'part', function ( part ) {
            part.pipe( ... );
        } );

        form.parse( req );
    } );
                        

z.B. in einem Rutsch durch FFmpeg/IMagick jagen und die Antwort an den Client zurücksenden.

Warum Node.js

PHP ODM - Doctrine


    <?php
        use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

        /** @ODM\Document */
        class User
        {
            /**
             * @ODM\Id
             * @JMS\Type("integer")
             */
            private $id;

        ...
                        

Doctrine löst das u.a. mit Annotations.
Produktiv ist die Erzeugung von Proxies Pflicht!

Warum Node.js

MongoDB - Mongoose


    // Define user schema
    var schema = new Schema( {
        id: String
    } );
                        

Warum Node.js

PHP Errors vs. Exceptions


    try {
        $row->insert();
    } catch (Exception $e) {
        echo "There was an error inserting the row - " . $e->getMessage();
    }
                        

Klassische Exceptions (inkl. Bubbling), aber ...


    $conn = mysql_connect( 'localhost', 'root', 'password' );
    if (!$conn) {
        throw new Exception( 'Ab und zu muss man selber ran.' );
    }
                        

Es gibt noch mehr ↓

Warum Node.js

PHP Errors vs. Exceptions


    trigger_error( 'Error: foo', E_USER_ERROR );
    // Wird abgefangen mit
    set_error_handler( ... );
                        

bei einem FATAL Error bleibt jedoch nur mehr das:


    // FATAL ERROR
    register_shutdown_function( 'fatal_handler' );
                        

Das sollte man nicht machen!

Warum Node.js

Errors in Node.js

Auch nicht toll, aber es gibt eine Konvention.


    mysql.connect( { ... }, function( err, conn ) {
        // Das erste Argument ist ein Error oder "undefined"
        if (err) ...
    } );
                        

Und wenn ich einen Error erzeuge, sieht das immer so aus:


    throw 'Huhu ich bin ein Fehler';
                        

Warum Node.js

PHP Templates

  • Smarty → Templating für Masochisten!
    • - Kopiert PHP Funktionalität als eigene Sprache
    • - Darf zu viel! (SQL Queries)
    • + Trennt Logik vom View
  • Twig → Nett, dennoch unbrauchbar: Gegenmeinung!
  • PHP → Superglobals: $_GET, $_POST
    • - Kann zu viel
    • - Keine Trennung von Logik und View
    • - Verbos
    • + Keine Abhängigkeiten
    • + Schnell!

Rasmus Lerdorf → 4 Reasons Why All PHP Frameworks Suck!

Warum Node.js

Express Templates

  • JADE → Lecker!
  • EJS → Für den Notfall
  • Handlebars → Populär
  • Engine Chooser

Ich bevorzuge Template Engines, in denen ich die Sprache nutzen kann in der sie geschrieben wurden.


    // EJS
    <% _.each(items, function( item ) { %>
        
  • <%= item.key %>
  • <% } %>

    Noch besser: Sinnvolle Abkürzungen

    
        // JADE erlaubt JavaScript Expressions
        each item in items
            li(value=item.val)= item.key
                            

    Warum Node.js

    PHP Performance

    • + Einfacher Code ist sehr schnell
    • ◦ PHP ohne AST/Optimizer ist produktiv zu langsam
    • MVC Frameworks
      • - Große Dispatch Stacks
      • - Hoher Speicherverbrauch
      • - Geschwindigkeit leidet bei Template Engines
      • - Caching wird schnell zur Regel
      • - Bootstrapping ist ein Problem

    Warum Node.js

    Node.js Performance

    • + V8 ist eine Rakete
    • Frameworks
      • + Leichtgewichtig (wenig Code, meist nahe an HTTP)
      • + Bootstrapping außerhalb des Request/Response Cycles → flott!
      • - Viele Abhängigkeiten
      • - Geschwindigkeit bei Templates ist unter aller Sau (Precompiled sehr schnell)
    • + Der GC arbeitet recht vernünftig

    Warum Node.js

    PHP - Dependency Management

    • composer/packagist.org
    • Pyrus
    • PEAR
    • PECL
    
        # Composer
        curl -sS https://getcomposer.org/installer | php
    
        php composer.phar install monolog/monolog
    
        # composer.json file
                            

    Warum Node.js

    Node.js - Dependency Management

    • NPM/npmjs.org
    
        # npm
        curl http://npmjs.org/install.sh | sh
    
        npm install express
    
        # package.json file
                            

    Warum Node.js

    PHP Tests

    • ◦ PHPUnit
    • - Inkompatibilitäten: Frameworks und PHPUnit Versionen
    • - Enormer Konfigurationsaufwand
    • - TDD ist schwer in Teams zu etablieren
    • + Viele zusätzliche Tools verfügbar!

    Warum Node.js

    JavaScript Tests

    • Mocha
      • + Große Auswahl an Assertion Bibliothekten
      • + An natürliche Sprache angelehnt: z.B. should.js/BDD
        
            window.should.be.exactly( window );
                                                
    • Buster.js
    • Karma
    • TestSwarm
    • JsTestDriver
    • ... viele mehr
    • + Im Allgemeinen schmerzfreies Setup

    Warum Node.js

    PHP - Continous Integration/Deployment

    • ANT + Jenkins
    • Phing

    Warum Node.js

    Node.js - Continous Integration/Deployment

    • Grunt + Jenkins (Travis-CI, Gitlab-CI)

    Fazit

    Projekt

    • Reduktion Infrastruktur Code ~ 60 - 80%
    • Weniger Frust
    • Mehr Releases in kürzerer Zeit
    • Flexibler

    Fazit

    Ökosysteme / Community

    • Rießige Basis
    • Bei JavaScript und PHP gibt es sehr viel Schrott!
    • Tendenz Codequalität: JavaScript > PHP
    • PHP: Alles ist neu → hacklang.org
    • JavaScript: Erfindet sich selbst Neu → Funktionale Paradigmen, JSLint, Require.js, Grunt.js, ...

    Reiseende

    Hannes Moser / box@cnuddl.com