SPA mit Ember im Unternehmen

Ein Ember-Reisebericht
Kommentare

In diesem Artikel beschreiben wir die Erfahrungen unseres .NET-Teams auf der Entdeckungsreise durch die neue Welt der Single Page Applications (SPA) mit JavaScript. Gesammelt haben wir diese Erfahrungen während der Neuimplementierung einer gewachsenen Kernanwendung zur Verwaltung von Produktstammdaten. Das Altsystem auf Basis von ASP.NET und Oracle PL/SQL ist den sich häufenden Änderungsanforderungen nicht mehr gewachsen und wird nach und nach abgelöst. Anwender des Systems sind rund 1 000 firmeninterne Mitarbeiter, die weltweit verteilt einen sprunghaft wachsenden Produktstamm von mehr als 500 000 Artikeln bearbeiten.

Der Arbeitsablauf der Anwender wird in unserer Neuimplementierung durch ein taskbasiertes Workflowsystem unterstützt. Abbildung 1 zeigt einen exemplarischen Screenshot.

Abb. 1: Screenshot der Anwendung, dargestellt wird eine Liste der durch den Anwender zu bearbeitenden Tasks

Auf unserer Reise durch die neue Technologiewelt mussten wir uns an mancher Kreuzung für einen Weg entscheiden, nur um dann später festzustellen, dass wir doch im Kreis gelaufen sind. Es gab dunkle Schluchten, die durchschritten werden mussten, und Punkte mit herrlicher Aussicht. Damit Teams in einer ähnlichen Situation die Reise besser vorbereiten können, folgt hier unser Reisebericht.

Projektkontext und Kernanforderungen

Wir standen vor einer Situation, wie sie jeder von uns kennt: Ein Projekt steht vor einer Neuausrichtung und es stellt sich die Frage, auf welches Pferd, respektive, welche Technologie man zum gegebenen Zeitpunkt am besten setzen soll. In diesem Fall ging es besonders um die Clienttechnologie, die im Zuge der Durchdringung von mobilen Endgeräten und Trends wie HTML5 massiv in Bewegung ist.
In einer frühen Phase des Projekts hatte unser Entwicklerteam einen attraktiven Prototyp auf Basis von Microsoft Silverlight entwickelt. Damit konnten erfolgreich komplexe Anforderungen an der Oberfläche umgesetzt werden, wie zum Beispiel die asynchrone, clientseitige Auswertung von komplexen Regelprüfungen oder die visuelle Unterstützung beim Vergleich und Zusammenführen von Artikeln. Der Schwerpunkt unseres Teams lag damals auf der Entwicklung von Lösungen mit Microsoft .NET. Vor diesem Hintergrund haben wir uns für Silverlight entschieden und den Prototyp mit einer relativ steilen Lernkurve entwickeln können.
Am Ende der Prototyping-Phase waren wir also recht zufrieden. Doch wie das Leben in IT-Projekten so spielt, hatte Microsoft andere Pläne. Im Sommer 2012 gab Microsoft eine veränderte Clientstrategie bekannt, in der Silverlight nicht mehr vorkam. Als Architekt kommt man dann schnell in Argumentationsnöte, wenn man für ein strategisch wichtiges Projekt eine Technologie ausgewählt hat, die in der Presse [1] für tot erklärt wird.
Die Entscheidung für Silverlight als Clienttechnologie wurde also noch einmal auf den Prüfstand gestellt. Mittlerweile war auch klar, dass das Altsystem nicht in einem Schritt abgelöst werden kann und die neue Lösung sich daher gut in die vorhandene Webapplikationslandschaft integrieren lassen sollte. Dies führte im Herbst 2012 schließlich zu der Entscheidung für einen Webclient. Stellte sich nur die Frage nach der passenden Technologie.
Microsoft stellt mit ASP.NET MVC zwar ein Framework für die Serverseite zur Verfügung, lässt die Clientimplementierung jedoch weitgehend offen. Der Begriff der Single Page Application ist nach AJAX, jQuery und Co der letzte Schrei (Kasten: „Single Page Application“).

Single Page Application
SPAs laden beim ersten Aufruf den kompletten Code in Form von JavaScript und UI-Templates und führen danach nur noch Datenaufrufe durch.

Eine Untersuchung des unübersichtlichen Markts der wie Pilze aus dem Boden schießenden SPA-Frameworks ergab, dass zwei grundsätzliche Ansätze konkurrieren: Auf der einen Seite stehen jene, die den All-inclusive-Ansatz verfolgen und von Architekturvorgaben bis zur Deployment-Unterstützung alles mitliefern. Auf der anderen Seite steht der Best-of-Breed-Ansatz. Hier muss sich der Architekt für jeden Aspekt seiner SPA das jeweils am besten passende Framework aussuchen.
Die Untersuchung engte die Auswahl auf Sencha Ext JS als Vertreter des All-inclusive-Ansatzes und Ember als zentrales MVC-Framework eines Best-of-Breed-Ansatzes ein. Obwohl ein erster Durchstich mit Ext JS vielversprechende Funktionen darstellen konnte, gaben die Zweifel an deren Erweiterbarkeit bzw. an der Integrationsfähigkeit zusätzlicher JavaScript-Komponenten den Ausschlag für die Entscheidung zu einem Best-of-Breed-Ansatz. In der Folge beschreiben wir, mit welchen Komponenten wir diesen Ansatz ausgestaltet haben und welche Erfahrungen wir nach den ersten drei Monaten Entwicklung gemacht haben.

Vorgehensweise bei der Zusammenstellung unseres „SPA-Stacks“

In diesen Abschnitt stellen wir unseren JavaScript-Baukasten vor. Wir sind der Ansicht, dass er Softwarearchitekten und Entscheidern beim Aufbau einer eigenen Architektur weiterhelfen kann.
Doch bevor wir unser Design vorstellen, sei erwähnt, dass wir zum Zeitpunkt des Schreibens Ember in der Version 1.0 RC 3 verwenden. Ember Data (autarke Bibliothek, um Daten aus einer Persistenzschicht zu laden und zu ändern) ist derzeit nicht für den produktiven Einsatz geeignet [2] und kam daher bei der Zusammenstellung des Stacks nicht in Frage. Da wir uns für den Best-of-Breed-Ansatz, Ember, entschieden haben, mussten weitere grundlegende Konzepte und Softwarebausteine definiert werden.
Trotz oder gerade wegen des vielen JavaScripts wollten wir nicht auf bewährte Entwicklungsmuster und -vorgehen verzichten, wie wir sie aus anderen objektorientierten Programmiersprachen kennen. Die Unternehmenssoftware sollte erweiterbar, robust und gut zu warten sein. Insbesondere bei der Codequalität wollten wir so wenige Abstriche wie möglich von der .NET-Welt machen. So wollten wir beispielsweise nicht auf Unit Testing und Codemetriken verzichten.
Mit diesen Gedanken im Hinterkopf haben wir funktionale Bausteine gebildet, die wir unbedingt in unserem Design verwenden wollten. Parallel dazu haben wir den stark florierenden JavaScript-Markt beobachtet, um herauszufinden, was zurzeit technisch möglich ist und welche JS-Bibliotheken am populärsten sind. Frei nach dem Motto „Was viele andere verwenden, kann so schlecht nicht sein.“ Anschließend haben wir für jeden Baustein passende JS-Bibliotheken ausgesucht und in unseren Stack integriert. Herausgekommen ist folgender „SPA-Stack“ aus Abbildung 2.

Abb. 2: Funktionale Bausteine der Clientarchitektur mit JavaScript-Frameworks („unser SPA-Stack“)

Die in Abbildung 2 grün markierten Bausteine wirken sich direkt auf das UI aus. Der wichtige Teil unserer Anwendung, unser Applikationskern, wird von den blauen Bausteinen repräsentiert. Die roten Balken stellen Querschnittfunktionen dar. Nachfolgend die Beschreibung dessen, was wir unter diesen Bausteinen verstehen und wie wir sie technisch abgebildet haben.

Aufmacherbild: Circuit board tree pattern with place for your text. Raster von Shutterstock / Urheberrecht: Khvost

[ header = User Interface ]

User Interface

Styling

Keine Webapplikation kommt ohne Cascading Style Sheets (CSS) aus. Wir wollten jedoch von Beginn an CSS nicht nativ verwenden, sondern auf einen Preprocessor setzen, um einfacher und eleganter Gestaltungsvorlagen zu erstellen. Die beiden Platzhirsche sind Sass [3] und LESS [4]. Trotz marginaler Unterschiede haben wir uns für LESS entschieden. Wer mehr über die Unterschiede erfahren möchte, der findet von Chris Coyier eine ausführliche Erläuterung [5]. Um das Ramp-up zu begünstigen, setzen wir auf Bootstrap; eine von Twitter entwickelte Bibliothek zur Gestaltung von Webseiten. Diese enthält neben soliden Gestaltungsvorlagen auch spezielle Steuerelemente auf Basis von JavaScript. Dessen CSS-Definitionen dienen als gutes Fundament und lassen sich problemlos erweitern.

Templating

Die verschiedenen Sichten zerfallen in wiederverwendbare HTML-Brocken. So kann es beispielsweise eine HTML-Repräsentation eines Kunden oder Auftrags geben. Diese Brocken lassen sich in unterschiedlichen Stellen der Applikation integrieren. Don’t Repeat Yourself (DRY)! Darüber hinaus bedarf es einer Templatesprache, um die HTML-Repräsentation semantisch zu beschreiben. Wer Ember sagt, muss auch Handlebars sagen! Handlebars [6] ist eine Templatesprache. Zusammen mit Ember hat man ein ausgereiftes Templatesystem.

UI Controls

Auch hier war uns von Beginn an klar, dass die Standard-HTML-Elemente nicht ausreichen würden. Mit HTML5 sind zwar einige Controls bzw. Input Types, wie z. B. der Typ date, dazu gekommen, jedoch variiert deren Unterstützung mit jedem Browser. Eigene Controls zu erstellen, ist teuer. Und warum sollte man das Rad neu erfinden wollen? Am liebsten wollten wir hier eine UI-Bibliothek verwenden, die relativ schlank und einfach zu bedienen ist. Darüber hinaus sollte sie sich problemlos in unseren Stack integrieren lassen. Da Ember keine komplexen Controls anbietet, hatten wir zwei Möglichkeiten: vorhandene UI-Bibliotheken „emberifizieren“ oder selbst entwickeln. Dabei sollte man tunlichst darauf achten, dass die Controls Two Way Binding (siehe „Data Binding“) unterstützen, um dem Konzept hinter Ember treu zu bleiben und Entwicklungsaufwand zu sparen. Wir haben jQuery UI ausgewählt. Hierfür existieren auch schon Implementierungen, die ein Zusammenspiel mit Ember ermöglichen [7]. Wer gerade auf der Suche nach passenden UI Controls ist, für den könnte Flame.js [8] interessant sein. Flame.js ist speziell für Ember ausgelegt. Für unsere Zwecke ist die Bibliothek zu schwergewichtig.

DOM-Manipulation

Darunter verstehen wir Änderungen, die wir programmatisch am Document Object Model (DOM) vornehmen – beispielsweise das Ein- und Ausblenden von UI-Bereichen. Hier kommt natürlich jQuery zum Einsatz.

Keyboard

Unsere Anwendung wird vorrangig von Powerusern bedient. Diese Usergruppe möchte schnell und effektiv mit der Tastatur arbeiten können. Aus diesem Grund muss eine Keyboardbedienung bedacht werden. Technisch setzen wir auf das jQuery-Plug-in Hotkeys [9]. Geschrieben von John Resig, der auch als jQuery-Erfinder bekannt ist. Mit diesem Plug-in können wir auf beliebige Tastenkombinationen innerhalb eines DOM-Knotens lauschen.

[ header = Applikationskern ]

Applikationskern

History/Routing

Anders als bei klassischen Webseiten wird bei einer SPA nur eine große Datei ausgeliefert. Der Aufbau einer neuen Seite erfolgt mittels JavaScript und nicht über das Öffnen einer neuen HTML-Seite. Damit der User weiterhin die Browserhistorie nutzen kann, werden sog. # (Hash)-URLs für die Navigation verwendet. Ember ist für die Verwaltung der URLs verantwortlich und trumpft mit einem ausgezeichneten State Management [10] auf.

MVC

Jeder kennt UI Design Patterns. Model View Controller (MVC) gehört zu den bekannteren Stellvertretern. Welches Pattern eingesetzt wird, ist aus unserer Sicht zweitrangig. Wichtiger ist die Trennung zwischen Markup und UI-Logik zwecks Wartbarkeit. Der UI-Designer kann sich mit dem wesentlichen beschäftigen – nämlich HTML und CSS – ohne viel JavaScript bearbeiten zu müssen. Ember implementiert ein MVC Pattern [11].

Data Binding

Gekoppelt mit MVC versprechen wir uns durch Data Binding einen enormen Produktivitätsgewinn: Daten, die der User in die Eingabeelemente pflegt, müssen nicht programmatisch ausgelesen werden. Daten, die vom Backend geladen werden, müssen ebenfalls nicht an der Oberfläche mühselig „per Hand“ angezeigt werden. Endlich ist Schluss mit den ganzen jQuery-Selektoren. Diese fehleranfälligen Funktionen sollen automatisch passieren, indem das Model über einen Controller an die View gebunden wird. Das Ganze kennt man auch unter dem Namen Two Way Binding. Auch hier hilft uns Ember weiter: Sowohl One als auch Two Way Binding zählen zu dessen Stärken.

Change Tracking

Wir möchten in der SPA zu jedem Zeitpunkt nachvollziehen können, ob ein Model durch eine Useraktion geändert wurde. Dadurch könnten nur die geänderten Daten an das Backend geschickt werden. Das spart zum einem Traffic. Zum anderen können wir den User darauf hinweisen, dass Eingaben verloren gehen, wenn ein Kontextwechsel stattfindet und die Daten noch nicht gespeichert wurden. An dieser Stelle sind wir um etwas Eigenentwicklung nicht herumgekommen. In Ember sucht man Change Tracking vergebens. Auch konnten wir keine andere Bibliothek am Markt finden, die solche Funktionalität bereitstellt und sich gut mit Ember verträgt. Wagemutige können ein Blick auf Ember Data bzw. auf die IsDirty-Eigenschaft eines Ember Data Models werfen [12].

Validation

Darunter verstehen wir das Validieren von Usereingaben. Die Eingaben sollen selbstverständlich schon in der SPA geprüft werden und nicht ausschließlich durch das Backend. Der positive Effekt ist, dass der User während der Eingabe sofortige Rückmeldung bekommt. Die Validierung soll nicht an der View verankert sein, sondern am Controller bzw. Model. Verwende ich ein Model in mehreren Views, so greifen in jeder View die Validierungsregeln. Validierung ist ein weißer Fleck bei Ember. Selbst mit Ember Data scheint sich in dieser Richtung nichts zu bewegen. Validierung ist eine Schlüsselfunktion in unserer Applikation, deshalb haben wir uns für eine Eigenentwicklung entschieden. Dabei machen wir uns das Observable[13]-Konzept von Ember zu nutze. Diejenigen, die die Eigenentwicklung scheuen, können sich Ember Validations [14] anschauen.

Communication

Um Softwaremodule lose gekoppelt miteinander kommunizieren zu lassen, haben wir das Publish-und-Subscribe-Muster vorgesehen. Das Muster sollte mit Bedacht eingesetzt werden, da die Nachvollziehbarkeit des Codes enorm leidet: „Wer konsumiert eigentlich diese Nachricht?“ Bislang hatten wir noch keinen Bedarf, um auf diese Kommunikationsart zurückzugreifen. Wenn es sich in Zukunft ergeben würde, so würden wir AmplifyJS hierfür einsetzen wollen. Bei AmplifyJS handelt es sich um eine Wundertüte, über die sich jeder Webentwickler freut: Neben einem Messaging Pattern [15] bekommt man auch noch gleich eine Local Storage und AJAX-Request-Implementierung. Alles Cross-Browser-kompatibel versteht sich.

Storage

Daten, die sehr selten Änderungen unterworfen sind, könnten lokal zwischengespeichert werden, sodass die Anwendung schneller reagiert. Userspezifische Daten oder vom User präferierte Applikationseinstellungen könnten ebenfalls hier abgelegt werden. Um das Speichern von lokalen Daten sollte man sich auf keinen Fall selbst kümmern, da Browser und Browserversionen in dieser Hinsicht sehr heterogen sind. Aufgrund von projektspezifischen Anforderungen haben wir statt AmplifyJS jStorage [16] verbaut. Dadurch abstrahieren wir von der jeweiligen browserspezifischen Umsetzung und verwenden ein konsistentes API.

AJAX

Asynchronous JavaScript and XML – ist eine Schlüsseltechnik für SPAs. Anforderungen an das Backend erfolgen asynchron, so dass das User Interface nicht blockiert wird. Das ist natürlich nichts Neues. Klassische Webseiten setzten schon seit Langem auf AJAX. Bei SPAs werden jedoch keine HTML- oder JavaScript-Fragmente ausgetauscht, sondern reine Nutzdaten. In unserem Kontext bedeutet das, Domänendaten laden und schreiben – die typischen Create-Retrieve-Update-and-Delete-(CRUD-)Operationen. Im Kern verwenden wir AmplifyJS.

Security

Darunter verstehen wir insbesondere die Autorisierung des Users. Welche Aktionen kann der eingeloggte User ausführen und welche Applikationsmodule stehen ihm zur Verfügung. Mit Ember allein steht man etwas blank da. Mittels eines selbstgeschriebenen Handlebars Helper blenden wir unterschiedliche UI-Bereiche ein und aus. Eine generische Ember-Route implementiert die Autorisierungslogik. Hat der User nicht die benötigten Rechte, um auf die Ressource zuzugreifen, so findet eine Weiterleitung statt.

Mocking

Um möglichst unabhängig vom Backend entwickeln zu können, ist Mocking der Kommunikation notwendig. Dadurch kann der UI-Designer mit der Erstellung der HTML-Templates voranschreiten, ohne auf die Fertigstellung des Backends warten zu müssen. Ein netter Nebeneffekt: Wenn die Daten automatisiert generiert werden, kann auf Knopfdruck geprüft werden, wie das UI mit Massendaten umgehen kann. Mocking vereinfacht auch das Unit Testing: Es findet keine Netzwerkkommunikation statt. MockJSON [17] ist ein jQuery-Plug-in und generiert templatebasiert JSON (JavaScript Object Notation) on-the-fly. Mithilfe von AmplifyJS tauschen wir beim Applikationsstart die AJAX-Endpunkte aus. Wenn wir diesen Baustein neu designen dürften, würden wir definitiv einen Blick auf jQuery Mockajax [18] riskieren.

Mapping

Hier werden die Daten vom Backend zu Modellen, die in der SPA verwendet werden, überführt und vice versa. Mapping ist notwendig, da für den Client spezialisierte Modelle benötigt werden: In erster Linie müssen sie Two Way Binding (siehe „Data Binding“) unterstützen. Des Weiteren enthalten sie Eigenschaften, die ausschließlich für das UI von Nutzen sind. Ember hilft hier leider nicht weiter aus. Wir haben uns für jede Domäne dedizierte Objekte definiert, die das Mapping übernehmen.

[ header = Querschnittsfunktionen ]

Querschnittsfunktionen

Unit Testing

Wie bereits erwähnt, wollten wir nicht auf Unit Tests verzichten. Dabei sollte der gesamte Applikationskern mit Unit Tests abgedeckt werden, sodass die Korrektheit der Implementierung sichergestellt werden kann. Als Unit-Test-Framework setzen wir Jasmine [19] ein. Als Test Runner können wir Karma [20] empfehlen. Mit Karma lässt sich ohne großen Aufwand Istanbul [21] integrieren, mit dessen Hilfe die Code Coverage gemessen werden kann.

Logging

Mittels Logging werden relevante Ereignisse in der Software festgehalten, um Bugs schneller auf die Schliche zu kommen. Auftretende Fehler können protokolliert und an das Backend zur Analyse weitergeleitet werden. Das Logging umfasst den gesamten Applikationskern und Bereiche des UI. An dieser Stelle kommt log4javascript [22] zum Einsatz.

Documentation

Es ist damit zu rechnen, dass sich die Zusammenstellung des Entwicklungsteams im weiteren Projektverlauf ändert. Um insbesondere Neulingen den Einstieg in den Code und die Softwarearchitektur zu erleichtern, versehen wir unseren Code mit einer inline API-Dokumentation. Jeder Softwareentwickler kennt das illusionäre Ziel, Dokumentation aktuell zu halten. Aus diesem Grund haben wir uns entschieden, auch nur Schlüsselobjekte und deren öffentliche Schnittstellen zu dokumentieren. Auf dem JavaScript-Markt hat sich JSDoc [23] bewährt. Wer einen Blick in den nicht komprimierten JavaScript-Code von Ember wirft, der wird feststellen, dass dessen API-Dokumentation [24] ebenfalls mit JSDoc erzeugt wurde.

Optimization

Hört man Single Page Application, so denkt man an schnell reagierende Webseiten. Um diesen Anspruch gerecht zu werden, möchten wir die Payload vom Server zum Client so gering wie möglich halten. Das soll durch Bundling und Minifying des JavaScript-Codes erreicht werden. Das generierte CSS wird ebenfalls komprimiert ausgeliefert. RequireJS [25] sollte unserer Meinung nach in keinem großen JavaScript-Projekt fehlen. In erster Linie hilft RequireJS bei der Codestrukturierung und der automatischen Abhängigkeitsauflösung. Eine nette Nebenfunktion ist der RequireJS Optimizer. Er komprimiert JavaScript und sogar CSS.

[ header = Zusammenfassung ]

Zusammenfassung

Der Best-of-Breed-Ansatz mit Ember hat zwei wesentliche Vorteile. Wenn man bei der Auswahl der jeweiligen Spezialisten auf Integrierbarkeit achtet und diese mit Bedacht in seine Applikation einarbeitet, so entsteht eine hochflexible Softwarearchitektur. Des Weiteren kann der Architekt für jeden funktionalen Baustein die für das Projekt am besten geeignete Bibliothek auswählen.
Unser Designansatz hat natürlich auch eine Kehrseite. Adäquate Bibliotheken zu identifizieren und in unserem SPA-Stack zu integrieren, ist mit nicht zu verachtendem Aufwand verbunden. Das JavaScript-Ecosystem wächst rasant an. Deshalb müssen wir unseren SPA-Stack inkrementell auf Praxistauglichkeit prüfen. So versprechen wir uns gerade im Zusammenhang mit Ember.Data einen Produktivitätsgewinn, da insbesondere Eigenlösungen (wie das Mapping) abgelöst werden könnten.

Erfahrungen mit Ember

Als Neulinge im Bereich Ember stand für uns zu Beginn des Projekts erst einmal eine Getting-Started-Phase an. In diesem einige Wochen dauernden Abschnitt galt es, die Unterschiede und Besonderheiten von Ember im Vergleich zum alten Technologiestack kennenzulernen. So kämpft man sich durch neue Konzepte, die durch Ember eingeführt werden und die man aus der klassischen MVC-Entwicklung nicht unbedingt kennt. So zum Beispiel – als Spezialist für State Management – das Konzept des Routers sowie reduzierte Verantwortlichkeiten von Controllern. Auf diese und weitere Besonderheiten geht der nachfolgende Abschnitt näher ein. Dabei beschreiben wir Fallstricke und mögliche Lösungsstrategien.

Convention over Configuration

Was zu Beginn als Ansatz sehr gut klang, sorgte ab und zu für mehr Falten auf der Stirn, als dies so manche Hautcreme kompensieren könnte. Konventionen muss man ja schließlich kennen, um sie anwenden zu können. Das allerdings fällt gerade zu Beginn der Arbeit mit Ember schwer. Als Lösungsstrategie können wir empfehlen:

· die vorhandene Dokumentation aufzusaugen [26]
· ausreichend Log-Ausgaben in Templates, Views und Controllern unterzubringen
· gern auch mal den Debugger anzuwerfen

Router

Basiskonzepte von Ember sind Ressourcen und Routes [27]. Ressourcen sind Objekte, in unserem Kontext (Taskmanagement) beispielsweise die Ressource eines Tasks. Routes sind Aktionen, die man auf den Ressourcen durchführt. In unserem Fall beispielsweise das Anzeigen oder Bearbeiten einer Task-Ressource. Jede Route, also jeder Zustand, kennt den zu verwendenden Controller und bietet unter anderem Erweiterungspunkte, um Modelle aus einem URL zu deserialisieren, den entsprechenden Controller zu initialisieren sowie auf zustandsübergreifende Ereignisse zu reagieren. Eine wesentliche Herausforderung war, zu verstehen, wie diese Konzepte zusammenwirken. Als Lösungsstrategie können wir empfehlen:

· Zu verstehen, wann und wie man Modelle deserialisieren muss. Informationen hierzu findet man unter anderem in der API-Dokumentation und hier.
· Die Initialisierungslogik für Controller komplett in die Routes zu verlagern. Hier haben wir anfangs noch zu sehr im klassischen MVC gedacht.

Data Binding und Handlebars Templating

Eine Herausforderung war hier die Modularisierung von Templates in separate Dateien. Wir wollten sicherstellen, dass jedes Template in seiner eigenen Datei existiert. Ein weiterer Punkt, der oft für Verwirrung gesorgt hat, war der sich ändernde Data Context innerhalb verschiedener Handlebars Helpers. Meist wird der entsprechende Controller als Context verwendet, aber nicht immer. Als Lösungsstrategie können wir empfehlen

· für die Modularisierung ein hierfür geschriebenes RequireJS-Plug-in [28] zu verwenden.
· sich darüber im Klaren zu sein, dass der Context von Handlebars Helpers von deren Implementierung abhängt. Mittels entsprechender Debug-Ausgaben (z. B. {{content}}) kann man dem richtigen Context auf die Schliche kommen.

Dokumentation

Dokumentation und Codebeispiele sind eine wesentliche Ressource, um Aufwände zum Erreichen eines gewissen Produktivitätslevels in den Griff zu bekommen. Anfang 2013 hat das Ember-Team massive Änderungen im API [29] durchgeführt. Diese Änderungen waren motiviert durch Kritik der Nutzer beispielsweise am Router-V1-API und haben einen Großteil der Diskussionen und Inhalte in Communities [30] schlagartig obsolet werden lassen. Das Versprechen der Ember-Entwickler ist, derzeit keine dermaßen einschlagenden Änderungen mehr vorzunehmen, sodass sich seitdem die Wogen geglättet haben. Als Lösungsstrategie können wir empfehlen:

· Als Entwickler und auf der Suche nach Dokumentation, Tutorials und Codebeispielen sollte man darauf achten, für welche Version die entsprechenden Inhalte gelten. Als Daumenregel gilt: Alles ab 2013 bezieht sich höchstwahrscheinlich auf die aktuellere Version V2 des Router-API.
· Eine weitere Herausforderung ist die teilweise unvollständige Dokumentation. Viele wichtige Aspekte, wie zum Beispiel die Runloop, werden nicht ausreichend erklärt. Als Webentwickler kennt man dieses Problem und kommt zwangsläufig damit klar, indem man einen scharfen Blick in den Sourcecode [31] wirft.

[ header = Fazit ]

Fazit

Für unser erstes Release hatten wir uns viel vorgenommen. Wir wollten auf einer soliden Architektur aufbauen und damit eine qualitativ gute Software mit reichlich nutzbringenden Features liefern. Aufgrund der flachen Lernkurve und der Anfangsschwierigkeiten mussten wir aber etliche Features streichen und während der Entwicklung auch einige technische Schulden anhäufen, die es während der nächsten Releases abzutragen gilt. Wir haben in diesem ersten Release jede Menge Erfahrungen gesammelt – positive wie negative. Um uns stetig weiter zu verbessern und anderen den Einstieg in die Welt der SPAs etwas zu erleichtern, blicken wir nun noch einmal zurück und erläutern, was für uns gut funktioniert hat und was weniger.
Für dieses Projekt auf Basis einer neuer Technologie hat es uns geholfen, eine explizite Prototyping-Phase dem Projekt voranzustellen. Während dieser Phase konnten wir verschiedene Technologiealternativen ausprobieren und unsere eigenen Eindrücke sammeln. Gerade wenn eine Technologie noch recht unreif und mit einem hohen Risiko behaftet ist, lohnt sich diese Investition. Auch eine Beratung durch externe Spezialisten sollte man in Betracht ziehen. Diese Prototyping-Phase bringt vor allem dann den erhofften Erkenntnisgewinn, wenn das Team bereits das notwendige Basis-Know-how hat. In unserem Fall mussten wir erst einmal die JavaScript-Kenntnisse im Team auffrischen und JavaScript als vollwertige Programmierplattform begreifen, zu der es sich in den letzten Jahren entwickelt hat. Die Theorie haben uns die Bücher „JavaScript: The Definitive Guide“ und „JavaScript: The Good Parts” und die zahlreichen Blogs vermittelt. Praktisch vertieft haben wir das Wissen während der Prototyping-Phase und in unseren JavaScript-Coding-Dojos.
Auf dem Gebiet der Single Page Applications und JavaScript-Frameworks/-Libraries ist noch vieles im Wandel. Manche Funktionalitäten, die in anderen Plattformen Basisfunktionalitäten darstellen, müssen hier erst noch gebaut werden. Manchmal sind Workarounds für Probleme unumgänglich, oder es kommt einfach ein neues vielversprechendes Framework auf den Markt, das evaluiert werden sollte. All diese Aufgaben sollten als „F&E“- Aufgaben geplant werden, und für die Durchführung haben sich auch hier externe Spezialisten als gute Alternative zur Inhouse-Entwicklung herausgestellt.
Ebenfalls zu Beginn der Entwicklung haben wir festgelegt, an welche Konventionen wir uns während der Entwicklung halten wollten. Diese Konventionen sorgten auch bei einem wachsenden Team für gleichmäßige Code- und Projektstrukturen und erleichtern damit unsere tägliche Arbeit. Relativ leicht ließen sich die JavaScript Coding Guidelines [32] erstellen. Wir haben uns über die gängigsten JavaScript-Konventionen informiert und diese dann für uns übernommen oder auch in Einzelfällen angepasst. Etwas schwieriger wurde es bei der Strukturierung unserer Codeartefakte (Verzeichnisse, Dateien), doch auch hier hat sich durch unsere Architektur recht schnell eine gute Aufteilung gefunden.
Eine wichtige Eigenschaft einer Single Page Application ist die Tatsache, dass eine SPA, wenn sie einmal in den Browser geladen wurde, hauptsächlich Daten mit dem Backend austauscht. Genau auf diese Datenschnittstelle zwischen SPA und Backend sollte man von Anfang an sehr genau achten. Wird hier geschludert und es werden zu viele kleine oder zu große Datenmengen transportiert, dann kann man viel von der gefühlt hohen Antwortgeschwindigkeit einer SPA verspielen. Wir haben hier während des ersten Releases kein Patentrezept gefunden und die Schnittstelle je nach Use Case entworfen. Dies war möglich, da wir unsere Domainlogik durch eine Fassade vor der SPA versteckt haben. Wir konnten damit der SPA eine für sie optimierte Schnittstelle anbieten und verloren so nicht die Wiederverwendbarkeit unserer Backendservices.
Mit der Entwicklung der SPA alleine war es dann ja noch nicht getan, sondern wir mussten uns ebenfalls mit den Themen Bundling, Minification, automatische Ausführung von Unit Tests, Generierung von Codedokumentation und statische Codeanalyse für JavaScript auseinandersetzen. Da wir mit unserem Team normalerweise Software auf Basis von Microsoft .NET entwickeln, war der Team Foundation Server (TFS) als Build-Umgebung gesetzt. Nachdem sich ziemlich schnell abgezeichnet hat, dass die Integration mit erheblichem Aufwand verbunden ist, haben wir uns für Grunt [33] als Build-Umgebung für unsere SPA entschieden. Grunt hat sämtliche SPA-spezifischen Build-Aufgaben übernommen und ließ sich in den TFS-Prozess einbinden. Die Integration ist ausreichend gut gelungen, und wir sind mit der Aufgabenteilung vorerst zufrieden.
Das Ziel von SPA-Anwendungen ist die Kombination des Funktionsreichtums einer Desktopanwendung mit der einfachen Bereitstellbarkeit einer Webanwendung. Bei der Planung muss man dazu die technischen Voraussetzungen im Unternehmen analysieren, d. h. welche Browserplattformen und -Versionen im Einsatz sind, die offiziell unterstützt und damit getestet werden. Aufgrund der umfangreichen Nutzung von JavaScript bringen erst die neueren Browserversionen mit Just-in-Time-Compilern die notwendige Performance für ein flüssiges Arbeiten. In unserem Umfeld ist Internet Explorer als Firmenstandard gesetzt – jedoch nicht nur in der aktuellen Version 10, sondern auch in älteren Versionen. Zum Zeitpunkt des Go-Live waren jedoch noch nicht alle internen WebaAnwendungen auf IE10 getestet, sodass wir mit einer Zwischenlösung an den Start gehen.
Zum jetzigen Zeitpunkt ist klar, wir sind noch lange nicht am Ziel unserer Reise. Aber: Würden wir den gleichen Weg nochmal gehen? Diese Frage haben wir uns nach dem ersten Release selbst gestellt, um uns darüber klar zu werden, wie es mit dem Projekt in den nächsten Releases weitergeht und auch weil wir früher oder später von anderen Projekten gefragt werden, ob wir unseren Technologiestack und unsere Architektur empfehlen können.
Um es kurz zu machen: Wir werden diesen Weg weiter gehen und abwarten, wie sich die Software in den nächsten Releases entwickeln wird. Wenn man in einer Technologie wie .NET zu Hause ist, dann hat man JavaScript bisher vielleicht nur benutzt, um ein Fenster zu öffnen oder allerhöchstens ein paar Daten asynchron zu laden. Wie wir oben beschrieben haben, gilt es nicht nur, eine neue Technologieplattform zu erlernen und zu strukturieren, sondern auch sie im Entwicklungsumfeld zu integrieren. Dies kostet zunächst Zeit und die Arbeit geht zu Beginn wirklich deutlich langsamer als gedacht von der Hand. Man wünscht sich schnell wieder WPF, Silverlight oder ASP.NET zurück. Doch die Produktivität steigt mit der zunehmenden Erfahrung und dem steigenden Verständnis für die neue Programmiersprache und die verwendeten Frameworks. Hat man aber JavaScript als eigene Programmierplattform begriffen und die Einarbeitungshürde genommen, dann kommt man sehr schnell darauf, dass man auch mit JavaScript modulare und testbare Software entwickeln kann und zwar mit SPAs(s).

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -