Skip to content

feat: Add support for custom failure converters #887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions docs/data-converter.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ function workflowInclusiveInstanceOf(instance: unknown, type: Function): boolean

Given the possibility of switching or adding other isolation methods in future, we opted to convert to/from Payloads inside the vm (`PayloadConverter`). We also added another transformer layer called `PayloadCodec` that runs outside the vm, can use node async APIs (like `zlib.gzip` for compression or `crypto.scrypt` for encryption), and operates on Payloads. A `DataConverter` is a `PayloadConverter` and zero or more `PayloadCodec`s:

Later on (2022-09-22) we added a `FailureConverter` that is responsible to convert from proto `Failure` instances to JS
`Error`s and back. The failure converter runs inside the Workflow vm and may place failure attributes in the
[`Failure.encoded_attributes`][encoded_attributes] `Payload`. If that payload is present, it will be encoded / decoded
with the configured set of `PayloadCodec`s.

The SDK contains a "default" `FailureConverter` that can be configured to encode error messages and stack traces.

```ts
export interface DataConverter {
payloadConverterPath?: string;
failureConverterPath?: string;
payloadCodecs?: PayloadCodec[];
}

Expand All @@ -36,6 +44,11 @@ export interface PayloadConverter {
fromPayload<T>(payload: Payload): T;
}

export interface FailureConverter {
errorToFailure(err: unknown): ProtoFailure;
failureToError(err: ProtoFailure): TemporalFailure;
}

export interface PayloadCodec {
encode(payloads: Payload[]): Promise<Payload[]>;
decode(payloads: Payload[]): Promise<Payload[]>;
Expand All @@ -50,15 +63,22 @@ Temporal Server <--> Wire <--> `PayloadCodec` <--> `PayloadConverter` <--> User

`PayloadCodec` only runs in the main thread.

When `WorkerOptions.dataConverter.payloadConverterPath` is provided, the code at that location is loaded into the main thread and the webpack Workflow bundle.
When `WorkerOptions.dataConverter.payloadConverterPath` (or `failureConverterPath`) is provided, the code at that
location is loaded into the main thread and the webpack Workflow bundle.

`Worker.create`:
_main thread_

- imports and validates `options.dataConverter.payloadConverterPath`
- imports and validates `options.dataConverter.failureConverterPath`
- passes `payloadConverterPath` to `WorkflowCodeBundler`

`worker-interface.ts#initRuntime`:
_workflow vm_

- Imports `__temporal_custom_payload_converter`, which will either be the code bundled from `payloadConverterPath` or `undefined`. If it's defined, sets `state.payloadConverter`.
- Imports `__temporal_custom_payload_converter`, which will either be the code bundled from `payloadConverterPath` or
`undefined`. If it's defined, sets `state.payloadConverter`.
- Imports `__temporal_custom_failure_converter`, which will either be the code bundled from `failureConverterPath` or
`undefined`. If it's defined, sets `state.failureConverter`.

[encoded_attributes]: https://github.com/temporalio/api/blob/ddf07ab9933e8230309850e3c579e1ff34b03f53/temporal/api/failure/v1/message.proto#L102
25 changes: 22 additions & 3 deletions packages/common/src/converter/data-converter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DefaultFailureConverter, FailureConverter } from './failure-converter';
import { PayloadCodec } from './payload-codec';
import { PayloadConverter } from './payload-converter';
import { defaultPayloadConverter } from './payload-converters';
import { defaultPayloadConverter, PayloadConverter } from './payload-converter';

/**
* When your data (arguments and return values) is sent over the wire and stored by Temporal Server, it is encoded in
Expand Down Expand Up @@ -29,11 +29,18 @@ import { defaultPayloadConverter } from './payload-converters';
export interface DataConverter {
/**
* Path of a file that has a `payloadConverter` named export.
* `payloadConverter` should be an instance of a class that implements {@link PayloadConverter}.
* `payloadConverter` should be an object that implements {@link PayloadConverter}.
* If no path is provided, {@link defaultPayloadConverter} is used.
*/
payloadConverterPath?: string;

/**
* Path of a file that has a `failureConverter` named export.
* `failureConverter` should be an object that implements {@link FailureConverter}.
* If no path is provided, {@link defaultFailureConverter} is used.
*/
failureConverterPath?: string;

/**
* An array of {@link PayloadCodec} instances.
*
Expand All @@ -49,10 +56,22 @@ export interface DataConverter {
*/
export interface LoadedDataConverter {
payloadConverter: PayloadConverter;
failureConverter: FailureConverter;
payloadCodecs: PayloadCodec[];
}

/**
* The default {@link FailureConverter} used by the SDK.
*
* Error messages and stack traces are serizalized as plain text.
*/
export const defaultFailureConverter: FailureConverter = new DefaultFailureConverter();

/**
* A "loaded" data converter that uses the default set of failure and payload converters.
*/
export const defaultDataConverter: LoadedDataConverter = {
payloadConverter: defaultPayloadConverter,
failureConverter: defaultFailureConverter,
payloadCodecs: [],
};
Loading