Steckt Delphi-Geschäftslogik in Ihren Formularen fest?
Finden und trennen Sie Delphi-Geschäftslogik in VCL-Formularen, datensensitiven Steuerelementen, Dataset-Ereignissen und Transaktionen.

Delphi-Formulare sind oft ausführbare Spezifikationen mit einer Benutzeroberfläche darüber. Ein Klick auf eine Schaltfläche berechnet Rabatte, TDBEdit.OnExit normalisiert einen Kontocode, BeforePost weist eine geschlossene Periode zurück und ein Grid-Ereignis ändert das Ergebnis der nächsten Abfrage. Wer diese Handler als entbehrlichen Darstellungscode behandelt, bekommt einen Ersatz, der fertig aussieht und doch still das Geschäft verändert.
Eine direkte Portierung verschärft dieses Risiko. Jedes Formular in einem Browser oder einem anderen Desktop-Toolkit nachzubauen, erhält die alten Grenzen und zwingt Sie dann, deren verborgene Kopplung in einem weniger nachsichtigen Ereignismodell erneut zu entdecken. Sicherer ist es, beobachtbares Verhalten zu identifizieren, Regeln hinter ausdrücklichen Schnittstellen herauszulösen und während des Übergangs sowohl die alte VCL-Anwendung als auch das neue System dieselben konzeptionellen Operationen aufrufen zu lassen.
Wie gelangte Geschäftslogik in Delphi-Formulare?
Geschäftslogik landete in Delphi-Formularen, weil die VCL den kürzesten Weg zu funktionierender Software extrem kurz machte. Man setzt ein Dataset, eine TDataSource, einige datensensitive Steuerelemente und eine Schaltfläche auf ein Formular und legt die Entscheidung neben das Ereignis, das sie benötigt. Diese Entscheidung war vernünftig, als ein Entwickler die Anwendung verantwortete und die Benutzer in der Nähe der Datenbank saßen. Jahre von Änderungen machten aus räumlicher Nähe eine Architektur.
Die Formularklasse sammelte danach verschiedene Zuständigkeiten. Sie liest den Zustand von Steuerelementen, deutet die Absicht des Benutzers, wendet Richtlinien an, startet Transaktionen, aktualisiert Datasets, formatiert Meldungen und entscheidet, welcher Bildschirm als Nächstes geöffnet wird. Die .dfm-Datei fügt eine weitere Ebene hinzu, denn Eigenschaftswerte und Komponentenverknüpfungen verändern das Laufzeitverhalten, ohne in der gerade gelesenen Pascal-Methode aufzutauchen.
Deshalb unterschätzen Zeilenzahlen die Migration. Eine Unit mit 250 Zeilen kann von Dutzenden geerbten Eigenschaften, persistenten Feldern, Aktionen, gemeinsam genutzten Datenmodulen und in der DFM gespeicherten Ereigniszuweisungen abhängen. Selbst ein scheinbar leeres TDBEdit schreibt über TDataSource in den Puffer eines Datasets. Sein Verhalten hängt vom Dataset-Zustand, von Feldereignissen, Eingabemasken und dem späteren Ablauf in BeforePost ab.
Klassifizieren Sie nicht jeden Ereigniscode als Geschäftslogik. Ein Dialog, ein Fokuswechsel und die Größenanpassung von Spalten gehören zur Darstellung. Die Entscheidung, dass eine Rechnung nach dem Periodenabschluss nicht gebucht werden darf, ist eine Regel. Die Übersetzung einer Kundenstufe in einen Rabatt ist eine Berechnung. Der Aufruf einer Stored Procedure kann Anwendungssteuerung oder Datenzugriff sein, je nachdem, welchen Vertrag die Prozedur anbietet. Diese Unterscheidung zählt, weil jede Art von Code ein anderes Ziel braucht.
Ein hilfreicher Test entfernt das Formular gedanklich. Muss die Entscheidung bei einem Import, einer API-Anfrage oder einem Batchlauf weiterhin gelten, handelt es sich um Geschäftslogik. Hilft das Verhalten nur einer Person bei der Bedienung dieses Bildschirms, kann es in der UI bleiben. Koordiniert es einen Anwendungsfall, ohne selbst eine Regel zu enthalten, gehört es in einen Anwendungsdienst.
Die Formulardatei ist nur das halbe Programm
Vor jeder Extraktion brauchen Sie ein Inventar des ausführbaren Verhaltens. Es muss Pascal-Code, DFM-Ressourcen, geerbte Formulare und Datenbankobjekte umfassen. Wer nur nach Click-Handlern sucht, übersieht automatische Bearbeitungen und Lebenszyklusereignisse. Wer nur die DFM liest, übersieht zur Laufzeit zugewiesene Handler.
Beginnen Sie mit einer mechanischen Karte. Erfassen Sie für jedes Formular alle Komponentenereignisse, Dataset-Ereignisse, Aktionen, Timer, Message-Handler und Aufrufe in andere Units. Nehmen Sie OnCreate, OnShow, OnCloseQuery, OnChange, OnExit, OnClick, OnExecute, BeforeEdit, BeforePost, AfterPost, OnCalcFields und Exception-Handler auf. Suchen Sie nach Zuweisungen wie Button.OnClick :=, denn Anwendungen verdrahten Verhalten gelegentlich nach der Konstruktion neu.
Fügen Sie danach die impliziten Pfade hinzu. Halten Sie fest, welche Steuerelemente auf welche TDataSource zeigen, welches Dataset jede Quelle bereitstellt, ob AutoEdit aktiviert ist und welche persistenten TField-Objekte Validierungs- oder Änderungsereignisse haben. Die Dokumentation von Embarcadero bezeichnet TDataSource als Verbindung zwischen einem Dataset und datensensitiven Steuerelementen. Das unscheinbare Wort Verbindung ist die Warnung: Ein Tastendruck kann die UI-Grenze überschreiten, bevor irgendeine Speichern-Schaltfläche ausgeführt wird.
Erstellen Sie eine Tabelle mit einer Zeile pro beobachtetem Verhalten, nicht pro Methode. Diese Spalten legen die meisten Fallen offen:
Trigger Reads Writes Rule owner
btnPostClick invoice fields, role invoice status posting policy
AmountFieldValidate amount, currency record buffer money rule
CustomerDataChange current customer filter params query orchestration
Die Spalte Evidence erzwingt Genauigkeit. Ein Handlername ist kein Beleg. Erfassen Sie Eingabewerte, Dataset-Zustand, SQL-Aufrufe, zurückgegebene Zeilen, Meldungen und endgültig gespeicherte Werte. Hängt ein Handler von der Reihenfolge der VCL-Ereignisse ab, halten Sie diese Reihenfolge fest. Sie ist Teil des Verhaltens, bis Sie belegen, dass Benutzer und Integrationen sie nicht beobachten können.
Geerbte Formulare verdienen einen eigenen Durchgang. Eine untergeordnete DFM kann eine Eigenschaft überschreiben und zugleich ein Ereignis von einem Vorfahren erben, der in einem anderen Projektverzeichnis liegt. Die untergeordnete Unit kann harmlos wirken, obwohl das Basisformular in OnShow Datasets öffnet oder Berechtigungen ändert. Entfalten Sie die Vererbungskette und erfassen Sie die wirksame Komponentenkonfiguration der gebauten Anwendung, nicht nur den Text einer Datei.
Auch Aktionen verbergen Wiederverwendung. Ein einziges TAction.OnExecute kann von Menüeintrag, Symbolleistenschaltfläche und Tastenkürzel ausgelöst werden, während OnUpdate die Verfügbarkeit anhand globalen Zustands bestimmt. Kopiert der neue Client nur die sichtbare Schaltfläche, verlieren Tastaturbenutzer womöglich einen Weg und die Autorisierung wandert in ein kosmetisches Deaktiviert-Kennzeichen. Behandeln Sie die Ausführungsberechtigung als Regel an der Befehlsgrenze und den Aktiviert-Zustand als Ansicht dieser Entscheidung.
Lösen Sie Entscheidungen vor den Steuerelementen heraus
Extrahieren Sie zuerst reine Entscheidungen und Berechnungen, denn sie schaffen stabile Nähte, ohne den Bildschirm zu stören. Lassen Sie den Event-Handler an seinem Platz, reduzieren Sie ihn aber auf das Sammeln von Eingaben, den Aufruf einer Regel und die Darstellung des Ergebnisses. Die laufende Anwendung bleibt nutzbar, während sich die Regel ohne Formular aufrufen lässt.
Angenommen, ein Auftragsformular trifft eine Kreditentscheidung in btnApproveClick. Der ursprüngliche Handler liest Felder, prüft ein Kundenkennzeichen, vergleicht Summen, aktualisiert Steuerelemente, postet das Dataset und zeigt eine Meldung. Trennen Sie die Entscheidung von diesen Effekten:
type
TApprovalInput = record
OrderTotal: Currency;
CreditLimit: Currency;
AccountOnHold: Boolean;
end;
TApprovalDecision = record
Allowed: Boolean;
ReasonCode: string;
end;
function DecideApproval(const Input: TApprovalInput): TApprovalDecision;
begin
if Input.AccountOnHold then
Exit(TApprovalDecision.Create(False, 'ACCOUNT_HOLD'));
if Input.OrderTotal > Input.CreditLimit then
Exit(TApprovalDecision.Create(False, 'LIMIT_EXCEEDED'));
Result := TApprovalDecision.Create(True, 'APPROVED');
end;
Die genaue Record-Syntax muss möglicherweise an die Delphi-Version im Bestand angepasst werden. Entscheidend ist der Entwurf: Die Funktion nimmt Werte entgegen, gibt eine Entscheidung zurück und weiß nichts von TEdit, TField, modalen Ergebnissen oder Transaktionen. Ein Unit-Test kann sie abdecken, ein Batchimport kann eine entsprechende Operation aufrufen und ein Zieldienst kann denselben Vertrag umsetzen.
Verschieben Sie den alten Handler nicht unverändert in eine Klasse namens TOrderService. Eine Methode, die ein Formular entgegennimmt oder durch ein globales Datenmodul greift, bleibt an die UI gekoppelt. Zwanzig Steuerelemente als Parameter zu übergeben, versteckt die Kopplung nur in der Signatur. Definieren Sie Eingaben in Geschäftsbegriffen und liefern Sie genug Kontext für eine deterministische Entscheidung.
Widerstehen Sie auch einer zu frühen Extraktion gemeinsamer Hilfsfunktionen. Zwei Handler, die beide Steuern berechnen, können sich unterscheiden, weil einer Gutschriften verarbeitet und der andere Rechnungen vor einer Regeländerung. Charakterisieren Sie zuerst beide Verhaltensweisen. Führen Sie sie erst zusammen, wenn die Belege zeigen, dass der Unterschied unbeabsichtigt ist.
Datensensitive Steuerelemente schaffen einen unsichtbaren Schreibpfad
Datensensitive Steuerelemente brauchen im Ersatz ein ausdrückliches Bearbeitungsmodell, weil sie Anzeige, Navigation, Pufferung, Validierung und Persistenz verbinden. Ein Browser-Eingabefeld mit JSON-Bindung ist kein gleichwertiger Ersatz für ein TDBEdit, das über TDataSource mit einem aktiven TDataSet verbunden ist.
Das erste verborgene Verhalten ist der Eintritt in den Bearbeitungsmodus. Embarcadero dokumentiert, dass TDataSource.AutoEdit standardmäßig true ist und die Methode Edit des Datasets aufruft, sobald ein Benutzer ein gebundenes Steuerelement zu ändern versucht. Die ursprüngliche Anwendung kann deshalb beim ersten Tastendruck eine Zeile sperren, einen Datensatz als geändert markieren oder Post- und Cancel-Aktionen aktivieren. Eine neue UI, die auf ausdrückliches Speichern wartet, besitzt ein anderes Nebenläufigkeitsmodell, selbst wenn die Felder gleich aussehen.
Das zweite verborgene Verhalten ist die Pufferung. Ein angezeigter Feldwert muss weder dem zuletzt festgeschriebenen Datenbankwert entsprechen noch dem Wert, den ein anderes Steuerelement nach einem Ereignis sieht. Edit, Insert, Post, Cancel, zwischengespeicherte Updates und Provider-Einstellungen bestimmen, wann Änderungen dauerhaft werden. Schreiben Sie diese Zustände als kleine Zustandsmaschine auf. Zum Beispiel: View erlaubt Navigation; Edit hält einen lokalen Entwurf; Saving validiert den Entwurf und sendet einen Befehl; Conflict bewahrt den Entwurf des Benutzers und zeigt zugleich die neuere Serverversion.
Das dritte Verhalten betrifft den Ort der Validierung. Eine Eingabemaske prüft Zeichen während der Eingabe. Das OnValidate eines Feldes prüft einen vollständigen Wert, bevor er in den Datensatzpuffer gelangt. BeforePost kann den gesamten Datensatz prüfen. Datenbank-Constraints greifen später und können Beziehungen abdecken, die kein Formular kennt. Die Dokumentation zu TField.OnValidate von Embarcadero weist ausdrücklich darauf hin, dass eine programmatische Zuweisung EditMask umgeht, während OnValidate das Feld vor dem Posten weiterhin prüft. Das ist ein guter Grund, dauerhafte Regeln unterhalb der Widget-Ebene anzusiedeln.
Verteilen Sie die Validierung beim Umzug auf vier Bereiche:
- Eingabehilfen wie Formatierung und sofortige Rückmeldung zu Zeichen bleiben im Client.
- Feldinvarianten wie eine erlaubte Codemenge leben in der Domänenoperation und dürfen für eine schnelle Rückmeldung zusätzlich im Client laufen.
- Feldübergreifende Regeln und Berechtigungsprüfungen laufen auf dem Server oder an der Anwendungsgrenze, die den Befehl besitzt.
- Referenzielle und Eindeutigkeits-Constraints bleiben in Postgres erzwungen, auch wenn freundlichere Prüfungen früher stattfinden.
Eine Regel für sofortige Rückmeldung zu duplizieren ist nur vertretbar, wenn eine Implementierung maßgeblich bleibt. Der Server muss einen ungültigen Befehl ablehnen, unabhängig von der Prüfung im Client. Sonst können ein Import, eine Integration oder ein veralteter Client das Geschäft umgehen.
Master-Detail-Bindung bringt eine weitere Falle mit. Das Verschieben des Master-Cursors kann automatisch Parameter ändern und Detailzeilen neu laden. Benutzer nehmen das womöglich als einen zusammenhängenden Arbeitsbereich wahr, doch die Implementierung stützt sich auf eine Cursorposition statt auf eine ausdrückliche ID. Der Ersatz sollte Details per Master-ID anfordern, den Auswahlzustand im Client halten und festlegen, was beim Wechsel des Masters mit einem ungespeicherten Detailentwurf geschieht.
Auch berechnete Felder und Lookup-Felder brauchen einen Eigentümer. Ein berechnetes Feld, das nur der Anzeige dient, gehört in eine Abfrageprojektion oder ein View Model. Liest eine andere Regel seinen Wert, verschieben Sie die Berechnung in die Domänenoperation und testen Sie ihre Eingaben. Ein Lookup-Feld kann einen Datenbankzugriff oder einen veralteten Cachewert verbergen. Erfassen Sie deshalb, ob das heutige Verhalten Live-Daten, Daten vom Öffnungszeitpunkt oder durch ein bestimmtes Ereignis aktualisierte Daten sieht.
Dataset-Ereignisse sind kein Domänenmodell
Datasets in ein TDataModule zu verschieben verbessert die Organisation, trennt die Geschäftslogik aber nicht automatisch. Embarcadero beschreibt TDataModule als Ort zur Zentralisierung nicht visueller Komponenten und erlaubt dort sogar Geschäftsregeln. Dieser Rat räumt Formulare auf. Er schafft keine Grenzen, ausdrücklichen Eingaben oder unabhängig testbaren Anwendungsfälle.
Dataset-Ereignisse mischen häufig drei Aufgaben. BeforePost kann eine Invariante validieren, Auditfelder füllen und eine weitere Abfrage ausführen. AfterScroll kann ein Detail-Dataset aktualisieren und eine Aktion aktivieren. OnCalcFields kann einen Anzeigewert berechnen, den ein anderer Handler später als verbindlich behandelt. Wer diese Ereignisse in ein Repository oder einen ORM-Hook kopiert, erzeugt dieselbe Unklarheit erneut.
Ordnen Sie jedes Ereignis danach ein, was es auslöst und was es garantiert. Eine Domänenoperation sollte laufen, weil ein Aufrufer ApproveOrder angefordert hat, nicht weil irgendein Dataset zufällig postet. Ein Repository sollte einen genehmigten Auftrag speichern, nicht über die Genehmigung entscheiden. Ein View Model darf Anzeigetext berechnen, doch gespeicherte Summen müssen aus der Regel stammen, die Geldberechnungen besitzt.
Es gibt einen unbequemen Fall: Fremdcode kann Post direkt aufrufen und sich darauf verlassen, dass BeforePost den Datensatz schützt. Löschen Sie diese Sicherung während der Extraktion nicht. Legen Sie die Regel in eine aufrufbare Unit, rufen Sie sie aus BeforePost auf und führen Sie neue Befehle durch dieselbe Regel. Entfernen Sie das alte Ereignis erst, wenn Traces zeigen, dass jeder Schreibpfad die neue Grenze nutzt.
Globale Datenmodule erfordern besondere Sorgfalt. Ein Formular kann voraussetzen, dass dmMain.qryCustomer bereits geöffnet, auf denselben Kunden positioniert und Teil einer anderswo gestarteten Transaktion ist. Das ist gemeinsam veränderlicher Zustand. Fassen Sie diese Vorbedingungen als ausdrückliche IDs und Transaktionsbereiche. Eine CustomerId zu übergeben ist sicherer als die aktuelle Zeile eines Datasets, dessen Cursor ein anderes Ereignis verschieben kann.
Transaktionen müssen dem Anwendungsfall folgen
Transaktionsgrenzen sollten eine Geschäftsoperation umschließen, nicht einen Button-Handler oder jedes Dataset-Post. Der alte Code kann eine Transaktion in einem Ereignis beginnen, durch verschachtelte Aufrufe mehrere Datasets verändern und in einem anderen Ereignis festschreiben. Wenn Sie diese Sequenz auf mehrere HTTP-Anfragen verteilen, kann unvollständige Arbeit zurückbleiben, die die Desktop-Anwendung nie erlaubte.
Verfolgen Sie eine erfolgreiche Operation und jeden wesentlichen Fehler. Erfassen Sie SQL-Anweisungen, Aufrufe von Stored Procedures, erzeugte IDs, Sperrverhalten sowie Commit- oder Rollback-Punkt. Benennen Sie die Operation danach in der Sprache des Geschäfts. ClosePeriod könnte den Periodendatensatz aktualisieren, Ledger-Einträge anlegen und offene Entwürfe ablehnen. Diese Änderungen gehören in einen Anwendungsbefehl, selbst wenn die VCL sie über drei Formulare erreicht.
Definieren Sie eine Anfrage und ein Ergebnis, bevor Sie Transportdetails wählen:
{
"operation": "ApproveOrder",
"order_id": 4812,
"expected_version": 17,
"actor_id": 204,
"decision_input": {
"order_total": "1250.00",
"currency": "EUR"
}
}
Ein erfolgreiches Ergebnis sollte die neue Version, den resultierenden Status und stabile Ursachencodes zurückgeben. Ein Konflikt sollte die aktuelle Version melden, ohne sie still zu überschreiben. Der Dezimalwert ist hier eine Zeichenfolge, damit eine Währungsentscheidung im TypeScript-Client nicht durch Gleitkommaarithmetik verfälscht wird.
Stellen Sie keinen generischen Endpunkt UpdateOrder bereit, der jede Spalte annimmt. Er überträgt die Dataset-Abstraktion über das Netz und lädt Aufrufer dazu ein, Zustände zu erzeugen, die das Formular früher verhindert hat. Befehle wie ApproveOrder, ReleaseHold und ChangeDeliveryDate drücken eine Absicht aus und geben jeder Transaktion eine vertretbare Grenze.
Stored Procedures erschweren die Eigentümerschaft, nicht aber die Methode. Enthält eine Prozedur Regeln, charakterisieren Sie ihre Eingaben, Ausgaben, Änderungen und ihr Fehlerverhalten als Teil des heutigen Systems. Behalten Sie sie zunächst hinter einem Adapter. Schreiben Sie sie erst um, wenn Paritätstests das Verhalten abdecken, besonders wenn Delphi-Code Fehlercodes des Anbieters deutet oder von Nebenwirkungen eines Triggers abhängt.
Charakterisierungstests sind Ihre erste Spezifikation
Charakterisierungstests sollten die beobachtbaren Ergebnisse der alten Anwendung mit den Ergebnissen der extrahierten oder neu geschriebenen Operation vergleichen. Aus erinnerten Anforderungen geschriebene Unit-Tests sind später nützlich, können aber nicht zeigen, auf welches undokumentierte Verhalten sich das Geschäft bereits verlässt.
Erfassen Sie repräsentativen Produktionsverkehr, soweit Richtlinien dies erlauben, und entfernen oder schützen Sie sensible Daten. Machen Sie jede Operation zu einem wiederholbaren Fall. Bei einer Desktop-Anwendung umfasst Verkehr mehr als Netzwerkanfragen. Erfassen Sie den anfänglichen Datenbankzustand oder ein stabiles Fixture, Benutzereingaben, relevante Berechtigungen, die ausgelöste Aktion, Meldungen oder Ursachencodes, SQL-Auswirkungen und endgültige Zeilen. Ergänzen Sie Fälle für Abbruch, Doppelklicks, veraltete Datensätze, Nullwerte, Rundungsgrenzen und Datenbankfehler.
Ein kompaktes Fixture erleichtert die Prüfung:
case: approve-order-over-limit
given:
order_id: 4812
order_total: "1250.00"
credit_limit: "1000.00"
account_on_hold: false
when: ApproveOrder
expect:
allowed: false
reason_code: LIMIT_EXCEEDED
order_status: DRAFT
committed_writes: 0
Führen Sie den Fall gegen eine kontrollierte Instanz des Delphi-Pfads und gegen den neuen Pfad aus. Vergleichen Sie Geschäftsergebnisse, gespeicherten Zustand und relevante Nebenwirkungen. Vergleichen Sie keine nebensächlichen Unterschiede wie erzeugte Zeitstempel, sofern sie keinen Vertrag berühren. Normalisieren Sie von der Datenbank erzeugte IDs, wenn die Identität selbst keine Bedeutung hat.
Golden-Master-Tests haben Grenzen. Das alte System kann falsch liegen, und jedes Defizit blind zu erhalten friert es ein. Kennzeichnen Sie Abweichungen als erwartete Parität, genehmigte Korrektur oder ungeklärten Unterschied. Eine genehmigte Korrektur braucht einen benannten Verantwortlichen und einen Test des beabsichtigten Verhaltens. Sonst erklären Entwickler Überraschungen zu Fehlerbehebungen und Prüfer können Migration nicht mehr von Neuentwurf unterscheiden.
Zeit und Gebietsschema brauchen gezielte Fixtures. Delphi-Anwendungen wandeln Datumswerte oft über Arbeitsplatz-Einstellungen um und runden Währungen an verschiedenen Stellen durch Datenbanktypen, Feldtypen und Anzeigeformate. Nehmen Sie Tagesendwerte, Zeitumstellungen bei relevanten Zeitstempeln, Dezimalhälften, leere Zeichenfolgen und Nullwerte auf. Prüfen Sie gespeicherte Werte und Ursachencodes statt formatierter Beschriftungen, es sei denn, die Beschriftung ist selbst ein vertragliches Dokumentfeld.
Testen Sie auch die Unterdrückung von Ereignissen. Code kann Steuerelemente vorübergehend deaktivieren, einen Handler lösen oder ein Ladekennzeichen setzen, um rekursive Aktualisierungen zu verhindern. Die neue Operation sollte diese mechanischen Tricks nicht nachbauen, ihr Endergebnis muss aber übereinstimmen. Eine Wiedergabe, die nur die erfolgreiche Anfrage und die letzte Zeile erfasst, kann doppelte Nebenwirkungen in der Mitte der Sequenz übersehen.
Hier setzt CodeHero einen Paritätsharness gegen aufgezeichneten Produktionsverkehr ein. Das nützliche Prinzip hängt nicht von unserer Plattform ab: Ersatz ist eine Beweisfrage, und Bildschirmähnlichkeit ist ein schwacher Beleg.
Eine direkte UI-Portierung erhält die teure Grenze
Eine direkte UI-Portierung ist meist der teuerste Weg, weil sie Bildschirme neu baut, bevor sie die darunterliegenden Operationen entdeckt. Teams reproduzieren Registerkarten, modale Dialoge, Grids und Navigation und verbinden sie anschließend mit generischen CRUD-Endpunkten. Jede verborgene Regel taucht spät als UI-Fehler, API-Exception oder Streit darüber auf, was Speichern früher bedeutete.
Der kopierte Bildschirm überträgt außerdem Desktop-Annahmen in ein verteiltes System. Die VCL-Anwendung kann einen aktiven Dataset-Cursor halten, eine Verbindung gemeinsam nutzen und synchron auf Feldereignisse reagieren. Ein Webclient muss mit Latenz, Wiederholungen, gleichzeitigen Änderungen, abgelaufenen Sitzungen und doppelt eintreffenden Anfragen umgehen. Ein zustandsbehaftetes Dataset über HTTP zu simulieren, erzeugt geschwätzige APIs und brüchige Clientlogik.
Pixelparität ist deshalb das falsche Abnahmekriterium. Erhalten Sie Aufgabenparität und Geschäftsparität. Ein Benutzer muss weiterhin den richtigen Auftrag genehmigen, den Grund eines Fehlschlags sehen, einen Konflikt beheben und die Aufgabe mit den erforderlichen Informationen abschließen können. Der neue Bildschirm darf alte Dialoge zusammenfassen oder Navigationsschritte entfernen, sofern Operation und Kontrollmöglichkeiten klar bleiben.
Es gibt Fälle, in denen eine dünne UI-Portierung sinnvoll ist. Wenn das unmittelbare Ziel die Kompatibilität mit dem Betriebssystem ist, Datenbank und Integrationen unverändert bleiben und das Formular wenig Geschäftscode enthält, kann ein kompatibler Desktop-Ersatz Zeit gewinnen. Behandeln Sie ihn als Eindämmung mit festgelegter Lebensdauer. Nennen Sie ihn nicht architektonische Trennung.
Definieren Sie das Ziel bei den meisten langlebigen Systemen anhand von Befehlen, Abfragen und ausdrücklichem Zustand. Go-Dienste eignen sich für transaktionsorientierte Anwendungsoperationen und Datenzugriff. Rust passt zu numerischen Kernen, bei denen exaktes Verhalten und Leistung eine enge Grenze verdienen. TypeScript-Clients sollten Interaktionszustand und Darstellungslogik besitzen, während Postgres dauerhafte relationale Constraints erzwingt. Diese Auswahl stammt aus dem bereitgestellten Projektkontext und ist keine Vorgabe, dass jeder Delphi-Bestand alle vier Technologien braucht.
Stellen Sie nach Operation um, nicht nach Formular
Stellen Sie jeweils eine Geschäftsoperation um, weil Formulare selten sauberen Dienstgrenzen entsprechen. Ein Auftragsformular kann Kundensuche, Preisbildung, Genehmigung, Druck und Zahlungsaktionen enthalten. Der Austausch des ganzen Formulars zwingt alle fünf Pfade, gleichzeitig fertig zu werden, und schafft eine große Rollback-Einheit.
Wählen Sie eine Operation mit klaren Eingaben, messbaren Ergebnissen und wenig gemeinsamem Zustand. Setzen Sie eine Schnittstelle vor die alte Implementierung und fügen Sie danach die neue Implementierung hinter demselben Vertrag hinzu. Das VCL-Formular kann diese Grenze aufrufen, bevor der neue Client existiert. So entsteht ein Beleg für die Trennung, ohne die Domänenextraktion an einen visuellen Neubau zu binden.
Eine brauchbare Abfolge umfasst fünf Teile:
- Zeichnen Sie das heutige Verhalten und die Fehlerfälle der ausgewählten Operation auf.
- Extrahieren Sie ihre Regeleingaben und Ergebniscodes, während das Formular weiterhin die Darstellung besitzt.
- Setzen Sie Persistenz hinter einen Adapter und definieren Sie die Transaktionsgrenze.
- Spielen Sie Paritätsfälle gegen beide Implementierungen ab und klassifizieren Sie jeden Unterschied.
- Leiten Sie einen kontrollierten Anteil der Aufrufe auf den neuen Pfad, mit einem ausdrücklichen Rollback-Schalter.
Vermeiden Sie doppelte Schreibvorgänge, sofern Sie sie nicht idempotent machen und abgleichen können. Zum Vergleich aus beiden Systemen zu lesen ist sicherer, als beide maßgebliche Daten ändern zu lassen. Würde eine Schattenausführung E-Mails, Zahlungen, Druckaufträge oder Audit-Einträge auslösen, ersetzen Sie diese Effekte in der Vergleichsumgebung durch Recorder.
Machen Sie Rollback zu einer Routing-Entscheidung pro Operation. Scheitert der neue Genehmigungspfad, schicken Sie Genehmigungen zurück an die alte Implementierung, ohne bereits umgestellte Kundensuchen zurückzunehmen. Halten Sie die Datenbankkompatibilität ausdrücklich fest, solange beide Pfade laufen. Eine Schemaänderung, nach der die alte Anwendung eine Zeile nicht mehr lesen kann, beseitigt Ihr Rollback, auch wenn das Feature-Flag noch existiert.
Beobachten Sie während der Umstellung Geschäftsergebnisse. Zählen Sie Ursachencodes, Konflikte, Abbrüche und abgeschlossene Operationen und vergleichen Sie deren Verteilung mit der aufgezeichneten Basis. Rohe Fehlerraten sind zu grob: Ein neuer Pfad kann HTTP-Erfolg melden und trotzdem Aufträge genehmigen, die die alten Regeln abgelehnt hätten. Untersuchen Sie veränderte Entscheidungen, bevor Sie mehr Verkehr umleiten.
Die schwierigste Abhängigkeit sollte die Reihenfolge bestimmen. Hängt die Genehmigung von der Preisbildung ab, extrahieren Sie zuerst die Preisbildung oder halten Sie sie hinter einem Adapter, den beide Versionen aufrufen. Migrieren Sie nicht einfache Bildschirme und lassen Sie die zentrale Transaktion als letzte Überraschung liegen. Das erzeugt sichtbaren Fortschritt, aber wenig Risikominderung.
CodeHero liest die gesamte Delphi-Codebasis einschließlich verbundener Sprachen und modernisiert die Architektur, während das Verhalten am Original ausgerichtet bleibt. Für ein manuelles Programm gilt dieselbe Disziplin: global kartieren, lokal schneiden und nie aus einem kompilierten Bildschirm auf Sicherheit schließen.
Der Ersatz muss verborgenen Zustand unmöglich machen
Das Ziel ist bereit, wenn Geschäftsentscheidungen nicht mehr von einem Steuerelement, der aktuellen Dataset-Zeile, der Erstellungsreihenfolge von Formularen oder einer impliziten globalen Transaktion abhängen. Sie sollten eine Operation mit serialisierter Eingabe aufrufen, ein stabiles Ergebnis beobachten und sie testen können, ohne ein Fenster zu erzeugen.
Dieser Maßstab legt unvollständige Extraktionen offen. Ein Dienst, der Screen.ActiveForm liest, ein Repository, das Richtlinien in einem generischen Speicher-Hook ausführt, oder ein TypeScript-Client, der die verbindliche Rechnungssumme berechnet, trägt weiterhin das alte Problem. Die Namen haben sich geändert, doch das Verhalten bleibt hinter Infrastrukturereignissen verborgen.
Halten Sie bei der Codeprüfung eine kurze Grenz-Checkliste bereit:
- Nimmt die Operation Geschäftswerte und IDs statt Steuerelementen oder Dataset-Cursorn entgegen?
- Können alle dauerhaften Regeln für UI-, Import- und API-Aufrufer laufen?
- Umfasst eine Transaktion die vollständige geschäftliche Änderung?
- Sind Ergebnisse von Nebenläufigkeit und Wiederholung ausdrücklich beschrieben?
- Kann ein aufgezeichneter Fall Parität belegen, ohne Pixel zu vergleichen?
Während des Übergangs bleibt manches Verhalten in der VCL-Hülle, und das ist in Ordnung. Fokussteuerung, Tastenkürzel, Layout und lokale Entwurfsdarstellung brauchen keine voreilige Abstraktion. Verteidigen müssen Sie die Autorität: Die Darstellung darf eine Änderung vorschlagen und erklären, aber eine Anwendungsoperation entscheidet darüber und schreibt sie fest.
Beginnen Sie nicht mit dem Neuzeichnen des größten Formulars. Wählen Sie die darin liegende Operation, deren Fehler am teuersten ist, erfassen Sie ihre heutigen Belege und geben Sie ihr einen Namen. Sobald diese Operation ohne Formular laufen kann, besitzt der Rest der Modernisierung eine Grenze, auf der er aufbauen kann.
FAQ
Woran erkenne ich Geschäftslogik in einem Delphi-Event-Handler?
Stellen Sie sich vor, dieselbe Operation werde aus einem Import oder einer API ohne Formular aufgerufen. Entscheidungen, die dann weiter gelten müssen, sind Geschäftsregeln; Fokuswechsel, Dialoge und Layout bleiben Darstellungsverhalten.
Sollte ich Formularcode zuerst in ein TDataModule verschieben?
Ein TDataModule kann Formulare aufräumen, schafft aber keine Geschäftsgrenze. Verschieben Sie Regeln in Units mit ausdrücklichen Werteingaben und Ergebnissen, die Formular und Datenmodul gemeinsam aufrufen.
Kann ich datensensitive VCL-Steuerelemente sicher durch normale Web-Eingaben ersetzen?
Erst nachdem Sie deren verborgenes Verhalten bei Bearbeitung, Pufferung, Validierung, Post und Abbruch modelliert haben. Ähnliche Felder auf dem Bildschirm garantieren keine gleichen Nebenläufigkeits- oder Persistenzregeln.
Wohin gehören Delphi-OnValidate-Regeln in einem neuen System?
Legen Sie dauerhafte Feldinvarianten in die Anwendungs- oder Domänenoperation, die jeder Aufrufer benutzt. Der Client darf eine Prüfung für schnelle Rückmeldung wiederholen, aber er darf nicht maßgeblich sein.
Wie migriere ich Logik aus BeforePost sicher?
Extrahieren Sie die Regel in eine aufrufbare Unit und lassen Sie BeforePost sie aufrufen, solange alte Schreibpfade existieren. Entfernen Sie das Ereignis erst, wenn Traces belegen, dass jeder Schreibvorgang durch die neue Befehlsgrenze läuft.
Warum ist eine direkte Delphi-UI-Portierung so teuer?
Sie baut Bildschirme neu, bevor sie die darunterliegenden Operationen und impliziten Zustände offenlegt. Teams zahlen dann zweimal: für die visuelle Reproduktion und nochmals, wenn verborgene Regeln Änderungen an UI und API erzwingen.
Muss ich für Verhaltensparität jeden Fehler erhalten?
Nein. Ordnen Sie jeden Unterschied als nötige Parität, genehmigte Korrektur oder ungeklärtes Verhalten ein. Eine Korrektur braucht einen Verantwortlichen und einen Test, damit aus Migration kein ungeprüfter Neuentwurf wird.
Können Unit-Tests aufgezeichnete Produktionsfälle ersetzen?
Unit-Tests belegen die Regeln, die Sie kennen und aufschreiben. Aufgezeichnete Fälle zeigen Ereignisfolgen, Datenformen und Nebenwirkungen, an die sich niemand erinnerte, daher brauchen Sie beides für unterschiedliche Zwecke.
Sollte die neue API generische CRUD-Endpunkte anbieten?
Vermeiden Sie generische Updates für Datensätze mit vielen Regeln. Befehle wie ApproveOrder drücken die Absicht aus, definieren eine Transaktionsgrenze und verhindern, dass Aufrufer Feld für Feld ungültige Zustände bauen.
Was ist die sicherste Einheit für eine Delphi-Umstellung?
Stellen Sie eine benannte Geschäftsoperation mit klaren Eingaben, Ergebnissen und Rollback um, selbst wenn sie mehrere Formulare umfasst. Ein ganzes Formular ist meist zu breit, ein einzelner Feld-Handler meist zu schmal.