en
de

REST basierte Webapplikation mit Jersey und AngularJS – Teil 1

24 Juni 2013
| |
Lesezeit: 6 Minutes

Aktuelle Webarchitekturen setzen heute vermehrt auf einen API-Ansatz. Dieses Tutorial zeigt auf, wie sich eine solche Web-API und eine darauf zugreifende Client-Applikation mit der JAX-RS Referenzimplementation Jersey, AngularJS und einer relationalen Datenbank (z.B. MySQL) realisieren lässt.

Als Beispiel wird eine Lernkartei erstellt, welche als HTML5 Applikation realisiert wird und die Anzeige der verschiedenen Lernkarteien und Karten ermöglicht. Diese Beispielapplikation wird in 2 Teilen erstellt. Dieser erste Beitrag beschäftigt sich mit der Server-Seite. Der nächste Beitrag wird sich entsprechend um den Client kümmern.

Eingesetzte Tools, Umgebung

Für die Umsetzung des Beispielprojekts habe ich folgende Umgebung eingesetzt:

Folgende Libraries werden in diesem Tutorial eingesetzt:

Die Architektur

Folgende Grafik vermittelt eine Übersicht über die gewählte Architektur auf der Client- und Server-Seite.

Übersicht über die Architektur der Lernkartei Webapplikation mit Jersey und AngularJS.

Das Datenbankmodell

Folgendes Diagramm zeigt das einfache Datenmodell, welches für die Lernkartei Applikation benötigt wird.

Übersicht über das Datenbankmodell der Lernkartei Webapplikation mit Jersey und AngularJS.

Die Hibernate Entitäten

Für die Persistierung in der Datenbank werden Hibernate Entitäten definiert. Diese dienen dazu, Entitäten in die Datenbank zu schreiben und daraus zu lesen. Ich habe die Entitäten auf Klassenebene mit folgenden Annotationen versehen:

@Entity
@Table(name = "Beliebiger Tabellenname")

Die Annotation @Entity dient dazu, eine Klasse als Entität zu definieren. Mit der Annotation @Table besteht anschliessend die Möglichkeit, Hibernate (in unserem Fall) mittzuteilen, welche Tabelle für die Persistierung der entsprechenden Entities verwendet werden soll.

Für das Beispielprojekt werden folgende Hibernate Entitäten benötigt:

CardEntity

Diese Entität stellt eine Karte innerhalb einer Lernkartei dar und besitzt folgende Attribute:

@Entity
@Table(name = "cards")
public class CardEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  private String question;
  private String answer;
  @ManyToOne
  private CardDeckEntity carddeck;

  // Hier fehlen noch die notwendigen Getters & Setters
}

CardDeckEntity

Diese Entität stellt eine Lernkartei welche eine beliebige Menge an Karten beinhaltet dar und besitzt folgende Attribute:

@Entity
@Table(name = "carddecks")
public class CardDeckEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;
  private String title;
  @Column(name = "cover_image")
  private String coverImage;
  @OneToMany(mappedBy = "carddeck")
  private List<CardEntity> cards;

  // Hier fehlen noch die notwendigen Getters & Setters
}

Die Domain Entitäten

Für das realitätsgetreue Abbild der Elemente Karte und Lernkartei werden Domänen Entitäten definiert. Diese werden innerhalb der Server Applikation in der Business-Logik verwendet und über die REST Ressourcen nach aussen zur Verfügung gestellt. Bei den Domänen Entitäten handelt es sich um ganz gewöhnliche POJO’s ohne jegliche Annotationen und dergleichen. Die Definition von einer separaten Hibernate und Domänen Entität wurde deshalb gemacht, weil so eine zu starke Kopplung zwischen den über die Schnittstelle zur Verfügung gestellten Elementen und den Elementen, die in die Datenbank persistiert werden, verhindert werden kann. Dank dieser Trennung ist es beispielsweise möglich, bei den Domänen Entitäten, welche via REST veröffentlicht werden, weitere Attribute (in unserem Fall die Anzahl Karten innerhalb einer Lernkartei) zu einer Entität hinzuzufügen, ohne dies dabei auf die Datenbank durchzuschreiben. Selbstverständlich könnten die Hibernate-Entitäten auch direkt via REST exponiert werden.

Für das Beispielprojekt werden folgende Domänen Entitäten benötigt:

Card

Diese Domänen Entität ist das pendant zur Hibernate Entität CardEntity und besitzt leicht veränderte Attribute:

public class Card {
  private int id;
  private String question;
  private String answer;
  private int cardDeckId;

  // Hier fehlen noch die notwendigen Getters & Setters
}

CardDeck

Diese Domänen Entität ist das pendant zur Hibernate Entität CardDeckEntity und besitzt leicht veränderte Attribute:

public class CardDeck {
  private int id;
  private String title;
  private String coverImage;
  private int numberOfCards;

  // Hier fehlen noch die notwendigen Getters & Setters
}

Die Mapper

Für die Konvertierung einer Hibernate Entität in eine Domänen Entität, habe ich für dieses Beispielprojekt pro Entität einen Mapper definiert. Dieser könnte selbstverständlich auch entsprechend erweitert werden, um die Konvertierung in die umgekehrte Richtung zu ermöglichen. Da dieses Beispielprojekt allerdings nur aus der Datenbank liest und nicht in diese schreibt, wird dies nicht benötigt. Als Alternative eines eigenen Mappers pro Entätit könnte auch eine externe Bibliothek (z.B. Dozer) für das Mapping verwendet werden.

Als Basis für alle Mapper wurde ein abstrakter Mapper definiert. Dieser definiert die abstrakte Methode mapToDomain, welche die Konvertierung einer Hibernate Entität in eine Domänen Entität ermöglicht und von den konkreten Mappern implementiert werden muss. Gleichzeitig bietet er die Methode mapToDomainList an, mittels welcher eine Liste von Hibernate Entitäten direkt in Domänen Entitäten umgewandelt werden kann.

public abstract class AbstractMapper<D, E> {

	public abstract D mapToDomain(final E entityItem);

	public final List<D> mapToDomainList(final List<E> entityItems) {
		List<D> mappedItems = new ArrayList<D>(entityItems.size());
		for (E entityItem : entityItems) {
			mappedItems.add(mapToDomain(entityItem));
		}
		return mappedItems;
	}

}

Der konkrete Mapper, welcher pro Entität definiert werden muss, erweitert entsprechend den abstrakten Mapper und implementiert die mapToDomain Methode.

public class CardDeckMapper extends AbstractMapper<CardDeck, CardDeckEntity> {

	@Override
	public final CardDeck mapToDomain(final CardDeckEntity cardDeckEntity) {
		CardDeck cardDeck = new CardDeck();

		cardDeck.setCoverImage(cardDeckEntity.getCoverImage());
		cardDeck.setId(cardDeckEntity.getId());
		cardDeck.setNumberOfCards(cardDeckEntity.getCards().size());
		cardDeck.setTitle(cardDeckEntity.getTitle());

		return cardDeck;
	}

}

Die Services

Für die minimale Business-Logik, welche in diesem Beispielprojekt benötigt wird, habe ich einfache Services definiert. Diese übernehmen folgende Aufgaben:

  • Öffnen und Schliessen der Hibernate Session
  • Verwalten der Transaktionen
  • Laden der Hibernate Entitäten aus der Hibernate Session
  • Konvertierung der Hibernate Entitäten in Domänen Entitäten mittels Mapper

In dieser Beispielapplikation wird pro Entität ein Service definiert. Dieser stellt dabei jeweils eine Methode für das Laden sämtlicher Einträge einer bestimmten Entität und das Laden eines einzelnen Eintrags (per ID) zur Verfügung.

public class CardDeckService {

	private final AbstractMapper<CardDeck, CardDeckEntity> cardDeckMapper = new CardDeckMapper();

	public final CardDeck getCardDeckById(final int id) {
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction transaction = session.beginTransaction();

		CardDeckEntity cardDeckEntity = (CardDeckEntity) session.get(
				CardDeckEntity.class, id);
		CardDeck cardDeck = cardDeckMapper.mapToDomain(cardDeckEntity);

		transaction.commit();
		session.close();

		return cardDeck;
	}

	public final List<CardDeck> getAllCardDecks() {
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction transaction = session.beginTransaction();

		final Query query = session.createQuery("from CardDeckEntity");
		List<CardDeckEntity> cardDeckEntities = query.list();
		List<CardDeck> cardDecks = cardDeckMapper
				.mapToDomainList(cardDeckEntities);

		transaction.commit();
		session.close();

		return cardDecks;
	}

}

Die REST Ressourcen

Um die Daten via REST einer Client-Applikation zur Verfügung zu stellen, wird Jersey, die Referenzimplementation von Jax-RS, eingesetzt. Für die Beispielapplikation wird dabei pro Entität eine Ressource definiert, welche die Domänen Entitäten über entsprechend annotierte Methoden via REST Schnittstelle veröffentlicht.

Anbei ein kurzer Überblick über die wichtigsten Annotationen in Jax-RS:

  • @GET, @POST, @PUT, @DELETE
    HTTP Methode definieren, auf welche eine Java Methode in einer Ressource reagieren kann.

  • @Path
    Auf Klassenebene festlegen, auf welchen Pfad/URL eine Ressource reagiert. Auf Methodenebene erlaubt die Annotation zusätzlich, festzulegen, welche Parameter die Methode verarbeiten kann.

  • @Consumes
    Datenformate definieren, welche eine Ressource oder eine einzelne Java Methode konsumieren kann.

  • @Produces
    Datenformate definieren, welche eine Ressource oder eine einzelne Java Methode konsumieren kann.

Die Jersey Ressourcen im Beispielprojekt sind sehr einfach gehalten und ermöglichen es, eine Liste mit sämtlichen Elementen einer bestimmten Entität oder eine einzelne bestimme Entität (per ID) abzufragen.

@Path("/carddecks")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public class CardDeckResource {

	private final CardDeckService cardDeckService = new CardDeckService();

	@GET
	public final List<CardDeck> getAllCardDecks() {
		return cardDeckService.getAllCardDecks();
	}

	@GET
	@Path("{id}")
	public final CardDeck getCardDeckById(@PathParam("id") final String id) {
		return cardDeckService.getCardDeckById(Integer.valueOf(id));
	}

}

Das Deployen

Es sind nun alle notwendigen Klassen definiert, welche für das erfolgreiche Deployen der Applikation notwendig sind. Selbstverständlich müssen noch die notwendigen Konfigurationen (web.xml, hibernate.cfg.xml, etc.) festgelegt werden und ein entsprechender Webcontainer bestimmt werden. Ich meinerseits habe die Applikation auf einem Apache Tomcat 7 veröffentlicht.

Sobald der Server läuft und die Applikation daraf deployed ist, kann auf die REST-Schnittstelle zugegriffen werden.

Untenstehender Aufruf (je nach Konfiguration leicht verändert) sollte dann ein JSON-Array mit sämtlichen in der Datenbank vorhandenen Lernkarteien zurückliefern.

Aufruf

http://localhost:8080/carddecks

Antwort (JSON-Array)

[
  {
    "coverImage":"carddeck_swiss_history.jpg",
    "numberOfCards":2,
    "title":"Schweizer Geschichte","id":1
  }
  ,
  {
    "coverImage":"carddeck_hollywood_stars.jpg",
    "numberOfCards":3,
    "title":"Hollywood Sternchen","id":2
  }
]

Abschluss

Ich hoffe Sie haben den Artikel mit Interesse zu Ende gelesen und damit begonnen, Ihre eigene Implementation der Beispielapplikation umzusetzen.

Im nächsten Artikel dieser Serie werde ich Ihnen zeigen, wie Sie einen auf AngularJS basierenden Client zu der entwickelten Server-Applikation entwickeln können.

Hinweis: Die veröffentlichten Code-Snippets sind nicht vollständig. Dies ist bewusst so gewählt, damit ein gewisser Lerneffekt beim Durcharbeiten des Blog-Beitrags entsteht.
Falls Sie Fragen und/oder Anregungen haben, würde es mich freuen, wenn Sie einen Kommentar hinterlassen oder mit mir in Kontakt treten würden.

Bis zum nächsten Mal – ich freue mich.

Kommentare (7)

Jonas

25 Juni 2013 um 23:17

Danke für den Post. Ich bin gespannt auf den zweiten Teil.

Für den zweiten Teil würde mich besonders interessieren wie man eine Authentifizierung/Autorisierung über REST realisiert. Wenn ich die Rest-API nur für angemeldete Benutzer zur Verfügung stellen möchte, wie ist das möglichst einfach zu realisieren?

Auch sehr cool für solche Code-lastige Blog-Posts wäre es wenn das gesamte lauffähige Projekt als Download zur Verfügung gestellt würde… z.B. auf GitHub.

    Marc Baur

    Marc Baur

    26 Juni 2013 um 07:18

    Lieber Jonas.

    Es freut mich, dass dir der Post gefällt. Aktuell bin ich gerade dabei, den 2. Teil zu verfassen. Das mit der Authentifizierung ist eine gute Idee. Werde schauen, ob ich dies im 2. Post berücksichtigen kann. Sonst ev. in einem nächsten zukünftigen Beitrag. Vielen Dank auch für den Vorschlag bezüglich GitHub. Ich teile deine Meinung absolut. Ich werde das entsprechende Projekt nach dem 2. Post (wenn Client und Server vorhanden sind) auf GitHub veröffentlichen.

    Viele Grüsse
    Marc

Matze

16 Oktober 2013 um 22:06

Wann kommt Teil 2? VG

    Marc Baur

    Marc Baur

    18 Oktober 2013 um 09:41

    Hi Matze
    Teil 2 ist praktisch fertig und sollte in den nächsten 1-2 Wochen aufgeschaltet werden.
    Vielen Dank für dein Interesse.

    Gruss, Marc

[…] sich der erste Teil dieser zweiteiligen Blog-Serie mit der Server-Seite beschäftigt hat, geht es in diesem zweiten Beitrag nun um die Client-Seite, welche mit AngularJS […]

Pierre

17 Januar 2014 um 21:18

Hallo Marc,

ich war über die letzten 6 Monate damit beschäftigt eine größere Webapplication neu aufzubauen. Hierfür habe ich intensiven Gebrauch von AngularJS und REST (Jersey) gemacht. Nun wollte ich mal sehen, was andere von dieser Kombination halten bzw. wie sie Application implementieren. Dabei bin ich über deinen Post gestolpert. Deiner Beschreibung nach haben wir nahezu einen identischen Ansatz gewählt (ich nutz lediglich EclipseLink zur Persistierung 🙂 ).

In meiner Application habe ich ebenfalls Entitäten und POJOS definierte. Zum mappen verwende ich allerdings den DozerBeanMapper. Dieser nimmt einem sehr viel Schreibarbeit ab. Wäre vielleicht auch was für dich!?

Für meine nächstes Projekt habe ich mir allerdings vorgenommen, die Codedopplung (Entities/POJOS) zu vermeiden. Ich möchte die Entities direkt über REST zurück geben. Sicherlich soll das Frontend andere Informationen erhalten als sie im Backend benötigt werden. Ich möchte versuchen das alles über die Annotationen @javax.xml.bind.annotation.XmlTransient bzw. @javax.persistence.Transient abzudecken.

Gruß
Pierre

Hamid

25 März 2015 um 19:43

Sehr gut erklärt. Danke

×

Updates

Schreiben Sie sich jetzt ein für unsere zwei-wöchentlichen Updates per E-Mail.

This field is required
This field is required
This field is required

Mich interessiert

Select at least one category
You were signed up successfully.