Übungsaufgaben zur Codegenerierung: Mitfahrgesuche aus dem Vertrag

https://bildung.social/@oerinformatik/

https://oer-informatik.de/spring-boot-openapi-codegenerierung-uebungsaufgaben

OpenAPI Generator, Codegenerierung, Contract First, Bean Validation, Interface, Spring Boot, HTTP-Statuscodes, Fehlersuche, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (ca. 45 min Lese- und Bearbeitungszeit): Übungen zur Codegenerierung im Mitfahr-Backend: Du ordnest Angaben im Vertrag dem erzeugten Java-Code zu, sagst Statuscodes voraus, bestimmst die erzeugte Schnittstelle für die Gesuche, implementierst sie, entscheidest, welche Regeln Bean Validation prüft, findest Fehler anhand von Symptomen und beurteilst Contract First gegenüber Code First [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über ein REST-Backend mit Spring Boot: Überblick und Fahrplan / 01 Anforderungen und API-Vertrag / 02 Projekt anlegen / 03 Codegenerierung / Übungsaufgaben zu 03

Diese Übungsaufgaben gehören zum Artikel Vom Vertrag zum Code: Schnittstellen mit dem OpenAPI Generator erzeugen und setzen den Übungsstrang zu den Mitfahrgesuchen fort. Ausgangspunkt ist das Projekt im Ordner src/03-openapi-codegenerierung des Repositorys.

Fahrplan der Artikelreihe im V-Modell, hervorgehoben ist die Implementierung, zu der Artikel 03 gehört (mit KI erstellt)

Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1 und Aufgabe 1.2. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1, Aufgabe 2.2 und Aufgabe 2.3. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1 und Aufgabe 3.2) keine Probleme bereiten. Notiere Deine Antworten zuerst vollständig, bevor Du die Beispiellösung aufklappst. Vorhersagen zu Statuscodes kannst Du anschließend mit dem Projekt aus dem Repository und einer .http-Datei überprüfen; andere fachlich richtige Lösungen sind ebenso richtig.

Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?

Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.

Erstelle neue Übungsaufgaben zum Thema „Codegenerierung: Mitfahrgesuche
aus dem Vertrag“.

Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.

Fachliche Grundlage:
- https://oer-informatik.de/spring-boot-openapi-codegenerierung
- Stilbeispiel: https://oer-informatik.de/spring-boot-openapi-codegenerierung-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- OpenAPI Generator
- Codegenerierung
- Contract First
- Bean Validation
- Interface
- Spring Boot
- HTTP-Statuscodes
- Fehlersuche

Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan

Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
   abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
   zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
   Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
   Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
   Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
   danach frage.

Aufgabe 1.1: Vom Vertrag zum Java-Code (Basiskompetenz)

Das Projektteam ergänzt den Vertrag um die Gesuche. Gib an, was der OpenAPI Generator (Generator spring, Optionen wie im Lehrartikel) aus jeder Angabe erzeugt. (6 Punkte)

Angaben im Vertrag für die Gesuche
Angabe in openapi.yaml erzeugt im Java-Code
tags: [gesuche] an den Operationen
operationId: stelleGesuchEin
Schema GesuchNeu
required: [zeitVon, …] im Schema GesuchNeu
pattern: '^([01][0-9]|2[0-3]):[0-5][0-9]$' am Feld zeitVon
Schema GesuchStatus mit enum: [offen, vermittelt, zurueckgezogen, abgelaufen]

Je Zeile 1 Punkt:

Angabe in openapi.yaml erzeugt im Java-Code
tags: [gesuche] Schnittstelle GesucheApi im Paket de.dbh.mitfahrlabor.api
operationId: stelleGesuchEin default-Methode stelleGesuchEin(…) in GesucheApi
Schema GesuchNeu Klasse GesuchNeu im Paket de.dbh.mitfahrlabor.api.model
required: [zeitVon, …] @NotNull am Getter getZeitVon()
pattern: … @Pattern(regexp = "^([01][0-9]|2[0-3]):[0-5][0-9]$") am Getter
GesuchStatus mit enum enum GesuchStatus mit den Konstanten OFFEN("offen"), VERMITTELT("vermittelt"), ZURUECKGEZOGEN("zurueckgezogen"), ABGELAUFEN("abgelaufen")

Aufgabe 1.2: Statuscodes vorhersagen (Basiskompetenz)

Die Anwendung im Stand des Lehrartikels läuft (HaltepunktController und vorläufiger AngebotController, noch keine Gesuche). Gib für jede Anfrage den Statuscode an und begründe ihn in einem Satz. Das gültige Angebot aus dem Lehrartikel heißt hier kurz „Angebot A“. (6 Punkte)

  1. POST /v1/angebote mit Angebot A
  2. POST /v1/angebote mit Angebot A, aber "freiePlaetze": 0
  3. POST /v1/angebote mit Angebot A, aber "kostenbeitragEur": 9.99
  4. GET /v1/angebote/1/buchungen
  5. PATCH /v1/angebote/1 mit {"status": "abgesagt"}
  6. PATCH /v1/angebote/1 mit {"status": "offen"}

Je Anfrage 1 Punkt:

  1. 201 Created: gültig, legeAngebotAn() ist implementiert und antwortet mit Location-Header.
  2. 400 Bad Request: @Min(value = 1) aus minimum: 1 verletzt; die Methode wird gar nicht aufgerufen.
  3. 201 Created: Der Vertrag fordert nur minimum: 0; die Grenze je Kilometer ist eine Fachregel, die noch niemand prüft.
  4. 404 Not Found: Der Pfad gehört zum tag buchungen; BuchungenApi wird von keiner Klasse implementiert, Spring kennt den Pfad nicht.
  5. 501 Not Implemented: AngebotController implementiert AngeboteApi, überschreibt aendereAngebot() aber nicht; die default-Methode antwortet.
  6. 400 Bad Request: Im Schema AngebotAenderung erlaubt status nur den Wert abgesagt; offen lässt sich nicht umwandeln, noch bevor eine Methode aufgerufen wird.

Aufgabe 2.1: Die Schnittstelle für Gesuche bestimmen (Kernkompetenz)

Der Vertrag wird um die Schemas aus den Übungen zu Artikel 01 (GesuchNeu, Gesuch, GesuchStatus) und diese Pfade ergänzt:

paths:
  /gesuche:
    get:
      tags: [gesuche]
      operationId: sucheGesuche
      summary: offene Gesuche suchen
      parameters:
        - name: richtung
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/Richtung'
      responses:
        '200':
          description: passende offene Gesuche
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Gesuch'
    post:
      tags: [gesuche]
      operationId: stelleGesuchEin
      summary: ein Mitfahrgesuch einstellen
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GesuchNeu'
      responses:
        '201':
          description: Gesuch angelegt
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Gesuch'
        '400':
          $ref: '#/components/responses/UngueltigeAnfrage'
  /gesuche/{gesuchId}:
    get:
      tags: [gesuche]
      operationId: holeGesuch
      summary: ein Gesuch abrufen
      parameters:
        - name: gesuchId
          in: path
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: das Gesuch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Gesuch'
        '404':
          $ref: '#/components/responses/NichtGefunden'

a) Nenne die Dateien, die der Generator beim nächsten Build zusätzlich erzeugt. (2 Punkte)

Im Paket api: GesucheApi.java (1 Punkt). Im Paket api.model: Gesuch.java, GesuchNeu.java und GesuchStatus.java (1 Punkt). Die Schemas Richtung und Problem gab es schon.

b) Notiere die Köpfe der drei Methoden von GesucheApi mit Rückgabetyp, Name und Parametern (ohne Annotationen). (4 Punkte)

ResponseEntity<List<Gesuch>> sucheGesuche(Richtung richtung)
ResponseEntity<Gesuch> stelleGesuchEin(GesuchNeu gesuchNeu)
ResponseEntity<Gesuch> holeGesuch(Long gesuchId)

Bewertung: Methodennamen aus den operationIds (1 Punkt), Rückgabetypen mit ResponseEntity und List bei der Suche (1 Punkt), Parametertypen Richtung, GesuchNeu, Long (2 Punkte). Im erzeugten Code tragen die Parameter zusätzlich @RequestParam, @Valid @RequestBody und @PathVariable.

Aufgabe 2.2: Die Schnittstelle implementieren (Kernkompetenz)

Schreibe die Klasse GesuchController im Paket de.dbh.mitfahrlabor.web, die GesucheApi implementiert. Umzusetzen sind die Suche (ohne Parameter alle Gesuche, sonst nur die der angegebenen Richtung) und das Abrufen eines Gesuchs mit 404 für unbekannte IDs. Die drei Gesuche aus den Übungen zu Artikel 02 gehören Oksana Melnyk (Nutzer 3) und haben den Status offen. Das Einstellen von Gesuchen bleibt vorerst offen. (8 Punkte)

package de.dbh.mitfahrlabor.web;

import java.time.LocalDate;
import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import de.dbh.mitfahrlabor.api.GesucheApi;
import de.dbh.mitfahrlabor.api.model.Gesuch;
import de.dbh.mitfahrlabor.api.model.GesuchStatus;
import de.dbh.mitfahrlabor.api.model.Richtung;

@RestController
public class GesuchController implements GesucheApi {

    private final List<Gesuch> gesuche = List.of(
            new Gesuch().id(1L).mitfahrerId(3L).haltepunktId(1L).richtung(Richtung.ZUR_SCHULE)
                    .datum(LocalDate.of(2026, 11, 23)).zeitVon("06:45").zeitBis("07:15")
                    .status(GesuchStatus.OFFEN),
            new Gesuch().id(2L).mitfahrerId(3L).haltepunktId(3L).richtung(Richtung.ZUR_SCHULE)
                    .datum(LocalDate.of(2026, 11, 23)).zeitVon("07:00").zeitBis("07:20")
                    .status(GesuchStatus.OFFEN),
            new Gesuch().id(3L).mitfahrerId(3L).haltepunktId(1L).richtung(Richtung.NACH_HAUSE)
                    .datum(LocalDate.of(2026, 11, 23)).zeitVon("15:00").zeitBis("16:00")
                    .status(GesuchStatus.OFFEN));

    @Override
    public ResponseEntity<List<Gesuch>> sucheGesuche(Richtung richtung) {
        List<Gesuch> treffer = gesuche.stream()
                .filter(g -> richtung == null || g.getRichtung() == richtung)
                .toList();
        return ResponseEntity.ok(treffer);
    }

    @Override
    public ResponseEntity<Gesuch> holeGesuch(Long gesuchId) {
        return gesuche.stream()
                .filter(g -> g.getId().equals(gesuchId))
                .findFirst()
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
}

Bewertung: @RestController und implements GesucheApi (2 Punkte), Startdaten mit den erzeugten Klassen (2 Punkte; Konstruktor mit allen Werten ist ebenso richtig), Suche mit Filter und null für „alle“ (2 Punkte), Abrufen mit equals() und 404 (2 Punkte). Die Richtung wird mit == verglichen, weil Richtung eine Aufzählung ist; IDs vom Typ Long dagegen mit equals().

Aufgabe 2.3: Was prüft Bean Validation? (Kernkompetenz)

Mit dem Controller aus Aufgabe 2.2 werden diese Bodys an POST /v1/gesuche geschickt. Gib jeweils den Statuscode an und begründe ihn. (8 Punkte)

  1. {"mitfahrerId": 3, "haltepunktId": 1, "richtung": "zur-schule", "datum": "2026-11-23", "zeitVon": "06:45", "zeitBis": "07:15"}
  2. wie 1, aber "zeitVon": "6:45"
  3. wie 1, aber ohne datum
  4. wie 1, aber "zeitVon": "07:30", "zeitBis": "07:00"

Je Body 2 Punkte (Code 1 Punkt, Begründung 1 Punkt):

  1. 501 Not Implemented: Der Body ist gültig, stelleGesuchEin() wird aber nicht überschrieben; die default-Methode antwortet.
  2. 400 Bad Request: 6:45 passt nicht zum Muster HH:MM (@Pattern); geprüft wird vor dem Aufruf der Methode.
  3. 400 Bad Request: datum ist Pflichtfeld (@NotNull).
  4. 501 Not Implemented: Beide Uhrzeiten passen zum Muster. Dass das Zeitfenster rückwärts läuft, kann die Validierung aus dem Vertrag nicht erkennen, weil sie jedes Feld einzeln prüft. Diese Regel muss das Backend selbst prüfen und dann mit 400 oder 422 antworten.

Aufgabe 3.1: Fehler anhand der Symptome finden (Profilkompetenz)

Im Projekt eines anderen Teams treten drei Probleme auf. Erläutere jeweils die Ursache und gib eine Korrektur an. (9 Punkte)

a) GET /v1/gesuche liefert alle Gesuche, GET /v1/gesuche?richtung=nach-hause aber 400 Bad Request. In der Konfiguration des Generators fehlt die Option configPackage.

b) Eine Kollegin hat in build/generated/openapi/…/model/GesuchNeu.java die Annotation @Future an das Datum geschrieben, damit nur künftige Gesuche angenommen werden. Am nächsten Morgen ist die Annotation verschwunden.

c) Nachdem im Vertrag operationId: holeGesuch in operationId: zeigeGesuch geändert wurde, bricht der Build ab: GesuchController.java:36: error: method does not override or implement a method from a supertype.

Je Teil 3 Punkte (Ursache 2 Punkte, Korrektur 1 Punkt):

a) Der Generator legt die Klasse EnumConverterConfiguration, die nach-hause in Richtung.NACH_HAUSE umwandelt, ohne die Option ins Paket org.openapitools.configuration. Das liegt außerhalb von de.dbh.mitfahrlabor, der Komponentenscan findet die Klasse nicht, und Spring versucht Richtung.valueOf("nach-hause"), was scheitert. Korrektur: "configPackage" to "de.dbh.mitfahrlabor.api.config" (bzw. <configPackage> bei Maven).

b) Der Ordner build/generated wird bei jedem Build aus dem Vertrag neu geschrieben, Änderungen von Hand gehen verloren. Korrektur: Die Regel gehört nicht in erzeugten Code. „Datum nicht in der Vergangenheit“ ist eine Fachregel, die im Backend geprüft wird (Artikel 04 und 08). Der OpenAPI Generator bietet zusätzlich die Erweiterung x-field-extra-annotation an, mit der man im Vertrag eine zusätzliche Annotation für ein Feld angeben kann; auch dann steht die Regel im Vertrag und nicht in der erzeugten Datei.

c) Die Schnittstelle heißt jetzt zeigeGesuch(…); die mit @Override gekennzeichnete Methode holeGesuch(…) überschreibt nichts mehr. Korrektur: Methode im Controller in zeigeGesuch umbenennen oder, falls die Umbenennung im Vertrag ein Versehen war, die operationId zurücksetzen. Ohne @Override würde der Build durchlaufen und der Endpunkt mit 501 antworten.

Aufgabe 3.2: Contract First oder Code First? (Profilkompetenz)

Ein Teammitglied schlägt vor, auf den handgeschriebenen Vertrag zu verzichten: „Wir schreiben die Controller wie in Artikel 02 von Hand und lassen eine Bibliothek wie springdoc-openapi den Vertrag beim Start aus dem Code erzeugen (Code First). Das spart die YAML-Datei.“

Beurteile den Vorschlag für das Mitfahrlabor. Nenne je zwei Argumente dafür und dagegen und triff eine begründete Entscheidung. (6 Punkte)

Für Code First (je 1 Punkt, zwei davon):

  • Es gibt nur eine Quelle, den Code; der erzeugte Vertrag passt immer zum Code.
  • Kein Generator im Build, kein erzeugter Code, weniger Konfiguration.
  • Schneller Einstieg für kleine Projekte, bei denen nur ein Team an Backend und Frontend arbeitet.

Gegen Code First (je 1 Punkt, zwei davon):

  • Der Vertrag entsteht erst, wenn das Backend programmiert ist. Die drei Frontend-Teams des Mitfahrlabors können nicht parallel anfangen.
  • Der Vertrag ist dann ein Nebenprodukt; Änderungen an der Schnittstelle passieren beim Programmieren, ohne dass sie bewusst abgestimmt werden.
  • Der Systemtest in Artikel 11 soll gegen den Entwurf prüfen. Wird der Vertrag aus dem Code erzeugt, prüft man den Code gegen sich selbst.

Entscheidung (2 Punkte): Für das Mitfahrlabor passt Contract First besser, weil mehrere Frontends dieselbe Schnittstelle nutzen, der Vertrag im V-Modell als Systementwurf vor der Implementierung feststehen soll und der Systemtest eine unabhängige Grundlage braucht. Eine gut begründete Entscheidung für Code First (etwa für einen Prototyp mit nur einem Team) ist ebenfalls richtig.


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zur Codegenerierung: Mitfahrgesuche aus dem Vertrag” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/spring-boot-openapi-codegenerierung-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: