Suche
Hoch hinaus im Web

Lift: Modernes Webframework auf Scala-Basis

Michael Adams
© shutterstock.com/bofotolux

Durch ihre mächtigen Konzepte und nahtlose Verwendbarkeit im Java-Umfeld findet die Sprache Scala in einer wachsenden Anzahl von Projekten Verwendung. Das Framework Lift, auf Scala basierend und für die Webentwicklung gedacht, bringt abstrakte Konzepte gängiger Webframeworks wie Ruby on Rails, Django und andere in eleganter Weise zur Anwendung. Aufbauend auf dem zweiteiligen Artikel über Scala und Xtext in den Eclipse-Magazin-Ausgaben 1.2013 und 2.2013 und dem dort verwendeten Ansatz einer Architekturdomänensprache in Eclipse Xtext sollen in einer weiteren zweiteiligen Artikelserie verschiedene Ausprägungen von Lift-Architekturen erstellt werden. Dabei reicht die Bandbreite von einfachen CRUD-Webanwendungen bis hin zu Eclipse-RCP-ähnlichen Workbench-Portalen.

Lift ist ein modernes Webframework, das sich durch seine offene Architektur und Flexibilität von der Menge gängiger Frameworks unterscheidet. Da es auf Scala-Basis entstanden ist und seinen Ursprung nicht in der Java-Welt hat, nutzt es die Mechanismen von Scala intensiv aus und eignet sich somit auch als Anschauungsbeispiel für ein professionell umgesetztes Scala-Projekt. Lift setzt jedoch viel Erfahrung mit objektorientierten Konzepten voraus, der Einarbeitungsaufwand ist also vergleichsweise hoch. Der Bekanntheitsgrad von Lift ist hierzulande relativ gering, obwohl es schon seit über sechs Jahren existiert. Dies mag zum einen daran liegen, dass Lift auf Scala basiert und Scala den Durchbruch in größeren Softwareprojekten noch nicht geschafft hat. Auch die andere Herangehensweise, die Art, wie Webanwendungen mit Lift erstellt werden, könnte ein Grund sein. Das wiederum bedingt, dass der Einarbeitungsaufwand höher als bei anderen Webframeworks und somit nicht für jedermann ohne Weiteres möglich ist. Die verbesserungswürdige Dokumentation tut ein Übriges.

Somit stellt sich zunächst die Frage, warum man schon wieder ein neues Webframework erlernen sollte, vor allem deshalb, da auf dem Markt bereits eine Vielzahl solcher Frameworks existiert, die, zumindest was den Einarbeitungsaufwand und die vorhandenen Dokumente angeht, Lift überlegen sind. Um es kurz zu sagen: Nach meiner Einschätzung lohnt sich zumindest das Studium der grundlegenden Konzepte von Lift, die sich von den Konzepten aller mir bekannten Webframeworks unterscheidet. David Pollak, der Erfinder von Lift, hat bei der Konzeption die seiner Meinung nach wesentlichen Vorzüge anderer Webframeworks vereinigt und diese mit neuen Ideen unter der Ausnutzung der Spracheigenschaften von Scala kombiniert.

Architektur von Lift

Warum ist Lift nun so anders als andere Webframeworks? Abgesehen von der Übernahme bewährter Funktionalitäten ist als wesentliches Merkmal die Architektur zu nennen. Entgegen einer MVC-Architektur, die als De-facto-Standard den Controller als zentrales Element in der Ablaufsteuerung von Webanfragen stellt, welches zunächst die Anfragen entgegennimmt, diese verarbeitet und mit den ausgewerteten Ergebnissen die Benutzeroberfläche aktualisiert, arbeitet Lift nach dem View-first-Ansatz. Das bedeutet, dass benutzerdefinierte Webseiten keine Geschäftslogik enthalten dürfen, da Lift die Darstellungslogik vor der Geschäftslogik auf der Serverseite auswertet. Der Vorteil dieser Vorgehensweise ist eine saubere Trennung von Präsentation und Anwendung, Personen unterschiedlicher Rollen wie Webseitendesigner und Softwareentwickler können parallel und unabhängig voneinander arbeiten. Zudem ergibt sich wie stets bei einer sauberen Trennung von Konzepten in der Analyse- und Designphase von Softwaresystemen eine bessere Testbarkeit, Wartbarkeit, Erweiterbarkeit und Wiederverwendung.

Lift-Template, View und Model

Unterstützt wird diese Vorgehensweise durch die Nutzung eines Templatemechanismus, der strukturell sowohl die Einbettung eines Templates auf höherer Ebene als auch die Ersetzung von Tags durch Templates auf niedriger Ebene erlaubt. Ein Beispiel (Template auf Wurzelebene, base.html) kann Listing 1 entnommen werden.

<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
    'http: //www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>

<html xmlns='http: //www.w3.org/1999/xhtml' xmlns:lift='http://liftweb.net/'>


  <head>
    ...
  </head>
  <body>
    <div id='header'>
      <lift:bind name='header'/>
    </div>
    <div id='cont'>
      <lift:bind name='content'/>
    </div>
    <div id='footer'>
      <lift:bind name='footer'/>
    </div>
  </body>
</html>

Dieses Template ersetzt an den vorgegeben Stellen die Tags durch die Tags eines Templates, welches sich in dieses Template einbettet. Ein Beispiel (eingebettetes Template, default.html) ist in Listing 2 zu sehen.

<lift:surround with="base">
  <lift:bind-at name="header"/>
    <head_merge><lift:bind name='hd'/></head_merge>"
  </lift:bind-at>
  <lift:bind-at name="content"/>
    <div class='container'>
    ... 
      <div class='column span-17 last'>
        <lift:bind name='cont'/>
      </div>
      ...
    </div>
  </lift:bind-at>
  <lift:bind-at name="footer"/><lift:bind name='ft'/>
</lift:bind-at>

Das surround-Tag gibt an, worin das Template integriert wird, das jeweilige bind-at-Tag definiert den Bereich, der im Eltern-Template die Tag-Stelle ersetzt.
Zusammen mit dem embed-Tag, das den umgekehrten Effekt des surround-Tags hat, erlaubt dieser Mechanismus die Konstruktion komplexer Templatehierarchien, die den eigentlichen Inhalt der jeweiligen Seite umfließen. Im vorliegenden Beispiel ist dieser Inhaltsbereich durch das bind-Tag mit dem Namen cont markiert. Ein Beispiel (beliebige Seite, die das Template default.html nutzt) ist in Listing 3 zu sehen.

<lift:surround with="default">

  <lift:bind-at name="hd"/>
  ...  
  </lift:bind-at>

  <lift:bind-at name="cont"/>
  ...
  </lift:bind-at>

  <lift:bind-at name="ft"/>
  ...        

  </lift:bind-at>  
  
</lift:surround>

Der View-first-Ansatz in Lift stellt sicher, dass so konstruierte Seiten, die Views, der XHTML-Struktur genügen müssen, bevor sie auf Serverseite in den Lift-Snippets weiterverarbeitet werden.
Snippets transformieren die Vieweingaben in eine mit Geschäftslogik angereicherte, resultierende XHTML-Struktur, die an den Browser gesendet wird. Dabei ist ein Snippet nicht mit einem Controller in einer MVC-Struktur gleichzusetzen. Vielmehr definiert es das lose gekoppelte Bindeglied zwischen Präsentations- und der Anwendungslogik, besitzt jedoch keinerlei Kontrollflusslogik. Der View-first-Ansatz erlaubt zudem die Referenzierung mehrerer Snippets auf einer Seite, was in einer MVC-gesteuerten Architektur durch den Kontrollfluss Controller-View nicht möglich ist. Um die Kompatibilität zu MVC-gesteuerten Anwendungen dennoch zu gewährleisten, kann das View-first-Prinzip in Lift umgangen werden.
Modelle stehen in Lift für datenzentrierte Strukturen, die in den meisten Fällen persistent definiert werden. Dazu stellt Lift mehrere Möglichkeiten zur Verfügung: einerseits über eigene Persistenz-Wrapper, andererseits über Adapter für Standards wie das Java-Persistent-API. Dabei können Modelle ähnlich wie in Ruby-on-Rails CRUD-Strukturen erzeugen, die sich direkt in die Lift-Benutzeroberfläche integrieren.
Modelle wie auch der grundlegende Rahmen einer Lift-Benutzeroberfläche lassen sich in einer eignen Boot-Klasse konfigurieren. Hier legte das Lift-Team Wert auf die Tatsache, dass für die Konfiguration keine neue Sprache (wie z. B. XML) zum Einsatz kommen sollte, sondern diese Konfiguration durch Konventionen gesteuert in Scala zu erfolgen hat.

Aufmacherbild: cable car – ski lift von Shutterstock / Urheberrecht: bofotolux

[ header = Seite 2: Websicherheit in Lift ]

Websicherheit in Lift

Sicherheit sollte in der heutigen Zeit ein zentraler Aspekt jeder Webanwendung sein. In Lift ist er ein wesentlicher Bestandteil der Architektur, sodass er für den Entwickler einer Anwendung transparent ist. Dies lässt sich an der Abarbeitung einer Lift-Anfrage erklären. Lift verwendet einen Mechanismus, der von dem üblichen HTTP-Anfrage-Antwort-Zyklus abstrahiert. Lift-Tags auf der Darstellungsseite sind auf der Serverseite über so genannte GUIDs (Global Unique Identifier) an Closure-Methoden, die ihren Kontext mitführen, gebunden. Die Ausführung einer Aktion durch den Benutzer veranlasst Lift, die entsprechenden Funktionen auf dem Server mit den Eingabewerten als Parameter aufzurufen. Da die GUIDs sessionspezifisch und zufällig sind, sind Sicherheitslücken in Lift-Anwendungen schwierig bis unmöglich zu finden. Als Beispiel sei dazu die Foursquare-Anwendung genannt, die auf Lift basiert und einer Prüfung durch den Sicherheitsexperten Rasmus Lerdorf, ohne auch nur eine Sicherheitslücke zu finden, standhielt:

ajaxButton("Accept", () => {request.accept.save; SetHtml("acceptrejectspan", <span/>}) ++ 
ajaxButton("Reject", () => {request.reject.save; SetHtml("acceptrejectspan", <span/>})

Dieses Beispiel zeigt die Foursquare-Anfragen zur Annahme und Ablehnung eines Freunds. Es demonstriert gleichzeitig, wie einfach AJAX-Anfragen durch die konsequente Ausnutzung der Scala-Funktionalitäten in Lift umgesetzt sind.
Es werden auf Serverseite zwei AJAX-Buttons als Reaktion auf eine Webanfrage erstellt. Diese Aufrufe erzeugen GUIDs für die hier dargestellten beiden Closure-Methoden, die ihren Request kapseln, um zu einem späteren Zeitpunkt der Ausführung darauf zurückgreifen zu können. Es sind keine weiteren Informationen zwischen Anfrage und Antwort durch Aktivieren des Buttons durch den Benutzer auszutauschen. Der SetHtml-Aufruf ersetzt asynchron das Tag mit der ID acceptrejectspan durch den angegeben HTML-Content.
Lift setzt die Abstraktion des HTTP-Mechanismus konsequent und offen um. Das bedeutet, dass es viele Möglichkeiten gibt, die Sicherheit einer HTTP-Anfrage durch eigene Prüfungen zu einem möglichst frühen Zeitpunkt zu gewährleisten. Dazu dient beispielsweise die URL-Manipulation in einer REST-Anfrage:

serve {
  case "api" :: "user" :: AsUser(user) :: _ XmlGet _ => <b>{user.name}</b>
  case "api" :: "user" :: AsUser(user) :: _ JsonGet _ => JStr(user.name)
}

Dieser Aufruf zerlegt den eingehenden URL anhand Scala-Pattern-Matching in drei Bestandteile, wobei der dritte Teil die Berechtigung des Benutzeraufrufs prüft. Eine weitere Einflussmöglichkeit hinsichtlich der Sicherheit bietet der Menümechanismus, der Zugriffskontrolle, Seitennavigation und Menüstrukturierung verbindet.

Eine Xtext-Definition für eine Lift-DSL

Das Erlernen neuer Konzepte bedeutet für den Leser auch immer, dass er eine Einstiegshürde nehmen muss, die umso größer ist, je weniger Unterstützung durch Artefakte im Umfeld des Produkts zur Verfügung stehen. Hier macht Lift keine Ausnahme. Die vorliegende Dokumentation ist trotz der mehrjährigen Existenz des Frameworks unzureichend, und die Beispiele der Lift-Demoseite setzen bereits tiefergehendes Wissen über die Grundlagen voraus.
Vor diesem Hintergrund beschreibt der vorliegende Artikel eine Architekturdomänensprache für die Konstruktion einer Lift-Architektur. Der Vorteil einer solchen Architekturdomänensprache besteht darin, dass eine einheitliche Sprachdefinition für eine Vielzahl von Architekturen verwendet werden kann.
Die in dem Abschnitt „Architektursprache für komponentenorientiertes Softwaredesign“ des Artikels „Scala + X“ in der Ausgabe 2.2013 des Eclipse Magazins auf Seite 61 und Seite 62 (Listing 4) beschriebene Xtext-Beschreibung bildet die Grundlage für diese Architekturdefinition. Da Lift einige Konzepte verwendet, die in dieser Art in anderen Frameworks nicht zu finden sind, muss sie jedoch an einigen Stellen erweitert werden. Die Xtext-Beschreibung in Listing 4 zeigt lediglich die wichtigsten Elemente auf, die für eine Lift-DSL notwendig sind.

Model returns Namespace:
  (elements+=ModelElement)*;

ModelElement:
  Namespace | Type | Entity | Component | TransportObject | View | WebTemplate;

Namespace:
  'namespace' name=(FQN|STRING) '{'
    (elements+=ModelElement)*
  '}';

Type returns Type:
  SimpleType | Entity | TransportObject | DataType | Interface;

Component:
  'component' name=ID ('extends' extendsClass=([Component]))? '{'
    (clDesc=ClassDescription)?
    classBodies+=(RequiredPart|MethodPart|Property)*
  '}' |
  'component' name=ID ('provides' (extendsIf+=[Interface]+))? '{'
    (clDesc=ClassDescription)?
    classBodies+=(RequiredPart|MethodPart|Property)*
  '}' |
  'component' name=ID ('extendsType' (extendsType+=[SimpleType]+))? '{'
    (clDesc=ClassDescription)?
    classBodies+=(RequiredPart|MethodPart|Property)*
  '}';

TransportObject:
  'transport' name=ID ('extends' (extendsIf+=[TransportObject]+))? '{'
    properties+=Property*
    ('showAs' dn=STRING 'plural' pl=STRING)?
    ('singleton')?
  '}';

View:
   WebView|PortalView;

WebTemplate:
  ('webTemplateView' name=("base"|"default") ('{' 
    webTemplateViewDescAttrs+=(WebTemplateViewDescAttr)+
  '}')?) |
  ('webTemplateView' name=(FQN|STRING) '{' 
    webTemplateViewDescAttrs+=(WebTemplateViewDescAttr)+
  '}')
  ;

Die Xtext-Beschreibung und eine daraus resultierende DSL definieren sich in der Struktur über Namensräume, die auf Schichten eines Softwaresystems abgebildet werden. Diese Schichten sind für eine Lift-Architektur die folgenden: View und WebTemplate für die Präsentationsschicht, Component für die Geschäftslogikschicht und Entity für den Datenzugriff.
In der Paket- und Deploymentsicht definiert ein Namensraum ein Verzeichnis. Die Verzeichnisstruktur einer Lift-Anwendung ist vorgegeben und kann anhand der Namensgebung von geschachtelten Namensräumen durch eine Lift-DSL erzeugt werden (Listing 5).

namespace <application-name> {
  
  namespace src {
    
    namespace test {
    }
    
    namespace main {
      
      namespace resources {
      }
      
      namespace webapp {
        
        namespace templates-hidden {
        }
      }
      
      namespace scala {
        
        namespace <code6gt {
          
          namespace model {
          }
          
          namespace snippet {
          }
        }
        
        namespace bootstrap {
          
          namespace liftweb {
          }
        }
      }
    }
  }
}

[ header = Seite 3: Umsetzung der Lift-DSL ]

Umsetzung der Lift-DSL

Wie können nun verschiedene Ausprägungen einer Lift-DSL bezogen auf die vorgegebene Struktur aussehen? Dazu nun verschiedene Anwendungsfälle.
Die Veranschaulichung der grundlegenden Vorgehensweise der Konstruktion einer Lift-DSL soll die Formulierung einer einfachen Kundenverwaltung anhand CRUD zeigen. Die Idee der Integration prototypischer CRUD-Unterstützung hat Lift weitestgehend von Ruby-on-Rails übernommen, auf die Details der Konfiguration dieses Mechanismus geht der Artikel nicht weiter ein.
Die Erfassung der Lift-DSL erfolgt durch den Standardmechanismus anhand Eclipse und Xtext. Dazu erzeugt man nach dem Öffnen der Eclipse-Laufzeitumgebung aus dem Xtext-Projekt ein neues Standardprojekt und bestätigt die Frage nach dem Hinzufügen der Xtext-Eigenschaft zum Projekt.
Um eine neue Lift-DSL zu formulieren, empfiehlt es sich zunächst, die oben gezeigte Struktur zu kopieren, um den grundlegenden Rahmen zu erzeugen. Im Anschluss daran beginnt die Formulierung der Typen (Abb. 1), die in dem Namensraum der Anwendung bekannt sein sollen.

Abb. 1: Definition eigener Typen

Um nicht jedes Mal alle benötigen Pfade und Dateien anlegen zu müssen, existieren Vorlagen, die die Formulierung der DSL unterstützen, z. B. für die Erzeugung der Testverzeichnisse, die Anlage der Ressourcen beispielsweise für die Lokalisierung, Bilder und CSS-Dateien sowie die Anlage der Lift-Templateverzeichnisse und der Templates selbst (Abb. 2). Alternativ ist es auch möglich, die Lift-Verzeichnisse und Lift-Templates manuell zu erzeugen (Abb. 3).

Abb. 2: Formulierung von Templates

Abb. 3: Nutzung von Standardtemplates

Dabei sind die Templates base und default vorgegebene Templates, die von untergeordneten Templates oder Sichten referenziert werden. Diese Templates wurden bereits zu Anfang des Artikels beschrieben.
Die webView „index“ (Abb. 4) definiert die Standardstartsicht der Anwendung. Sie ist eingebettet in das Standardtemplate und definiert keine zusätzlichen Kopf- oder Fußzeilenbestandteile. Der Inhalt der Startsicht besteht lediglich aus der Anzeige einer Willkommensnachricht mit dem aktuellen Zeitstempel. Der Inhalt zeigt zudem, wie Sichten in Lift Anwendungscode referenzieren. Dies geschieht durch die Angabe der Logikkomponente, hier Start. Ist keine explizite Methode angegeben, ruft Lift per Default die Methode render auf.

Abb. 4: Darstellung der Einstiegswebsicht mit Standardtemplates

Die Definition der Websicht kann natürlich auch rein manuell erfolgen. Statt des Standardtemplates kann sich die Sicht auch in ein eigenes Template integrieren, das dann im templates-hidden-Namensraum definiert sein sollte.

Abb. 5: Erfassung der Einstiegswebsicht ohne Standardtemplates

Während der webapp-Namensraum die Websichten und damit die Präsentationsebene beschreibt, erfolgt die Definition der Anwendungslogik und der Datenhaltung im Namensaum scala.
Für die Definition der Datenhaltung dient hier der model-Namensraum. In diesem sind die Entitäten abgelegt. Die Beispielkundenverwaltung kennt einen Kunden mit den Eigenschaften Name, Geburtstag und Adresse.
Ein spezielles Tag definiert der Abschnitt withDescription. Hierdurch kann das jeweilige Konstrukt (hier: die Entität) eine zusätzliche Beschreibung definieren, eine Art Annotation, die die Codeerzeugung für die aktuell verwendete Zielsprache und Plattform nutzt. Für die Lift-Plattform sind dies unter anderem die Tags für die CRUD-Nutzung, der Anzeigename und die Validierungen.

Abb. 6: Formulierung von Entitäten

Die Anwendungslogik ist in dem Namensraum snippet abgelegt. Dieser definiert in der Startkomponente, wiederum in einem speziellen Annotationsblock, die Liste der Sichten, die diese Komponente referenziert. Damit dies funktionieren kann, müssen sie dem Namensraum bekannt sein.
Innerhalb einer einzelnen Sichtbeschreibung ermöglicht das tags-Element die Abbildung der Tags auf die Anwendungslogik, die das View-first-Prinzip verwendet. Diese Logik besteht hier lediglich aus der Anzeige des aktuellen Zeitstempels, welche die Methode now beschreibt.

Abb. 7: Erfassung von Anwendungslogik

Der Konfigurationsbereich definiert sich über den Namensraum bootstrap.liftweb und hier in der fest vorgegebenen Boot-Klasse. Diese ermöglicht zum einen die Datenbank- und Datenzugriffskonfiguration, zum anderen die Definition des Anwendungseinstiegs, der Zugriffsregeln und der Menüstruktur. Die vorliegende Kundenverwaltung verwendet die mitgelieferte H2-Datenbank, das Standard-Datenbankzugriffs-Framework und keinerlei Benutzerverwaltung und Zugriffsregeln. Die Registrierung der Modellentitäten erfolgt in dem Block modelEntities. Die CRUD-Verwendung der Kundenentität erfordert zusätzlich die Definition eines eigenen Menüeintrags.

Abb. 8: Konfiguration der Menüsteuerung

Abb. 9: Darstellung der Startseite

Ausblick

Der anhand dieser Sprachdefinition erzeugte Rahmen kann als Ausgangspunkt für die weitere Integration von Funktionalitäten dienen. Diese Integration beschreibt der zweite Teil des Artikels. Anhand der vorgestellten Xtext-DSL erläutert er zudem anhand von Beispielen, welche nutzbaren Vorteile der Einsatz von Lift im Vergleich mit gängigen Webframeworks bietet.
Zur Veranschaulichung der Flexibilität der DSL beschreibt er weiterhin die Möglichkeit der Verwendung moderner JavaScript-Frameworks anhand der Definition eines Webportal-Anwendungsrahmens im Zusammenspiel mit jQuery-Widgets.

Hinweis: Der zweite Teil des Artikels erscheint am 3. Januar 2014 hier auf jaxenter.de.

Geschrieben von
Michael Adams
Michael Adams
Michael Adams ist technischer Seniorsoftwareberater bei der NTT Data, Deutschland in Köln und beschäftigt sich mit Softwarearchitekturthemen in Java, modellgesteuerter Entwicklung und Domänensprachen.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.