enhancement
major
minor
defect
critical
major
minor
major
#30151
React-View-Schicht: Befehle ohne HandlerResult - View-Befehle melden Fehler als Exception, React-Befehlshandler liefern ihre Antwort als msgbuf-Nachricht
Ausgangslage
Ein @ReactCommandHandler gab com.top_logic.tool.boundsec.HandlerResult zurück (oder void, was HandlerResult.DEFAULT_RESULT bedeutete), den Ergebnistyp der Command-Handler der klassischen Oberfläche. Seit #30126 kann ein Handler seinem Aufrufer Felder zurückgeben (ReactCommandReply, z. B. die Kennung eines angelegten Objekts). Die Antwort wurde als TypedAnnotatable-Property an das HandlerResult gehängt:
{{{#!java @ReactCommandHandler(CMD_CREATE) HandlerResult handleCreate(Map<String, Object> arguments) { Item item = createItem(arguments); return ReactCommandReply.create().put(FIELD_ID, item.getId()).toResult(); } }}}
ReactServlet las die Antwort mit ReactCommandReply.of(result) wieder aus und schrieb ihre Felder neben success in die JSON-Antwort. Hochladen (UploadHandler) ging denselben Weg. Auch ein Befehl der View-Schicht (ViewCommand.execute()) und die Befehlskette der React-Controls (CommandModel, ButtonAction, CommandItem, UploadCommandModel, Aktivierungs- und Auswahl-Callbacks) lieferten ein HandlerResult.
Probleme
- Der Rückgabetyp eines React-Befehls war ein Typ der klassischen Oberfläche, der viel mitbringt, was hier keine Bedeutung hat: Verarbeitungsfehler-Listen, Fortsetzungen, DefaultHandlerResult.
- Dass ein Befehl etwas zurückgibt, stand nicht in seiner Signatur. Es war eine unsichtbare Annotation, die nur ReactCommandReply.of() fand.
- Das gemeinsame HandlerResult.DEFAULT_RESULT darf nicht annotiert werden. ReactCommandReply.attachTo() legte deshalb stillschweigend ein neues Ergebnis an, und der Aufrufer musste das zurückgegebene Objekt verwenden statt des übergebenen.
- Fehler und Antwort liefen über zwei getrennte Kanäle: HandlerResult.isSuccess() und Fehlerschlüssel für das Fenster (CommandErrors), die Annotation für den Aufrufer. Ein fehlgeschlagener Befehl antwortete dem Client mit success: true.
- Ein Befehl der View-Schicht kann nur gelingen oder scheitern; ein Ergebnis hat dort keine Bedeutung. Fehler liefen über HandlerResult.error()/`notExecutable()` statt über Exceptions.
Lösung
HandlerResult ist aus der Befehlskette der React-Oberfläche entfernt; es bleibt nur in klassischen Komponenten (in tl-ai-core z. B. die Befehle der Tool-Zugriffsrichtlinie).
View-Schicht und Befehlskette der React-Controls
- ViewCommand.execute(...), CommandModel.executeCommand/perform, UploadCommandModel, ButtonAction, CommandItem.Command und die Aktivierungs-Handler von Tabelle, Baum und Link sind void; Menü-Auswahl und Feld-Commit sind Consumer.
- Eine Verweigerung ist throw CommandErrors.notExecutable(state) (eine TopLogicException mit Schweregrad Warnung und dem Grund als Detail), ein fachlicher Fehler throw new TopLogicException(key), ein interner Fehler eine beliebige RuntimeException.
- CommandErrors zeigt eine Exception im Fenster an: Titel und Details aus den I18NFailure`s der Ursachenkette, Schweregrad aus `I18NRuntimeException.getSeverity(); eine andere Exception mit dem allgemeinen Titel "Befehl fehlgeschlagen", ihrer Meldung als Detail und einem Log-Eintrag.
- Aufrufer, die das Ergebnis eines Befehls auswerteten (ValueInputElement, ConfigFormStateHandler, ChatCommandTool, StartJobAction, VerifyIdentityAction, ViewUploadCommandModel), fangen die Exception.
React-Befehlshandler
- Ein @ReactCommandHandler und ein UploadHandler geben void oder ihre Antwort als msgbuf-Nachricht (DataObject) zurück. ReactCommandMap lässt nur diese Rückgabetypen zu; ReactCommandAnswer weist eine Antwortnachricht mit einer Eigenschaft success, error oder errorCode zurück.
- Fehler sind Exceptions. ReactServlet zeigt sie im Fenster an und antwortet {"success":false,"error":...}; bei Erfolg {"success":true, ...Felder der Antwort}. Eine Anfrage, die der Server gar nicht verarbeiten kann (unbekanntes Control, veraltete Oberfläche), behält ihren Fehlerstatus mit errorCode (RouteRefusal in answer.proto).
- ScriptingSession.act/`replay` liefern die Antwort und werfen den Fehler; ReactWindowReplay, StepReplayAction und AgentServlet folgen demselben Vertrag.
- Die Annotation REPLY und ReactCommandReply sind entfernt. Die Antworten des Chats (ChatControl) sind Nachrichten in messages.proto (StreamAttachReply, SessionOpenReply, SessionDeleteReply, ChatSendReply, ChatStatusReply, SubagentMessageReply, CancelReply, AttachmentUploadReply).
- Die Entwickler-Artikel view-layer/client-server-protocol, view-layer/commands, view-layer/headless-interface und view-layer/agent-access beschreiben den Vertrag.
Migration
Anwendungen mit eigenen ViewCommand-Implementierungen, CommandModel`s, `ButtonAction`s oder `@ReactCommandHandler-Methoden stellen diese um:
- Rückgabetyp void statt HandlerResult; return HandlerResult.DEFAULT_RESULT; entfällt.
- return HandlerResult.error(key) wird zu throw new TopLogicException(key), return HandlerResult.notExecutable(...) bzw. die Prüfung eines ExecutableState zu throw CommandErrors.notExecutable(state).
- Ein Aufrufer, der command.execute(...) auswertet, fängt TopLogicException, statt isSuccess() zu prüfen.
- Ein Handler, der seinem Aufrufer antwortet, deklariert die Antwort als msgbuf-Nachricht und gibt sie zurück, statt ReactCommandReply.create().put(...).toResult() zu liefern. Client-Code liest die Antwortfelder wie bisher; ein fehlgeschlagener Befehl antwortet jetzt mit success: false und error.