Die Logik, die außerhalb der Forms lebt
Ein Energiehandelsunternehmen bat uns letztes Frühjahr um eine Migrationsschätzung. Das .fmb-Inventar umfasste 184 Dateien und rund 340.000 Zeilen eingebettetes PL/SQL. Vernünftiger Umfang. Dann führten wir die Abhängigkeitsanalyse gegen die Datenbank durch. Die Forms referenzierten 412 PL/SQL-Pakete, 1.840 Datenbank-Trigger und 96 Package-State-Variablen, die sitzungsübergreifend geteilt wurden. Die Datenbankseite enthielt 2,7-mal mehr Code als die Forms selbst.
Dieses Verhältnis ist typisch. Die Forms sind die Spitze. Der Eisberg liegt in der Datenbank.
Datenbank-Trigger sind keine Form-Trigger
Form-Trigger feuern als Reaktion auf UI-Events. Datenbank-Trigger feuern als Reaktion auf DML — INSERT, UPDATE, DELETE — und laufen innerhalb der Transaktion, die die Anweisung ausgelöst hat. Sie erzwingen referentielle Regeln, befüllen Audit-Spalten, kaskadieren Löschungen und führen Geschäftslogik aus, die die Forms als automatisch voraussetzen.
Ein typischer Audit-Trigger:
CREATE OR REPLACE TRIGGER orders_audit_trg
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO orders_audit (order_id, action, changed_by, changed_at,
old_amount, new_amount)
VALUES (COALESCE(:NEW.order_id, :OLD.order_id),
CASE WHEN INSERTING THEN 'I'
WHEN UPDATING THEN 'U' ELSE 'D' END,
USER, SYSDATE, :OLD.amount, :NEW.amount);
END;
Migrationen, die auf eine neue Anwendungsschicht wechseln, nehmen oft an, dass sie dies durch anwendungsseitige Audit-Protokollierung ersetzen können. Diese Annahme bricht in dem Moment, in dem ein Batch-Job, ein SQL*Plus-Skript oder ein Report Writer dieselbe Tabelle modifiziert. Der Datenbank-Trigger erfasste alle Schreiber. Der Anwendungs-Logger erfasst einen.
Package State ist ein versteckter Vertrag
Oracle PL/SQL-Pakete können sitzungsbezogenen Zustand halten: Variablen, die auf Paketebene deklariert sind und über Aufrufe innerhalb derselben Datenbanksitzung hinweg bestehen bleiben. Forms-Anwendungen nutzen dies intensiv — ein Benutzer meldet sich an, die Sitzung führt eine Login-Prozedur aus, und 40 Paketvariablen werden mit Benutzer-ID, Rolle, Kostenstelle, Geschäftskalender und Genehmigungslimits befüllt. Jeder nachfolgende Form-Aufruf liest aus diesem Zustand, ohne ihn neu zu laden.
Wir haben zwischen 20 und 180 Package-State-Variablen pro Forms-Anwendung gezählt. Der Median liegt bei 58. Keine davon ist in den .fmb-Dateien sichtbar. Keine davon überlebt eine zustandslose REST-Architektur ohne explizite Neumodellierung.
Die vier Fallen, die wir wiederholt sehen
Über 14 Migrationsprojekte hinweg verursachen dieselben vier Probleme die meisten Zeitplanverzögerungen auf der Datenbankseite:
- Unsichtbare Commit-Punkte. Forms führen implizite Commits bei Block-Navigation aus. Datenbank-Trigger setzen voraus, dass diese Commits stattfinden. REST-Endpunkte, die mehrere Operationen in eine Transaktion bündeln, brechen Trigger-Annahmen.
- Session-State-Drift. Paketvariablen, die beim Login befüllt werden, werden veraltet, wenn die neue Architektur Datenbankverbindungen poolt. Dieselbe Verbindung bedient verschiedene Benutzer über Requests hinweg.
- Trigger-Kaskaden. Ein UPDATE feuert einen Trigger, der ein weiteres UPDATE auslöst, das wiederum einen weiteren Trigger feuert. Die Kaskadentiefe in unserer Stichprobe erreicht 7. Anwendungsseitige Ersetzungen übersehen Zwischenschritte.
- REF-Cursor-Leaks. Stored Procedures geben REF-Cursor zurück, die Forms zeilenweise konsumieren. Ein REST-Endpunkt muss die vollständige Ergebnismenge materialisieren, was die Speichercharakteristik verändert und manchmal ORA-04030 bei großen Abfragen auslöst.
Jedes Problem ist lösbar. Keines ist allein aus den .fmb-Dateien ersichtlich.
Was die Abhängigkeitsanalyse erfassen muss
Bevor eine Zeile Migrationscode geschrieben wird, extrahieren wir den vollständigen Abhängigkeitsgraphen: jede Tabelle, View, jedes Paket, jede Prozedur, Funktion, jeden Trigger und jede Sequenz, die die Forms berühren, plus alles, was diese Objekte wiederum berühren. Der Graph für eine mittelgroße Anwendung umfasst 4.000 bis 12.000 Knoten.
Der Graph zeigt uns, ob Package State durch einen typisierten Session-Store ersetzt werden kann, ob Datenbank-Trigger belassen werden können oder ob die Audit-Logik auf der Anwendungsschicht neu geschrieben werden muss. Diesen Schritt zu überspringen ist der teuerste Fehler bei Oracle Forms-Migrationen. Wir haben gesehen, wie er Projekten, die am ersten Tag sauber aussahen, sechs bis neun Monate hinzufügte.
Datenbank-Trigger belassen
Unsere Standardempfehlung ist, Datenbank-Trigger während und nach der Migration weiterlaufen zu lassen. Sie sind kampferprobt, sie erfassen Nicht-Anwendungs-Schreiber, und sie befinden sich bereits in der Kontrollmatrix der Prüfer. Die neue TypeScript-Anwendung ruft Stored Procedures für komplexe Transaktionen auf, statt die Logik neu zu implementieren.
Das ist nicht immer möglich — einige Trigger rufen DBMS_ALERT, UTL_HTTP oder andere Funktionen auf, die neu geschrieben werden müssen — aber wenn es möglich ist, eliminiert es eine ganze Klasse von Migrationsrisiko. Die Datenbank erzwingt weiterhin, was sie immer erzwungen hat. Die Anwendungsschicht ändert sich drum herum.
Die Erkenntnis
Oracle Forms-Migrationen sind Datenbankmigrationen im UI-Gewand. Die .fmb-Dateien sind die sichtbare Oberfläche; der eigentliche Vertrag sind die PL/SQL-Pakete, Datenbank-Trigger und der Package State, auf den sich die Forms seit Jahrzehnten stützen. Jedes Projekt, das wir termingerecht geliefert haben, begann mit einer vollständigen Abhängigkeitsanalyse der Datenbankseite — bevor jemand ein Formular geöffnet hat. Jedes Projekt, das sich verzögerte, hat diesen Schritt übersprungen.