Was eine Prozessanwendung von einem CRUD-Backend unterscheidet #
Eine Prozessanwendung ist eine Webanwendung, deren Kern ein Ablauf mit Zuständen, Regeln und Beteiligten ist: ein Auftrag, der von „eingegangen“ über „geprüft“ und „freigegeben“ bis „abgerechnet“ wandert, ein Antrag mit Vertretungsregeln, ein Kundenportal, in dem Bestellungen, Rückfragen und Dokumente zusammenlaufen. Anders als bei einem reinen CRUD-Backend ist nicht das Anlegen und Ändern von Datensätzen die Schwierigkeit, sondern die Frage, welche Übergänge unter welchen Bedingungen erlaubt sind und was dabei in anderen Systemen passieren muss.
Laravel bringt für solche Anwendungen die passenden Bausteine mit: Eloquent für das Datenmodell, Form Requests und Policies für Validierung und Rechte, Events und Queues für Nebenwirkungen, den Scheduler für wiederkehrende Läufe und ein Test-Framework, mit dem sich Regeln als ausführbare Beispiele festhalten lassen. Entscheidend ist, wie diese Bausteine zusammengesetzt werden. Dieser Artikel beschreibt die Struktur, die sich in meinen Projekten bewährt hat, und die Stellen, an denen ein Entwurf im Betrieb bricht.
Zustände und Übergänge explizit modellieren #
Der häufigste Fehler in gewachsenen Anwendungen ist ein Statusfeld, das von überall her beschrieben wird. Ein Controller setzt status = 'freigegeben', ein Import setzt ihn woanders auf abgeschlossen, und nach einem Jahr weiß niemand mehr, welche Übergänge eigentlich gültig sind.
Stabiler ist ein explizites Modell: Der Status ist ein PHP-Enum, die erlaubten Übergänge stehen an einer Stelle, und jeder Übergang ist eine benannte Aktion, etwa ApproveOrder oder CancelOrder. Die Aktion prüft die Vorbedingungen, ändert den Zustand, schreibt einen Eintrag in die Historie und löst ein Event aus. Kein anderer Code setzt das Statusfeld direkt.
- Enum mit allen Zuständen und einer Methode, die die erlaubten Folgezustände liefert.
- Eine Aktionsklasse je Übergang, aufrufbar aus Controller, Command, Job und Test.
- Historientabelle mit Vorher, Nachher, auslösender Person und Zeitpunkt, damit jede Freigabe nachvollziehbar bleibt.
- Datenbanktransaktion um Zustandswechsel und Historieneintrag, damit beides zusammen gilt oder gar nicht.
Der Gewinn zeigt sich bei der ersten Änderung der Regeln: Ein neuer Zwischenschritt „Rückfrage beim Kunden“ ist ein neuer Enum-Wert, ein neuer Übergang und eine neue Aktion, nicht eine Suche nach allen Stellen, die den Status berühren.
Validierung, Rechte und Geschäftsregeln trennen #
Drei Fragen werden gern in einer Methode vermischt: Sind die Eingaben formal gültig? Darf diese Person das tun? Ist der Übergang fachlich erlaubt? Laravel hat für jede Frage einen eigenen Ort, und die Trennung zahlt sich aus, sobald derselbe Übergang aus zwei Richtungen ausgelöst wird, etwa aus der Oberfläche und aus einer Schnittstelle.
Form Requests prüfen Format und Pflichtfelder, bevor die Aktion überhaupt aufgerufen wird. Policies beantworten die Rechtefrage anhand von Rolle, Zuständigkeit und Zustand: Eine Freigabe darf nur die zuständige Person erteilen, und nur, wenn der Auftrag im Zustand „geprüft“ ist. Die Aktion selbst prüft die fachlichen Vorbedingungen, die von den Daten abhängen: Ist der Kunde nicht gesperrt, sind alle Positionen bepreist, liegt die Bonitätsprüfung vor?
Vertretungsregeln, Vier-Augen-Prinzip und Eskalation nach Frist gehören zu den Regeln, die früh geklärt sein sollten. Sie sind fachlich einfach, verlangen im Datenmodell aber eigene Felder und in der Historie eigene Einträge. Wer sie nachträglich einbaut, ändert meist mehr als geplant.
Nebenwirkungen in Jobs auslagern und idempotent halten #
Ein Zustandswechsel hat Folgen: eine E-Mail an den Kunden, ein Eintrag in der Warenwirtschaft, ein PDF, eine Nachricht in einen Chat. Werden diese Folgen im selben Request ausgeführt, hängt die Antwort an der langsamsten externen Schnittstelle, und ein Timeout beim ERP lässt die Freigabe scheitern, obwohl sie fachlich längst gültig war.
Die Lösung in Laravel ist der Weg über Events und Queue-Jobs: Die Aktion löst OrderApproved aus, Listener stellen die Folgen als Jobs in die Warteschlange, ein Worker arbeitet sie ab. Damit das im Betrieb trägt, müssen die Jobs drei Eigenschaften haben:
- Idempotent: Ein Job, der zweimal läuft, erzeugt keinen zweiten ERP-Auftrag. Dafür bekommt jede Übertragung eine eindeutige Kennung, die das Zielsystem oder eine eigene Tabelle prüft.
- Wiederholbar mit Abstand: Bei einem nicht erreichbaren System wartet der Job mit wachsendem Abstand statt sofort erneut zu versuchen; Laravel bietet dafür
backoffund eine begrenzte Zahl an Versuchen. - Sichtbar im Fehlerfall: Endgültig gescheiterte Jobs landen in
failed_jobsund lösen eine Meldung aus. Ein stiller Fehler in der Warteschlange ist der teuerste, weil er erst auffällt, wenn der Kunde nachfragt.
Die Queue-Dokumentation von Laravel beschreibt Versuche, Backoff und Unique Jobs im Detail. Für Massendaten, etwa den nächtlichen Bestandsabgleich, gilt dasselbe Muster in Paketen; der Fachartikel Große Datenimporte in Laravel zeigt den Ablauf mit Fortschritt und Fehlerkorrektur.
Schnittstellen als eigene Schicht mit klarem Vertrag #
Jedes angebundene System bekommt eine eigene Klasse, die den Zugriff kapselt: Authentifizierung, Aufruf, Fehlerabbildung, Zeitlimits. Der Rest der Anwendung spricht nur mit dieser Klasse und kennt weder URL noch Format des Fremdsystems. Das macht die Anbindung austauschbar und, wichtiger noch, testbar: In Tests wird die Klasse durch eine Attrappe ersetzt, die die dokumentierten Antworten und Fehlerfälle nachstellt.
Für eingehende Schnittstellen, etwa Webhooks vom Shop oder eine API für das Kundenportal, gilt das Gleiche mit umgekehrter Richtung: Ein API-Controller validiert und übersetzt, ruft dann dieselbe Aktion auf wie die Oberfläche. So gibt es genau einen Ort, an dem ein Auftrag freigegeben wird, egal woher der Anstoß kommt. Welches System bei jedem Feld führt und wie Retouren, Teillieferungen und Stornos behandelt werden, sollte vor der Umsetzung feststehen; der Ratgeber Schnittstellen planen enthält die Prüffragen dafür.
Regeln als Tests festhalten #
In einer Prozessanwendung sind die Regeln das Produkt. Sie gehören deshalb in Feature-Tests, die den Ablauf mit echten Daten durchspielen: Ein Auftrag im Zustand „eingegangen“ kann nicht freigegeben werden; eine Freigabe durch eine unzuständige Person schlägt fehl; nach der Freigabe liegt genau ein Job für das ERP in der Warteschlange; ein zweiter Aufruf desselben Jobs erzeugt keinen zweiten Auftrag.
Solche Tests sind gleichzeitig die Dokumentation, die mit dem Code aktuell bleibt. Wenn eine Fachabteilung eine Regel ändert, ist der erste Schritt ein angepasster Test, der zweite die Umsetzung. Laravel unterstützt das mit Factories für Testdaten, Queue::fake() für die Warteschlange und HTTP-Fakes für externe Schnittstellen, sodass ein vollständiger Ablauf in Sekunden statt Minuten läuft.
Betrieb: Worker, Scheduler und Beobachtbarkeit #
Eine Prozessanwendung läuft nicht nur im Request. Queue-Worker müssen als Dienst überwacht und bei Deployments neu gestartet werden, der Scheduler braucht einen verlässlichen Cron-Eintrag, und beides muss sichtbar sein: Wie viele Jobs warten, wie viele sind gescheitert, wann lief der letzte Abgleich? Ohne diese Sicht bemerkt man einen stehenden Worker erst, wenn Kunden keine Bestätigungen mehr bekommen.
Dazu gehören ein Health-Endpunkt, der auch den Zustand der Warteschlange meldet, strukturierte Logs mit der Kennung des Vorgangs und Meldungen bei gescheiterten Jobs. Für Redis als Queue-Backend, Docker für reproduzierbare Umgebungen und GitHub Actions für Tests vor jedem Deployment finden sich auf den Technologieseiten die Details.
Wenn du eine solche Anwendung planst oder eine bestehende Laravel-Anwendung auf diese Struktur bringen möchtest, unterstütze ich dich als Laravel Freelancer, von der Modellierung der Zustände bis zu Schnittstellen und Betrieb. Die Einordnung nach Projektziel findest du unter Webanwendungen und Backends entwickeln lassen.
Du möchtest das in deinem Projekt angehen?
Beschreibe kurz deine Anwendung, die beteiligten Systeme und dein Ziel. Gemeinsam klären wir den nächsten sinnvollen Schritt.
Projekt besprechen