Skip to content

Commit

Permalink
Merge commit '4b04d634a0574dc4155c0fbcd16260d1f858c7bb' into sdk-1486…
Browse files Browse the repository at this point in the history
…-python-message-handlers
  • Loading branch information
dandavison committed Aug 16, 2024
2 parents ccf39ea + 4b04d63 commit 4fbaaca
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 52 deletions.
153 changes: 112 additions & 41 deletions docs/develop/typescript/data-encryption.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,129 @@ tags:
description: Learn how to create a custom Payload Converter in TypeScript with Temporal SDKs to handle non-JSON-serializable values, configure your Data Converter, and utilize protobufs and encryption seamlessly in your Workflows and Activities.
---

## How to use a custom payload converter in TypeScript {#custom-payload-conversion}
## Payload Converter and Payload Codec Summary

Temporal SDKs provide a [Payload Converter](/dataconversion#payload-converter) that can be customized to convert a custom data type to a [Payload](/dataconversion#payload) and back.
This section summarizes the difference between a Payload Converter and Payload Codec.

Implementing custom Payload conversion is optional.
It is needed only if the [default Data Converter](/dataconversion#default-data-converter) does not support your custom values.
### Payload Converter

To support custom Payload conversion, create a [custom Payload Converter](/dataconversion#composite-data-converters) and configure the Data Converter to use it in your Client options.
Payload Converters are responsible for serializing application objects into a Payload and deserializing them back into application objects. A Payload, in this context, is a binary form suitable for network transmission that may include some metadata. This serialization process transforms an object (like those in JSON or Protobuf formats) into a binary format and vice versa. For example, an object might be serialized to JSON with UTF-8 byte encoding or to a protobuf binary using a specific set of protobuf message definitions.

The order in which your encoding Payload Converters are applied depend on the order given to the Data Converter.
You can set multiple encoding Payload Converters to run your conversions.
When the Data Converter receives a value for conversion, it passes through each Payload Converter in sequence until the converter that handles the data type does the conversion.
Due to their operation within the Workflow context, Payload Converters run inside the Workflow sandbox. Consequently, Payload Converters cannot access external services or employ non-deterministic modules, which excludes most types of encryption due to their non-deterministic nature.

To send values that are not [JSON-serializable](https://en.wikipedia.org/wiki/JSON#Data_types) like a `BigInt` or `Date`, provide a custom [Data Converter](https://typescript.temporal.io/api/interfaces/common.DataConverter/) to the Client and Worker:
### Payload Codec

- [new Client(\{ ..., dataConverter })](https://typescript.temporal.io/api/interfaces/client.ClientOptions#dataconverter)
- [Worker.create(\{ ..., dataConverter })](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#dataconverter)
Payload Codecs transform one Payload into another, converting binary data to a different binary format. Unlike Payload Converters, Payload Codecs do not operate within the Workflow sandbox. This allows them to execute operations that can include calls to remote services and the use of non-deterministic modules, which are critical for tasks such as encrypting Payloads, compressing data, or offloading large payloads to an object store. Payload Codecs can also be implemented as a Codec Server (which will be described later on).

A Data Converter has two parts:
### Operational Chain

In practice, these two components operate in a chain to handle data securely. Incoming data first passes through a Payload Converter through the `toPayload` method, turning application objects into Payloads. These Payloads are then processed by the Payload Codec through the `encode` method, which adjusts the Payload according to the required security or efficiency needs before it is sent to the Temporal Cluster.

The process is symmetric for outgoing data. Payloads retrieved from the Temporal Cluster first pass through the Payload Codec through the `decode` method, which reverses any transformations applied during encoding. Finally, the resulting Payload is converted back into an application object by the Payload Converter through the `fromPayload` method, making it ready for use within the application.

## Payload Codec

> API documentation: [PayloadCodec](https://typescript.temporal.io/api/interfaces/common.PayloadCodec)
- [Payload Converter](#payload-converter): Sync methods that sometimes run inside the Workflow isolate (and are thus limited).
- [Payload Codec](#payload-codec): Async methods that run outside the isolate.
The default `PayloadCodec` does nothing. To create a custom one, you can implement the following interface:

```ts
interface DataConverter {
payloadConverterPath?: string;
payloadCodecs?: PayloadCodec[];
interface PayloadCodec {
/**
* Encode an array of {@link Payload}s for sending over the wire.
* @param payloads May have length 0.
*/
encode(payloads: Payload[]): Promise<Payload[]>;

/**
* Decode an array of {@link Payload}s received from the wire.
*/
decode(payloads: Payload[]): Promise<Payload[]>;
}
```

### Payload Converter
## Use custom payload conversion

> API documentation: [PayloadConverter](https://typescript.temporal.io/api/interfaces/common.PayloadConverter)
Temporal SDKs provide a [Payload Converter](/dataconversion#payload-converter) that can be customized to convert a custom data type to a [Payload](/dataconversion#payload) and back.

```ts
The order in which your encoding Payload Converters are applied depending on the order given to the Data Converter.
You can set multiple encoding Payload Converters to run your conversions.
When the Data Converter receives a value for conversion, the value gets passes through each Payload Converter in sequence until the converter that handles the data type does the conversion. You will explore more in detail now. You will explore more in detail now.

## Composite Data Converters

Use a [Composite Data Converter](https://typescript.temporal.io/api/classes/common.CompositePayloadConverter) to apply custom, type-specific Payload Converters in a specified order. Defining a new Composite Data Converter is not always necessary to implement custom data handling. You can override the default Converter with a custom Codec, but a Composite Data Converter may be necessary for complex Workflow logic.

A Composite Data Converter can include custom rules created, and it can also
leverage the default Data Converters built into Temporal. In fact, the default
Data Converter logic is implemented internally in the Temporal source as a
Composite Data Converter. It defines these rules in this order:

```typescript
export class DefaultPayloadConverter extends CompositePayloadConverter {
constructor() {
super(
new UndefinedPayloadConverter(),
new BinaryPayloadConverter(),
new JsonPayloadConverter()
);
}
}
```

The order of applying the Payload Converters is important. During
serialization, the Data Converter tries the Payload Converters in that specific
order until a Payload Converter returns a non-null Payload.

To replace the default Data Converter with a custom `CompositeDataConverter`, use the following:

```typescript
export const payloadConverter = new CompositePayloadConverter(
new UndefinedPayloadConverter(),
new EjsonPayloadConverter()
);
```

You can do this in its own `payload-conterter.ts` file for example.

In the code snippet above, a converter is created that first attempts to handle `null` and `undefined` values. If the value isn't `null` or `undefined`, the EJSON serialization logic written in the `EjsonPayloadConverter` is then used. The Payload Converter is then provided to the Worker and Client.

Here is the Worker code:

```typescript
const worker = await Worker.create({
workflowsPath: require.resolve("./workflows"),
taskQueue: "ejson",
dataConverter: {
payloadConverterPath: require.resolve("./payload-converter"),
},
});
```

With this code, you now ensure that the Worker serializes and deserializes Workflow and Activity inputs and outputs using your EJSON-based logic, along with handling undefined values appropriately.

Here is the Client:

```typescript
const client = new Client({
dataConverter: {
payloadConverterPath: require.resolve("./payload-converter"),
},
});
```

You can now use a variety of data types in arguments.

## How to use a custom payload converter in TypeScript {#custom-payload-conversion}

To support custom Payload conversion, create a [custom Payload Converter](/dataconversion#composite-data-converters) and configure the Data Converter to use it in your Client options.
You can use Custom Payload Converters to change how application objects get serialized to binary Payload. To handle custom data types that are not natively JSON-serializable (e.g., `BigInt`, `Date`, or binary data), you can create a custom Payload Converter. A Custom Payload Converter is responsible for converting your custom data types to a payload format that Temporal can manage.

To implement a Custom Payload Converter in TypeScript, you need to do the following steps:

1. **Implement `PayloadConverter` Interface**: Start by creating a class that implements Temporal's [`PayloadConverter`](https://typescript.temporal.io/api/interfaces/common.PayloadConverter) interface.

```typescript
interface PayloadConverter {
/**
* Converts a value to a {@link Payload}.
Expand All @@ -60,6 +148,10 @@ interface PayloadConverter {
}
```

This custom converter should include logic for both serialization (`toPayload`) and deserialization (`fromPayload`), handling your specific data types or serialization format. The method `toPayload` returns a Payload object, which is used to manage and transport serialized data. The method `fromPayload` returns the deserialized data. This ensures that the data returned is in the same format as it was before serialization, allowing it to be used directly in the application.

2. Configure the Data Converter. To send values that are not JSON-serializable like a `BigInt` or `Date`, provide the custom Data Converter to the Client and Worker as described in the [Composite Data Converters](#composite-data-converters) section.

#### Custom implementation

Some example implementations are in the SDK itself:
Expand Down Expand Up @@ -412,27 +504,6 @@ export async function protoActivity(

<!--SNIPEND-->

### Payload Codec

> API documentation: [PayloadCodec](https://typescript.temporal.io/api/interfaces/common.PayloadCodec)

The default `PayloadCodec` does nothing. To create a custom one, we implement the following interface:

```ts
interface PayloadCodec {
/**
* Encode an array of {@link Payload}s for sending over the wire.
* @param payloads May have length 0.
*/
encode(payloads: Payload[]): Promise<Payload[]>;

/**
* Decode an array of {@link Payload}s received from the wire.
*/
decode(payloads: Payload[]): Promise<Payload[]>;
}
```

#### Encryption

> Background: [Encryption](/dataconversion#encryption)
Expand Down
2 changes: 1 addition & 1 deletion docs/encyclopedia/retry-policies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Non-Retryable Errors = []
- Setting the value to 1 means a single execution attempt and no retries.
- Setting the value to a negative integer results in an error when the execution is invoked.
- **Use case:** Use this attribute to ensure that retries do not continue indefinitely.
However, in the majority of cases, we recommend relying on the Workflow Execution Timeout, in the case of Workflows](/workflows), or Schedule-To-Close Timeout, in the case of [Activities](/activities), to limit the total duration of retries instead of using this attribute.
In most cases, we recommend using the Workflow Execution Timeout for [Workflows](/workflows) or the Schedule-To-Close Timeout for Activities to limit the total duration of retries, rather than using this attribute.

### Non-Retryable Errors

Expand Down
41 changes: 32 additions & 9 deletions docs/production-deployment/cloud/account-setup/users.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ You can invite users pragmatically using the Cloud Ops API.

When a Global Admin invites a user to join an account, the Global Admin selects one of the following Roles for that user:

- **Account Owner**
- Has full administrative permissions across the account, including users, usage and billing
- Has Namespace Admin permissions on all Namespaces in the account
- **Global Admin**
- Has full administrative permissions across the account, including users and usage
- Has Namespace Admin [permissions](#namespace-level-permissions) on all [Namespaces](/namespaces) in the account
Expand All @@ -109,6 +112,29 @@ When a Global Admin invites a user to join an account, the Global Admin selects
- Has Namespace Admin permissions for each Namespace created by that user
- **Read-Only:** Can only read information

:::note Default Role

When the account is created, the initial user who logs in is automatically assigned the Account Owner role.
If your account does not have an Account Owner, please reach out to [Support](https://temporalsupport.zendesk.com/) to assign the appropriate individual to this role.

:::

## Using the Account Owner Role

The Account Owner role (i.e., users with the Account Owner system role) holds the highest level of access in the system.
This role configures account-level parameters and manages Temporal billing and payment information.
It allows users to perform all actions within the Temporal Cloud account.

:::tip Best Practices

Temporal strongly recommends the following precautions when assigning the Account Owner role to users:

- Assign the role to at least two users in your organization.
Otherwise, limit the number of users with this role.
- Associate an email address with each Account Owner role so Temporal Cloud support can be in contact for urgent situations.

:::

## What are the Namespace-level permissions for users in Temporal Cloud? {#namespace-level-permissions}

A [Global Admin](#account-level-roles) can assign permissions for any [Namespace](/namespaces) in an account.
Expand All @@ -122,13 +148,10 @@ For a Namespace, a user can have one of the following permissions:

## How to update an account-level Role in Temporal Cloud {#update-roles}

You can update the account-level [Role](#account-level-roles) for a user by using either Web UI or tcld.

:::info

To update an account-level Role, a user must have the Global Admin account-level Role.

:::
With Global Admin or Account Owner privileges, you can update any user's account-level [Role](#account-level-roles) using either the Web UI or `tcld`.
This does not apply to the Account Owner role.
For security purposes, Account Owners can only be changed through Temporal Support.
To create, update, or delete an Account Owner, you must submit a [support ticket](https://temporalsupport.zendesk.com/).

{/* How to update an account-level Role in Temporal Cloud using Web UI */}

Expand Down Expand Up @@ -167,7 +190,7 @@ You can update Namespace-level [permissions](#namespace-level-permissions) by us

:::note

A user who has the Global Admin account-level [Role](#account-level-roles) has Namespace Admin permissions for all Namespaces.
A user with the Account Owner or Global Admin account-level [Role](#account-level-roles) has Namespace Admin permissions for all Namespaces.

:::

Expand All @@ -189,7 +212,7 @@ You can delete a user from your Temporal Cloud Account by using either Web UI or

:::info

To delete a user, a user must have the Global Admin account-level [Role](#account-level-roles).
To delete a user, a user must have the Account Owner or Global Admin account-level [Role](#account-level-roles).

:::

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,4 @@ To import a dashboard in Grafana, do the following.

1. Go to **Create&nbsp;> Import**.
2. You can either copy and paste the JSON from [Temporal Cloud](https://github.com/temporalio/dashboards/tree/master/cloud) and [Temporal SDKs](https://github.com/temporalio/dashboards/tree/master/sdk) sample dashboards, or import the JSON files into Grafana.
If you import a dashboard from the repositories, ensure that you update dashboard data sources (`"uid": "${datasource}"`) in the JSON to the names you configured in the [Data sources configuration](#grafana-data-sources-configuration) section.
3. Save the dashboard and review the metrics data in the graphs.

0 comments on commit 4fbaaca

Please sign in to comment.