Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de

Ein REST-Server mit Spring Boot

Wie kommuniziert ein Arduino-Board serverseitig mit einer REST API, um Sensor-Messungen über ein objektrelationales Mapping zu speichern?
Author Image
Michael Stal

Chefredakteur von JavaSPEKTRUM


  • 23.11.2023
  • Lesezeit: 17 Minuten
  • 111 Views

Arduino-Boards, die über eine optionale Echtzeituhr (RTC = Real Time Clock) und ein WiFi-Modul verfügen, lassen sich ins Internet der Dinge integrieren, etwa über die Arduino Cloud oder AWS. Wer lieber on-premises agieren möchte, kann anders vorgehen und Server beziehungsweise Clients im heimischen Netz zur Verfügung stellen. Das bringt den Vorteil mit sich, immer die Kontrolle über die Bereitstellung (Deployment) zu behalten. Wie Entwicklerinnen und Entwickler dabei vorgehen können, zeigen dieser und der nachfolgende Online-Artikel [Stal23-b]. Während zunächst die Server-Anwendung zur Sprache kommt, dreht sich der zweite Teil um die eingebetteten Clients.

Gleich vorweg: Alle Implementierungsdateien liegen auf [Git-Hub] parat. Eine gute Gelegenheit, um sich mühselige Handarbeit zu ersparen.

Grobe Systemarchitektur

Im Beispielszenario kommen ein Arduino Board mit C++-Sketch und ein Server mit Java und Spring Boot 3 zum Einsatz. Abbildung 1 zeigt die Systemarchitektur.

Abb. 1: Ein Arduino-Client kommuniziert mit der REST-API auf dem Server

Auf der linken Seite ist das Arduino-Board, konkret ein Arduino Giga R1 WiFi, zu sehen. Verwendbar sind aber grundsätzlich alle Arduino-Boards oder Arduino-unterstützte Boards wie etwa ein ESP32. Am Arduino ist ein BME688-Sensor von Adafruit über I2C oder SPI angeschlossen. Der Arduino-Sketch macht periodisch Messungen von Temperatur, Feuchtigkeit, Luftdruck und Gaswiderstand und verschickt das Ergebnis zusammen mit dem von der Echtzeituhr ausgelesenen Zeitstempel über einen POST-Aufruf an den Server:

<hostname>:8080/measurement/api. <hostname>

kann dabei die IP-Adresse oder der DNS- beziehungsweise Server-Name sein.

Serverseitig (rechte Seite in Abb. 1) fungiert eine in Java geschriebene Spring-Boot-3-REST-Anwendung als Kommunikationspartner für das Arduino-Board. Die zugehörige Datenbank läuft auf PostgreSQL, das die Anwendung als Docker-Container bereitstellt. So ist sichergestellt, dass Entwicklerinnen und Entwickler PostgreSQL auf dem eigenen Computer nicht extra installieren müssen.

Zur Laufzeit sendet das Arduino-Board POST-Nachrichten an den Server. Der dazugehörige REST-Endpunkt sorgt dann dafür, dass die Messung in die Datenbank übernommen wird.

Der Server

Selbstverständlich lassen sich auch andere Datenbankmanagementsysteme (DBMS) nutzen, etwa MySQL (MariaDB). In diesem Fall müssten nur die Konfigurationsdateien application.yml sowie docker-compose.yml angepasst werden. Und in der Konfigurationsdatei pom.xml – das Projekt nutzt Maven für den Build-Prozess – sind entsprechend die PostgreSQL-Referenzen durch die für die alternative Datenbank notwendigen Abhängigkeiten (<dependencies>) zu ersetzen.

Wie aus der Abhängigkeitsfestlegung in Listing 1 ersichtlich, gilt das nur für die zweite Abhängigkeit in Bezug auf PostgreSQL – in diesem Fall den pgJDBC-Treiber.

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
 </dependencies>
Listing 1: Maven pom.xml Konfiguration

Der soll übrigens nur zur Laufzeit aktiv sein, deshalb ist der benötigte Scope in der pom. xml-Datei mit runtime festgelegt. Alle anderen Abhängigkeiten beziehen sich auf Spring Boot, im Detail auf die Nutzung von Spring Web, Spring Data JPA (Java Persistence API) und Spring Test.

Als Adapter nutzt die Server-Anwendung einen über die URL [PGSQL] bereitstehenden JDBC-Treiber. Für den Fall, dass Entwickler und Entwicklerinnen bereits eine lokale Instanz von postgreSQL einsetzen, ist in der Konfiguration des Docker-Containers ein Port-Mapping definiert. Dieses verbindet den Server-Port 5332 mit dem Container-Port 5432 und verhindert somit Konflikte mit lokalen PostgreSQL-Installationen, die auf Port 5432 lauschen.

Sobald der Arduino-Client einen POST-Aufruf mit einer Messung auf die Reise schickt, nimmt die Server-Schnittstelle die Messung als JSON-Body im jeweiligen REST-POST-Endpunkt entgegen und speichert die Messdaten in der PostgreSQL-Datenbank measurement. Die REST-Anwendung nutzt Port 8080, was sich in der Konfigurationsdatei application.yml (s. Listing 2) bequem ändern lässt.

server:
  port: 8080

#turn off the web server
spring:
  datasource:
    url: jdbc:postgresql://localhost:5332/measurement
    username: michael
    password: michael
 jpa:
   hibernate:
     ddl-auto: create-drop
      properties:
        hibernate:
          dialect: org.hibernate.dialect.PostgreSQLDialect
          format_sql: true
       show-sql: true
  main:
    web-application-type: servlet
Listing 2: Konfigurationsdatei application.yml

Inbetriebnahme

Wer einfach loslegen will, startet nach Download der Quellen zunächst das Docker-Image. Dazu ist es erforderlich, auf dem eigenen Server beziehungsweise Desktop Docker zu installieren. Mittels der URL [Docker] bedeutet das zunächst, die entsprechende Docker-Implementierung für Linux, Windows oder macOS herunterzuladen und zu starten. In der Werkzeugleiste des Betriebssystems erscheint danach das für Docker typische Icon, ein Container-Frachtschiff.

Im nächsten Schritt sollten Entwicklerinnen und Entwickler die Konfiguration docker-compose.yml auf ihre Bedürfnisse anpassen, s. Listing 3.

services:
  db:
    container_name: postgres
    image: postgres
    environment:
      POSTGRES_USER: michael
      POSTGRES_PASSWORD: michael
      PGDATA: /data/postgres
    volumes:
     - db:/data/postgres
   ports:
     - "5332:5432"
   networks:
     - db
    restart: unless-stopped
 networks:
   db:
     driver: bridge

voumes:
  db:
Listing 3: Konfiguration docker-compose.yml

Am Anfang der Konfiguration sind die bereitgestellten Dienste spezifiziert. Als Basis soll der Container das auf Docker Hub bereitgestellte PostgreSQL-Image verwenden.

Im Bereich ENVIRONMENT erfolgt die Festlegung des Anwendernamens und des zugehörigen Passworts sowie des Ordners im Container, der die PostgreSQL-Dateien enthalten soll. Das interne Netzwerk heißt DB und ist über eine Bridge erreichbar. Stoppt der Container, löscht er die Datenbanktabelle. Wer ein anderes DBMS nutzt, muss diese YAML-Datei entsprechend anpassen.

Im Ordner, in dem die YAML-Datei liegt, erfolgt nun der Start des Containers über:

%docker compose up -d

Nach Eingabe von

%docker container ls

in der Kommandozeile müsste der Container auftauchen:

CONTAINER ID IMAGE COMMAND CREATED STA
d0cc2733c93a postgres "docker-entrypoint.s…" 4 hours ago Up

Damit ist es allerdings nicht getan, denn es fehlt noch die Datenbank für die Messungen. Zunächst öffnen wir dazu eine Shell im Container für den interaktiven Zugriff:

docker exec -it postgres bash

In der Container-Session ist im Falle von PostgreSQL das Kommando:

psql - U michael

notwendig, wobei U für den Nutzer steht. Ohne Änderungen ist im Beispiel michael als Benutzername und Passwort für PostgreSQL voreingestellt. Über psql lassen sich mit \l die existierenden Datenbanken abrufen. Hier erscheint in etwa die folgende Ausgabe:

Name      | Owner | Encoding | Collate | Ctype | ICU Locale
-------------+---------+----------+------------+------------+-------
libc          |
michael | michael | UTF8        | en_US.utf8 | en_US.utf8 |
postgres | michael | UTF8        | en_US.utf8 | en_US.utf8 |
template0| michael | UTF8        | en_US.utf8 | en_US.utf8 |
          |         |             |            |            |
template1 | michael | UTF8        | en_US.utf8 | en_US.utf8 |
          |         |             |            |            |
(4 rows)

Um eine neue Datenbank zu erzeugen, geben wir Folgendes ein:

CREATE DATABASE measurement;

Achtung: Der Strichpunkt ist unbedingt erforderlich. Mit \cmeasurement verbinden wir uns mit der Datenbank und können danach mit \dt die vorhandenen Tabellen beziehungsweise Datenbankschemas analysieren. Noch ist dort nichts zu sehen, weil für das Datenbankschema die Spring-Boot-3-Anwendung sorgt. Spring Boot kreiert zu diesem Zweck selbstständig folgendes SQL-DDL-Kommando:

create table measurement (
id integer not null,
date date,
humidity float(53),
pressure float(53),
resistance float(53),
temperature float(53),
time time,
primary key (id)
)

Wer eine professionelle IDE wie IntelliJ IDEA Ultimate (s. Abb. 2) nutzt, kann viele der beschriebenen und kommenden Schritte bequem über die IDE anstoßen.

Abb. 2: In IntelliJ IDEA Ultimate findet sich Unterstützung für die Arbeit mit Docker und Spring Boot

Nun müssen wir noch die Java-basierte Spring-Boot-3-Anwendung starten (mittels Main.class), wobei wir eine ähnliche Ausgabe wie die folgende erhalten sollten – nur die letzten zwei Zeilen sind hier abgebildet:

…. noch viel mehr ….
2023-06-04T15:18:01.952+02:00 INFO 51585 --- [ main]
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s):
8080 (http) with context path ''

2023-06-04T15:18:01.959+02:00 INFO 51585 --- [ main] de.stal.Main :
Started Main in 3.172 seconds (process running for 3.807)

Es ist aus der Konsolenausgabe ersichtlich, dass Spring Boot für Webanwendungen automatisch den eingebetteten Web-Server Tomcat startet. Alternativ könnten wir auch Jetty nutzen, was die Konfigurationsänderung aus Listing 4 im pom.xml von Maven erfordert.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
   </exclusions>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
     </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Listing 4: Maven-Konfiguration für Jetty

In der Ausgabe befindet sich des Weiteren eine Warnung über Views, die wir in diesem Fall aber getrost ignorieren können. Im Falle von Gradle als Build-Tool wäre es stattdessen:

configurations {
compile.exclude module: "spring-boot-starter-tomcat"
}
dependencies {
compile(
"org.springframework.boot:spring-boot-starter-web:2.0.0.BUILD-
SNAPSHOT")
}

Das aber nur am Rande, weil das Beispielprojekt, wie schon erwähnt, Maven einsetzt.

Immer wenn der Postman klingelt

Möchte man den Server auch ohne Arduino-Client testen, leistet die Anwendung Postman hierfür gute Dienste. Sie ermöglicht das Aufrufen von APIs und benutzt curl als Basis dafür.

Abb. 3: Mit postman lassen sich bequem REST-APIs testen

Das implementierte REST-Beispiel unterstützt folgende Aufrufe von REST-Endpunkten, um CRUD-Funktionalität (CRUD = Create Read Update Delete) bereitzustellen:

  • POST-Aufrufe dienen zum Anlegen neuer Messungen über hostname:port/measurements/api, wobei im Body ein JSON-Objekt mit den Messergebnissen enthalten sein muss.
  • GET-Aufrufe wie hostname:port/measurements/api brauchen keinen Body im HTTP-Paket (none). Der Endpunkt liefert die Liste aller gespeicherten Messungen zurück. Das gilt auch für das Auslesen von individuellen Einträgen über hostname:port/measurements/api/42 . In diesem Fall beziehen wir uns auf die Messung mit der Id 42.
  • Der PUT-Aufruf wie etwa hostname:port/measurements/api/1 benötigt ein JSON-Objekt als Body, das die Werte des zum Datenbankeintrag gehörigen Primärschlüssels 1 aktualisiert.
  • Ein DELETE-Aufruf wie zum Beispiel hostname:port/measurements/api/2 löscht den Eintrag mit dem Primärschlüssel 2 aus der Datenbank. Ebenso wie bei GET ist kein Body (none) vonnöten.

Ein Beispiel für ein mitgeliefertes JSON-Objekt bei POST- oder PUT-Aufrufen könnte wie folgt aussehen:

{
  "temperature": 23.5,
"humidity": 12.7,
"pressure": 990.5,
"resistance": 23.8,
"date": "2023-06-10",
"time": "06:24:56"
}

Let’s Spring Boot

Für die eigentliche Magie in der Server-Anwendung sorgt Spring Boot 3. Laut der Spring-Seite gilt für Spring Boot:

„Das Spring Framework bietet ein umfassendes Programmier- und Konfigurationsmodell für moderne Java-basierte Unternehmensanwendungen – auf jeder Art von Einsatzplattform.

Ein Schlüsselelement von Spring ist die infrastrukturelle Unterstützung auf der Anwendungsebene: Spring konzentriert sich auf das ‚Klempnerhandwerk‘ von Unternehmensanwendungen, sodass sich die Teams auf die Geschäftslogik auf Anwendungsebene konzentrieren können, ohne unnötige Bindungen an bestimmte Bereitstellungsumgebungen.“

Das Java-Framework integriert einen Webserver mit Servlet-Unterstützung (Tomcat oder optional Jetty), über den es die REST-API im Web beziehungsweise Netzwerk zur Verfügung stellt. Die ganze Servlet-Maschinerie bleibt Entwicklerinnen und Entwicklern erspart. Zudem bietet es eine Repository-Schnittstelle, mit deren Hilfe die REST-Endpunkte neue Einträge beispielsweise speichern, ändern, löschen oder abrufen. Außerdem sorgt es über Annotationen für das objektrelationale Mapping zwischen der measurement-Datenbanktabelle und dem entsprechenden Java-Objekt.

In der Datei Main.java (s. Listing 5 und Ausschnitt unten) ist die Klasse Main als @SpringBootApplication annotiert.

package de.stal;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@SpringBootApplication
@RequestMapping("measurements/api")
public class Main {
  private final MeasurementRepository measurementRepository;
  public Main(MeasurementRepository measurementRepository) {
    this.measurementRepository = measurementRepository;
}
public static void main(String[] args) {
  SpringApplication.run(Main.class, args);
}
@GetMapping
public List<Measurement> getMeasurement() {
  return measurementRepository.findAll();
}
@GetMapping("{id}")
public Optional<Measurement> getMeasurementById(
   @PathVariable("id") Integer id) {
  return measurementRepository.findById(id);
}
@PostMapping
public void addMeasurement(@RequestBody NewMeasurementRequest request) {
  Measurement measurement = new Measurement();
  measurement.setDate(request.date);
  measurement.setTime(request.time);
  measurement.setTemperature(request.temperature);
  measurement.setHumidity(request.humidity);
  measurement.setPressure(request.pressure);
  measurement.setResistance(request.resistance);
  measurementRepository.save(measurement);
}
record NewMeasurementRequest(
  java.sql.Date date,
  java.sql.Time time,
  Double temperature,
  Double humidity,
  Double pressure,
  Double resistance
){}
@DeleteMapping("{measurementId}")
public void deleteMeasurement(
   @PathVariable("measurementId") Integer id) {
  measurementRepository.deleteById(id);
}
// assignment
@PutMapping("{measurementId}")
public void updateMeasurement(@PathVariable("measurementId") Integer id,
    @RequestBody NewMeasurementRequest msmUpdate) {
  Measurement existingMeasurement = measurementRepository.findById(id)
    .orElseThrow();
  existingMeasurement.setDate(msmUpdate.date);
  existingMeasurement.setTime(msmUpdate.time);
  existingMeasurement.setTemperature(msmUpdate.temperature);
  existingMeasurement.setHumidity(msmUpdate.humidity);
  existingMeasurement.setPressure(msmUpdate.pressure);
  existingMeasurement.setResistance(msmUpdate.resistance);
  measurementRepository.save(existingMeasurement);
 }
}
Listing 5: Hauptprogramm mit Mappings

Das sorgt dafür, dass Spring Boot nach Komponenten und Entitäten sucht und weitere Handarbeiten automatisch erledigt. Gleichzeitig fungiert die Klasse auch als @RestController, weshalb sich in ihr REST-Endpunkte befinden müssen. Mittels der Annotation @RequestMapping("measurements/api") legt man fest, welches Präfix jeder REST-Endpunkt bekommen soll. Externe Aufrufe beginnen dann immer mit diesem Präfix, also zum Beispiel GET <hostname>:8080/measurements/api. Der Konstruktor der Klasse Main enthält als Parameter ein MeasurementRepository, über das die REST-Endpunkte Aktionen auf dem aktuellen persistierten Objekt durchführen, etwa um eine neue Messung in der Datenbank zu speichern:

public Main(MeasurementRepository measurementRepository) {
this.measurementRepository = measurementRepository;
}

Die Schnittstelle MeasurementRepository erzeugt Spring Boot automatisch und übergibt sie per Dependency Injection an den Konstruktor. Es ist dementsprechend als @Repository definiert und leitet sich von JpaRepository ab. Die zwei Typparameter von JpaRepository beziehen sich auf die Persistenzklasse für Datenbankeinträge (Measurement) und auf den Datentyp des Primärschlüssels (Integer):

@Repository
public interface MeasurementRepository
extends JpaRepository<Measurement, Integer> {
}

Einer der definierten REST-Endpunkte ist etwa folgendes parametrisiertes GET:

@GetMapping("{id}")
public Optional<Measurement> getMeasurementById(
@PathVariable("id") Integer id)

Die GetMapping-Annotation sorgt dafür, dass beim GET-Aufruf des Endpunkts mit der URL <hostname>:port/measurements/api/3 die Methode das entsprechende Datenbankobjekt mit Primärschlüssel 3 zurückliefert. Sie nimmt dabei das angesprochene MeasurementRepository zu Hilfe:

return measurementRepository.findById(id);

Wichtig in Zusammenhang mit REST-Endpunkten ist die Tatsache, dass bei Rückgaben von Ergebnissen Spring Boot dafür sorgt, diese zuvor in ein JSON-Objekt umzuwandeln – es wären im Übrigen auch andere Formate möglich. Zugleich erwartet jeder REST-Endpunkt, dass ihm entsprechende Messergebnisse im Body des HTTP-Pakets als JSON-Objekte übergeben werden. Das ist insbesondere bei POST und PUT notwendig, gilt aber nicht für in der URL angegebene Parameter wie etwa die gewünschte Id des "REST-Objekts" (s. GET und DELETE). Letztere wird als Parameter in die URL integriert, zum Beispiel: localhost:8080/measurements/api/12 , s. Listing 5. Die Klasse Measurement.java (s. Listing 6) definiert die eigentliche Entität, die über ein objektrelationales Mapping mit der Datenbank verbunden ist. Dementsprechend enthält die Klasse eine @Entity-Annotation.

package de.stal;
import jakarta.persistence.*;
import java.sql.Date;
import java.sql.Time;
import java.util.Objects;

@Entity
public class Measurement {
  @Id
  @SequenceGenerator(
    name = "measurement_id_sequence",
    sequenceName = "measurement_id_sequence",
    allocationSize = 1
)
@GeneratedValue(
  strategy = GenerationType.SEQUENCE,
  generator = "measurement_id_sequence"
)
private Integer id;
private Double temperature;
private Double humidity;
private Double pressure;
private Double resistance;
private java.sql.Date date;
private java.sql.Time time;

public Measurement(Integer id, Double temperature,
   Double humidity, Double pressure, Double resistance,
   Date date, Time time) {
  this.id = id;
  this.temperature = temperature;
  this.humidity = humidity;
  this.pressure = pressure;
  this.resistance = resistance;
  this.date = date;
  this.time = time;
}
public Measurement() {
}

@Override
public String toString() {
  return "Measurement{" +
    "id=" + id +
    ", temperature=" + temperature +
    ", humidity=" + humidity +
    ", pressure=" + pressure +
    ", resistance=" + resistance +
    ", date=" + date +
    ", time=" + time +
    '}';
}
@Override
public boolean equals(Object o) {
  if (this == o) return true;
  if (o == null || getClass() != o.getClass()) return false;
  Measurement that = (Measurement) o;
  return Objects.equals(id, that.id) &&
   Objects.equals(temperature, that.temperature) &&
   Objects.equals(humidity, that.humidity) &&
   Objects.equals(pressure, that.pressure) &&
   Objects.equals(resistance, that.resistance) &&
   Objects.equals(date, that.date) &&
   Objects.equals(time, that.time);
}
@Override
public int hashCode() {
  return Objects.hash(id, temperature, humidity,
    pressure, resistance, date, time);
}
public Integer getId() {
  return id;
}
public void setId(Integer id) {
  this.id = id;
}
public Double getTemperature() {
  return temperature;
}
public void setTemperature(Double temperature) {
  this.temperature = temperature;
}
 public Double getHumidity() {
   return humidity;
}
 public void setHumidity(Double humidity) {
   this.humidity = humidity;
}
 public Double getPressure() {
   return pressure;
}
 public void setPressure(Double pressure) {
   this.pressure = pressure;
}
 public Double getResistance() {
   return resistance;
}
public void setResistance(Double resistance) {
   this.resistance = resistance;
}
 public Date getDate() {
   return date;
}
 public void setDate(Date date) {
   this.date = date;
}
 public Time getTime() {
   return time;
}
 public void setTime(Time time) {
   this.time = time;
 }
}
Listing 6: Die persistente Measurement-Klasse

Wer IntelliJ IDEA oder eine andere fortschrittliche IDE nutzt, kann "Boilerplate"-Code wie Setters, Getters, toString(), equals(), hashcode() und Konstruktoren von der IDE generieren lassen. Andernfalls ist manuelles Eintippen nötig, was sich bei späteren Refactoring-Maßnahmen als umständlich erweist.

Nicht zu vergessen: Die Klasse Measurement benötigt einen parameterlosen Konstruktor mit leerem Rumpf, damit das objektrelationale Mapping von Spring Boot aus JSON-Nachrichten Java-Objekte generieren kann.

Die Datenfelder der Klasse Measurement entsprechen den gewünschten Attributen des Datenbankeintrags, in unserem Fall Temperatur, Feuchtigkeit, Luftdruck, Gaswiderstand, Datum und Zeit. Den Primärschlüssel Integer id lassen wir Spring Boot für das Datenbanksystem automatisch generieren. Dazu dienen die Annotationen: @Id, @SequenceGenerator und @GeneratedValue. Der Primärschlüssel soll mit 1 starten und bei jedem neuen Datenbankeintrag um 1 hochgezählt werden. Die Annotation @id weist das gleichnamige Datenfeld als Primärschlüssel aus.

Um die (Web-Server-)Anwendung zu konfigurieren, gibt es eine YAML-Datei mit dem Namen application.yml im resources-Ordner, s. Listing 7.

server:
 port: 8080

#turn off the web server
spring:
  datasource:
   url: jdbc:postgresql://localhost:5332/measurement
   username: michael
   password: michael
  jpa:
   hibernate:
     ddl-auto: create-drop
   properties:
     hibernate:
       dialect: org.hibernate.dialect.PostgreSQLDialect
       format_sql: true
    show-sql: true
  main:
    web-application-type: servlet
Listing 7: application.yml revisited

Hier legen Entwicklerinnen und Entwickler unter anderem Port, Passwort und Benutzername fest, zudem die URL zum Zugriff auf die PostgreSQL-Datenbank. Benutzername und Passwort müssen mit der korrespondierenden Konfiguration in docker-compose.yml übereinstimmen. Mit ddl-auto weisen wir Spring Boot JPA an, die Tabelle measurement zu löschen, falls die Anwendung beziehungsweise Session terminiert (drop measurement). Über web-application-type: servlet bestimmt die Konfiguration, dass wir zum Bereitstellen unserer REST-API einen Webserver benötigen, der Java-Servlets unterstützt. Das können Tomcat oder Jetty sein.

Damit wären wir am Ende der notwendigen Java- und Konfigurationsdateien angekommen. Der Gesamtumfang aller Dateien liegt bei rund 250 Codezeilen zuzüglich Konfigurationsfestlegungen und inklusive der mittels IDE (IntelliJ IDEA) generierten Codes. Der Aufwand, um kleinere REST-APIs zu entwickeln, hält sich daher dank Spring Boot in Grenzen.

Fazit

Durch Spring Boot fällt es leicht, recht schnell und effizient eine REST-API zusammenzubauen. Natürlich gäbe es auch Alternativen wie Quarkus oder Micronaut, aber Spring Boot ist sehr verbreitet und besitzt eine große Community. Das Beispiel ist für die Übergabe von Messungen des BME688 festgelegt. Es ist allerdings sehr leicht, die Anwendung für andere Sensoren oder andere Zwecke auszulegen.

  • Denkbar ist zum Beispiel der Anschluss mehrerer Boards mit Sensoren. Dafür müssten Entwicklerinnen und Entwickler die Messobjekte mit dem Attribut Client-ID ausstatten sowie weitere beziehungsweise andere Attribute hinzufügen.
  • Denkbar wäre es auch, das Datenbanksystem und/oder die Spring-Anwendung in die Cloud (AWS, Azure) zu laden. Dafür bedarf es allerdings zuerst eines Sicherheitskonzepts, etwa mithilfe von OAuth2 und JWT (JavaScript Web Token).
  • Auch weitere REST-Endpunkte wären möglich. Dementsprechend repräsentiert das hier beschriebene Beispiel lediglich einen von vielen möglichen Anwendungsfällen.

Während sich dieser Beitrag auf die Server-Seite fokussiert hat, beschreibt Teil 2, wie sich auf Mikrocontroller-Boards wie dem Arduino Giga die dazu passenden REST-Clients erstellen lassen. Sie finden Teil 2 auf dem heise Developer Blog des Autors: [Stal23-b].

Weitere Informationen

[Docker] https://www.docker.com/

[GitH] Code zum Artikel,
https://github.com/ms1963/RESTCommunication

[PGSQL] https://jdbc.postgresql.org/

[Postman] https://www.postman.com/

[Stal23-a] M. Stal, Gib mir den REST – Teil 1: Der REST-Server, heise online, 7.6.2023,
https://www.heise.de/blog/Gib-mir-den-REST-Teil-1-Der-REST-Server-9179764.html

[Stal23-b] M. Stal, Gib mir den REST – Teil 2: Der Client, heise online, 5.7.2023,
https://www.heise.de/blog/Gib-mir-den-REST-Teil-2-Der-Client-9179967.html

. . .

Author Image

Michael Stal

Chefredakteur von JavaSPEKTRUM
Zu Inhalten

Prof. Dr. Michael Stal beschäftigt sich bei der Corporate Technology der Siemens AG mit Software- und Systemarchitekturen, Digitalisierung und KI. An der University of Groningen hält er Vorlesungen und betreut Doktoranden. Außerdem ist er Chefredakteur von JavaSPEKTRUM.


Artikel teilen