diff --git a/docs/features/versioning.md b/docs/features/versioning.md index d23ad549c68..ace6ff202ca 100644 --- a/docs/features/versioning.md +++ b/docs/features/versioning.md @@ -71,6 +71,68 @@ Restore code that accepts the original recorded input, then use the [supported s Safe replacement routing also needs the [entry-validation worker capability](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution). A changed fingerprint is neither proof of replay compatibility nor a reason to rewrite a run's original fingerprint. Version durable-command changes inside a compatible entry method and test cold replay before promotion. +## Evolving Activity Arguments and Results {#evolving-activity-arguments-and-results} + +An activity has two stored contracts: the arguments recorded when it was scheduled and the result recorded when it completed. A deployment does not migrate either. This applies to embedded PHP and service workers using the PHP, Python and Rust SDKs. + +A pending attempt or retry receives the original scheduled arguments. It runs the activity implementation selected by the worker's registration and build-routing policy, which may still be an older build. Deploying a new activity implementation does not guarantee every attempt uses it, and does not update queued arguments. + +When a completed activity is replayed, the current workflow code receives its recorded result without running the activity again. Matching an activity step and type is not a schema check of its arguments or result. A changed payload shape can therefore fail application code without a history mismatch. + +### Add Optional Input Without Reinterpreting Old Values {#additive-activity-input} + +Keep existing positional arguments and their meaning. In embedded PHP, an optional trailing parameter lets an old scheduled input such as `[7]` continue to bind: + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +The default fills a missing argument. It does not convert an incompatible recorded value or repair a reordered argument. Keep the activity's external effects compatible too: a retry can happen after the deployment and must remain idempotent. + +PHP service handlers receive their activity context followed by recorded positional arguments. Python handlers receive the recorded positional arguments directly. Both need defaults for omitted arguments. Python annotations alone do not validate or convert values. Rust typed activities deserialize one request value; add an optional field or a field with an explicit Serde default while preserving the existing representation. A newly required field can fail decoding before the Rust handler runs. See the [PHP](https://php.durable-workflow.com/), [Python](https://python.durable-workflow.com/) and [Rust](https://rust.durable-workflow.com/) SDK guides for their activity APIs. + +### Read Older Results With a Fallback {#additive-activity-result} + +When a new activity result adds `currency`, workflows must still accept older completed results that contain only `amount`: + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +Choose a fallback that preserves the old business meaning. Retain it for every history that may be replayed, including queries and recovery. Deploy compatible readers before writers that emit a changed representation, and verify rollback readers against newly recorded results too. A default on the activity's input does not add a field to an already recorded result. + +### Use a New Type for a Different Contract {#incompatible-activity-contract} + +For a renamed field, a different representation or a changed business meaning, keep the old activity type available and introduce a new one. Use a workflow `getVersion()` branch at the scheduling call when old histories must keep their old activity step and new runs should schedule the new type. A branch inside the activity cannot fix arguments that fail before its body runs, and a version marker does not rewrite results already in history. + +Test two cold deployment boundaries against the actual installed packages: schedule with old code and execute with candidate activity code; then complete with old activity code and replay with candidate workflow code. Include incompatible inputs and results as failure controls. Do not assume the workflow-entry safeguards above also protect activity signatures or application result access. The confirmed embedded activity DTO defect is tracked in [Workflow #910](https://github.com/durable-workflow/workflow/issues/910). + +### Verified Failure Boundaries {#activity-compatibility-failures} + +The [published-package checks in #363](https://github.com/durable-workflow/durable-workflow.github.io/issues/363) used fresh processes across each deployment boundary. These are observed baselines, not minimum supported versions: + +| Published package | Incompatible activity input | Incompatible result reader | +| --- | --- | --- | +| Embedded Workflow 2.11.2 | A missing required argument produces `ArgumentCountError` and an activity failure. | Accessing a missing array field under Laravel error handling produces `ErrorException` and workflow failure. | +| PHP SDK 3.2.0 with Server 2.11.0 | A missing required positional argument is reported as `ArgumentCountError` in the activity failure. | Passing a missing field to a required string reader produces `TypeError` and workflow failure. | +| Python SDK 2.9.0 with Server 2.11.0 | A missing required positional argument produces `TypeError` and an activity failure. | Indexing a missing result key produces `KeyError` and workflow failure. | +| Rust SDK 4.2.2 with Server 2.11.0 | A newly required request field fails typed input decoding before the activity handler runs. | A newly required result field fails typed result decoding in the workflow. | + +The service checks recorded `max_attempts = 1`; the unhandled activity failure then failed the workflow. In general, activity failures follow their recorded retry and timeout policy. A workflow can handle an exhausted activity failure. A result-reader error is an application workflow failure, not a request to rerun the completed activity. All additive controls completed with the original amount `7` and default currency `USD`, with one activity attempt and no repairs. In result controls, the completed activity stayed completed. + +Embedded Workflow 2.11.2 also accepts a changed activity signature that replaces recorded scalar `7` with an injected DTO whose default amount is `0`, and can incorrectly complete with `0`. [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) tracks that product defect. Do not use dependency injection as an activity-input migration or assume the workflow-entry safeguard covers it. Restoring compatible code does not reopen an already terminal run; inspect the actual run and follow the [recovery guide](../failures-and-recovery.md) without changing recorded input, history or deadlines. + +Review [recorded workflow start arguments](#evolving-recorded-entry-arguments) and the [signal payload evolution recipe](signals.md#evolve-a-signal-payload) alongside these activity contracts. + ## Using `getVersion` `getVersion()` is a replay-safe straight-line helper. Each change point records a durable version marker for the run on first execution, and every later replay reuses that committed value instead of recalculating the branch from today's code. Old runs that predate a newly introduced branch point conservatively receive `DEFAULT_VERSION`. diff --git a/i18n/de/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/de/docusaurus-plugin-content-docs/current/features/versioning.md index a9e2dd01d93..7df410ff4f4 100644 --- a/i18n/de/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/de/docusaurus-plugin-content-docs/current/features/versioning.md @@ -75,6 +75,68 @@ Stellen Sie Code wieder her, der die ursprüngliche aufgezeichnete Eingabe akzep Sicheres Routing zu einem Ersatz benötigt außerdem die [Worker-Fähigkeit zur Einstiegsprüfung](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution). Ein geänderter Fingerabdruck beweist weder Replay-Kompatibilität noch rechtfertigt er, den ursprünglichen Fingerabdruck eines Laufs umzuschreiben. Versionieren Sie Änderungen an dauerhaften Befehlen innerhalb einer kompatiblen Einstiegsmethode und testen Sie kaltes Replay vor der Promotion. +## Argumente und Ergebnisse von Aktivitäten weiterentwickeln {#evolving-activity-arguments-and-results} + +Eine Aktivität hat zwei gespeicherte Verträge: die beim Einplanen aufgezeichneten Argumente und das beim Abschluss aufgezeichnete Ergebnis. Ein Deployment migriert keines von beiden. Das gilt für eingebettetes PHP und für Service-Worker mit den PHP-, Python- und Rust-SDKs. + +Ein ausstehender Versuch oder eine Wiederholung erhält die ursprünglich eingeplanten Argumente. Welche Implementierung ausgeführt wird, bestimmen die Worker-Registrierung und die Routing-Richtlinie für Builds. Dabei kann weiterhin ein älterer Build zum Einsatz kommen. Ein neuer Build garantiert weder, dass jeder Versuch ihn verwendet, noch aktualisiert er Argumente in der Warteschlange. + +Beim Replay einer abgeschlossenen Aktivität erhält der aktuelle Workflow-Code das aufgezeichnete Ergebnis, ohne die Aktivität erneut auszuführen. Ein passender Schritt und Aktivitätstyp sind keine Schemaprüfung der Argumente oder des Ergebnisses. Eine geänderte Datenstruktur kann deshalb im Anwendungscode fehlschlagen, ohne einen History-Konflikt auszulösen. + +### Optionale Eingaben ergänzen und die Bedeutung alter Werte erhalten {#additive-activity-input} + +Behalten Sie vorhandene Positionsargumente und ihre Bedeutung bei. In eingebettetem PHP ermöglicht ein optionaler letzter Parameter, bereits eingeplante Argumente wie `[7]` weiterhin anzunehmen: + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +Der Standardwert füllt ein fehlendes Argument. Er konvertiert weder einen inkompatiblen aufgezeichneten Wert noch korrigiert er eine andere Reihenfolge. Auch externe Auswirkungen müssen kompatibel bleiben: Eine Wiederholung kann nach dem Deployment erfolgen und muss weiterhin idempotent sein. + +PHP-Handler im Service-Modus erhalten zuerst den Aktivitätskontext und danach die aufgezeichneten Positionsargumente. Python-Handler erhalten die Positionsargumente direkt. Beide benötigen Standardwerte für ausgelassene Argumente. Python-Typannotationen allein prüfen oder konvertieren keine Werte. Typisierte Rust-Aktivitäten deserialisieren einen einzelnen Anfragewert. Ergänzen Sie ein optionales Feld oder ein Feld mit einem expliziten Serde-Standardwert und behalten Sie die vorhandene Darstellung bei. Ein neues Pflichtfeld kann die Dekodierung bereits vor dem Rust-Handler scheitern lassen. Die Aktivitäts-APIs stehen in den SDK-Anleitungen für [PHP](https://php.durable-workflow.com/), [Python](https://python.durable-workflow.com/) und [Rust](https://rust.durable-workflow.com/). + +### Alte Ergebnisse mit einem Ersatzwert lesen {#additive-activity-result} + +Wenn ein neues Aktivitätsergebnis `currency` ergänzt, müssen Workflows weiterhin ältere abgeschlossene Ergebnisse annehmen, die nur `amount` enthalten: + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +Wählen Sie einen Ersatzwert, der die bisherige fachliche Bedeutung erhält. Behalten Sie ihn für jede History bei, die erneut abgespielt werden kann, einschließlich Abfragen und Wiederherstellung. Stellen Sie kompatible Leser vor dem Code bereit, der die neue Darstellung schreibt. Prüfen Sie auch die Leser eines Rollbacks mit neu aufgezeichneten Ergebnissen. Ein Standardwert für ein Aktivitätsargument ergänzt kein Feld in einem bereits aufgezeichneten Ergebnis. + +### Für einen anderen Vertrag einen neuen Typ verwenden {#incompatible-activity-contract} + +Wenn Sie ein Feld umbenennen, seine Darstellung oder seine fachliche Bedeutung ändern, behalten Sie den alten Aktivitätstyp bei und führen Sie einen neuen ein. Verwenden Sie einen `getVersion()`-Zweig im Workflow am Aufruf zur Einplanung, wenn alte Histories ihren bisherigen Aktivitätsschritt behalten und neue Runs den neuen Typ einplanen sollen. Ein Zweig innerhalb der Aktivität kann Argumente nicht reparieren, die schon vor Eintritt in den Funktionsrumpf fehlschlagen. Ein Versionsmarker schreibt auch keine Ergebnisse in der History um. + +Prüfen Sie mit den tatsächlich installierten Paketen zwei Deployment-Grenzen in neuen Prozessen: mit altem Code einplanen und mit dem neuen Aktivitätscode ausführen; danach mit alter Aktivität abschließen und mit dem neuen Workflow-Code wiedergeben. Nehmen Sie inkompatible Eingaben und Ergebnisse als Fehlerkontrollen auf. Nehmen Sie nicht an, dass die oben beschriebenen Prüfungen am Workflow-Einstieg auch Aktivitätssignaturen oder den Ergebniszugriff der Anwendung schützen. Der bestätigte DTO-Fehler in eingebetteten Aktivitäten wird in [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) verfolgt. + +Prüfen Sie diese Aktivitätsverträge zusammen mit den Regeln für [aufgezeichnete Workflow-Startargumente](#evolving-recorded-entry-arguments) und der [Anleitung zum Weiterentwickeln von Signal-Nutzdaten](signals.md#evolve-a-signal-payload). + +### Verifizierte Fehlergrenzen {#activity-compatibility-failures} + +Die [Prüfungen veröffentlichter Pakete in #363](https://github.com/durable-workflow/durable-workflow.github.io/issues/363) nutzten neue Prozesse an jeder Deployment-Grenze. Dies sind geprüfte Ausgangsversionen, keine unterstützten Mindestversionen: + +| Veröffentlichtes Paket | Inkompatible Aktivitätseingabe | Inkompatibler Ergebnisleser | +| --- | --- | --- | +| Eingebetteter Workflow 2.11.2 | Ein fehlendes Pflichtargument erzeugt `ArgumentCountError` und einen Aktivitätsfehler. | Der Zugriff auf ein fehlendes Array-Feld unter Laravels Fehlerbehandlung erzeugt `ErrorException` und einen Workflow-Fehler. | +| PHP SDK 3.2.0 mit Server 2.11.0 | Ein fehlendes Positionspflichtargument wird im Aktivitätsfehler als `ArgumentCountError` gemeldet. | Ein fehlendes Feld an einen Leser mit erforderlichem String-Argument zu übergeben erzeugt `TypeError` und einen Workflow-Fehler. | +| Python SDK 2.9.0 mit Server 2.11.0 | Ein fehlendes Positionspflichtargument erzeugt `TypeError` und einen Aktivitätsfehler. | Der Zugriff auf einen fehlenden Ergebnisschlüssel erzeugt `KeyError` und einen Workflow-Fehler. | +| Rust SDK 4.2.2 mit Server 2.11.0 | Ein neues Pflichtfeld der Anfrage lässt die typisierte Eingabedekodierung vor dem Aktivitätshandler scheitern. | Ein neues Pflichtfeld des Ergebnisses lässt die typisierte Ergebnisdekodierung im Workflow scheitern. | + +Die Service-Prüfungen zeichneten `max_attempts = 1` auf. Der unbehandelte Aktivitätsfehler ließ anschließend den Workflow scheitern. Allgemein folgen Aktivitätsfehler ihrer aufgezeichneten Wiederholungs- und Timeout-Richtlinie. Ein Workflow kann einen Aktivitätsfehler nach ausgeschöpften Versuchen behandeln. Ein Fehler beim Ergebnislesen ist ein Anwendungsfehler im Workflow und keine Aufforderung, die abgeschlossene Aktivität zu wiederholen. Alle kompatiblen Erweiterungskontrollen schlossen mit dem ursprünglichen Betrag `7` und der Standardwährung `USD` ab, mit einem Aktivitätsversuch und ohne Reparaturen. Bei den Ergebniskontrollen blieb die abgeschlossene Aktivität abgeschlossen. + +Eingebetteter Workflow 2.11.2 akzeptiert außerdem eine Signaturänderung, die den aufgezeichneten Skalar `7` durch ein injiziertes DTO mit Standardbetrag `0` ersetzt, und kann fälschlich mit `0` abschließen. [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) verfolgt diesen Produktfehler. Verwenden Sie Dependency Injection nicht zur Migration von Aktivitätseingaben und nehmen Sie nicht an, dass die Workflow-Einstiegsprüfung diesen Fall abdeckt. Kompatiblen Code wiederherzustellen öffnet keinen bereits terminalen Run. Prüfen Sie den tatsächlichen Zustand und folgen Sie der [Wiederherstellungsanleitung](../failures-and-recovery.md), ohne aufgezeichnete Eingaben, History oder Fristen zu ändern. + ## `getVersion` verwenden {#using-getversion} `getVersion()` ist ein Replay-sicherer geradliniger Helfer. Jeder Änderungspunkt diff --git a/i18n/de/source-hashes.json b/i18n/de/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/de/source-hashes.json +++ b/i18n/de/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb", diff --git a/i18n/es/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/es/docusaurus-plugin-content-docs/current/features/versioning.md index 5a2eda81781..41543024d9e 100644 --- a/i18n/es/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/es/docusaurus-plugin-content-docs/current/features/versioning.md @@ -71,6 +71,68 @@ Restaure código que acepte la entrada registrada original y siga el [procedimie El enrutamiento seguro hacia una sustitución también requiere la [capacidad de validación de entrada del worker](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution). Un fingerprint diferente no demuestra compatibilidad de replay ni justifica reescribir el fingerprint original de una ejecución. Versione los cambios de comandos durables dentro de un método de entrada compatible y valide el replay en frío antes de promover el build. +## Evolucionar argumentos y resultados de actividades {#evolving-activity-arguments-and-results} + +Una actividad tiene dos contratos almacenados: los argumentos registrados al programarla y el resultado registrado al completarla. Un despliegue no migra ninguno de los dos. Esto se aplica a PHP integrado y a los workers de servicio con los SDK de PHP, Python y Rust. + +Un intento pendiente o un reintento recibe los argumentos originales registrados. Ejecuta la implementación que seleccionan el registro del worker y la política de enrutamiento de builds, que puede seguir siendo una build anterior. Desplegar una nueva implementación no garantiza que todos los intentos la usen ni actualiza los argumentos en cola. + +Al reproducir una actividad completada, el código actual del workflow recibe el resultado registrado sin volver a ejecutar la actividad. La coincidencia del paso y del tipo de actividad no valida el esquema de sus argumentos ni de su resultado. Un cambio de estructura puede fallar en el código de la aplicación sin producir una discrepancia de historial. + +### Añadir entradas opcionales sin reinterpretar los valores anteriores {#additive-activity-input} + +Conserve los argumentos posicionales existentes y su significado. En PHP integrado, un parámetro opcional al final permite seguir aceptando argumentos ya programados como `[7]`: + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +El valor predeterminado cubre un argumento ausente. No convierte un valor registrado incompatible ni corrige un cambio de orden. Mantenga también la compatibilidad de los efectos externos: un reintento puede ejecutarse después del despliegue y debe seguir siendo idempotente. + +Los handlers de PHP en modo servicio reciben el contexto de actividad seguido de los argumentos posicionales registrados. Los de Python reciben directamente esos argumentos posicionales. Ambos necesitan valores predeterminados para los argumentos omitidos. Las anotaciones de Python por sí solas no validan ni convierten valores. Las actividades tipadas de Rust deserializan un único valor de petición. Añada un campo opcional o un campo con un valor predeterminado explícito de Serde, conservando la representación existente. Un nuevo campo obligatorio puede impedir la decodificación antes de ejecutar el handler de Rust. Consulte las API de actividades en las guías de los SDK de [PHP](https://php.durable-workflow.com/), [Python](https://python.durable-workflow.com/) y [Rust](https://rust.durable-workflow.com/). + +### Leer resultados anteriores con un valor de reserva {#additive-activity-result} + +Si un nuevo resultado de actividad añade `currency`, los workflows deben seguir aceptando resultados completados anteriormente que solo contienen `amount`: + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +Elija un valor de reserva que conserve el significado de negocio anterior. Manténgalo para todos los historiales que puedan reproducirse, incluidas las consultas y la recuperación. Despliegue lectores compatibles antes de los escritores que generan la nueva representación. Compruebe también los lectores de rollback con los nuevos resultados registrados. Un valor predeterminado de entrada no añade campos a un resultado ya registrado. + +### Usar un tipo nuevo para un contrato diferente {#incompatible-activity-contract} + +Para renombrar un campo, cambiar su representación o alterar su significado de negocio, mantenga disponible el tipo de actividad anterior e introduzca uno nuevo. Use una rama `getVersion()` en la llamada que programa la actividad cuando los historiales existentes deban conservar su paso anterior y los nuevos runs deban programar el tipo nuevo. Una rama dentro de la actividad no puede corregir argumentos que fallan antes de entrar en su cuerpo. Un marcador de versión tampoco reescribe los resultados del historial. + +Pruebe dos límites de despliegue en procesos nuevos con los paquetes realmente instalados: programar con código anterior y ejecutar con la actividad candidata; después, completar con la actividad anterior y reproducir con el workflow candidato. Incluya entradas y resultados incompatibles como controles de fallo. No suponga que las protecciones de entrada del workflow descritas arriba protegen también las firmas de actividad o el acceso de la aplicación a sus resultados. El defecto confirmado de inyección de DTO en actividades integradas se sigue en [Workflow #910](https://github.com/durable-workflow/workflow/issues/910). + +Revise junto con estos contratos de actividad las reglas de [argumentos iniciales registrados del workflow](#evolving-recorded-entry-arguments) y la [receta para evolucionar el payload de una señal](signals.md#evolve-a-signal-payload). + +### Límites de fallo verificados {#activity-compatibility-failures} + +Las [pruebas de paquetes publicados de #363](https://github.com/durable-workflow/durable-workflow.github.io/issues/363) usaron procesos nuevos en cada límite de despliegue. Son versiones verificadas, no versiones mínimas compatibles: + +| Paquete publicado | Entrada de actividad incompatible | Lector de resultados incompatible | +| --- | --- | --- | +| Workflow integrado 2.11.2 | Un argumento obligatorio ausente produce `ArgumentCountError` y un fallo de actividad. | Acceder a un campo de array ausente con el manejo de errores de Laravel produce `ErrorException` y un fallo del workflow. | +| PHP SDK 3.2.0 con Server 2.11.0 | Un argumento posicional obligatorio ausente se comunica como `ArgumentCountError` en el fallo de actividad. | Pasar un campo ausente a un lector que exige un string produce `TypeError` y un fallo del workflow. | +| Python SDK 2.9.0 con Server 2.11.0 | Un argumento posicional obligatorio ausente produce `TypeError` y un fallo de actividad. | Acceder por índice a una clave de resultado ausente produce `KeyError` y un fallo del workflow. | +| Rust SDK 4.2.2 con Server 2.11.0 | Un nuevo campo obligatorio de petición falla al decodificar la entrada tipada, antes de ejecutar el handler de actividad. | Un nuevo campo obligatorio de resultado falla al decodificar el resultado tipado en el workflow. | + +Las pruebas de servicio registraron `max_attempts = 1`; después, el fallo de actividad no capturado hizo fallar el workflow. En general, los fallos de actividad siguen su política registrada de reintentos y timeouts. El workflow puede manejar un fallo de actividad tras agotar sus intentos. Un error del lector de resultados es un fallo del código del workflow, no una petición de repetir la actividad completada. Todos los controles aditivos completaron con la cantidad original `7` y la moneda predeterminada `USD`, con un intento de actividad y sin reparaciones. En los controles de resultados, la actividad completada permaneció completada. + +Workflow integrado 2.11.2 también acepta una nueva firma que sustituye el escalar registrado `7` por un DTO inyectado con cantidad predeterminada `0`, y puede completar incorrectamente con `0`. [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) sigue este defecto del producto. No use la inyección de dependencias para migrar entradas de actividad ni suponga que la protección de entrada del workflow cubre este caso. Restaurar código compatible no reabre un run terminal. Inspeccione el estado real y siga la [guía de recuperación](../failures-and-recovery.md) sin cambiar entradas registradas, historial ni plazos. + ## Usar `getVersion` {#using-getversion} `getVersion()` es un helper secuencial seguro para la reproducción. En la primera ejecución registra un marcador duradero por punto de cambio; las reproducciones posteriores reutilizan el valor confirmado sin recalcular la rama con el código actual. Las ejecuciones anteriores a la nueva bifurcación reciben conservadoramente `DEFAULT_VERSION`. diff --git a/i18n/es/source-hashes.json b/i18n/es/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/es/source-hashes.json +++ b/i18n/es/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb", diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/fr/docusaurus-plugin-content-docs/current/features/versioning.md index 5938a27571c..3fb663cf53d 100644 --- a/i18n/fr/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/fr/docusaurus-plugin-content-docs/current/features/versioning.md @@ -71,6 +71,68 @@ Restaurez un code acceptant l’entrée d’origine enregistrée, puis suivez la Le routage sûr vers un remplacement exige aussi la [capacité worker de validation de l’entrée](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution). Une empreinte différente ne prouve pas la compatibilité de relecture et ne justifie pas de réécrire l’empreinte d’origine d’une exécution. Versionnez les changements de commandes durables dans une méthode d’entrée compatible et testez une relecture à froid avant la promotion. +## Faire évoluer les arguments et les résultats des activités {#evolving-activity-arguments-and-results} + +Une activité possède deux contrats enregistrés : ses arguments lors de sa planification et son résultat lors de son achèvement. Un déploiement ne migre ni les uns ni l’autre. Cela vaut pour PHP embarqué et les workers en mode service utilisant les SDK PHP, Python et Rust. + +Une tentative en attente ou relancée reçoit les arguments initialement enregistrés. Elle exécute l’implémentation sélectionnée par l’enregistrement du worker et la politique de routage des builds, qui peut encore être une ancienne build. Déployer une nouvelle implémentation ne garantit pas que toutes les tentatives l’utilisent et ne modifie pas les arguments en file. + +Lors de la relecture d’une activité terminée, le code actuel du workflow reçoit le résultat enregistré sans réexécuter l’activité. La correspondance de l’étape et du type d’activité ne valide pas le schéma des arguments ou du résultat. Une modification de structure peut donc faire échouer le code applicatif sans provoquer de divergence d’historique. + +### Ajouter une entrée facultative sans réinterpréter les anciennes valeurs {#additive-activity-input} + +Conservez les arguments positionnels existants et leur sens. En PHP embarqué, un paramètre facultatif ajouté à la fin permet de toujours accepter des arguments déjà planifiés tels que `[7]` : + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +La valeur par défaut remplit un argument absent. Elle ne convertit pas une valeur enregistrée incompatible et ne corrige pas un changement d’ordre. Préservez aussi la compatibilité des effets externes : une nouvelle tentative peut survenir après le déploiement et doit rester idempotente. + +Les handlers PHP en mode service reçoivent le contexte d’activité, puis les arguments positionnels enregistrés. Les handlers Python reçoivent directement ces arguments positionnels. Tous deux ont besoin de valeurs par défaut pour les arguments omis. Les annotations Python ne valident ni ne convertissent les valeurs à elles seules. Les activités typées Rust désérialisent une valeur de requête unique. Ajoutez un champ facultatif ou un champ avec une valeur par défaut Serde explicite, en préservant la représentation existante. Un nouveau champ obligatoire peut faire échouer le décodage avant l’exécution du handler Rust. Les API d’activité figurent dans les guides des SDK [PHP](https://php.durable-workflow.com/), [Python](https://python.durable-workflow.com/) et [Rust](https://rust.durable-workflow.com/). + +### Lire les anciens résultats avec une valeur de repli {#additive-activity-result} + +Lorsqu’un nouveau résultat d’activité ajoute `currency`, les workflows doivent toujours accepter les résultats déjà terminés qui ne contiennent que `amount` : + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +Choisissez une valeur de repli qui conserve le sens métier précédent. Gardez-la pour tous les historiques susceptibles d’être relus, y compris pour les requêtes de lecture et la récupération. Déployez les lecteurs compatibles avant le code qui écrit la nouvelle représentation. Vérifiez aussi les lecteurs de retour arrière avec les nouveaux résultats enregistrés. Une valeur par défaut en entrée de l’activité n’ajoute aucun champ à un résultat déjà enregistré. + +### Utiliser un nouveau type pour un contrat différent {#incompatible-activity-contract} + +Pour renommer un champ, changer sa représentation ou son sens métier, gardez l’ancien type d’activité disponible et introduisez-en un nouveau. Placez une branche `getVersion()` du workflow à l’appel de planification si les anciens historiques doivent conserver leur étape d’activité et les nouveaux runs doivent planifier le nouveau type. Une branche dans l’activité ne corrige pas des arguments qui échouent avant l’entrée dans son corps. Un marqueur de version ne réécrit pas non plus les résultats de l’historique. + +Vérifiez deux frontières de déploiement dans de nouveaux processus avec les paquets réellement installés : planifier avec l’ancien code et exécuter avec l’activité candidate ; puis terminer avec l’ancienne activité et relire avec le workflow candidat. Incluez des entrées et des résultats incompatibles comme témoins d’échec. Ne supposez pas que les protections d’entrée du workflow décrites plus haut protègent aussi les signatures d’activité ou la lecture des résultats par l’application. Le défaut confirmé d’injection de DTO dans les activités embarquées est suivi dans [Workflow #910](https://github.com/durable-workflow/workflow/issues/910). + +Examinez ces contrats avec les règles des [arguments de démarrage enregistrés du workflow](#evolving-recorded-entry-arguments) et la [recette d’évolution des données d’un signal](signals.md#evolve-a-signal-payload). + +### Frontières d’échec vérifiées {#activity-compatibility-failures} + +Les [vérifications des paquets publiés de #363](https://github.com/durable-workflow/durable-workflow.github.io/issues/363) ont utilisé de nouveaux processus à chaque frontière de déploiement. Ce sont des versions vérifiées, pas des versions minimales prises en charge : + +| Paquet publié | Entrée d’activité incompatible | Lecture de résultat incompatible | +| --- | --- | --- | +| Workflow embarqué 2.11.2 | Un argument obligatoire absent produit `ArgumentCountError` et un échec d’activité. | Lire un champ de tableau absent avec la gestion d’erreurs Laravel produit `ErrorException` et un échec du workflow. | +| PHP SDK 3.2.0 avec Server 2.11.0 | Un argument positionnel obligatoire absent est signalé comme `ArgumentCountError` dans l’échec d’activité. | Passer un champ absent à un lecteur exigeant une chaîne produit `TypeError` et un échec du workflow. | +| Python SDK 2.9.0 avec Server 2.11.0 | Un argument positionnel obligatoire absent produit `TypeError` et un échec d’activité. | Accéder à une clé de résultat absente produit `KeyError` et un échec du workflow. | +| Rust SDK 4.2.2 avec Server 2.11.0 | Un nouveau champ de requête obligatoire fait échouer le décodage d’entrée typée avant le handler d’activité. | Un nouveau champ de résultat obligatoire fait échouer le décodage du résultat typé dans le workflow. | + +Les vérifications en mode service ont enregistré `max_attempts = 1` ; l’échec d’activité non intercepté a ensuite fait échouer le workflow. En général, les échecs d’activité suivent leur politique enregistrée de tentatives et de délais. Un workflow peut traiter l’échec d’une activité dont les tentatives sont épuisées. Une erreur de lecture de résultat est un échec applicatif du workflow, pas une demande de réexécuter l’activité terminée. Tous les témoins additifs ont terminé avec le montant original `7` et la devise par défaut `USD`, avec une seule tentative d’activité et aucune réparation. Dans les témoins de résultat, l’activité terminée est restée terminée. + +Workflow embarqué 2.11.2 accepte aussi une modification de signature qui remplace le scalaire enregistré `7` par un DTO injecté dont le montant par défaut est `0`, et peut terminer à tort avec `0`. [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) suit ce défaut du produit. N’utilisez pas l’injection de dépendances comme migration d’entrée d’activité et ne supposez pas que la protection d’entrée du workflow couvre ce cas. Restaurer un code compatible ne rouvre pas un run déjà terminal. Inspectez son état réel et suivez le [guide de récupération](../failures-and-recovery.md) sans modifier les entrées enregistrées, l’historique ou les échéances. + ## Utiliser `getVersion` {#using-getversion} `getVersion()` est une fonction séquentielle sûre à relire. Chaque point de changement enregistre un marqueur durable de version lors de la première exécution. Les relectures réutilisent cette valeur validée au lieu de recalculer la branche d’après le code actuel. Par prudence, les exécutions antérieures à un nouveau point de branchement reçoivent `DEFAULT_VERSION`. diff --git a/i18n/fr/source-hashes.json b/i18n/fr/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/fr/source-hashes.json +++ b/i18n/fr/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb", diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/ja/docusaurus-plugin-content-docs/current/features/versioning.md index c4d66326a34..f52eac9cd1b 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/features/versioning.md @@ -71,6 +71,68 @@ Server のタスク固有の `failure_reason` は `workflow_entry_arguments_mism 安全な置き換えルーティングには、[エントリ検証の Worker 能力](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution)も必要です。フィンガープリントが変わったことはリプレイ互換性の証明でも、実行の元のフィンガープリントを書き換える理由でもありません。永続コマンドの変更は互換性のあるエントリメソッド内でバージョン分岐し、昇格前にコールドリプレイを検証してください。 +## アクティビティの引数と結果を変更する {#evolving-activity-arguments-and-results} + +アクティビティには、スケジュール時に記録された引数と、完了時に記録された結果という、保存済みの二つの契約があります。デプロイしても、どちらも移行されません。これは組み込み PHP と、PHP、Python、Rust SDK を使うサービスモードの Worker に共通です。 + +未実行の試行や再試行には、最初のスケジュール時の引数が渡されます。実行される実装は Worker の登録とビルドルーティングのポリシーで決まり、古いビルドの場合もあります。新しい実装をデプロイしても、すべての試行で使われる保証はなく、キュー内の引数も更新されません。 + +完了したアクティビティをリプレイすると、現在のワークフローコードは、アクティビティを再実行せずに記録済みの結果を受け取ります。ステップとアクティビティ型の一致は、引数や結果のスキーマ検証ではありません。データ構造を変更すると、履歴の不一致にならずにアプリケーションコードが失敗する場合があります。 + +### 既存の値の意味を保って省略可能な入力を追加する {#additive-activity-input} + +既存の位置引数とその意味を保ってください。組み込み PHP では、末尾に省略可能な引数を追加すると、`[7]` のような記録済みの引数を引き続き受け取れます。 + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +デフォルト値が補うのは省略された引数です。記録済みの互換性のない値を変換したり、引数の並べ替えを修正したりはしません。外部への副作用にも互換性が必要です。再試行はデプロイ後にも発生するため、冪等性を維持してください。 + +PHP のサービスモードハンドラーは、アクティビティコンテキストに続いて記録済みの位置引数を受け取ります。Python のハンドラーは位置引数を直接受け取ります。どちらも、省略された引数にはデフォルト値が必要です。Python の型注釈だけでは、値の検証や変換は行われません。Rust の型付きアクティビティは、一つのリクエスト値をデシリアライズします。既存の表現を保ち、省略可能なフィールドか、Serde のデフォルト値を明示したフィールドを追加してください。必須フィールドを追加すると、Rust ハンドラーの実行前にデコードが失敗する場合があります。API は [PHP](https://php.durable-workflow.com/)、[Python](https://python.durable-workflow.com/)、[Rust](https://rust.durable-workflow.com/) SDK のガイドを参照してください。 + +### フォールバックで古い結果を読み取る {#additive-activity-result} + +新しいアクティビティ結果に `currency` を追加しても、ワークフローは `amount` しか含まない過去の完了結果を受け取れる必要があります。 + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +従来の業務上の意味を保つフォールバックを選んでください。クエリや復旧を含む、リプレイされる可能性のあるすべての履歴で必要です。新しい表現を書き込むコードより先に、互換性のある読み取りコードをデプロイしてください。ロールバック先の読み取りコードでも、新しく記録された結果を検証します。入力引数のデフォルト値は、記録済みの結果にフィールドを追加しません。 + +### 契約が異なる場合は新しい型を使う {#incompatible-activity-contract} + +フィールド名、表現、業務上の意味を変更するときは、既存のアクティビティ型を残して新しい型を導入してください。古い履歴では従来のステップを維持し、新しい実行では新しい型をスケジュールする必要がある場合は、スケジュール呼び出しにワークフローの `getVersion()` 分岐を置きます。アクティビティ内部の分岐では、本体に入る前に失敗する引数を修復できません。バージョンマーカーも、履歴内の結果を書き換えません。 + +実際にインストールされたパッケージと新しいプロセスで、二つのデプロイ境界を検証してください。古いコードでスケジュールして候補のアクティビティで実行し、次に古いアクティビティで完了して候補のワークフローでリプレイします。互換性のない入力と結果も失敗対照として含めてください。前述のワークフロー入口の保護が、アクティビティのシグネチャやアプリケーションの結果読み取りも保護するとは限りません。組み込みアクティビティの DTO に関する確認済みの不具合は [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) で追跡しています。 + +これらの契約と併せて、[記録済みのワークフロー開始引数](#evolving-recorded-entry-arguments)と[シグナルペイロードの変更手順](signals.md#evolve-a-signal-payload)も確認してください。 + +### 検証済みの失敗境界 {#activity-compatibility-failures} + +[#363 の公開パッケージ検証](https://github.com/durable-workflow/durable-workflow.github.io/issues/363)では、各デプロイ境界で新しいプロセスを使用しました。以下は実測した基準バージョンであり、最低対応バージョンではありません。 + +| 公開パッケージ | 互換性のないアクティビティ入力 | 互換性のない結果読み取り | +| --- | --- | --- | +| 組み込み Workflow 2.11.2 | 必須引数の欠落は `ArgumentCountError` となり、アクティビティが失敗します。 | Laravel のエラー処理下で存在しない配列フィールドにアクセスすると、`ErrorException` でワークフローが失敗します。 | +| PHP SDK 3.2.0 と Server 2.11.0 | 必須の位置引数の欠落は、アクティビティの失敗で `ArgumentCountError` として報告されます。 | 欠落したフィールドを必須文字列の読み取り関数に渡すと、`TypeError` でワークフローが失敗します。 | +| Python SDK 2.9.0 と Server 2.11.0 | 必須の位置引数の欠落は `TypeError` となり、アクティビティが失敗します。 | 結果に存在しないキーを参照すると、`KeyError` でワークフローが失敗します。 | +| Rust SDK 4.2.2 と Server 2.11.0 | 新しい必須リクエストフィールドは、アクティビティハンドラー実行前の型付き入力デコードで失敗します。 | 新しい必須結果フィールドは、ワークフロー内の型付き結果デコードで失敗します。 | + +サービスモードの検証では `max_attempts = 1` を記録しました。その後、未処理のアクティビティ失敗によりワークフローが失敗しました。通常、アクティビティ失敗は記録済みの再試行・タイムアウトポリシーに従います。ワークフローは再試行を使い切ったアクティビティ失敗を処理できます。結果の読み取りエラーはアプリケーションのワークフロー失敗であり、完了したアクティビティの再実行要求ではありません。互換性のある追加変更の対照はすべて、元の金額 `7` とデフォルト通貨 `USD` で完了し、アクティビティ試行は一回、修復はゼロでした。結果の対照では、完了したアクティビティは完了状態を保ちました。 + +組み込み Workflow 2.11.2 では、記録済みのスカラー `7` を、金額のデフォルト値が `0` の注入された DTO に置き換えるシグネチャ変更が受け入れられ、誤って `0` で完了する場合もあります。この製品不具合は [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) で追跡しています。依存性注入を入力の移行として使わず、ワークフロー入口の保護がこのケースも対象にすると思わないでください。互換コードを戻しても、終了済みの実行は再開されません。実際の状態を確認し、記録済みの入力、履歴、期限を変更せずに[復旧ガイド](../failures-and-recovery.md)に従ってください。 + ## `getVersion` の使用 {#using-getversion} `getVersion()` は安全にリプレイできる逐次ヘルパーです。各変更点は初回に実行の永続バージョンマーカーを記録し、後のリプレイは現在のコードから分岐を計算せず、その値を再利用します。新しい分岐点より前に始まった実行は保守的に `DEFAULT_VERSION` になります。 diff --git a/i18n/ja/source-hashes.json b/i18n/ja/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/ja/source-hashes.json +++ b/i18n/ja/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb", diff --git a/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/versioning.md index 90ef6f0d3c8..2a365158278 100644 --- a/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/versioning.md @@ -71,6 +71,68 @@ Restaure código que aceite a entrada original registrada e siga o [procedimento O roteamento seguro para uma substituição também exige a [capacidade de validação de entrada do worker](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution). Um fingerprint diferente não comprova compatibilidade de replay nem justifica reescrever o fingerprint original de uma execução. Versione as mudanças de comandos duráveis dentro de um método de entrada compatível e valide o replay a frio antes de promover o build. +## Evoluir argumentos e resultados de atividades {#evolving-activity-arguments-and-results} + +Uma atividade tem dois contratos armazenados: os argumentos registrados no agendamento e o resultado registrado na conclusão. Um deploy não migra nenhum deles. Isso vale para PHP incorporado e para workers em modo serviço com os SDKs de PHP, Python e Rust. + +Uma tentativa pendente ou repetida recebe os argumentos originais registrados. Ela executa a implementação selecionada pelo registro do worker e pela política de roteamento de builds, que ainda pode ser uma build anterior. Publicar uma nova implementação não garante que todas as tentativas a utilizem nem atualiza os argumentos na fila. + +No replay de uma atividade concluída, o código atual do workflow recebe o resultado registrado sem executar a atividade novamente. A correspondência do passo e do tipo da atividade não valida o esquema dos argumentos ou do resultado. Uma mudança de estrutura pode causar falha no código da aplicação sem uma divergência de histórico. + +### Adicionar entrada opcional sem reinterpretar valores antigos {#additive-activity-input} + +Preserve os argumentos posicionais existentes e seu significado. No PHP incorporado, um parâmetro opcional no final permite continuar aceitando argumentos já agendados como `[7]`: + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +O valor padrão preenche um argumento ausente. Ele não converte um valor registrado incompatível nem corrige argumentos reordenados. Preserve também a compatibilidade dos efeitos externos: uma nova tentativa pode ocorrer depois do deploy e deve continuar idempotente. + +Handlers de PHP em modo serviço recebem o contexto da atividade seguido dos argumentos posicionais registrados. Handlers de Python recebem esses argumentos posicionais diretamente. Ambos precisam de valores padrão para argumentos omitidos. As anotações de Python, por si só, não validam nem convertem valores. Atividades tipadas de Rust desserializam um único valor de requisição. Adicione um campo opcional ou um campo com padrão explícito do Serde, preservando a representação existente. Um novo campo obrigatório pode causar falha de decodificação antes da execução do handler Rust. Consulte as APIs de atividades nos guias dos SDKs de [PHP](https://php.durable-workflow.com/), [Python](https://python.durable-workflow.com/) e [Rust](https://rust.durable-workflow.com/). + +### Ler resultados antigos com um valor de fallback {#additive-activity-result} + +Quando um novo resultado de atividade adiciona `currency`, os workflows ainda devem aceitar resultados antigos concluídos que contêm apenas `amount`: + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +Escolha um fallback que preserve o significado de negócio anterior. Mantenha-o para todo histórico que possa passar por replay, inclusive consultas e recuperação. Faça o deploy de leitores compatíveis antes do código que grava a nova representação. Verifique também os leitores de rollback com os resultados novos já registrados. Um padrão no argumento da atividade não adiciona um campo a um resultado já registrado. + +### Usar um novo tipo para um contrato diferente {#incompatible-activity-contract} + +Para renomear um campo, mudar sua representação ou alterar seu significado de negócio, mantenha disponível o tipo anterior da atividade e introduza um novo. Use uma ramificação `getVersion()` na chamada de agendamento quando os históricos antigos precisarem manter seu passo anterior e os novos runs precisarem agendar o novo tipo. Uma ramificação dentro da atividade não corrige argumentos que falham antes da entrada no corpo. Um marcador de versão também não reescreve resultados no histórico. + +Teste dois limites de deploy em processos novos com os pacotes realmente instalados: agendar com o código antigo e executar com a atividade candidata; depois, concluir com a atividade antiga e fazer replay com o workflow candidato. Inclua entradas e resultados incompatíveis como controles de falha. Não suponha que as proteções de entrada do workflow descritas acima também protejam assinaturas de atividades ou o acesso da aplicação aos resultados. O defeito confirmado de injeção de DTO nas atividades incorporadas está em [Workflow #910](https://github.com/durable-workflow/workflow/issues/910). + +Revise, junto com estes contratos de atividade, as regras de [argumentos iniciais registrados do workflow](#evolving-recorded-entry-arguments) e a [receita de evolução do payload de sinais](signals.md#evolve-a-signal-payload). + +### Limites de falha verificados {#activity-compatibility-failures} + +As [verificações de pacotes publicados em #363](https://github.com/durable-workflow/durable-workflow.github.io/issues/363) usaram processos novos em cada limite de deploy. São versões verificadas, não versões mínimas compatíveis: + +| Pacote publicado | Entrada de atividade incompatível | Leitura de resultado incompatível | +| --- | --- | --- | +| Workflow incorporado 2.11.2 | Um argumento obrigatório ausente produz `ArgumentCountError` e falha da atividade. | Acessar um campo ausente de array com o tratamento de erros do Laravel produz `ErrorException` e falha do workflow. | +| PHP SDK 3.2.0 com Server 2.11.0 | Um argumento posicional obrigatório ausente é informado como `ArgumentCountError` na falha da atividade. | Passar um campo ausente a um leitor que exige uma string produz `TypeError` e falha do workflow. | +| Python SDK 2.9.0 com Server 2.11.0 | Um argumento posicional obrigatório ausente produz `TypeError` e falha da atividade. | Acessar uma chave de resultado ausente produz `KeyError` e falha do workflow. | +| Rust SDK 4.2.2 com Server 2.11.0 | Um novo campo obrigatório da requisição causa falha de decodificação da entrada tipada antes do handler da atividade. | Um novo campo obrigatório do resultado causa falha de decodificação do resultado tipado no workflow. | + +As verificações de serviço registraram `max_attempts = 1`; depois, a falha de atividade não tratada fez o workflow falhar. Em geral, falhas de atividade seguem sua política registrada de tentativas e timeouts. Um workflow pode tratar a falha após o esgotamento das tentativas da atividade. Um erro na leitura do resultado é uma falha da aplicação no workflow, não um pedido para repetir a atividade concluída. Todos os controles aditivos concluíram com o valor original `7` e a moeda padrão `USD`, com uma tentativa de atividade e sem reparos. Nos controles de resultado, a atividade concluída permaneceu concluída. + +Workflow incorporado 2.11.2 também aceita uma mudança de assinatura que substitui o escalar registrado `7` por um DTO injetado com valor padrão `0`, e pode concluir incorretamente com `0`. [Workflow #910](https://github.com/durable-workflow/workflow/issues/910) acompanha esse defeito do produto. Não use injeção de dependências como migração de entrada da atividade nem suponha que a proteção de entrada do workflow cubra esse caso. Restaurar código compatível não reabre um run terminal. Inspecione o estado real e siga o [guia de recuperação](../failures-and-recovery.md) sem alterar entradas registradas, histórico ou prazos. + ## Uso de `getVersion` {#using-getversion} `getVersion()` é um helper de fluxo sequencial seguro para replay. Cada ponto de mudança registra um marcador durável de versão para a execução na primeira passagem, e todo replay posterior reutiliza esse valor confirmado, em vez de recalcular a ramificação a partir do código atual. Execuções antigas que precedem um ponto de ramificação recém-introduzido recebem `DEFAULT_VERSION` de forma conservadora. diff --git a/i18n/pt-BR/source-hashes.json b/i18n/pt-BR/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/pt-BR/source-hashes.json +++ b/i18n/pt-BR/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb", diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md index 9d7a352d82d..fb06d216883 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md @@ -71,6 +71,68 @@ Workflow **2.7.3** перевіряє записані вхідні аргуме Безпечна маршрутизація до заміни також потребує [можливості worker перевіряти вхід](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution). Змінений fingerprint не доводить сумісність replay й не є підставою переписувати початковий fingerprint run. Версіонуйте зміни durable-команд усередині сумісного методу входу та перевіряйте холодний replay перед promotion. +## Зміна записаних аргументів і результатів activity {#evolving-activity-arguments-and-results} + +Activity має два збережені контракти: аргументи, записані під час її планування, і результат, записаний після завершення. Розгортання не переносить ці дані до нової схеми. Це стосується вбудованого PHP та сервісних worker з PHP, Python і Rust SDK. + +Відкладена або повторна спроба отримує початкові записані аргументи. Вона виконує реалізацію activity, обрану за реєстрацією worker і політикою маршрутизації збірок. Це може бути старіша збірка. Розгортання нової реалізації не гарантує, що кожна спроба використає її, і не змінює аргументи в черзі. + +Під час replay завершеної activity поточний код workflow отримує записаний результат без повторного виконання activity. Зіставлення кроку та типу activity не перевіряє схему аргументів чи результату. Зміна структури даних може спричинити помилку в коді застосунку без невідповідності історії. + +### Додавайте необов’язковий аргумент, зберігаючи значення старих даних {#additive-activity-input} + +Зберігайте наявні позиційні аргументи та їхнє значення. У вбудованому PHP необов’язковий останній параметр дозволяє прийняти старі заплановані аргументи, наприклад `[7]`: + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +Типове значення заповнює відсутній аргумент. Воно не перетворює несумісне записане значення і не виправляє змінений порядок аргументів. Зовнішні ефекти activity також мають залишатися сумісними: повторна спроба може відбутися після розгортання і повинна зберігати ідемпотентність. + +Обробники PHP у сервісному режимі отримують контекст activity, а потім записані позиційні аргументи. Обробники Python отримують позиційні аргументи безпосередньо. В обох випадках для пропущених аргументів потрібні типові значення. Самі лише анотації Python не перевіряють і не перетворюють значення. Типізовані activity Rust десеріалізують одне значення запиту. Додавайте необов’язкове поле або поле з явним типовим значенням Serde, зберігаючи наявне представлення. Нове обов’язкове поле може спричинити помилку декодування ще до виклику обробника Rust. API activity описано в посібниках SDK для [PHP](https://php.durable-workflow.com/), [Python](https://python.durable-workflow.com/) і [Rust](https://rust.durable-workflow.com/). + +### Читайте старі результати з резервним значенням {#additive-activity-result} + +Коли новий результат activity додає `currency`, workflow має й надалі приймати старі завершені результати, які містять лише `amount`: + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +Оберіть резервне значення, що зберігає попередній бізнесовий зміст. Підтримуйте його для всіх історій, які можуть відтворюватися, зокрема для query і відновлення. Розгорніть сумісних читачів перед кодом, що записує нове представлення. Перед відкотом перевірте, чи старі читачі приймають нові записані результати. Типове значення аргументу activity не додає поле до вже записаного результату. + +### Для іншого контракту використовуйте новий тип {#incompatible-activity-contract} + +Якщо ви перейменовуєте поле, змінюєте представлення або бізнесовий зміст, залиште старий тип activity доступним і додайте новий. Використовуйте гілку `getVersion()` у місці планування, коли старі історії повинні зберегти свій крок activity, а нові запуски мають планувати новий тип. Гілка всередині activity не виправить аргументи, що спричиняють помилку до входу в її тіло. Маркер версії не переписує результати в історії. + +Перевіряйте дві межі розгортання в нових процесах на фактично встановлених пакетах: планування старим кодом із виконанням новим кодом activity, а потім завершення старим кодом activity з replay новим кодом workflow. Додайте несумісні аргументи та результати як контрольні помилкові випадки. Не припускайте, що описаний вище захист аргументів входу workflow також захищає сигнатури activity або читання результату застосунком. Підтверджений дефект із DTO у вбудованих activity відстежується в [Workflow #910](https://github.com/durable-workflow/workflow/issues/910). + +Перегляньте разом із цими контрактами activity правила [записаних початкових аргументів workflow](#evolving-recorded-entry-arguments) та [рецепт зміни даних signal](signals.md#evolve-a-signal-payload). + +### Перевірені межі помилок {#activity-compatibility-failures} + +[Перевірки опублікованих пакетів у #363](https://github.com/durable-workflow/durable-workflow.github.io/issues/363) використовували нові процеси на кожній межі розгортання. Це перевірені базові версії, а не мінімальні підтримувані версії: + +| Опублікований пакет | Несумісні аргументи activity | Несумісне читання результату | +| --- | --- | --- | +| Вбудований Workflow 2.11.2 | Відсутній обов’язковий аргумент спричиняє `ArgumentCountError` і помилку activity. | Читання відсутнього поля масиву з обробкою помилок Laravel спричиняє `ErrorException` і помилку workflow. | +| PHP SDK 3.2.0 із Server 2.11.0 | Відсутній обов’язковий позиційний аргумент передається як `ArgumentCountError` у помилці activity. | Передавання відсутнього поля читачу з обов’язковим рядковим аргументом спричиняє `TypeError` і помилку workflow. | +| Python SDK 2.9.0 із Server 2.11.0 | Відсутній обов’язковий позиційний аргумент спричиняє `TypeError` і помилку activity. | Звернення за відсутнім ключем результату спричиняє `KeyError` і помилку workflow. | +| Rust SDK 4.2.2 із Server 2.11.0 | Нове обов’язкове поле запиту спричиняє помилку типізованого декодування до виклику обробника activity. | Нове обов’язкове поле результату спричиняє помилку типізованого декодування результату у workflow. | + +Сервісні перевірки записували `max_attempts = 1`, після чого необроблена помилка activity завершувала workflow з помилкою. Загалом помилки activity підпорядковуються записаній політиці повторних спроб і таймаутів. Workflow може обробити помилку activity після вичерпання повторних спроб. Помилка читання результату є помилкою коду workflow, а не запитом повторити завершену activity. Усі сумісні контрольні випадки завершилися з початковим значенням `7` і типовою валютою `USD`, з однією спробою activity та без repair. У перевірках результатів завершена activity залишалася завершеною. + +Вбудований Workflow 2.11.2 також приймає зміну сигнатури activity, що замінює записаний скаляр `7` ін’єктованим DTO з типовою сумою `0`, і може помилково завершитися зі значенням `0`. Цей дефект продукту відстежує [Workflow #910](https://github.com/durable-workflow/workflow/issues/910). Не використовуйте ін’єкцію залежностей для перенесення аргументів activity і не вважайте, що захист входу workflow охоплює цей випадок. Повернення сумісного коду не відкриває термінальний запуск знову. Перевірте фактичний стан і дотримуйтеся [посібника з відновлення](../failures-and-recovery.md), не змінюючи записаних аргументів, історії чи кінцевих строків. + ## Використання `getVersion` {#using-getversion} `getVersion()` — це безпечна щодо replay функція для послідовного коду. Під час першого виконання кожна точка зміни записує стійкий маркер версії запуску. Кожен подальший replay використовує це зафіксоване значення замість повторного визначення гілки за поточним кодом. Старі запуски, що передують новій точці розгалуження, консервативно отримують `DEFAULT_VERSION`. diff --git a/i18n/uk/source-hashes.json b/i18n/uk/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/uk/source-hashes.json +++ b/i18n/uk/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb", diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/versioning.md index 4ab5723adbf..b8b749cab11 100644 --- a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/versioning.md +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/versioning.md @@ -71,6 +71,68 @@ Server 任务的具体 `failure_reason` 为 `workflow_entry_arguments_mismatch` 安全的替换路由还需要 [Worker 入口验证能力](../polyglot/worker-compatibility-routing.md#definition-fingerprint-evolution)。指纹发生变化既不能证明重放兼容,也不是改写运行原始指纹的理由。请在兼容的入口方法内部对持久命令变更进行版本分支,并在提升构建之前测试冷重放。 +## 演进活动参数和结果 {#evolving-activity-arguments-and-results} + +活动有两个已存储的契约:调度时记录的参数,以及完成时记录的结果。部署不会迁移其中任何一项。这同样适用于嵌入式 PHP,以及使用 PHP、Python 和 Rust SDK 的服务模式 Worker。 + +尚未执行的尝试或重试会收到最初调度时记录的参数。实际执行的活动实现由 Worker 注册和构建路由策略决定,也可能仍是旧构建。部署新实现既不保证每次尝试都使用它,也不会更新队列中的参数。 + +重放已完成的活动时,当前工作流代码会收到已记录的结果,而不会再次执行活动。活动步骤和类型匹配,并不代表参数或结果通过了模式校验。因此,数据结构变化可能导致应用代码失败,却不产生历史不匹配错误。 + +### 添加可选输入时保留旧值的含义 {#additive-activity-input} + +保留现有位置参数的顺序和含义。在嵌入式 PHP 中,末尾添加可选参数,可继续接受已调度的旧参数,例如 `[7]`: + +```php +use Workflow\V2\Activity; + +final class PriceActivity extends Activity +{ + public function handle(int $amount, string $currency = 'USD'): array + { + return ['amount' => $amount, 'currency' => $currency]; + } +} +``` + +默认值只填补缺失的参数,不会转换不兼容的已记录值,也不会修复参数顺序变化。活动的外部副作用也必须保持兼容:重试可能发生在部署之后,并且仍须满足幂等性要求。 + +PHP 服务模式处理函数先接收活动上下文,再接收已记录的位置参数。Python 处理函数直接接收已记录的位置参数。两者都需要为省略的参数提供默认值。Python 类型注解本身不会校验或转换值。Rust 类型化活动反序列化一个请求值;添加可选字段或具有明确 Serde 默认值的字段,同时保留原有表示。新增必填字段可能在 Rust 处理函数运行前就导致解码失败。活动 API 参见 [PHP](https://php.durable-workflow.com/)、[Python](https://python.durable-workflow.com/) 和 [Rust](https://rust.durable-workflow.com/) SDK 指南。 + +### 使用回退值读取旧结果 {#additive-activity-result} + +新的活动结果添加 `currency` 时,工作流仍须接受仅含 `amount` 的旧完成结果: + +```php +$result = activity(PriceActivity::class, 7); +$currency = $result['currency'] ?? 'USD'; +``` + +选择能保留原有业务含义的回退值,并为所有可能被重放的历史保留它,包括查询和恢复。先部署兼容的读取代码,再部署写入新表示的代码;回滚前也要用新记录的结果验证旧读取代码。活动输入参数的默认值不会为已记录的结果补充字段。 + +### 契约不同时使用新类型 {#incompatible-activity-contract} + +重命名字段、改变表示或改变业务含义时,应保留旧活动类型并引入新类型。如果旧历史必须保留原来的活动步骤,而新运行应调度新类型,请在调度调用处使用工作流 `getVersion()` 分支。活动内部的分支无法修复进入函数体之前就绑定失败的参数。版本标记也不会重写历史中已有的结果。 + +使用实际安装的包,在新进程中验证两个部署边界:先用旧代码调度、用候选活动代码执行,再用旧活动代码完成、用候选工作流代码重放。加入不兼容输入和结果作为失败对照。不要假定上文的工作流入口保护也会保护活动签名或应用代码对结果的访问。已确认的嵌入式活动 DTO 缺陷见 [Workflow #910](https://github.com/durable-workflow/workflow/issues/910)。 + +请将这些活动契约与[已记录的工作流启动参数规则](#evolving-recorded-entry-arguments)和[信号载荷演进方法](signals.md#evolve-a-signal-payload)一起检查。 + +### 已验证的失败边界 {#activity-compatibility-failures} + +[#363 的已发布包验证](https://github.com/durable-workflow/durable-workflow.github.io/issues/363)在每个部署边界使用新进程。以下是实际验证的基线,并非最低支持版本: + +| 已发布包 | 不兼容的活动输入 | 不兼容的结果读取 | +| --- | --- | --- | +| 嵌入式 Workflow 2.11.2 | 缺少必需参数产生 `ArgumentCountError`,活动失败。 | 在 Laravel 错误处理下访问缺失的数组字段产生 `ErrorException`,工作流失败。 | +| PHP SDK 3.2.0 与 Server 2.11.0 | 缺少必需的位置参数,在活动失败中报告为 `ArgumentCountError`。 | 将缺失字段传给要求字符串的读取函数产生 `TypeError`,工作流失败。 | +| Python SDK 2.9.0 与 Server 2.11.0 | 缺少必需的位置参数产生 `TypeError`,活动失败。 | 按键访问结果中不存在的字段产生 `KeyError`,工作流失败。 | +| Rust SDK 4.2.2 与 Server 2.11.0 | 新增必填请求字段在活动处理函数运行前导致类型化输入解码失败。 | 新增必填结果字段导致工作流中的类型化结果解码失败。 | + +服务模式验证记录了 `max_attempts = 1`;随后,未处理的活动失败使工作流失败。一般情况下,活动失败遵循已记录的重试和超时策略。工作流可以处理重试次数耗尽后的活动失败。结果读取错误属于应用工作流失败,并不要求重跑已完成的活动。所有增量兼容对照都以原始金额 `7` 和默认货币 `USD` 完成,活动仅尝试一次,未进行修复。在结果对照中,已完成的活动始终保持完成状态。 + +嵌入式 Workflow 2.11.2 还会接受一种签名变化:以默认金额为 `0` 的注入式 DTO 替换已记录的标量 `7`,并可能错误地以 `0` 完成。[Workflow #910](https://github.com/durable-workflow/workflow/issues/910) 跟踪此产品缺陷。不要将依赖注入当作活动输入迁移,也不要假定工作流入口保护覆盖此情况。恢复兼容代码不会重新打开已终结的运行;请检查实际状态,并按照[恢复指南](../failures-and-recovery.md)操作,不要修改已记录的输入、历史或截止时间。 + ## 使用 `getVersion` {#using-getversion} `getVersion()` 是可安全重放、按顺序编写的辅助函数。每个变更点在首次执行时为运行记录持久版本标记,后续每次重放都复用该已提交值,不会根据当前代码重新计算分支。早于新引入分支点的旧运行会保守地获得 `DEFAULT_VERSION`。 diff --git a/i18n/zh-Hans/source-hashes.json b/i18n/zh-Hans/source-hashes.json index fa7887d5b55..c53ce2a4783 100644 --- a/i18n/zh-Hans/source-hashes.json +++ b/i18n/zh-Hans/source-hashes.json @@ -53,7 +53,7 @@ "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", - "features/versioning.md": "b9d9f70ecd6e194d1cb0134d612a9b0ef5528970762a9cf7f948dfa5cd31e866", + "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", "features/webhooks.md": "edcdfc05ccb0d34a77c51594f85bc3cab343e787f8e0e08d2cd4ddebd048e8c8", "features/worker-sessions.md": "2b08a0cdaee486970e099ea8bcb91d7f6daf135b0bc98629ed47f3885e64c77d", "how-it-works.md": "90454c37f4244297d5c11bcc59549859acf59f88d15ecd26f0a6951c115f86eb",