From 52289a9499d74b49966d355e6c1870748e189637 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 23 Jul 2024 09:20:48 -0400 Subject: [PATCH] updates per mark review Signed-off-by: Hannah Hunter --- .../state-management/howto-outbox.md | 273 +++++++++++++----- 1 file changed, 205 insertions(+), 68 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md index 8f91acf64..a2facf91b 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md @@ -110,19 +110,64 @@ spec: ### Shape the outbox pattern message -You can override the outbox pattern message published to the pub/sub broker by setting a different message. This is done via a projected transaction payload, which is ignored, but used as the outbox pattern message published to the user topic. +You can override the outbox pattern message published to the pub/sub broker by setting a different message. This is done via a projected transaction payload, called `outboxProjections`, which is ignored when the state is written and is used as the outbox pattern message published to the user topic. -In order to override, the `key` values must match between the operation on the state store and the message projection. If the keys do not match, the whole transaction fails. +To set the `outboxProjections` to `true`, the `key` values must match between the operation on the state store and the message projection. If the keys do not match, the whole transaction fails. If you have two or more `outboxProjections` for the same key, the first one defined is used and the others are ignored. -{{< tabs JavaScript ".NET" Java Go HTTP >}} +[Learn more about default and custom CloudEvent messages.]({{< ref pubsub-cloudevents.md >}}) + +{{< tabs Python JavaScript ".NET" Java Go HTTP >}} + +{{% codetab %}} + + + +In the following Python SDK example of a state transaction, the value of `"2"` is saved to the database, but the value of `"3"` is published to the end-user topic. + +```python +DAPR_STORE_NAME = "statestore" + +async def main(): + client = DaprClient() + + # Define the first state operation to save the value "2" + op1 = StateItem( + key="key1", + value=b"2" + ) + + # Define the second state operation to publish the value "3" with metadata + op2 = StateItem( + key="key1", + value=b"3", + options=StateOptions( + metadata={ + "outbox.projection": "true" + } + ) + ) + + # Create the list of state operations + ops = [op1, op2] + + # Execute the state transaction + await client.state.transaction(DAPR_STORE_NAME, operations=ops) + print("State transaction executed.") +``` + +By setting the metadata item `"outbox.projection"` to `"true"` and making sure the `key` values match (`key1`): +- The first operation is written to the state store and no message is written to the message broker. +- The second operation value is published to the configured pub/sub topic. + +{{% /codetab %}} {{% codetab %}} -In the following .NET SDK example of a state transaction, the value of `"2"` is saved to the database, but the value of `"3"` is published to the end-user topic. +In the following JavaScript SDK example of a state transaction, the value of `"2"` is saved to the database, but the value of `"3"` is published to the end-user topic. ```javascript const { DaprClient, StateOperationType } = require('@dapr/dapr'); @@ -166,7 +211,10 @@ main().catch(err => { }); ``` -By setting the metadata item `"outbox.projection"` to `"true"`, the first transaction value published to the broker is ignored, while the second value is published to the configured pub/sub topic. +By setting the metadata item `"outbox.projection"` to `"true"` and making sure the `key` values match (`key1`): +- The first operation is written to the state store and no message is written to the message broker. +- The second operation value is published to the configured pub/sub topic. + {{% /codetab %}} @@ -214,7 +262,9 @@ public class Program } ``` -By setting the metadata item `"outbox.projection"` to `"true"`, the first transaction value published to the broker is ignored, while the second value is published to the configured pub/sub topic. +By setting the metadata item `"outbox.projection"` to `"true"` and making sure the `key` values match (`key1`): +- The first operation is written to the state store and no message is written to the message broker. +- The second operation value is published to the configured pub/sub topic. {{% /codetab %}} @@ -222,7 +272,7 @@ By setting the metadata item `"outbox.projection"` to `"true"`, the first transa -In the following Go SDK example of a state transaction, the value of `"2"` is saved to the database, but the value of `"3"` is published to the end-user topic. +In the following Java SDK example of a state transaction, the value of `"2"` is saved to the database, but the value of `"3"` is published to the end-user topic. ```java public class Main { @@ -263,7 +313,10 @@ public class Main { } ``` -By setting the metadata item `"outbox.projection"` to `"true"`, the first transaction value published to the broker is ignored, while the second value is published to the configured pub/sub topic. +By setting the metadata item `"outbox.projection"` to `"true"` and making sure the `key` values match (`key1`): +- The first operation is written to the state store and no message is written to the message broker. +- The second operation value is published to the configured pub/sub topic. + {{% /codetab %}} @@ -299,7 +352,9 @@ meta := map[string]string{} err := testClient.ExecuteStateTransaction(ctx, store, meta, ops) ``` -By setting the metadata item `"outbox.projection"` to `"true"`, the first transaction value published to the broker is ignored, while the second value is published to the configured pub/sub topic. +By setting the metadata item `"outbox.projection"` to `"true"` and making sure the `key` values match (`key1`): +- The first operation is written to the state store and no message is written to the message broker. +- The second operation value is published to the configured pub/sub topic. {{% /codetab %}} @@ -335,7 +390,9 @@ curl -X POST http://localhost:3500/v1.0/state/starwars/transaction \ }' ``` -By setting the metadata item `"outboxProjection"` to `"true"`, the first transaction value published to the broker is ignored, while the second value is published to the configured pub/sub topic. +By setting the metadata item `"outbox.projection"` to `"true"` and making sure the `key` values match (`key1`): +- The first operation is written to the state store and no message is written to the message broker. +- The second operation value is published to the configured pub/sub topic. {{% /codetab %}} @@ -343,9 +400,50 @@ By setting the metadata item `"outboxProjection"` to `"true"`, the first transa ### Override Dapr-generated CloudEvent fields -You can also override the [Dapr-generated CloudEvent fields]({{< ref "pubsub-cloudevents.md#dapr-generated-cloudevents-example" >}}) on the published outbox event with custom CloudEvent metadata. +You can override the [Dapr-generated CloudEvent fields]({{< ref "pubsub-cloudevents.md#dapr-generated-cloudevents-example" >}}) on the published outbox event with custom CloudEvent metadata. -{{< tabs JavaScript ".NET" Java Go HTTP >}} +{{< tabs Python JavaScript ".NET" Java Go HTTP >}} + +{{% codetab %}} + + + +```python +async def execute_state_transaction(): + async with DaprClient() as client: + # Define state operations + ops = [] + + op1 = { + 'operation': 'upsert', + 'request': { + 'key': 'key1', + 'value': b'2', # Convert string to byte array + 'metadata': { + 'cloudevent.id': 'unique-business-process-id', + 'cloudevent.source': 'CustomersApp', + 'cloudevent.type': 'CustomerCreated', + 'cloudevent.subject': '123', + 'my-custom-ce-field': 'abc' + } + } + } + + ops.append(op1) + + # Execute state transaction + store_name = 'your-state-store-name' + try: + await client.execute_state_transaction(store_name, ops) + print('State transaction executed.') + except Exception as e: + print('Error executing state transaction:', e) + +# Run the async function +if __name__ == "__main__": + asyncio.run(execute_state_transaction()) +``` +{{% /codetab %}} {{% codetab %}} @@ -398,32 +496,41 @@ public class StateOperationExample { var daprClient = new DaprClientBuilder().Build(); - // Define state operations - var ops = new List(); + // Define the value "2" as a string and serialize it to a byte array + var value = "2"; + var valueBytes = JsonSerializer.SerializeToUtf8Bytes(value); - var op1 = new StateOperation + // Define the first state operation to save the value "2" with metadata + // Override Cloudevent metadata + var metadata = new Dictionary { - OperationType = StateOperationType.Upsert, - Request = new SetStateRequest - { - Key = "key1", - Value = new byte[] { 50 }, // []byte("2") in Go is equivalent to new byte[] { 50 } in C# - Metadata = new Dictionary - { - { "id", "unique-business-process-id" }, - { "source", "CustomersApp" }, - { "type", "CustomerCreated" }, - { "subject", "123" }, - { "my-custom-ce-field", "abc" } - } - } + { "cloudevent.id", "unique-business-process-id" }, + { "cloudevent.source", "CustomersApp" }, + { "cloudevent.type", "CustomerCreated" }, + { "cloudevent.subject", "123" }, + { "my-custom-ce-field", "abc" } }; - ops.Add(op1); + var op1 = new StateTransactionRequest( + key: "key1", + value: valueBytes, + operationType: StateOperationType.Upsert, + metadata: metadata + ); - // Execute state transaction + // Create the list of state operations + var ops = new List { op1 }; + + // Execute the state transaction var storeName = "your-state-store-name"; - var metadata = new Dictionary(); + await daprClient.ExecuteStateTransactionAsync(storeName, ops); + Console.WriteLine("State transaction executed."); + } + + public static async Task Main(string[] args) + { + var example = new StateOperationExample(); + await example.ExecuteStateTransactionAsync(); } } ``` @@ -442,27 +549,36 @@ public class StateOperationExample { public static void executeStateTransaction() { // Build Dapr client - DaprClient daprClient = new DaprClientBuilder().build(); + try (DaprClient daprClient = new DaprClientBuilder().build()) { - // Define state operations - List> ops = new ArrayList<>(); + // Define the value "2" + String value = "2"; - State op1 = new State<>( - "key1", - "2", - new StateOptions<>(StateOperation.Type.UPSERT, new HashMap() {{ - put("id", "unique-business-process-id"); - put("source", "CustomersApp"); - put("type", "CustomerCreated"); - put("subject", "123"); - put("my-custom-ce-field", "abc"); - }}) - ); - ops.add(op1); + // Override CloudEvent metadata + Map metadata = new HashMap<>(); + metadata.put("cloudevent.id", "unique-business-process-id"); + metadata.put("cloudevent.source", "CustomersApp"); + metadata.put("cloudevent.type", "CustomerCreated"); + metadata.put("cloudevent.subject", "123"); + metadata.put("my-custom-ce-field", "abc"); - // Execute state transaction - String storeName = "your-state-store-name"; - Map metadata = new HashMap<>(); + // Define state operations + List> ops = new ArrayList<>(); + StateOperation op1 = new StateOperation<>( + StateOperationType.UPSERT, + "key1", + value, + metadata + ); + ops.add(op1); + + // Execute state transaction + String storeName = "your-state-store-name"; + daprClient.executeStateTransaction(storeName, ops).block(); + System.out.println("State transaction executed."); + } catch (Exception e) { + e.printStackTrace(); + } } } ``` @@ -473,26 +589,47 @@ public class StateOperationExample { ```go -ops := make([]*dapr.StateOperation, 0) +func main() { + // Create a Dapr client + client, err := dapr.NewClient() + if err != nil { + log.Fatalf("failed to create Dapr client: %v", err) + } + defer client.Close() -op1 := &dapr.StateOperation{ - Type: dapr.StateOperationTypeUpsert, - Item: &dapr.SetStateItem{ - Key: "key1", - Value: []byte("2"), - // Override the data payload saved to the database - Metadata: map[string]string{ - "id": "unique-business-process-id", - "source": "CustomersApp", - "type": "CustomerCreated", - "subject": "123", - "my-custom-ce-field": "abc", - }, - }, + ctx := context.Background() + store := "your-state-store-name" + + // Define state operations + ops := make([]*dapr.StateOperation, 0) + op1 := &dapr.StateOperation{ + Type: dapr.StateOperationTypeUpsert, + Item: &dapr.SetStateItem{ + Key: "key1", + Value: []byte("2"), + // Override Cloudevent metadata + Metadata: map[string]string{ + "cloudevent.id": "unique-business-process-id", + "cloudevent.source": "CustomersApp", + "cloudevent.type": "CustomerCreated", + "cloudevent.subject": "123", + "my-custom-ce-field": "abc", + }, + }, + } + ops = append(ops, op1) + + // Metadata for the transaction (if any) + meta := map[string]string{} + + // Execute state transaction + err = client.ExecuteStateTransaction(ctx, store, meta, ops) + if err != nil { + log.Fatalf("failed to execute state transaction: %v", err) + } + + log.Println("State transaction executed.") } -ops = append(ops, op1, op2) -meta := map[string]string{} -err := testClient.ExecuteStateTransaction(ctx, store, meta, ops) ``` {{% /codetab %}}