Dropwizard? Wir bauen ein UserService – Part II

Nachdem der erste Teil des Tutorials durchgearbeitet ist, haben wir ein lauffähiges Projekt. Darauf bauen wir auf und erstellen unseren ersten Service. Endlich! Ich verzichte an dieser Stelle auf ein langweiliges „Hello World“ Beispiel und erstelle einen Userservice.

Doch erstmal: Was sind denn eigentlich die wichtigsten Bestandteile eines Dropwizard Service?

Erstellen einer Konfigurationsdatei

Dropwizard unterstützt Konfigurationsdateien, so dass alle Konfigurationsparameter ohne neu zu kompilieren geändert werden können. Daher ist es eine gute Idee diese auch zu nutzen. Die Konfigurationsdatei muss im Project Root angelegt werden, andernfalls wird sie beim Starten des Servers nicht gefunden. Der Name spielt keine Rolle, da er beim Starten übergeben werden muss. Ihr könnt also immer nur eine Konfigurationsdateien gleichzeitig nutzen! Ich nenne meine Konfigurationsdatei user.yml. Da wir im weiteren Verlauf eine PostgreSQL Datenbank benutzen wollen, kommen die entsprechenden Einstellungen in die Konfigurationsdatei.

database:
    driverClass: org.postgresql.Driver
    user: John.Doe
    password:
    url: jdbc:postgresql://localhost/example_user
    properties:
      charSet: UTF-8
      maxWaitForConnection: 1s
      validationQuery: "/* MyService Health Check */ SELECT 1"
      minSize: 8
      maxSize: 32
      checkConnectionWhileIdle: false
      checkConnectionHealthWhenIdleFor: 10s
      closeConnectionIfIdleFor: 1 minute

Erstellen einer Klasse zum Nutzen der Konfigurationsdatei

Nun endlich fangen wir mit der eigentlichen Entwicklung an. Dropwizard mapped eure Konfigurationsdatei auf POJOs (Plain Old Java Objects). Zuerst müssen wir eins schreiben. Wir erstellen über unsere IDE ein neues Package (hier de.trollr.example.configuration) und in diesem Package legen wir unsere Konfigurationsklasse an.

package de.trollr.example.configuration;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.yammer.dropwizard.config.Configuration;
import com.yammer.dropwizard.db.DatabaseConfiguration;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 *
 * @author trollr
 */
public class UserConfiguration extends Configuration {

    @Valid
    @NotNull
    @JsonProperty
    private DatabaseConfiguration database = new DatabaseConfiguration();

    public DatabaseConfiguration getDatabaseConfiguration() {
        return database;
    }
}

Diese Klasse bildet unsere Konfigurationsdatei ab. Dort haben wir die Einstellungen für die Datenbankverbindung abgelegt. Wir werden später in der Hauptklasse unseres Service eine Instanz der Konfigurationsklasse erstellen um eine Hibernate Instanz zu initialisieren.

Ein Entitity-Objekt und ein Data Access Objekt

Als nächstes Erstellen wir unser Entitiy-Objekt. Auf die Annotations gehe ich nicht weiter ein, da es den Rahmen sprengen würde. Schaut einfach in die entsprechenden Dokumentationen, falls ihr Fragen dazu habt. Da Data Access Object (DAO) und Entitiy-Objekt für mich Core Funktionalitäten sind, habe ich ein neues Packagenamens de.trollr.example.core angelegt. 

package de.trollr.example.core;

import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
import java.sql.Date;
import javax.persistence.*;

/**
 *
 * @author trollr
 */
@Entity
@Table(name="users")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(name = "username", nullable = false, unique = true)
    private String username;
    @Column(name = "firstname", nullable = false)
    @JsonIgnore
    private String password;
    @Column(name = "password", nullable = false)
    private String firstname;
    @Column(name = "lastname", nullable = false)
    private String lastname;
    @Column(name = "birthdate", nullable = false)
    private Date birthdate;

    /**
     * @return the id
     */
    public long getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * @return the username
     */
    public String getUsername() {
        return username;
    }

    /**
     * @param password the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @return the firstname
     */
    public String getFirstname() {
        return firstname;
    }

    /**
     * @param firstname the firstname to set
     */
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    /**
     * @return the lastname
     */
    public String getLastname() {
        return lastname;
    }

    /**
     * @param lastname the lastname to set
     */
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    /**
     * @return the birthdate
     */
    public Date getBirthdate() {
        return birthdate;
    }

    /**
     * @param birthdate the birthdate to set
     */
    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    /**
     * @return the password
     */
    public String getPassword() {
        return password;
    }

}

Jetzt haben wir eine Entity, die den Benutzer darstellt. Sie ist noch relativ simpel, im späteren Verlauf werden wir aber noch weitere Felder hinzufügen. Nun fehlt nur noch das DAO. Dies ermöglicht es uns, Daten aus der Datenbank zu holen und das Entity-Objekt damit zu füllen.

package de.trollr.example.core;

import com.google.common.base.Optional;
import com.yammer.dropwizard.hibernate.AbstractDAO;
import java.util.List;
import org.hibernate.SessionFactory;

/**
 *
 * @author trollr
 */
public class UserDAO extends AbstractDAO<User> {

    public UserDAO(SessionFactory factory) {
        super(factory);
    }

    public Optional<User> findById(Long id) {
        return Optional.fromNullable(get(id));
    }

    public List<User> findAll() {
        List<User> users = super.currentSession().createQuery("FROM User").list();
        return users;
    }
}

Um das DAO zu verstehen müsst ihr euch etwas in Hibernate einlesen. Darum werdet ihr als Java Web-Entwickler kaum herum kommen. Deshalb werde ich auch hierauf nicht weiter eingehen.

Eine Ressourcenklasse erstellen

Als nächstes müssen wir die Ressourceklasse anlegen. Dafür erstellen wir wieder ein neues Package. Wir wollen es ja ordentlich strukturiert haben. Das Package nenne ich de.trollr.example.resources. In der Ressourcenklasse legt ihr die vorhandenen Endpoints unsere Restschnittstelle fest.

package de.trollr.example.resources;

import de.trollr.example.core.User;
import de.trollr.example.core.UserDAO;
import com.google.common.base.Optional;
import com.yammer.dropwizard.hibernate.UnitOfWork;
import java.util.List;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

/**
 *
 * @author trollr
 */
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {

    private final UserDAO userDAO;

    public UserResource(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @GET
    @UnitOfWork
    public List<User> listUsers() {
        return userDAO.findAll();
    }

    @GET
    @UnitOfWork
    @Path("/{id}")
    public Optional<User> getUser(@PathParam("id") long id) {
        return userDAO.findById(id);
    }
}

Wie ihr sicherlich schon gesehen habt, haben wir vorerst zwei Endpoints definiert: /users und /users/{id}. Wenn wir über den Browser /users aufrufen, erhalten wir ein JSON Objekt mit allen im System vorhandenen Nutzern. Hängen wir noch eine ID hinten dran, erhalten wir nur die Daten des Nutzer mit der entsprechenden ID. Später werden wir diverse Bereiche noch mit entsprechenden Rechten versehen. Wir wollen ja nicht das Hinz und Kunz unsere Nutzerdaten lesen können!

Die Serviceklasse

package de.trollr.example;

import de.trollr.example.configuration.UserConfiguration;
import de.trollr.example.resources.UserResource;
import de.trollr.example.core.UserDAO;
import de.trollr.example.core.User;
import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;
import com.yammer.dropwizard.db.DatabaseConfiguration;
import com.yammer.dropwizard.hibernate.HibernateBundle;

/**
 *
 * @author trollr
 */
public class UserService extends Service<UserConfiguration> {

    public static void main(String[] args) throws Exception {
        new UserService().run(args);
    }

    private final HibernateBundle<UserConfiguration> hibernate = new HibernateBundle<UserConfiguration>(User.class) {

        @Override
        public DatabaseConfiguration getDatabaseConfiguration(UserConfiguration configuration) {
            return configuration.getDatabaseConfiguration();
        }
    };

    @Override
    public void initialize(Bootstrap<UserConfiguration> bootstrap) {
        bootstrap.setName("UserService");
        bootstrap.addBundle(hibernate);
    }

    @Override
    public void run(UserConfiguration configuration, Environment environment) {
        final UserDAO users = new UserDAO(hibernate.getSessionFactory());
        environment.addResource(new UserResource(users));
        environment.addHealthCheck(new DatabaseHealthCheck(hibernate));
    }
}

Dies ist das Herzstück unseres REST Service. Wie ihr seht laufen hier alle Fäden zusammen. Klassen, die wir zuvor angelegt aber noch nicht genutzt haben, kommen hier das erste mal zu Einsatz. Der Weg hier her war aber auch ziemlich lang! Letztendlich ist unsere Serviceklasse für das Initialisieren und Bootstrapping zuständig. Zudem erstellen wir mit Hilfe unserer Konfigurationsklasse eine Hibernate Instanz. Wie euch (oder eurer IDE) sicherlich schon aufgefallen ist, wird in der Run Methode etwas genutzt, dass uns bisher noch fehlt – ein Healthcheck.

Die Healthchecks

Mit Healthchecks stellt ihr sicher, dass euer Service so funktioniert wie ihr es erwartet. Wenn ihr also eine Datenbank im Hintergrund nutzt, macht es Sinn zu prüfen, ob ihr eine Verbindung herstellen könnt. Diese Checks sind kein Muss, sollten aber trotzdem erstellt werden, da euer Service sonst vielleicht startet, ihr dann aber aus allen Wolken fallt, wenn er nicht richtig funktioniert.

package de.trollr.example.health;

import com.yammer.dropwizard.hibernate.HibernateBundle;
import com.yammer.metrics.core.HealthCheck;

/**
 *
 * @author trollr
 */
public class DatabaseHealthCheck extends HealthCheck {
    private final HibernateBundle hibernate;

    public DatabaseHealthCheck(HibernateBundle hibernate) {
        super("database");
        this.hibernate = hibernate;
    }

    @Override
    protected Result check() throws Exception {
       if(!hibernate.getSessionFactory().isClosed()) {
           return Result.healthy();
       } else {
           return Result.unhealthy("Cannot connect to database");
       }
    }
}

Die Datenbank

Damit Hibernate richtig funktioniert, müsst ihr in eurer PostgreSQL Datenbank noch eine Tabelle anlegen.

CREATE TABLE users
(
  id integer NOT NULL,
  username character varying,
  firstname character varying,
  lastname character varying,
  birthdate date,
  password character varying,
  CONSTRAINT id PRIMARY KEY (id)
)

Wir haben nun alles was wir brauchen und können unseren Service das erste Mal testen. Ihr habt unterschiedliche Möglichkeiten euren Service zu starten. Entweder könnt ihr in der pom.xml folgende Zeilen ergänzen:

<build>
      <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>de.trollr.example.UserService</mainClass>
                    <arguments>
                        <argument>server</argument>
                        <argument>user.yml</argument>
                    </arguments>
                </configuration>
            </plugin>
      </plugins>
</build>

Jetzt müsst ihr über die Konsole in das Project Root navigieren und den Service mit

mvn compile exec:java

zum Leben erwecken. Alternativ macht ihr es so wie es in der Dropwizard Dokumentation vorgeschlagen wird und gebt im Projekt Root folgenden Befehl ein:

java -jar target/userService-0.0.1-SNAPSHOT.jar server user.yml

Last But Not Least könnt ihr aber auch einfach in eurer IDE in den Projekteinstellungen die notwendigen Argumente beim Starten mitgeben. Dies ist mein bevorzugter Weg, da er am einfachsten ist. Wenn der Service läuft, könnt ihr ihn testen in dem ihr euren Browser startet und localhost:8080/users öffnet. Nun solltet ihr die in der Datenbank gepflegten Benutzer als JSON zurück bekommen. Über localhost:8081 erreicht ihr die von Dropwizard bereitgestellte Administrationsoberfläche. Dort könnt ihr euren Service überwachen.

Nach viel Arbeit, Schweiß und Zeit haben wir tatsächlich unseren ersten REST Service gebaut.

Teilen Sie diesen Beitrag

Das könnte dich auch interessieren …

2 Antworten

  1. Peter sagt:

    DatabaseConfiguration ist seit einiger Zeit abstrakt. Und nu?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert