Preis: 9,80 €
Erhältlich ab: April 2016
Umfang: 100
Da hat man sich gerade damit abgefunden, dass der über Jahre gewachsene Monolith aus der Mode gekommen sein soll und besser durch eine Hundertschaft von Microservices abgelöst werden sollte. Und schon erscheint ein neuer Stern am Firmament der Modularisierung. Nein, keine Angst, wir reden hier nicht über eine Reinkarnation von SOA, sondern über etwas mit deutlich höherem Potenzial: Self-contained Systems.
Mal ganz ehrlich, auch wenn die neue Wunderwelt der Microservices durchaus verlockend erscheint und man bei der Umsetzung eines Service von einigen wenigen Zeilen Code eigentlich kaum noch etwas falsch machen kann – ganz wohl ist den meisten nicht bei dem Gedanken, den über Jahre gewachsenen Monolith in ein stark verteiltes System von mehreren hundert oder gar tausend Microservices aufzutrennen à la Netflix oder Amazon.
Ok, wir haben alle verstanden, dass wir mit einem historisch gewachsenen Monolith, der von einer Heerschar von streng hierarchisch organisierten Architekten, Entwicklern, Testern und Fachexperten konzipiert und umgesetzt wird, nicht wirklich flexibel sind. Ist die Basis erst einmal ruiniert, helfen scheinbar auch Scrum und Co. nicht wirklich weiter – aber einen Versuch war es immerhin wert.
Es scheint also logisch, dass ein Auftrennen des großen Ganzen in fachlich sauber abgegrenzte Blöcke, mit möglichst wenigen Abhängigkeiten untereinander, automatisch zu besserer Software führt. So weit, so gut. Wäre da nicht die Verlagerung der Komplexität heraus aus der Fachlichkeit hinein in Themen wie Inter-Service-Kommunikation, Versionierung oder verteilte Transaktionen bzw. das Schaffen einer sich selbst heilenden Infrastruktur. Das in voller Konsequenz durchgezogene Microservices-Paradigma funktioniert nachweislich – keine Frage. Aber ist jeder von uns für diese Konsequenz bereit? Wollen wir denn wirklich ein extrem feingranulares, stark verteiltes System aufbauen oder wollen wir eigentlich nur ein wenig mehr Ordnung in unseren Monolith bekommen und so unsere Sünden der letzten fünf bis zehn Jahre ungeschehen machen?
Wie also sieht eine Lösung aus, die uns einen Großteil der Vorteile von Microservices liefert? Vorteile, wie fachlich in sich geschlossene Module, unabhängige Skalierung, modulbasiertes Deployment, Austauschbarkeit von Diensten oder einfach nur der Mehrwert der verbesserten Wartbarkeit? Eine Lösung, die gleichzeitig aber deutlich weniger Komplexität für Management, Monitoring und Operations bedeutet? Die Antwort findet sich in dem Architekturprinzip der Self-contained Systems [1].
Schaut man sich einmal die, aus meiner Sicht extrem gut gelungene, Präsentation zum Thema SCS sowie die textuelle „Definition“ auf [1] an, so positionieren sich Self-contained Systems, kurz SCS, irgendwo zwischen Monolithen und Microservices.
„The Self-contained System (SCS) approach is an architecture that focuses on a separation of the functionality into many independent systems, making the complete logical system a collaboration of many smaller software systems. This avoids the problem of large monoliths that grow constantly and eventually become unmaintainable. Over the past few years, we have seen its benefits in many mid-sized and large-scale projects.“
Was aber genau zeichnet einen SCS aus und wie unterscheidet er sich von einem Microservice? Der Begriff System – vs. Service – legt schon nahe, dass es sich irgendwie um etwas Größeres handelt, das gleichzeitig mehr oder minder autonom ist. Genau das findet man auch in der Auflistung der wesentlichen SCS-Charakteristika wieder, hier als Ausschnitt:
Jedes SCS ist eine autonome Webanwendung, die für ihre Domäne alle Daten, die Logik, diese Daten zu bearbeiten, und den Code zum Rendern des Webinterface beinhaltet. Ein SCS kann seinen primären Use Case unabhängig von anderen Systemen abarbeiten.
Ein SCS gehört einem Team.
Die Kommunikation mit anderen SCSs oder Drittsystemen erfolgt, wenn möglich, asynchron. Ein SCS sollte auch dann noch funktionieren, wenn andere SCSs offline sind.
Ein SCS kann über ein Service-API verfügen. Auch wenn die Kommunikation mit dem User über das eigene Web-UI erfolgt, kann das Service-API für andere SCSs oder Clients hilfreich sein.
Ein SCS enthält Daten und Logik (siehe auch erstes Charakteristikum). Nur so ist es möglich, dass ein SCS unabhängig von anderen SCSs agieren kann.
Die Funktionalität eines SCS sollte End-Usern via eigenem Web-UI zur Verfügung gestellt werden. Funktionalität anderer SCSs kann verlinkt werden.
Enge Kopplung zu anderen SCS durch gemeinsam verwendeten Businesscode sollte vermieden werden.
So wie es aussieht, haben SCSs und Microservices eine ganze Menge gemein. Die beiden Ansätze zeichnen sich insbesondere durch unabhängig zu deployende Einheiten aus, deren architektonische Grenzen sich auch in den verantwortlichen Teams widerspiegeln – Conway’s Law lässt grüßen.
Wo aber liegt nun der Unterschied der beiden Ansätze? Ein Microservice ist in der Regel kleiner als ein SCS. Wir reden also zum Beispiel nicht über einen Service der Granularität einer Adressvalidierung, sondern eher über ein Customer-Management- oder Billing-System eines Onlineshops. In logischer Konsequenz besteht die resultierende Anwendung aus deutlich weniger Services – sorry, ich meinte Systemen – als in der Microservices-Welt.
Ein weiterer, wesentlicher Unterschied ergibt sich aus dem Anspruch, dass ein SCS seine fachliche Logik zum größten Teil ohne Kommunikation mit anderen SCS oder Drittsystemen ausführen kann. Dies gilt insbesondere für den primären Use Case des SCS. Diese strikte Forderung finden wir bei den Microservices nicht. Sie wäre aufgrund des feingranularen Aufbaus auch nicht besonders realitätsnah. Stattdessen gibt es für Microservices eigene Patterns zum Aggregieren.
Auch auf Seiten des visuellen User Interface findet sich ein Unterschied. Während es bei Microservices zwar möglich, aber eher unüblich ist, dass ein Service sein eigenes UI rendert und an den Client ausliefert, ist dies bei den SCS der Standard. Die Integration von verschiedenen SCS findet also vorzugsweise auf dem UI-Layer statt, bei Microservices dagegen eher auf dem Business Logic Layer.
Bitte nicht falsch verstehen: Microservices können durchaus sinnvoll sein und funktionieren nachweislich, wie Netflix, Amazon, aber auch deutlich kleinere Anwendungen uns erfolgreich demonstrieren. Um erfolgreich zu sein, muss aber auch eine gewisse Konsequenz an den Tag gelegt werden. Und spätestens hier scheitert es bei den meisten Unternehmen allein schon aus organisatorischen Gründen.
Der Architekturansatz der SCS scheint da ein sehr guter Kompromiss zu sein, da der Fokus vor allem darauf liegt, große, nicht mehr zu handhabende Projekte so in überschaubare Teile aufzusplitten, dass diese einzelnen Teile von fachlich orientierten Teams unabhängig voneinander erfolgreich umgesetzt werden können. Microservices dagegen zielen eher auf eine erhöhte Flexibilität ab, um so zum Beispiel robustere und fehlertolerantere Systeme zu schaffen oder einzelne, stark frequentierte Services unabhängig skalieren zu können.
Schaut man sich einmal den einen oder anderen Blog zum Thema Microservices und die dort vorgestellten fachlichen Beispielservices an, so kann man sich des Eindrucks nicht erwehren, dass SCS am Ende das sind, was einige von uns bisher unter dem Begriff Microservices verstanden haben oder gerne verstehen wollten. SCS sind auf jeden Fall ein sehr interessanter Architekturansatz, den man im Blick behalten sollte! In diesem Sinne: Stay tuned!
Lars Röwekamp ist Geschäftsführer der open knowledge GmbH und berät seit mehr als zehn Jahren Kunden in internationalen Projekten rund um das Thema Enterprise Computing.
[1] Self-contained Systems: http://scs-architecture.org
Das Docker-Ökosystem wächst unaufhaltsam, und alle zwei Monate gibt es ein neues Release der Docker-Werkzeuge. Das Release von Anfang 2016 bringt größere Veränderungen und schafft neue Möglichkeiten. Denn erstmalig ist das Docker-Image-Format grundlegend verändert worden. Docker-Compose realisiert ein neues Format mit der Unterstützung von Multi-Host-Network- und Volume-Management.
Neben zahlreichen kleinen Ergänzungen und vielen Bugfixes bringt das neue Engine-Release eine sicherere Grundlage für die Referenzierung von Images und Layern. Endlich werden die Image-IDs auf der Basis der Inhalte eines Layers gebildet. Die neuen IDs ermöglichen dem Docker-Hub und der neuen Registry 2.3 endlich eine bessere Deduplication-Erkennung und damit das effektive Sparen von jeder Menge Plattenplatz. Images lassen sich nun einfach auf verschiedenen Hosts erzeugen, gleiche Inhalte generieren nun dieselben IDs. Außerdem ermöglicht ein neues Manifestformat schnelleres Laden von Layern. Nach dem Start der neuen Engine wird nun eine automatische Aktualisierung bestehender Images durchgeführt. Wer viele Images auf einem Docker-Host installiert hat, kann deshalb mit einer längeren Wartezeit rechnen. Für diesen Zweck ist das Docker-Migration-Tool besser geeignet, siehe [1] und Kasten: „Anleitung zur Migration der Docker-Images auf das neue Format“.
Stopp der alten Docker-Engine
Back-up des Docker-Hosts vor dem Update
Start der alten Docker-Engine
Start der Migration mit docker run --rm -v /var/lib/docker:/var/lib/docker --privileged docker/v1.10-migrator
Stopp der alten Docker-Engine
Update auf die neue Docker-Engine 1.10 und Start der neue Engine mit Containern
Eine erfreuliche Erweiterung ist die Möglichkeit, zur Laufzeit die Beschränkungen der Ressourcen CPU, Memory und I/O zur Laufzeit mit dem Docker-Befehl docker update verändern zu können, siehe Listing 1. Weitere Sicherheitsmerkmale sind in der Docker-Engine mit der Unterstützung von User-Maps zur Isolierung von Nutzern in Containern und der Nutzung des Seccomp-Standards hinzugekommen [2], [3]. Weitere Plug-in-APIs für Authentifikation und IPAM-Management sorgen für zusätzliche Möglichkeiten, die Engine zu erweitern. Für die flexiblere und schnellere Namensauflösung sorgt nun ein lokaler DNS-Server.
Listing 1: Docker-Update in Action
$ docker run -d --name node1 \
--cpu-shares 2048 --cpuset-cpus 0,1 infrabricks/stress
$ docker run -d --name node2 \
--cpu-shares 512 --cpuset-cpus 0,1 infrabricks/stress
$ docker stats
$ docker update --cpu-shares 4069 node2
$ docker stats
$ docker update --cpu-shares 2048 --cpuset-cpus 2,3 node2
$ docker stats
$ docker stop node1 node2
$ docker rm node1 node2
Die Erweiterungen von Compose sind mit dem Release 1.6 vielfältig. Das neue Beschreibungsformat bringt zahlreiche Neuerungen (Listings 2 und [4]). Endlich können Docker-Files und Build-Argumente in der Build-Deklaration angegeben werden. In der Kombination mit der Imageanweisung lässt sich das Docker-Image nun gleich mit einem verwendbaren Namen erzeugen. Wichtige Neuerungen sind auch die Unterstützung von Volume- und Multi-Host-Netzwerken. Ein Container kann nun gleich mit mehreren verschiedenen Netzwerken generiert werden. In Compose ist die Integration von Swarm stark verbessert worden. Das gesamte REST-Docker-Engine-API 1.23 zur Erzeugungen von Containern lässt sich nun mit Compose konfigurieren.
Listing 2: Docker-Compose-Multi-Network
version: "2"
services:
app:
build:
context: .
args:
buildno: 1
dockerfile: Dockerfile.dev
image: infrabricks/demo-inc-app
links:
- redis
ports:
- "8080"
networks:
- app
- db
environment:
- "affinity:container!=*app*"
redis:
image: redis
ports:
- "6379"
networks:
- db
command: [ "redis-server", "--appendonly", "yes" ]
volumes:
- redisdata:/data
networks:
db:
driver: overlay
app:
driver: overlay
volumes:
redisdata: {}
Viele Kleinigkeiten behebt das Release Docker Swarm 1.1. Und es verbessert die Unterstützung des Docker-Engine-API. Weiterhin wird der alternative Mesos-Scheduler immer leistungsfähiger. Ein paar entscheidende Merkmale fehlen in Docker Swarm bisher, die Kubernetes oder Mesos Marathon schon lange unterstützen. Den Neustart von Containern unterstützt die Docker-Engine schon länger, aber Swarm fehlen bisher die Features Rescheduling und Autoscaling. In Swarm 1.1 wird nun das Rescheduling als Experimentalfeature integriert, siehe Listing 3. Container, die mit einem Rescheduling-Flag als Label oder einer Environment-Variable markiert sind, können nach einem Crash einer Maschine auf andere Maschinen automatisch verteilt werden:
# Generierung des Cluster mit der Swarm Option -experimental nicht vergessen!
$ eval $(docker-machine env mob-net-001 --swarm)
$ docker run -d --env="reschedule:on-node-failure" redis
$ docker run -d -l 'com.docker.swarm.reschedule-policy=["on-node-failure"]' redis
Leider wird bei dem Umzug das Volume nicht einfach mit umgezogen, außer das installierte Volume-Plug-in, z. B. das Plug-in Flocker, unterstützt dies [5]. Im Test wechselte der umgezogene Container noch die IP-Adresse im Overlay-Network. Vielleicht kann dies in naher Zukunft besser gelöst werden.
Kleine Bugfixes und eine Vielzahl von Stabilitätsfeatures prägen das Release Docker Machine 0.6. Außerdem ist die Dokumentation verbessert worden. Das Skript create-network.sh zeigt die Verwendung zur Anlage eines lokalen Multi-Host-Network-Swarm-Clusters, siehe Listing 3. In einer separaten Maschine wird Consul installiert, um als Key-Value Store für das Overlay-Netzwerk und den Swarm-Cluster zu dienen. In einer Schleife werden dann die Docker-Hosts für den Cluster erzeugt (Listing 3).
Listing 3: Multi-Host-Netzwerk erzeugen: „create-network.sh“
#!/bin/bash
SWARM_OPT="--swarm-opt -experimental"
_s=$(docker-machine status moby-consul)
if [ $? -eq 1 ] ; then
docker-machine create \
-d virtualbox \
--engine-label "cluster=moby" \
moby-consul
docker $(docker-machine config moby-consul) run -d \
-e "GOMAXPROCS=2" \
-p "8500:8500" \
-h "consul" \
--restart=always \
progrium/consul -server \
-advertise $(docker-machine ip moby-consul) \
-ui-dir=/ui -data-dir=/data -bootstrap
sleep 5
fi
: ${NODE_FIRST:=1}
: ${NODE_LAST:=2}
: ${NODE_MEMORY:=1024}
: ${NODE_CPU:=1}
for NODE in $(seq -f "%03g" ${NODE_FIRST} 1 ${NODE_LAST}) ; do
_s=$(docker-machine status moby-net-${NODE})
if [ $? -eq 1 ] ; then
if [ "$NODE" == "001" ] ; then
MASTER="--swarm-master"
else
MASTER=""
fi
docker-machine create \
-d virtualbox \
--virtualbox-memory ${NODE_MEMORY} \
--virtualbox-cpu-count ${NODE_CPU} \
--engine-opt="cluster-store=consul://$(docker-machine ip moby-consul):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
--engine-label="cluster=moby" \
--swarm $MASTER $SWARM_OPT \
--swarm-discovery consul://$(docker-machine ip moby-consul):8500/moby \
moby-net-${NODE}
fi
done
Die Verteilung von Containern auf mehrere Maschinen durch den Swarm-Master moby-net-001 erfolgt mithilfe des Docker-Compose-Files aus Listing 2. Die Skalierung der Anwendung app kann auf verschiedene Maschinen durch die Affinity-Deklaration des App-Service gesteuert werden. Alle Anwendungen sind im DB-Netz und erreichen über die Linkanweisung dasselbe Redis-Backend. Aufpassen beim Neustart des eigenen Rechners müssen die Virtualbox-Maschinen mit denselben IP-Adressen, bzw. die in derselben Reihenfolge gestartet werden.
Listing 4: Erzeugen eines Multi-Host- und Network-Service
$ NODE_FIRST=1 NODE_LAST=3 ./create-network.sh
$ eval $(docker-machine env mob-net-001 —swarm)
$ docker-compose up -d
$ docker-compose scale app=3
Das Stöbern in der Dokumentation nach weiteren Neuigkeiten lohnt. Natürlich gibt es für die Mac- und Windows-Nutzer eine aktuelle Docker-Toolbox [6]. Wünsche für weitere Docker-Features bleiben natürlich immer bestehen. Docker Machine fehlt noch eine individuelle Provisionierungsmöglichkeit, die nach der Installation der Maschine erfolgt. Die Installation von Docker-Plug-ins ist nur mit individuellen Skripten oder Ansible möglich. Hilfreich wäre es natürlich auch, wenn lokale Installationen mit verschiedenen Linux-Distributionen unterstützt würden. Nach einer Bereitstellung von einem Docker-Machine-Cluster wäre es hilfreich, verschiedene Docker-Compose-Befehle auszuführen. Das Thema Volume-Management und automatische Migration auf andere Docker-Hosts wird sehnlichst von vielen Docker-Nutzern erwartet. Vielleicht bringt die geplante Unterstützung von ZFS in Ubuntu 16.04 hier bald Abhilfe [7], [8]. Mit der Bereitstellung der kommerziellen CaaS-Plattform von Docker Inc. gibt es sicherlich gute Chancen für die baldige Erfüllung einiger dieser Wünsche in naher Zukunft. Ein Blick auf die Fortschritte der Projekte Mesos, Kubernetes, OpenShift oder anderer Docker-Cloud-Angebote lohnt sich.
Peter Roßbach ist ein Infracoder, Systemarchitekt und Berater vieler Websysteme. Sein besonderes Interesse gilt dem Design und der Entwicklung von komplexen Infrastrukturen. Er ist Apache Tomcat Committer und Apache Member. Mit der bee42 solutions gmbh realisiert er Infrastrukturprodukte und bietet Schulungen auf der Grundlage des Docker-Ökosystems, aktuellen Webtechnologien, NoSQL-Datenbanken und Cloud-Plattformen an.
[1] docker/v1.10-migrator: https://github.com/docker/v1.10-migrator
[2] Seccomp security profiles for Docker: https://github.com/docker/docker/blob/master/docs/security/seccomp.md
[3] Secure Computing with filters: https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt
[4] Compose file reference: https://docs.docker.com/compose/compose-file/
[5] Flocker: https://clusterhq.com/flocker/introduction/
[6] Docker-Toolbox: https://www.docker.com/products/docker-toolbox
[7] Leemhuis, Thorsten: „Diskussionen über ZFS-Dateisystem in Linux-Distribution Ubuntu 16.04 LTS“: http://www.heise.de/open/meldung/Diskussionen-ueber-ZFS-Dateisystem-in-Linux-Distribution-Ubuntu-16-04-LTS-3111940.html
[8] Kirkland, Dustin: „ZFS ist he FS for Containers in Ubuntu 16.04!“: http://blog.dustinkirkland.com/2016/02/zfs-is-fs-for-containers-in-ubuntu-1604.html
Vor etwa zwei Jahren hob er steil nach oben ab, der Trend zu Microservices. Um einige der vielen Dimensionen von Microservices greifbar und verständlich zu machen, haben wir gemeinsam mit Eberhard Wolff, der auch den Leitartikel zum Thema geschrieben hat (Seite 33), den Schwerpunkt im vorliegenden Java Magazin entwickelt.
Microservices sollen kompakt sein und einfach zu deployen – so lauten zentrale Forderungen. Dabei sollen Entwickler unnötige Komplexität vermeiden. Dass sich diese als Konsequenz aber nicht einfach in Luft auflöst, ist allerdings eine Binsenweisheit: Sie findet sich in komplexeren Netzwerkbeziehungen zwischen den Services wieder. Aber dort ist sie – durch konsequente Automatisierungsansätze – eventuell besser zu handhaben, wie Alexander Schwartz in seinem Beitrag ab Seite 44 zeigt. Unter der Überschrift „Einfach mal loslegen“ teilt Alexander Heusingfeld (Seite 52) seine Erfahrungen, wie gut gemachte Microservices-Implementierungen den Wandel in der Technologie vereinfachen können. In seinem Beispiel geht es um den Wandel von einem monolithisch geprägten System hin zu ersten Microservices.
Womit wir bei einer weiteren wichtigen Motivation für Microservices wären: Sie ermöglichen Wandel. Da es heute schon zur Genüge Projekte gibt, die mit Legacy-Code in Form von J2EE-Monolithen zu kämpfen haben, und da wir heute natürlich nicht wissen, welche Anforderungen und Technologien in den nächsten zehn Jahren vorherrschend sein werden, ist es eine gute Idee, sich auf permanenten Wandel einzustellen. Die in unserem Schwerpunkt erwähnten Punkte erläutern plastisch, wie Microservices dabei helfen können.
Nun könnte man einwenden, dass Microservices ihrerseits in ein paar Jahren überholt sein werden und dass man dann in derselben Legacy-Falle stecken würde wie zuvor mit den Java-Enterprise-Monolithen. Darauf lässt sich nur antworten, dass Microservices besonders in Organisationen, wo der ständige Wandel eine herausragende Rolle spielt, sich besonders bewähren.
Um die erwähnte Vielschichtigkeit des Themas etwas besser einzufangen, haben wir uns außerdem entschlossen, gleich mehrere Experten zu Wort kommen zu lassen, und zwar in Form kurzer und knapper Statements (Seite 40).
Wer Ihnen heute jedoch Microservices als Silver Bullet zur Lösung sämtlicher Probleme verkauft, dem sollten Sie misstrauen. Wir leben in komplexen Zeiten im Zeichen ständigen geschäftlichen und technischen Wandels, da ist Offenheit für neue Ansätze immer eine gute Sache.
In diesem Sinne: Viel Spaß bei unserer vielseitigen Microservices-Ausgabe!
Bei Webanwendungen hat sich nicht nur bei den Businessfeatures etwas getan, sondern auch im UI-Bereich. Responsive Design und der Support für Monitore mit hoher DPI-Zahl sind nur einige der Features, die eine moderne Webseite heute bieten sollte. Da viele der Features mit HTML und CSS-Bordmitteln nicht umgesetzt werden konnten, griff man in den letzten Jahren gerne auf Bibliotheken wie Bootstrap zurück. Doch HTML und CSS legen nach und bieten einige interessante neue Features für moderne UIs.
Ich denke, dass die meisten Entwickler mir zustimmen werden, wenn ich sage, dass Layout und Styling im Web keine einfache Aufgabe sind. Vor allem durch das Hinzukommen der Unterstützung für Mobile und Responsive Design ist die Aufgabe in den letzten Jahren nicht einfacher geworden. Zum Glück wurden hier einige CSS-Erweiterungen wie Media Queries geschaffen, die verschiedene Layouts für unterschiedliche Ausgabegrößen und Geräte erlauben. Bibliotheken wie Bootstrap setzten bereits seit einiger Zeit genau auf diese Features, um responsive und konsistente UI-Toolkits bereitzustellen. Mit deren Komponenten und Layouts kann man einfach eigene Webanwendungen umsetzen. Aber sobald man begann, Bootstrap für die eigene Anwendung anzupassen oder zu erweitern, kam es oft zu Problemen. CSS-Klassen wurden doppelt genutzt, das gewünschte Layout funktionierte in bestimmten Konstellationen nicht mehr oder die Komplexität des CSS wurde extrem erhöht. Verschiedene neue HTML- und CSS-Features sollen in Zukunft helfen, genau diese Punkte besser zu machen. Dieser Artikel soll einige Neuheiten vorstellen, die 2016 interessant und wichtig für die Frontend-Entwicklung im Web werden könnten. Hierbei sollen allerdings keine hippen JavaScript-Frameworks im Vordergrund stehen. Vielmehr will ich auf einige neue Bestandteile der HTML und CSS Spec eingehen, die in den aktuellen oder folgenden Browserversionen von Haus aus jedem Entwickler und Designer zur Verfügung stehen werden.
Eines der für mich wichtigsten Features in der modernen Webentwicklung ist die Modularisierung und Kapselung von Komponenten. Hierdurch ist es endlich möglich, echte wiederverwendbare Komponenten für das Web zu erstellen. Durch die Web-Component-Spezifizierung lässt sich dies bei modernen und zukünftigen Browserversionen auch komplett ohne ein externes Framework erreichen.
Als Beispiel der Vorteile einer Komponente soll die YouTube-Webseite herhalten. Schaut man sich die Startseite von YouTube an, kann man erkennen, dass das UI aus verschiedenen visuellen Komponenten zusammengesetzt ist. Schaut man nun allerdings in den Quellcode, kann man von diesen Komponenten leider nicht viel erkennen. Als Beispiel habe ich eine Vorschaubox genommen, von der mehrere auf der YouTube-Startseite und auch beim Abspielen eines Videos im rechten Bereich auftauchen. Abbildung 1 zeigt eine solche Vorschau.
Den kompletten HTML-Code dieser kleinen Box will ich dem Leser ersparen, da er rund eine Seite im Magazin einnehmen würde. Und hierbei spreche ich rein von dem Code zur Visualisierung der Komponente. Interaktion und Logik sind hierbei komplett außen vor. Daher zeigt Abbildung 2 den Code, um eine grobe Vorstellung über die Komplexität und (Un-)lesbarkeit des Quellcodes zu erhalten.
Schauen wir uns nun an, wie man das Ganze zum Beispiel mit einem UI-Toolkit realisieren könnte, das wiederverwendbare Komponenten nutzt. Wir verwenden JavaFX als UI-Technologie. Der folgende Quellcode geht davon aus, dass es die Klasse PreviewThumbnail als Custom UI Component gibt und die Klasse PreviewData als passendes Datenmodell. Die PreviewThumbnail-UI-Komponente weiß in diesem Fall, wie die in PreviewData definierten Daten gerendert werden sollen. Den Code zur Einbindung einer solchen Komponente:
PreviewData data = loadData();
PreviewThumbnail thumbnail = new PreviewThumbnail();
thumbnail.setData(data);
mainPanel.getChildren().add(thumbnail);
Geht man nun davon aus, dass die PreviewThumbnail-Komponente ein UI, wie das von YouTube, rendern kann, so ist der Anwendungsentwickler mit diesen paar Zeilen bereits fertig mit der Integration einer Thumbnail-Ansicht in eine View. Will man nicht mit einem spezialisierten Datenmodell arbeiten, kann man die GUI-Komponente auch so definieren, dass alle Attribute einzeln gesetzt werden können.
PreviewThumbnail thumbnail = new PreviewThumbnail();
thumbnail.setName("Name of Video");
thumbnail.setLink("www.youtube.com/VIDEO_ID");
thumbnail.setOwner("Hendrik Ebbers");
thumbnail.setOwnerLink("www.youtube.com/channel/USER_ID");
thumbnail.setViewCount(1638);
thumbnail.setCreationDate(…);
...
mainPanel.getChildren().add(thumbnail);
Natürlich soll hierbei nicht verheimlicht werden, dass innerhalb der PreviewThumbnail-Klasse beliebig komplexer Code zu finden sein kann. Der große Vorteil hierbei ist aber, dass dieser Code vor dem Anwendungsentwickler verborgen ist und somit der Aufbau von Views und Dialogen einfacher und leserlicher beschrieben werden kann. Eine komplette vertikale Liste von Vorschauelementen, wie man sie aus YouTube kennt, lässt sich hierdurch einfacher beschreiben. Nutzt man nun noch FXML zur Beschreibung von Layouts in JavaFX so würde die View wie folgt aussehen.
<VBox>
<children>
<PreviewThumbnail name="My Video" owner="Hendrik Ebbers" ... />
<PreviewThumbnail name="Funny Cats" owner="Junior" ... />
<PreviewThumbnail name="Minions 2 Trailer" owner="Franky" ... />
<PreviewThumbnail name="Learn Java" owner="Nelly" ... />
</children>
</VBox>
Spätestens jetzt kann man die Ähnlichkeit zu HTML sehen. Allerdings sieht der Aufbau sauberer aus und der Code lässt sich innerhalb kurzer Zeit lesen und verstehen. Der Grund hierfür ist, dass die Komplexität des Preview-Thumbnails innerhalb der Komponente gekapselt wird und von außen nicht ersichtlich ist. Durch Setzen der Attribute bekommt die Komponente allerdings alle benötigten Informationen geliefert und kann so problemlos gerendert werden. Durch Webkomponenten lässt sich genau dieser Ansatz auch für Webanwendungen nutzen und ermöglicht so, dass auch bei komplexen Anwendungen HTML besser zu lesen ist und somit das Erweitern oder Umbauen einer Anwendung einfacher ausfällt.
Zur Realisierung von Web Components wurde HTML um vier Spezifikationen erweitert [1]: HTML Templates, Shadow DOM, Custom Elements und HTML Imports. Diese vier neuen Features erlauben es in Summe, dass sich wiederverwendbare Komponenten auch für Webseiten einfach realisieren lassen.
Mit HTML Templates ist es möglich, wiederverwendbares HTML zu definieren. Hierfür wurde der neue Tag <template> eingeführt. Innerhalb dieses Tags können beliebige HTML-Inhalte definiert werden, die dann durch JavaScript beliebig oft zum DOM hinzugefügt werden können. Hier die Definition eines Templates:
<template id="activity-template">
<div><img class="icon" src="" width="40" height="40"><div class="time"></div><div class="content"></div>
</div>
</template>
Wird ein solches Template innerhalb einer HTML-Datei definiert, ist es im Browser erst einmal nicht zu sehen. Das Template kann aber in JavaScript referenziert und Kopien des Inhalts beliebig oft in das DOM kopiert werden.
var template = document.querySelector('#activity-template');
var clone = document.importNode(template.content, true);
document.body.appendChild(clone);
Hierdurch ist es zwar möglich, neue Elemente zu einem DOM hinzuzufügen, diese sind allerdings noch komplett statisch und verbessern die Lesbarkeit des Codes auch noch nicht wirklich. Wie aber bereits gesagt, werden alle vier Bestandteile der Web-Component-Spezifikation benötigt, um wirklich komplett wiederverwendbare und konfigurierbare eigene Komponenten zu entwickeln.
Daher schauen wir uns nun die zweite Spezifikation genauer an. Mit Shadow DOM ist es möglich, einen Bereich des DOM vor ungewollten Zugriffen zu schützen. Abbildung 3 zeigt das Beispiel für eine DOM-Hierarchie.
In diesem Beispiel soll der grüne Teil im rechten Bereich eine wiederverwendbare Komponente darstellen. Würde dessen Inhalt einfach als neue Subhierarchie an das vorhandene DOM gehängt, würden die Style Sheets der Anwendung den Inhalt der Komponente direkt beeinflussen. Dazu kommt, dass man Inhalte der Komponente einfach über JavaScript abfragen und mutieren könnte. In der Regel soll eine Komponente aber den Inhalt verbergen, um so die Komplexität zu verringern. Dazu kommt, dass ein Entwickler, der nichts über den Inhalt einer Komponente weiß, diese versehentlich durch CSS verändern könnte, indem er z. B. in der Anwendung die gleichen Style-Klassen benutzt wie interne Bestandteile der Komponente. In diesem Fall hat man ein Problem und müsste die CSS-Regeln wieder deutlich komplizierter definieren, um solche Konstellationen zu umgehen. Mit dem Shadow DOM kann man nun aber hergehen und einen Teil des DOM isolieren und von außen nicht mehr über die gängigen Wege zugänglich machen. Hierdurch kann man einzelne Komponenten in einer Sandbox definieren und es so zum Beispiel verhindern, dass CSS-Regeln auf das Styling der Komponente und deren interne Bestandteile durchschlagen. Hierbei wird, wie in Abbildung 4 gezeigt, der Inhalt der Komponente in eine neue Subhierarchie des DOM abgelegt, dem Shadow DOM.
Der Inhalt ist beim Rendern zwar voll sichtbar und wird auch im Browser angezeigt, CSS und JavaScript können aber nicht ohne Weiteres darauf zugreifen. Theoretisch kann man beliebigen Inhalt innerhalb eines Shadow DOM verbergen. Besonders interessant ist das Ganze aber, um eine Web Component zu kapseln. Durch ein JavaScript-Snippet wird ein neues Shadow DOM erstellt, das den Inhalt eines Templates füllt.
var root = host.createShadowRoot();
var clone = document.importNode(template.content, true);
root.appendChild(clone);
Die dritte benötigte Spezifikation zur Erstellung von Web Components sind die Custom Elements. Durch dieses Feature kann man neue Tags zur Nutzung in HTML definieren. So kann man einer Web Component eine eigenständige Bezeichnung geben. Wichtig ist hierbei, dass die Bezeichnung ein „-“ enthält, damit der Name nicht mit möglichen Standardtags kollidiert, die in Zukunft zu HTML hinzugefügt werden könnten. Durch Hinzufügen dieses dritten Features kommen wir den wiederverwendbaren Komponenten bereits deutlich näher, da es nun möglich ist, eine HTML View zu schreiben.
<div>
<video-thumb name="video name" owner="Han Solo" ... />
<video-thumb name="video name" owner="Luke Skywalker" ... />
<video-thumb name="video name" owner="Anakin Skywalker" ... />
</div>
Um das neue HTML-Tag zu registrieren, ist allerdings noch JavaScript-Code nötig. Listing 1 zeigt, wie ein Custom Element auf Basis eines Templates erstellt wird. Hierbei wird für jedes videothumb-Element im DOM das Template video-thumb-template geladen und innerhalb eines Shadow DOM an den DOM gehängt. Der Inhalt der beiden Attribute name und owner wird hierbei an innere Elemente weitergereicht.
Listing 1
var activityPrototype = Object.create(HTMLElement.prototype);
activityPrototype.createdCallback = function() {
var template = $("#video-thumb-template");
var clone = document.importNode(template.content, true);
var host = $(this);
$(".name", clone).text(host.attr("name"));
$(".owner", clone).text(host.attr("owner"));
var shadow = this.createShadowRoot();
shadow.appendChild(clone);
};
document.registerElement("video-thumb", {prototype: activityPrototype});
Somit können wir auf Basis von Templates, dem Shadow DOM und Custom Elements wirkliche wiederverwendbare Komponenten in HTML definieren, die wir beliebig oft auf einer HTML-Seite verwenden und auch durch Attribute konfigurieren können.
Um die Komponenten nun aber anwendungsweit oder auch über mehrere Applikationen nutzen zu können, werden noch die HTML Imports benötigt. Dieses Feature erlaubt es, Bestandteile einer HTML-Datei auszulagern und diese einfach zu importieren. Definiert man so alle zu einer Web Component gehörenden Bestandteile – Definition des Templates und der JavaScript-Code zum Erstellen des Custom Elements – in einer eigenen HTML-Datei, so kann man diese leicht über einen link-Tag in jede beliebige andere HTML-Datei importieren.
<link rel="import" href="components/video-thumb.html">
Schaut man sich die Verfügbarkeit dieser Features in aktuellen Browserversionen an (Kasten „Browsersupport“), so wird man sehen, dass die Hersteller hier noch viel nachliefern müssen. Allerdings gibt es unter [2] ein Polyfill, das die Features bereits heute für alle gängigen Browserversionen nachliefert. Der Nutzung von Web Components steht also eigentlich nichts mehr im Weg.
Noch nicht alle Browser unterstützen die vorgestellten Features. Tabelle 1 zeigt hierbei eine Übersicht aller beschriebenen Features und ihren Support in den gängigen Browsern. Eine grüne Zelle bedeutet, dass das Feature komplett von der aktuellen Version des Browsers unterstützt wird. Rot bedeutet, dass das Feature überhaupt nicht oder nicht vollständig unterstützt wird. In diesem Fall kann man auf Polyfills des jeweiligen Features zurückgreifen, falls diese vorhanden sind. Möchte man mehr zu weiteren Features erfahren oder den Support eines Features für eine spezielle Browserversion überprüfen, kann ich die Nutzung von http://caniuse.com empfehlen.
IE |
Edge |
Firefox |
Chrome |
Safari |
Opera | |
---|---|---|---|---|---|---|
HTML Templates | ||||||
Shadow DOM | ||||||
Costum Elements | ||||||
HTML Imports | ||||||
FlexBox | ||||||
Grid | ||||||
CSS-Variable | ||||||
Picture |
Tabelle 1: Featureübersicht inklusive Browsersupport
Ich denke, so ziemlich jeder Webentwickler hat schon geflucht, sobald es darum ging, eine Webseite mit CSS zu layouten. Hierbei ist nicht das Stylen von einzelnen Komponenten durch die Definition der Hintergrundfarbe oder Fontsize gemeint. Vielmehr geht es darum, die einzelnen Bestandteile einer Webseite zueinander auszurichten und zu positionieren. Das Ganze am besten natürlich noch unter Berücksichtigung von Responsive Design, sodass das Ergebnis auf allen Bildschirmauflösungen gut aussieht. Dies ist auf jeden Fall auch heute noch eine schwierige Aufgabe. Aber auch in diesem Bereich tut sich einiges. Hier stechen vor allem zwei neue CSS-Layouts hervor, die das Positionieren von Elementen in Zukunft einfacher gestalten sollen: Flexbox und Grid. Während Flexbox bereits seit einiger Zeit von so ziemlich allen wichtigen Browsern implementiert ist, steckt Grid noch in den Babyschuhen und läuft aktuell nur auf einem Nightly Build des Chrome-Browsers.
Das CSS-Flexbox-Layout gibt es schon einige Zeit. Allerdings habe ich das Gefühl, dass es erst in der letzten Zeit wirklich bei den Entwicklern ankommt und genutzt wird. Das Layout ist durch seine hohe Konfigurierbarkeit extrem flexibel und ermöglicht es, verschiedene Darstellungen einfach umzusetzen. Bei den Layoutmöglichkeiten mag einem JavaFX-Entwickler folgender Vergleich helfen: Mit dem Flexbox-Layout lässt sich sehr einfach ein HBox-, VBox- oder Flow-Layout umsetzen. Initial ordnet das Layout einfach alle Kindelemente horizontal an. Eine vertikale Ausrichtung lässt sich durch die verschiedenen Properties der Flexbox ebenfalls konfigurieren. Gehen wir einmal davon aus, dass wir folgende HTML-Definition vorliegen haben.
<div class="parent">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
Wie man sehen kann, gibt es ein Hauptelement mit der Style Class parent, das mehrere Kindelemente mit der Style Class item enthält. Setzt man nur ein paar Style-Informationen, um Font und Farbe der Komponenten anzupassen, so sieht das Ergebnis im Browser wie in Abbildung 5 aus.
Durch folgendes CSS kann nun einfach Flexbox als Layout für das Element definiert werden.
.parent {
display: flex;
}
Das dazugehörige Ergebnis kann man in Abbildung 6 sehen.
Wie man sehen kann, passen sich nun alle Kindelemente durch Nutzung der Flexbox horizontal in ihrem Parent an. Hierbei füllen die Kindelemente den Parent im Idealfall bis zu ihrer Weite aus, werden allerdings nie größer als ihre definierte Weite. Ist dieses Verhalten nicht erwünscht, kann man durch Konfiguration der Flexbox einen automatischen Umbruch der Kindelemente definieren. Die Kindelemente nehmen in diesem Fall ihre definierte Weite an. Sobald ein neues Kindelement nicht mehr in den Parent passt, wird es in die folgende Zeile verschoben. Hierdurch ist es einfach möglich, ein flexibles Grid zu designen, wie man es aus Desktop- und Mobile-Anwendungen kennt. Das Ganze ist hierbei mit dem GridFX für JavaFX oder dem GridView in Android vergleichbar und eignet sich perfekt, um dynamische Listen von grafischen Elementen zu visualisieren. Um das Ganze umzusetzen, muss unser CSS angepasst werden.
.parent {
display: flex;
flex-wrap: wrap;
}
Die flex-wrap Property wird hierbei genutzt, um den Umbruch zu ermöglichen. Neben dem Standardwert nowrap und dem hier genutzten Wert wrap kann man die Property auch auf wrapreverse setzen, um so die automatischen Umbrüche nicht in die folgende Zeile nach unten, sondern nach oben durchzuführen. Abbildung 7 zeigt die Unterschiede.
Neben der Konfiguration der Hauptkomponenten kann man mit Flexbox auch das Layout einzelner Kindelemente konfigurieren. Als Beispiel sollen hier zusätzliche dieser CSS-Regeln genutzt werden.
.item:nth-child(2) {
flex-grow: 1;
}
.item:nth-child(4) {
order: -1;
}
Die flex-grow Property kann hierbei genutzt werden, um die Größenverhältnisse der Kindelemente zu beeinflussen. Der Wert definiert hierbei das Verhältnis der Komponenten zu den anderen Komponenten in der Flexbox. Da der Standardwert 0 ist, hat das zweite Kindelement – definiert durch nth-child(2) – mit einem flex-grow-Wert von 1 eine höhere Priorität beim Ausfüllen des vorhandenen Bereichs der Elternkomponente.
Über Definition der order Property kann man die Reihenfolge der Komponenten innerhalb der Flexbox beeinflussen. Auch hier ist 0 der Default-Wert. Und da ohne spezifische Definition der Property somit alle die gleiche Order haben, werden sie wie im HTML angegeben angeordnet. Im hier gezeigten CSS wird nun die order Property für das vierte Element auf -1 gesetzt, wodurch das Element eine geringere Order als alle anderen hat und somit vor den restlichen Komponenten angezeigt wird. Abbildung 8 zeigt das Ergebnis mit den neuen CSS-Regeln im Browser.
Neben den hier beschriebenen Konfigurationsmöglichkeiten bietet Flexbox noch deutlich mehr Optionen. So kann man das Basislayout von einer zeilenbasierten Ausrichtung in eine spaltenbasierte Ausrichtung umschalten. Durch zusätzliche Nutzung von Media Queries kann man so bereits sehr dynamische und responsive Layouts erstellen, die auch mit einer dynamischen Anzahl von Kindelementen gut umgehen können. Wer sich spielerisch mit den Möglichkeiten von FlexBox beschäftigen möchte, dem kann ich Flexbox Froggy [3] empfehlen. Ansonsten gibt es online mittlerweile zahlreiche Dokumentationen und Videos, welche die verschiedenen Möglichkeiten der Flexbox aufzeigen. Flexbox wird zum Beispiel bei der Polymer-Paper-Komponentenbibliothek intern genutzt, um einfache Layouts von Anwendungen zu ermöglichen.
Eine weitere neue Möglichkeit für Layouting über CSS ist das Grid. Viele werden Grid-Layouts sicherlich bereits von Bibliotheken wie Bootstrap kennen. Allerdings hat das neue CSS-Grid-Modul wenig mit dieser Art von Layouts gemein. Das aus Bootstrap basierte Layout definiert eine spaltenbasierte Positionierung von Komponenten. Hierbei wird der zur Verfügung stehende Bereich z. B. in zwölf Spalten aufgeteilt. Und eine Komponente kann beliebig viele dieser Spalten in der Breite einnehmen. Die Höhe der Komponente wird durch das Layout nicht vorgegeben. Das neue CSS-Grid-Layout kann vielmehr mit einem Grid-Bag-Layout verglichen werden, wie man es z. B. aus Swing oder JavaFX kennt. Hierbei wird der zur Verfügung stehende Bereich in ein wirkliches Grid aus Zeilen und Spalten aufgeteilt. Die Komponenten werden nun innerhalb des Grids positioniert. Darüber hinaus gibt es noch mehr Unterschiede zu den aus Bootstrap und Co. bekannten Layouts: Musste man bei Bootstrap die Komponenten mit den durch Bootstrap definierten Style-Klassen versehen, um die Position festzulegen, kann man beim neuen Grid-Layout einfach die komplette Positionierung im CSS definieren. Im Folgenden gehen wir einmal davon aus, dass das HTML durch das Grid-Layout ausgerichtet werden soll.
<div class="wrapper">
<div class="header">...</div>
<div class="menu">...</div>
<div class="content">...</div>
<div class="footer">...</div>
</div>
Um das Grid nutzen zu können, muss man im Elternelement die display Property auf grid setzen. Nun kann man durch die beiden CSS Properties grid-template-columns und gridtemplate-rows ein Grid definieren. Die CSS-Regel für die Hauptkomponente ist:
.wrapper {
display: grid;
grid-template-columns: 120px 500px;
grid-template-rows: 100px 400px 48px;
}
Das CCS spannt ein Grid über zwei Spalten und drei Zeilen auf. Hierbei werden für alle Spalten und Zeilen direkt die jeweiligen Größen angebenden. Hier ist es auch möglich, den auto-Wert anstelle eines fest definierten Werts für eine Spalte oder Zeile zu nutzen. Um das Grid nun zu füllen, müssen für die einzelnen Kindelemente ihre Position im Grid angegeben werden. Hierzu können die CSS Properties grid-column und grid-row genutzt werden. Diese definieren für jede Komponente, in welchen Zeilen oder Spalten sie positioniert werden soll. Die Dimensionen einer Komponente können hierbei problemlos über mehrere Spalten oder Zeilen aufgespannt werden. In diesem Fall definiert man die Startspalte (inklusive) und die Endspalte (exklusiv). Listing 2 zeigt, wie das CSS für das gezeigte Beispiel aussieht.
Listing 2
.header {
grid-column: 1 / 3;
grid-row: 1;
}
.menu {
grid-column: 1;
grid-row: 2;
}
.content {
grid-column: 2;
grid-row: 2;
}
.footer {
grid-column: 1 / 3;
grid-row: 3;
}
Abbildung 9 zeigt das Ergebnis des Layouts. Zur besseren Darstellung wurden hier noch ein paar zusätzliche Stylinginformationen definiert, wie Abstände oder die Hintergrundfarbe. Im hier gezeigten Beispiel definiert die Hauptkomponente ein Grid, und alle Kindelemente definieren per CSS-Regel, in welchen Zellen sie sich befinden.
Neben dieser Art der Konfiguration kann man die Positionierung von Kindelementen auch direkt in der CSS-Regel der Hauptkomponente definieren. Hierbei wird die grid-template-areas Property genutzt, die es ermöglicht, den Inhalt des Grids auf Basis beliebiger Komponentenbezeichnungen zu definieren. Durch die grid-area Property kann man in den CSS-Regeln den Kindelementen dann eine Bezeichnung zuweisen. Listing 3 zeigt, wie dies für das Beispiel aussehen würde.
Listing 3
.wrapper {
display: grid;
grid-template-columns: 200px 400px;
grid-template-rows: 100px 400px 100px;
grid-template-areas: "header header" "menu content" "footer footer";
}
.header {
grid-area: header;
}
.menu {
grid-area: menu;
}
.content {
grid-area: content;
}
.footer {
grid-area: footer;
}
Aktuell wird das CSS Grid leider von noch keinem Browser voll unterstützt. Nur der Chrome-Browser unterstützt das Grid aktuell als experimentelles Feature. Hierzu muss man „Experimental Web Platform Features Flag“ im Chrome aktivieren. Die Übersicht aller Feature-Flags von Chrome kann man erreichen, indem man chrome://flags/ im Chrome öffnet. Es gibt allerdings auch ein Polyfill des Features, das Grids bereits in aktuellen Browsern ermöglicht. Das Polyfill kann unter [4] gefunden werden. Unter [5] kann man eine Dokumentation des Grids mit verschiedenen Beispielen finden.
Wenn man eine große Webanwendung oder sogar sein eigenes UI-Set für verschiedene Anwendungen entwickeln möchte, kommt es im CSS-Bereich oft zu Problemen bei der Wartbarkeit des Codes. Oft wird man in den CSS-Dateien die gleichen Informationen an verschiedenen Stellen finden. Wenn man z. B. ein spezielles Farbschema definiert, so muss die Basisfarbe für eigentlich jede Komponente gesetzt werden. Dies führt zu langen und unleserlichen CSS-Regeln. Möchte man das Farbschema nun ändern, kann man in der Regel nur mit „find and replace“ arbeiten, indem man den Farbwert von brown nach pink an allen Stellen im Style Sheet ändert. Dies ist einer der Gründe, warum CSS-Präprozessoren wie Less oder Sass die Nutzung von Variablen eingeführt haben. Dieses Feature wird nun auch für CSS spezifiziert, um auf der einen Seite Informationen an einer zentralen Stelle zu verwalten und es zusätzlich zu ermöglichen, eigene CSS Properties zu definieren. Die so definierten Properties eigenen sich besonders in Verbindung mit Webkomponenten. Da man so auf einfache Art und Weise Komponenten bauen kann, deren Stylingmöglichkeiten durch eigenständige Properties definiert werden können. Schauen wir uns das Beispiel von CSS-Variablen einmal genauer an.
.header {
color: white;
background-color: brown;
}
.content {
color: brown;
}
.footer {
background-color: brown;
}
Das gezeigte Beispiel könnte ein Ausschnitt aus einem CSS sein, das ähnlich wie Bootstrap einen kompletten und wiederverwendbaren Style für Anwendungen definiert. In diesem Fall kann man sehen, dass die Farbe Braun offenbar eine der Basisfarben dieses Themes ist. Würde man die Farben des Themes ändern wollen, so muss man beim gezeigten Beispiel alle Stellen suchen, in denen brown als Farbe gesetzt ist und diese abändern. Durch CSS-Variablen kann man die gewünschten Informationen in einer globalen Variable definieren. Alle weiteren Regeln greifen dann intern auf diese Variable zu. So muss man die spezifische Farbe nur noch an einer einzigen Stelle ändern.
:root {
--main-bg-color: brown;
}
.header {
color: white;
background-color: var(--main-bg-color);
}
.content {
color: var(--main-bg-color);
}
.footer {
background-color: var(--main-bg-color);
}
In den gezeigten CSS-Regeln wird mit --main-bg-color bereits eine neue CSS Property definiert. Jede so definierte Property muss mit -- beginnen. Der Support von Custom Properties eignet sich allerdings nicht nur, um globale Variablen, sondern auch, um Komponenten besser zu definieren. Im folgenden Beispiel soll ein Button mit einem Icon definiert werden. Hierzu wird nun auf Basis der Web-Component-Spezifikation eine eigenständige Komponente erstellt. Da für die Komponente das Icon immer die gleiche Farbe wie der Text des Buttons haben soll, könnte man die internen CSS-Regeln der Komponente wie folgt definieren.
.iconButton {
background: var(--Button-backgroundColor);
border: 1px solid var(--Button-foregroundColor);
color: var(--Button-foregroundColor);
}
.iconButton .icon {
--Button-color: var(--Button-foregroundColor);
}
Basierend auf diesem CSS müssen nur noch die Werte für --Button-foregroundColor und --Button-backgroundColor gesetzt werden, um die Komponente innerhalb einer Anwendung zu stylen. Da Custom Properties innerhalb des DOM vererbt werden können, ist es kein Problem, spezielle Styles für einen bestimmten Kontext zu definieren.
Zuletzt möchte ich noch ein neues HTML-Element vorstellen, das den Weg in den HTML-Standard gefunden hat: das picture-Element. Mit diesem Element ist es möglich, einfach Bilder für verschiedene Devices und Auflösungen zu definieren. Durch Monitore mit immer höheren Auflösungen kam bei Webseiten schnell das Problem auf, dass Bilder entweder zu klein sind oder auf modernen Monitoren extrem verpixelt aussehen. Für die Retinadisplays von Apple gibt es daher auf vielen Webseiten alle genutzten Bilder in zwei Varianten: einmal für normale Displays und einmal für Retinadisplays. Bisher musste man für diesen Fall allerdings den Inhalt eines HTML-img-Elements händisch austauschen. Hierfür gibt es verschiedene JavaScript Libraries. Mit dem picture-Tag können nun aber einfach verschiedene Auflösungen für ein Bild hinterlegt werden. Listing 4 zeigt ein Beispiel, in dem das picture-Element genutzt wird. Hierbei wird nicht nur zwischen verschiedenen DPI unterschieden, sondern es werden auch basierend auf einer Media Query verschiedene Bilder abhängig von der Browsergröße gewählt. Hierdurch eignet sich das picture-Element auch ideal für Responsive Design.
Listing 4
<picture>
<source
media="(max-width: 992px)"
srcset="images/logo-mobile.png,
images/logo-mobile@1.5x.png 1.5x,
images/logo-mobile@2x.png 2x">
<img
src="images/logo-desktop.png"
srcset="images/logo-desktop@1.5x.png 1.5x,
images/logo-desktop@2x.png 2x"
alt="a cute kitten">
</picture>
Wie man im Kasten Browsersupport sehen kann, werden viele der Features noch nicht von heutigen Browsern unterstützt. Für die meisten gibt es allerdings Polyfills, die es bereits heute erlauben, die Features zu nutzen. Polymer [6] basiert z. B. auf einem Großteil der hier vorgestellten Features. Mit Polymer hat Google ein Framework zur einfachen Nutzung von Web Components entwickelt. Darüber hinaus bietet Polymer mit Polymer Paper eine komplette UI Library, die den Material-Design-Style für das Web bereitstellt. Intern basiert Polymer auf den Spezifikationen für Web Components und stellt diese durch Nutzung von Polyfills bereits heute zur Verfügung. Die in Polymer Paper definierten Komponenten können bereits durch Nutzung von CSS-Variablen und Custom CSS Properties gestylt werden. So besitzt die Polymer Paper Toolbar die Property --paper-toolbar-background, die in einem externen CSS zum Anpassen und Stylen der Toolbar definiert werden kann. Gegenüber den hier beschriebenen Spezifikationen, die teilweise etwas komplex daher kommen, bietet Google Polymer ein darauf basierendes API, das die Entwicklung von Komponenten deutlich vereinfacht.
Hendrik Ebbers ist Java-Entwickler bei der Canoo Engineering AG. Sein Hauptinteresse liegt hierbei in den Bereichen JavaFX, User Interfaces und Middleware. Hendrik leitet die JUG Dortmund. Auf seiner Webseite bloggt er regelmäßig über Architekturansätze im Bereich JavaFX und zu seinen verschiedenen Open-Source-Projekten wie Dolphin Platform oder DataFX. Sein Buch „Mastering JavaFX 8 Controls“ ist letzten Sommer bei Oracle Press erschienen. Hendrik ist JavaOne Rockstar, Java Champion und JCP Expert Group Member.
[1] Web Components Current Status: http://bit.ly/1PUXimm
[2] Polyfills: http://bit.ly/23vHIYi
[3] Flexbox Froggy: http://bit.ly/1Trwkq6
[4] CSS Grid Polyfill auf GitHub: http://bit.ly/1ROTmJE
[5] Grid by Example: http://bit.ly/1ORzouc
[6] Polymer Project: http://bit.ly/1sposKe
Die Latenz ist ein häufig unterschätzter Faktor der Performance von Websystemen. Lokal bei der Entwicklung nicht bemerkbar, kumulieren sich die Laufzeiten über die Distanz. Insbesondere bei Websystemen, bei denen die globale Verfügbarkeit oft als Vorteil ins Feld geführt wird, können so lange Laufzeiten entstehen. Wir erläutern die Hintergründe und zeigen Methoden zur Verbesserung auf.
Die Latenz wird durch die räumliche Distanz, das verwendete Protokoll und das Antwortverhalten unseres Websystems beeinflusst. Üblicherweise misst man sie in Millisekunden und unterscheidet zwischen One-Way- und Round-Trip-Messungen. Letztere sind einfacher durchzuführen, da man sich nicht um die Synchronisation der Uhren zwischen den beiden Messpunkten kümmern muss. Zudem enthält die Round-Trip-Messung auch die Antwortzeit unseres Websystems, sodass wir eine Zahl erhalten, die dem entspricht, was der entfernte Benutzer auf seinem Client erlebt. Zwei wesentliche Variablen spielen hier mit: die Kapazität unseres Systems sowie die individuelle Perspektive des Benutzers aufgrund der räumlichen Verteilung. Abbildung 1 zeigt die Konzepte rund um die Latenz, die wir in diesem Artikel besprechen.
„Man muss versuchen, bis zum Äußersten ins Innere zu gehen, denn der Feind des Menschen ist die Oberfläche.“ Samuel Beckett
Die Latenz von Kommunikationswegen wird maßgeblich vom Prt die Regeln für den Austausch von Informationen innerhalb eines Netzwerks. Ein Web Service verwendet verschiedene Protokolle, um mit den Umsystemen zu kommunizieren. Beim Aufruf eines URLs wird immer DNS verwendet, um die Adresse aufzulösen und HTTP oder HTTPS für die Kommunikation zwischen Browser und Web Service. Falls der Web Service mit einer Datenbank kommuniziert, wird dabei wieder ein spezifisches Protokoll verwendet (z. B. das MySQL-Protokoll [1]). Um den Einfluss von Protokollen auf die Performance zu verstehen, diskutieren wir zunächst den Unterschied von HTTP zu HTTP/2 und besprechen folgend das Vorgehen zur Messung von Latenz über verschiedene Protokolle.
Die erste Version des HTTP-Protokolls wurde 1997 publiziert, 2015 folgte HTTP/2, eine optimierte und verbesserte Version des Protokolls. Sie beinhaltet viele performancerelevante Verbesserungen basierend auf den Erfahrungen aus dem SPDY-Protokoll (Chromium-Projekt [2]). In HTTP/1.1 muss der Browser jeweils eine initiale Abfrage senden, um das HTML-Dokument zu erhalten. Danach werden sukzessive Anfragen für Ressourcen (z. B. CSS, JS und Bilder) einzeln gestartet. Ein Browser startet heute mehrere TCP-Verbindungen (vier bis acht) zu einem Webserver und verteilt die Anfragen auf die geöffneten Verbindungen. Dabei kommt es zu dem Head-of-line-Blocking-Problem, bei dem sich nur eine Anfrage pro Verbindung verarbeiten lässt. Entsprechend kann diese Verbindung den Fluss blockieren. Ebenfalls muss für die Verbindungen jeweils ein Handshake durchgeführt werden. Abbildung 2 zeigt ein entsprechendes Wasserfalldiagramm.
HTTP/2 optimiert die Anzahl der Verbindungen und den Handshake Overhead, indem eine TCP-Verbindung pro Host für mehrere Anfragen und Antworten verwendet werden kann (Multiplexing [3]). Das Protokoll erlaubt, dass mehrere Nachrichten miteinander vermischt versendet werden können. Eine einzelne TCP-Verbindung reduziert nicht nur die Anzahl der Handshakes, sondern bringt auch einen weiteren wichtigen Vorteil: Eine TCP-Verbindung ermittelt über den so genannten Slow Start [4] eigenständig die Übertragungsrate. Beim Slow Start beginnt die Übertragungsrate auf einem niedrigen Niveau und wird fortlaufend erhöht. Mit Multiplexing muss dieser Slow Start nur einmal ausgeführt werden.
Damit der Wasserfalleffekt reduziert werden kann, muss der Browser Heuristiken anwenden, die versuchen, die Abfragen der Ressourcen zu verteilen. In HTTP/2 können Ressourcen priorisiert werden [2], und der Browser kennzeichnet Abhängigkeiten zwischen Anfragen, um die Bandbreite maximal auszunutzen. Zum Beispiel ist es sinnvoll, dass zuerst CSS-Dateien geladen werden, damit der Browser bereits mit dem Rendering starten kann, bevor die Bilder einer Seite heruntergeladen werden.
Das HTTP-Protokoll beinhaltet einen ausführlichen Header, der jeweils über verschiedene Anfragen und Antworten ähnliche Felder beinhaltet. Zum Beispiel wird bei jeder Anfrage der User-Agent (Browserinformation) im Header festgehalten, oder es werden Cookies übermittelt. Das HTTP-Protokoll ist zustandslos implementiert, und es werden keine Informationen zur Kommunikation gespeichert. Mit HTTP/2 wird zumindest während einer Verbindung der Header zwischengespeichert, und somit lassen sich die redundanten Felder komprimieren.
Eine weitere Änderung in HTTP/2 ist das Push-Verfahren. Dabei kann der Server Ressourcen an den Client senden, die er noch nicht angefragt hat. Wie oben beschrieben, muss der Browser initial ein HTML herunterladen, dieses analysieren und dann die referenzierten Ressourcen abfragen. Der Server kann nun bereits die Ressourcen an den Browser senden, obwohl dieser noch das HTML analysiert und nicht weiß, dass die Ressourcen benötigt werden.
Die Performanceeigenschaften eines Protokolls zu verstehen, ist also wichtig, wie das Beispiel von HTTP/2 zeigt. Spannend erweisen sich diese Optimierungen vor allem in der Kommunikation mit mobilen Endgeräten, wo die Netzwerklatenz stark variiert und entsprechend minimiert wird. Es ist jedoch wichtig, sich bei der Performanceanalyse einer Applikation nicht nur auf das Protokoll zu fokussieren, sondern auch auf weitere klare Indizien, die auf Netzwerklatenz hindeuten.
HTTP ist ein spezieller Fall, da wir keinen Einfluss auf die Infrastruktur unserer Benutzer nehmen können. Wenn wir beispielsweise Latenzprobleme zwischen unserem Web Service und der MySQL-Datenbank erkennen, können wir die Latenz reduzieren, indem wir die physikalische Distanz verkürzen. Natürlich bietet das MySQL-Protokoll noch weitere Optionen, unter anderem die Komprimierung. Diese kann unsere Payload auf Kosten der CPU-Auslastung verkleinern. Diese Option ist nützlich, wenn sehr große Result-Sets von MySQL ausgeliefert werden.
Die geografische Verteilung hat den größten Einfluss auf die Latenz. Es kann aber auch sein, dass im lokalen System nicht nur der Administrator eine lange Leitung hat. Es sind immer wieder die eingebundenen externe Dienste, die uns unsere Performancesuppe versalzen: mit langsamen Antwortzeiten, also langen Round Trips. Um diese Probleme früh zu erkennen, lohnt sich ein Latenzdiagramm. Abbildung 3 zeigt ein solches Diagramm, in dem die beteiligten Bausteine, Protokolle und deren Latenzen abgebildet sind. Daraus lässt sich einfach die gesamte Latenz eines Zugriffs mittels Addition ableiten. Im Diagramm sind jeweils das Protokoll und die Latenz in Millisekunden angegeben.
Das Latenzdiagramm kann aus der Bausteinsicht unseres Websystems erstellt werden. Im Beispiel sind ein ESB und ein PIM eingezeichnet, die auf einer E-Commerce-Website ihre Dienste tun. Die Latenzen zum Zugriff wurden per Curl gemessen, die Verbindung zur MongoDB per Socket durch etwas handgestrickten PHP-Code im Akeneo. Bei der Messung per Curl wird ein statisches Asset abgerufen, damit die Geschwindigkeit der Webanwendung nicht das Ergebnis verschmutzt.
Ein solches Diagramm wie in Abbildung 3 zeigt eine stark vereinfachte, da statische Sicht der Dinge. Im Produktivbetrieb können externe Dienste, aber auch die eigene Infrastruktur, ihr Verhalten ändern. Dennoch erhöhen regelmäßige Messungen die Zuverlässigkeit unserer Architektur. So haben wir bereits Dienste aus dem synchronen Zugriff genommen, da sonst die Latenz zu groß gewesen wäre, denn das hätte rückläufige Besucherzahlen bedeutet. Wenn ein externer Dienst bis zu einer Minute benötigt, um eine Anfrage zu bearbeiten, dann ist man als Architekt hilflos und muss einen Workaround finden.
Innerhalb eines Rechenzentrums kann auch die physische Distanz eine Rolle spielen, denn Latenzen summieren sich. Wenn ein Seitenaufbau beispielsweise einige tausend DB-Abfragen benötigt, so potenziert sich jede Millisekunde zusätzliche Latenz zu einer spürbaren Wartezeit. In einem solchen Fall haben sie aber noch ganz andere Probleme, denn sie sollten zunächst mit dem Entwicklerteam sprechen, das die Seite gebaut hat.
Die geografische Nähe zum Benutzer ist ein wesentlicher Faktor für die Geschwindigkeit einer Webapplikation. Die durch Distanz eintretende Verzögerung der Netzwerkverbindung lässt sich dadurch optimieren, dass sich die Anzahl benötigter Round Trips zur Webapplikation verringert. Die eigentliche Latenz lässt sich aber nicht verbessern – denn die Lichtgeschwindigkeit ist eine physikalische Grenze.
Die Webapplikation näher an den Benutzer zu bringen, ist der einzige Weg für Verbesserung. Ein erster Schritt kann ein Reverse Proxy in Asien sein, der Inhalte zwischenspeichert. Ein virtueller Server lässt sich heute in vielen Regionen der Welt dank Cloud Computing einfach und schnell mieten. Bei steigendem Traffic oder der Expansion in andere Länder muss die immer komplexer werdende Infrastruktur kontinuierlich angepasst werden, was in den meisten Fällen weder aus wirtschaftlicher noch betrieblicher Sicht Sinn ergibt.
Ein Content Delivery Network (CDN), wie in Abbildung 4 dargestellt, ist im Prinzip genau das: ein weltweit verteilter Verbund aus Reverse-Proxy-Servern. Diese Reverse-Proxy-Server werden im Context von CDN häufig als Points of Presence (POP) bezeichnet, unser Websystem ist dabei der so genannte Origin. Akamai nennt die POPs auch Edge-Server.
Der CDN-Provider sorgt hierbei dafür, dass ein Benutzer immer über den geografisch nächsten POP geroutet wird. Der POP prüft, ob die angeforderte Ressource im Cache vorhanden ist, lädt diese bei Bedarf vom Origin und liefert diese schlussendlich aus. Der Origin kann das Verhalten des CDN über die Verwendung der passenden HTTP-Header für Caching und Time to Live (TTL) steuern. Das ist handelsübliche Technik, jedoch muss sich die Entwicklung genaue Gedanken über die richtigen Cache-Header machen, damit keine veralteten Ressourcen beim Benutzer landen.
Damit die Ressourcen über das CDN ausgeliefert werden, müssen sie richtig verlinkt werden. In unserem Beispiel ist unser Origin unter dem URL http://www.foo.com erreichbar. Der CDN-Provider stellt uns einen speziellen URL http://foo.cdn.com zur Verfügung, der auf das CDN zeigt. Die Webapplikation wird anschließend entsprechend konfiguriert, sodass alle statischen Assets über diesen URL verlinkt werden. Diese Requests landen damit automatisch auf dem CDN und werden gecacht.
Die Auslieferung von statischen Assets via CDN ist der klassische Anwendungsfall. Der initiale Request wird vom Origin beantwortet, die restlichen Requests für JavaScript, Bilder und CSS kommen vom CDN. Mit diesem Verfahren lässt sich eine Webapplikation global in vielen Fällen mit einer angemessenen Geschwindigkeit ausliefern. Zudem lassen sich große Datenmengen (Dowloads, Videos, Bilder etc.) effizient verteilen, da ein CDN durch das Caching auch automatisch den Origin in Sachen Bandbreitenbedarf und Traffic entlastet.
Der initiale Request auf http://www.foo.com wird damit aber nicht beschleunigt, die Latenz für den ersten Request bleibt hoch. Bei Webapplikationen mit wenig dynamischen Inhalten kann eine mögliche Lösung sein, dass der ganze Traffic durch das CDN geroutet wird. Dabei wird www.foo.com im DNS als CNAME Record auf foo.cdn.com eingetragen. Dadurch landen alle Requests auf dem CDN, und es können neben den statischen Assets auch ganze HTML-Seiten gecacht werden. In diesen Set-ups sollten sich Inhalte auf dem CDN gezielt invalidieren lassen, wenn sie veraltet sind – beispielsweise wenn die Redaktion eine neue Seite publiziert. Alle Anbieter haben für diesen Zweck ein API, das aus der Webapplikation angesprochen werden kann.
Webapplikationen mit hochdynamischen Inhalten sind heute eher die Regel, weshalb ganze HTML-Seiten selten gecacht werden können. Trotzdem kann es aus Sicht der Geschwindigkeit sinnvoll sein, den Traffic durch das CDN zu routen und zwar ohne Caching. Die CDN-Provider setzen dazu spezielle Techniken ein und behalten die TCP-Verbindungen von POP zu Origin permanent offen. Bei dynamischen Inhalten geht der Request dann zwar vom Browser via POP zur Origin, die mühsamen Round Trips für das Öffnen der TCP-Verbindung entfallen aber.
Wenn man sich für den Einsatz eines CDN entschieden hat, muss ein Anbieter evaluiert werden. Dabei gibt es verschiedene Kriterien, die je nach Einsatzzweck unterschiedlich gewichtet werden müssen. Grundlegend ist sicherlich, dass der Anbieter eine gute geografische Abdeckung im Gebiet hat, wo der Großteil der Benutzer zu finden ist. Ist das nicht der Fall, kann das CDN seinen eigentlichen Nutzen nicht erfüllen. Ein weiterer Punkt bei der Evaluation ist die allgemeine Funktionalität: Wie genau kann man das Caching steuern? Wie können Inhalte invalidiert werden? Und nicht zuletzt: Gibt es Möglichkeiten für Reportings und sonstige Auswertungen? Zu guter Letzt muss eine Kosten-Nutzen-Rechnung gemacht werden. Viele CDN-Anbieter rechnen die Kosten nach Traffic-Volumen ab. Das bedeutet nichts anderes, als dass die Kosten für ein CDN linear mit der Nutzung steigen.
Neben Optimierungen im Bereich Netzwerk und der physikalischen Distanz können wir weitere Faktoren beeinflussen. Wenn wir die Interaktion eines Benutzers mit unserem Web Service betrachten, dann können wir zwischen einer Pull- und Push-basierten Verbindung unterscheiden. Als gutes Beispiel dient hier der Livesupportchat. Ein solcher Chat sollte neue Nachrichten anzeigen, sobald diese abgesendet sind und nicht erst in einem bestimmten Poll Interval angefragt werden. Diese Echtzeitkommunikation kann z. B. mit einer WebSocket-Implementierung [5] umgesetzt werden.
WebSocket öffnet eine TCP-Verbindung zwischen Server und Client und erlaubt beiden Parteien gleichzeitig den Datenaustausch. Die Verbindung bleibt offen, auch wenn keine Daten übermittelt werden und schafft damit eine neue Herausforderung für das Backend: keep-alive auf vielen parallelen Verbindungen ohne wirkliche Übertragung.
Das Pull-Verfahren kann ebenfalls Daten nahe der Echtzeit anzeigen, indem der Server in einem bestimmten Intervall angefragt wird, ob Änderungen zur Verfügung stehen. Im Vergleich zur Push-Variante erzeugen wir bei Pull viel mehr Anfragen und somit auch mehr Last auf dem Backend. Eine solche Lösung skaliert nur sehr schlecht mit einer zunehmenden Anzahl an Benutzern und niedrigem Pull Interval. Die Methode lässt sich beispielsweise mit einem Web Worker [6] umsetzten. Der Web Worker ist ein Hintergrundprozess im Browser, unabhängig vom Page Rendering und dem JavaScript-Code und wird ausgeführt, solange sich der Benutzer auf der Seite befindet.
Die folgenden Begriffe werden bei der Diskussion von Performance genutzt. Die Literatur und der Sprachgebrauch sind nicht konsistent. Man sollte also bei Dokumentationen und Spezifikation die verwendeten Begriffe genau erläutern.
Eine Page View bezeichnet das Laden einer Seite mit allen benötigten Ressourcen. Ein Request ist der Abruf einer einzigen Ressource. Eine einzelne Page besteht immer aus einem einzigen HTTP-Request für das HTML sowie mehreren Requests für alle von der Seite für die Darstellung benötigten Inhalte (CSS Style Sheets, JavaScript-Dateien und Bilder). Eine Verfeinerung der Page View heißt „Above the fold“: Hier geht es darum, nur den Bereich zu messen, den ein Benutzer beim Laden der Seite tatsächlich sehen kann. Der Begriff kommt aus der Zeitungsbranche: Über dem Falz ist das, was die Leute morgens am Kiosk schon in der Zeitung sehen können, wenn sie noch im Regal liegt.
Die Performance ist die Zeit, die das System benötigt, um eine Transaktion auszuführen. Man muss für jedes System individuell definieren, was eine Transaktion ist. Man kann beispielsweise einen Request oder eine Page View als Transaktion definieren. Wichtig ist, dass es die Performance ist, die den Besucher interessiert. Ihm ist daran gelegen, dass seine Anfrage möglichst zügig beantwortet wird. Der Eindruck wird heute oft als Apdex angegeben. Die Latenz bezeichnet die Laufzeit von Traffic zwischen zwei Knoten. Da es schwierig sein kann, Latenz nur in eine Richtung zu messen, misst man meistens Round Trip, also Hin- und Rückweg des Signals. Das gängigste Werkzeug zur Messung von Latenz ist Curl. Wenn man Curl aber auf das Websystem anlegt, spielt auch das Antwortverhalten dieses Systems eine Rolle. Oft wird die Latenz also der Performance gleichgesetzt.
Die Latenz ist neben der Backend-Performance und der Kapazität ein wichtiger Faktor der User Experience unseres Websystems. Dabei ist vor allem darauf zu achten, dass die Maßnahmen zur Verbesserung auch den tatsächlichen Anforderungen entsprechen. Arbeitet ein System nur für den deutschen Markt, brauchen wir kein CDN in Asien. Die Anforderungen sind entsprechend genau zu erheben. Die lokale Performance ist außerdem kein Selbstzweck. Poweruser können mit mehreren Sekunden Latenz leben, wenn sie nicht den ganzen Tag mit der fraglichen Funktion arbeiten müssen. Unterm Strich sollte man aber Antwortzeiten von mehr als einer Sekunde vermeiden. Manchmal braucht es hierfür nur ein wenig strukturierte Analyse und selbstverständlich eine genaue Einstellung der HTTP-Cache-Header.
Nicolas Bär arbeitet als IT-Architekt bei der Unic AG in Zürich. Als Performanceenthusiast optimiert er Webapplikationen und engagiert sich im Open-Source-Umfeld.
Daniel Takai ist Technologiemanager bei der Unic AG in Bern. Er ist dort für die Entwicklungsprozesse, Technologieentwicklung und Softwarearchitekturen verantwortlich.
Christian Wittwer ist IT-Architekt bei der Unic AG in Bern. Er kümmert sich mit seinem Team um die Performance von Webapplikationen, von der Entwicklung bis in den Betrieb.
[1] MySQL-Client-Server-Protokoll: https://dev.mysql.com/doc/internals/en/overview.html
[2] SPDY, Chromium-Projekt: http://dev.chromium.org/spdy
[3] RFC 7540, HTTP/2-Protokoll: https://tools.ietf.org/html/rfc7540
[4] TCP Slow Start: https://en.wikipedia.org/wiki/Slow-start
[5] WebSocket-Protokoll; W3C: https://www.w3.org/TR/2012/CR-websockets-20120920/
[6] Web Worker; W3C: https://www.w3.org/TR/workers/