Einführung in die REST-basierte Webentwicklung mit Ruby on Rails

„Gib mir den REST.“
Kommentare

Der REST-Architekturstil könnte sich in den nächsten Jahren deutlich verbreiten und die Art der Programmierung im Netz nachhaltig beeinflussen. Selbst Analysten sehen mittlerweile einen Trend zu REST [1]. Ruby on Rails unterstützt bereits jetzt die Entwicklung REST-basierter Webanwendungen und wird seinen Fokus auch zukünftig auf diesen Architekturstil, statt auf SOAP legen. Dieser Artikel gibt einen kurzen Einblick in die REST-basierte Webentwicklung mit Ruby on Rails und zeigt, welche Unterstützung das Framework bereits heute hierfür bietet.

Eine Einführung zu Ruby on Rails [2] findet sich unter [3]. Was REST betrifft – der Begriff REST steht für REpresentational State Transfer und stammt aus der Dissertation von Roy Fielding [4] aus dem Jahr 2000. REST beschreibt ein Architektur-Paradigma für Web-Anwendungen, bei dem Ressourcen mit Hilfe der HTTP-Methoden GET, POST, PUT und DELETE angefordert und manipuliert werden.

REST

Im Sinne von REST ist eine Ressource eine Entität, die über eine URL adressiert wird und die über die HTTP-Methoden bearbeiten werden kann. So verweist z.B. die URL /addresses/1 auf eine Adresse (mit der Id 1), die mit Hilfe der GET-Methode geladen oder mit Hilfe der DELETE-Methode gelöscht werden kann.

Im Unterschied zur heutigen Nutzung definiert die URL dabei nicht die Ressource und die darauf aufzurufenden Aktionen (z.B. Laden oder Löschen), sondern nur die Ressource selbst. Für eine Rails-Anwendung lautet die URL im Beispiel der Adresse nicht mehr /addresses/show/1 oder addresses/destroy/1, sondern nur noch /addresses/1. Die gewünschte Aktion wird allein durch die HTTP-Methode bestimmt, z.B. GET (Laden) oder DELETE (Löschen).

Die Ressource wird gegebenenfalls in unterschiedlichen Formaten, z.B. HTML, XML, RSS, PDF usw. repräsentiert. Sofern möglich, liefert der Server das Format, das der Client wünscht. Die Anfrage der Ressource erfolgt immer über dieselbe URL, die z.B. eine HTML-Repräsentation der Adresse für einen Menschen oder eine XML-Repräsentation für einen Computer liefert. Unterschiedliche Formate einer Ressource werden im Sinne von REST nicht als unterschiedliche Ressourcen betrachtet.

Das gewünschte Format wird bei einer Rails-Anwendung über den HTTPHeader im Request oder über die Endung in der URL (z.B. /addresses/1.html oder /addresses/1.xml) definiert. Bei Letzterem handelt es sich ganz konkret gesehen nicht mehr um dieselbe URL. Im Falle der Rails-Anwendung definiert die Endung aber nur das gewünschte Format, die Ressource bleibt dieselbe. Sie ist jederzeit auch ohne Suffix unter /addresses/1 zu erreichen, z.B. um sie zu aktualisieren oder zu löschen.

Warum REST?

REST-Anwendungen veröffentlichen Ressourcen über ein einheitliches Interface nach außen. Die Anwendung wird zu einer API und „maschinenlesbar“. Weder client- noch serverseitig wird ein gesonderter Stack für die Nutzung bzw. die Entwicklung von Web Services benötigt. Durch REST wird das Web zu einer Programmierplattform für verteilte Anwendung und nutzt dabei vorhandene, etablierte und gut verstandene Technologien. Egal welchen Typs die Ressource ist (eine Adresse, ein Warenartikel, ein Blog-Eintrag), die Verwaltung erfolgt immer über die HTTP-Methoden und ist von der Art des Zugriffs immer gleich. Über ein und dieselbe URL werden unterschiedliche Formate unterstützt, die Anwendung wird Multiclient-fähig.

REST und Rails

Eine Motivation hinter der Entwicklung von Rails war und ist die Schaffung einfach zu benutzender Bibliotheken. Kein Wunder also, dass das Kernteam um David Heinemeier Hansson den „einfacheren“ Ansatz von REST gegenüber dem eher komplexen SOAP-Modell bevorzugt.

In der kommenden Rails-Version 2.0 wird daher das Subframework Action Web Service aus dem Rails-Kern entfernt. Es ist bisher für die Integration von Rails-basierten Anwendungen über SOAP oder per XML-RPC zuständig und steht dann nur noch als Plug-in zur Verfügung. Der Scaffold-Generator wird in Rails 2.0 ausschließlich REST-basierte Controller erzeugen und über das neue Subframework Active Resource werden Ressourcen im Netz analog den Datenbankmodellen angesprochen (s.u.). Bereits die Rails-Version 1.2 bietet Unterstützung für REST, die im Folgenden beschrieben wird.

Aufmacherbild: render of gears and the text ruby on rails von Shutterstock / Urheberrecht: Georgejmclittle

[ header = Bespielprojekt ]

Beispielprojekt

Als einfaches Beispiel einer REST-basierten Rails-Anwendung wird eine Adressenverwaltung gewählt. Dazu ist das Projekt per Kommandozeilenbefehl rails addressbook anzulegen, die Datenbank address_book_development zu erzeugen und die Konfigurationsdatei config/database.yml gegebenenfalls anzupassen. Falls lokal noch keine Rails-Version 1.2 vorhanden ist, wird diese innerhalb des Projekts installiert und beeinflusst so systemweite Rails-Versionen nicht (Listing 1).

$ cd addressbook
$ rake rails:freeze:edge TAG=rel_1-2-3

REST Scaffolding

Das Grundgerüst der Anwendung, genauer gesagt die Ressource AddressBook wird mit Hilfe des Generators für das Resource-Scaffolding erzeugt (Listing 2). Ein Blick in das generierte Modell AddressBook zeigt keine Unterschiede zu dem bekannten und initial leeren Modell (Listing 3). Die notwendige Datenbanktabelle zum Modell wird durch den Kommandozeilenbefehl rake db:migrate erstellt.

$ script/generate scaffold_resource address_books
name:string
$ …
# app/models/address_books.rb
class AddressBook < ActiveRecord::Base
end

Der zur Ressource erzeugte Controller heißt AddressBooksController und ist „RESTful“ (Listing 4). Rails verwendet hier die Pluralform der Ressource, da über den Controller quasi alle Adressbücher verwaltet werden. Die Pluralform im Namen ist wichtig, da dies von den Routing-Methoden (s. u.) erwartet wird.

# app/controllers/address_books_controller.rb
class AddressBooksController < ApplicationController
# GET /address_books
# GET /address_books.xml
def index…
# GET /address_books/1
# GET /address_books/1.xml
def show…
# GET /address_books/new
def new…
# GET /address_books/1;edit
def edit…
# POST /address_books
# POST /address_books.xml
def create…
# PUT /address_books/1
# PUT /address_books/1.xml
def update…
# DELETE /address_books/1
# DELETE /address_books/1.xml
def destroy…
end

Der AddressBooksController ist für die Bearbeitung der Ressource Address-Book zuständig, und zwar genau und nur für diese. Für jede REST-Operation GET, POST, PUT und DELETE steht genau eine Action zu Verfügung (Tabelle 1). Im Unterschied zum bekannten Scaffold-generierten Controller wurde über jeder Action ein Kommentar eingefügt, der die zugehörige HTTP-Methode und REST-URL beschreibt. Die Anzeige eines Adressbuchs im HTML-Format erfolgt z.B. über den Aufruf GET /address_books/1, der auf die Action show abgebildet wird. Für eine XML-Repräsentation des Adressbuchs lautet die URL GET/address_books/1.xml.

Tabelle 1: Genau eine Action pro REST-Operation

Die Action show in Listing 5 zeigt auch, wie der Controller die unterschiedlichen Formate einer Ressource mit Hilfe der Methode respond_to ausliefert. Je nach Format wird der entsprechende Code ausgeführt, der für die Erzeugung des Formats zuständig ist. Im Falle von HTML wird per Default der zur Action definierte View (show.rhtml) gerendert und ausgeliefert. Im Falle von XML erfolgt eine Konvertierung der Modellinstanz mit Hilfe der Methode to_xml. Wird bei der Anfrage kein Format definiert, greift die erste Formatdefinition, hier HTML. Eine Änderung oder die Erweiterung um eigene Formate ist jederzeit möglich [5]. Für die Auslieferung unterschiedlicher Formate gibt es keine unterschiedlichen Actions im Controller. Der Code ist kurz, verständlich und nicht redundant. Wie in Listing 4 zu sehen, existieren neben den vier REST-Actions zusätzlich index, new und edit, die nicht als reinen REST-Actions gelten, aber notwendig sind. Die Action index dient der Auslieferung aller Instanzen. Die in Rails-Controllern hierfür ursprünglich verwendete Action list entfällt. Die Actions new und edit sind notwendig, um den View für das Anlegen und Editieren von Ressourcen auszuliefern. Dass es sich nicht um reine REST-Actions handelt, spiegelt sich auch in deren URL /address_books/new und /address_books/1;edit wieder. In diesem Fall ist die Aktion in der URL enthalten.

# app/controllers/address_books_controller.rb
…
# GET /address_books/1
# GET /address_books/1.xml
def show
@address_book = AddressBook.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @address_book.to_xml }
end
end
…

Der Aufbau eines REST-basierten Controllers gilt natürlich nicht nur für Scaffold-generierte Controller, sondern auch für alle selbst geschriebenen. Wenn nötig, können auch eigene Actions hinzugefügt werden (s.h. [5]). Dies ist jedoch genau zu überlegen, da es die einheitliche Schnittstelle zerstört und damit nicht das REST-Paradigma fördert.

Für einen ersten visuellen Test wird der Webserver per Kommandozeilenbefehl ruby script/server gestartet und die Liste aller Addressbücher (noch leer) ist unter der URL http://localhost:3000/address_books zu sehen bzw. kann manipuliert werden.

REST Routes

Die vom Scaffold-Generator erzeugten Views sind ebenfalls bereits „RESTful“. Ein Blick in den View index.rhtml (Listing 6) zeigt die Verwendung der Methoden wie address_book_path-(address_book) oder auch new_address_book_path. Diese werden von Rails für die Verwendung in Controllern und Views bereitgestellt und erzeugen einen Link mit der entsprechende HTTP-Methode und der entsprechenden URL auf die REST-Action (z.B. GET/address_books/1 oder GET /address_books/new).

# app/views/address_books/index.rhtml
…
<% for address_book in @address_books %>
<tr>
<td><%=h address_book.name %></td>
<td><%= link_to 'Show', address_book_path
(address_book) %></td>
<td><%= link_to 'Edit', edit_address_book_path
(address_book) %></td>
<td><%= link_to 'Destroy', address_book_path
(address_book),
:confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
…
<%= link_to 'New address_book', new_address_book_
path %>
…

Die Tabelle 2 zeigt einen Überblick über die durch den Eintrag aus Listing 7 erzeugten Routing-Methoden. Zu jeder aufgeführten Methode gibt es eine weitere mit der Endung _url statt _path, die an Stelle des relativen Pfads (/address_books) die gesamte URL (http://localhost:3000/address_books) liefert.

# config/routes.rb
map.ressources :address_book
…

Tabelle 2: Überblick über erzeugte Routing-Methoden mit Listing 7

Durch die Verwendung der Routing-Methoden entfällt die explizite Angabe des Controller- und Action-Namens bei der Angabe von Links im View oder Controller. Der Einsatz dieser so genannten Named Routes ist auch für Links nicht- REST-basierter Controller sinnvoll.

Alleine ein Einsatz der Routing-Methode alleine aber noch nicht. Die Nutzung der HTTP-Methoden DELETE und PUT bei REST-basierten Anwendungen ist nämlich schön und gut, nur leider unterstützen heutige Browser die Methoden nicht und bieten nur GET und POST. Um die Methoden PUT und DELETE dennoch in der Rails-Anwendung nutzen zu können, wird diese über ein Hidden-Field an den Server übertragen und das Framework wandelt den Parameter in die entsprechende HTTP-Methode (siehe Link ‚Destroy‘ in Listing 6).

[ header = Verschachtelte Ressourcen ]

Verschachtelte Ressourcen

Interessant wird der REST-basierte Ansatz bei der Verwendung von verschachtelten Ressourcen. Ein Adressbuch enthält Adressen, die über das Modell Address repräsentiert werden. Der notwendige Code wird mit Hilfe des Scaffold-Generators erzeugt und die Datenbanktabelle angelegt (Listing 8). Über die URL http://localhost:3000/addresses/new können danach neue Adressen angelegt werden.

$ script/generate scaffold_resource address name:
string address_book_id:integer
$ rake db:migrate

Der Zugriff auf eine Adresse erfolgt aber immer über ein Adressbuch, denn die Adresse ist Teil eines Adressbuchs. Die Assoziation zwischen Adressbuch und Adresse wird in der Datenbanktabelle addresses über den Fremdschlüssel address_book_id (Listing 8) und in den Modellen AddressBook und Address über die Angabe von has_many und belongs_todefiniert (Listing 9).

# app/models/address_book.rb
class AddressBook < ActiveRecord::Base
has_many :addresses
end
# app/models/address.rb
class Address < ActiveRecord::Base
belongs_to :address_book
end

Es handelt sich bei Address somit um eine verschachtelte Ressource und beim Zugriff auf eine Adresse ist das Adressbuch in der URL mit anzugeben. Die durch das Scaffolding generierten Routing-Methoden zum Modell Address enthalten per Default keinerlei Informationen zum Adressbuch. Die Methode new_address_path erzeugt z.B. die URL /addresses/new. Ohne die Angabe des Adressbuchs kann die neu anzulegende Adresse jedoch keinem Adressbuch zugeordnet werden. Um das Adressbuch in der URL aufzunehmen, sind die durch den Scaffold-Generator eingetragenen Routing-Definition wie in Listing 10 zu ändern.

# config/routes.rb
map.resources :address_books
map.resources :addresses
map.resources :address_book do |address_books|
address_books.resources :addresses
end

Hierdurch wird die Verschachtelung definiert und die Routing-Methoden, wie z.B. new_address_path, erwarten zusätzlich das zugehörige Adressbuch als Parameter. Die Methode new_address_ path(@address_book) erzeugt somit z.B. eine URL der Form /addressbooks/47/addresses/new. Bei Rails wird die Hierarchie der Ressourcen somit auch über die URL sichtbar.

Durch die Verschachtelung sind die Actions im AddressesController anzupassen, die bisher nur auf Address arbeiten. Listing 11 zeigt die notwendige Änderung in der Action show. Die Routing-Methoden von Address senden durch die Parametrisierung mit einer Adressbuch-Instanz automatisch die Id des Adressbuchs im Parameter address_book_id. Aus dem Adressbuch kann dann die entsprechende Adresse über den Parameter id gelesen werden.

# app/controllers/addresses_controller.rb
def show
@address = Address.find(params[:id])
@address_book = AddressBook.find(params
[:address_book_id])
@address = @address_book.addresses.find
(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @address.to_xml }
end
end

Die anderen Actions sind analog anzupassen, genauso wie auch die Views. Listing 12 zeigt das Beispiel von index.rhtml, die neben der Adresse jetzt auch das Adressbuch an die Routing-Methoden übergibt.

# app/views/address_books/index.rhtml
…
<% for address in @addresses %>
<tr>
<td><%=h address.name %></td>
<td><%= link_to 'Show', address_path(address.
address_book, address) %></td>
<td><%= link_to 'Edit', edit_address_path(address.
address_book, address) %></td>
<td><%= link_to 'Destroy', address_path(address.
address_book, address),
:confirm => 'Are you sure?', :method => :delete
%></td>
</tr>
<% end %>
…
<%= link_to 'New address', new_address_path
(@address_book) %>
…

Es sei darauf hingewiesen, dass die nachträglichen Anpassungen durch des Scaffolding bedingt sind. In einen realen Projekt werden die Controller und Views typischer Weise von Hand erstellt und die Verschacheltung von vorne herein berücksichtigt.

Active Resource

Eine weitere Neuerung zur Unterstützung REST-basierter Entwicklung stellt das in Rails 2.0 enthaltene Subframework Active Resource da. Es dient der Erstellung von REST-basierten Webservice-Clients. Ein solcher Client kommuniziert über HTTP mit dem Server und verwendet dabei die vier REST-typischen HTTP-Verben. Durch Active Resource erfolgt der Zugriff auf den entfernten Dienst dabei genauso, wie der Zugriff auf die Datenbank mit Hilfe von Active Record (Listing 14). Active Resource ist bereits in der aktuellen Entwicklungsversion enthalten, die, wie in Listing 13 gezeigt, installiert werden kann.

$ cd addressbook/vendor
$ mv rails rails-1.2
$ svn co http: //dev.rubyonrails.org/svn/rails/
trunk rails
# test/active_resource_test.rb
require File.dirname(__FILE__) + “/../config/
environment“
require File.dirname(__FILE__) + “/../vendor/rails/
activeresource/lib/active_resource“
class AddressBook < ActiveResource::Base
self.site = “http: //localhost:3000“
end
class Address < ActiveResource::Base
self.site = "http: //localhost:3000/address_books/:
address_book_id"
end
address_book = AddressBook.find(1)
puts address_book.name
address = Address.find(2, :params => {:address_book_
id => address_book.id})
puts address.name

Für das Beispiel werden die Klassen AddressBook und Addressvon Hand erstellt (Listing 14). Diese erben von der Basisklasse des Active Resource-Frameworks und definieren die Domäne des entfernten Dienstes unter dem die Ressource zu erreichen ist. Das ist zunächst schon alles.

Der Aufruf einer Methode unterscheidet sich äußerlich nicht von dem Aufruf einer Methode eines Active Record-Modells. Im Hintergrund erfolgt aber statt der Ausführung eines SQL-Statements, der Aufruf des entfernen Dienst. Listing 14 zeigt Beispiele der find-Methode, die zu einem Aufruf GET /address_books/1.xml bzw. GET /address_books/1/addresses/2.xml auf dem Service unter der URL http://localhost:3000 führen. Die vom Dienst im XML-Format gelieferte Ressource wird vom Client in eine Instanz des Modells Address bzw. AddressBook konvertiert und zurückgegeben. Analog dazu funktioniert das Anlegen, Aktualisieren oder Löschen von entfernten Ressourcen mit Hilfe der HTTP-Methoden POST, PUT und DELETE.

Active Resource bietet eine sehr gute Unterstützung für die Entwicklung von lose gekoppelten Systemen in Rails. Die Analogie zu Actice Record fördert das Verständnis und macht die Handhabung einfach.

Fazit

Auch wenn es hier und da einem kleinen Trick bedarf, Ruby on Rails unterstützt bereits jetzt die REST-basierte Entwicklung sehr elegant. Die Programmierung muss nicht ganz oder gar nicht REST-basiert erfolgen. Eine Mischung aus REST und herkömmlichem Stil ist ebenso möglich, wie eine schrittweise Migration der Webanwendung. Bei neu zu erstellenden Anwendungen sollte darüber nachgedacht werden, diese von vorne herein RESTful zu entwickeln.

Nicht zuletzt durch die weitere Verbreitung von Rails und die konsequente Ausrichtung auf REST, wird sich auch das REST-Paradigma ausbreiten und die Art der Programmierung im Netz beeinflussen. Andersherum wird die Verbreitung von REST unabhängig von Rails auch Rails selbst weiter etablieren und verbreiten. Mit Rails und REST steht ein starkes Team für die Entwicklung zukünftiger Webanwendungen zur Verfügung.

Der Artikel stellt nur eine kurze Einführung in das Thema REST und Rails dar. Für weitere Informationen wird ein Blick in das PDF-Tutorial „RESTful Rails Development“ [5] und das Buch [6] empfohlen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -