|
| 1 | +--- |
| 2 | +type: "docs" |
| 3 | +title: "Configure a Sync Dapr State Store Reaction" |
| 4 | +linkTitle: "Sync Dapr State Store" |
| 5 | +weight: 30 |
| 6 | +description: > |
| 7 | + Learn how to configure a Reaction to synchronize Drasi Continuous Query results with a Dapr State Store. |
| 8 | +--- |
| 9 | + |
| 10 | +The Sync Dapr State Store Reaction materializes the results of Drasi Continuous Queries into a [Dapr state store](https://docs.dapr.io/developing-applications/building-blocks/state-management/state-management-overview/). It performs an initial bulk load of all query results and then incrementally processes changes (adds, updates, deletes) from a Continuous Query to keep the Dapr state store up-to-date. |
| 11 | + |
| 12 | +This enables Dapr-based microservices to easily access sophisticated, pre-computed, and continuously updated data views through the standard Dapr state management API. |
| 13 | + |
| 14 | +## Scenarios powered by this reaction |
| 15 | + |
| 16 | +This reaction can power several vital scenarios for Dapr users: |
| 17 | + |
| 18 | +* **Simplified Composite API Implementation**: Pre-compute and materialize aggregated data views from multiple sources. API-serving microservices can then read this data directly from Dapr State with low latency, simplifying their logic. |
| 19 | +* **Building New Functionality without Disruption**: Introduce new features or services that consume tailored data views from existing systems without modifying those original services. In Drasi, Continuous Queries transform and project data, and this reaction makes it available in Dapr State for new microservices. |
| 20 | +* **Efficient Query Side of CQRS**: Use Drasi Continuous Queries to define and maintain read models for CQRS. The reaction materializes these optimized query views into Dapr State, allowing query-handling microservices to read them efficiently. |
| 21 | +* **Decoupled Data Views**: Provide different microservices with specific "slices" or perspectives of the same underlying data, each materialized in Dapr State for easy consumption. |
| 22 | +* **Improved Read Performance**: Offload complex querying from source databases by having read-optimized views readily available in a Dapr state store, accessed via simple key-value lookups. |
| 23 | + |
| 24 | +## Requirements |
| 25 | + |
| 26 | +On the computer from where you will create the Reaction, you need the following software: |
| 27 | +- [Drasi CLI](/reference/command-line-interface/) |
| 28 | +- [Kubectl](https://kubernetes.io/docs/reference/kubectl/) (for Dapr component configuration) |
| 29 | +- Note the namespace in which Drasi was installed. By default, Drasi uses `drasi-system` namespace. If you chose a custom namespace during installation of Drasi, use that in place of `drasi-system` when configuring the reaction. |
| 30 | + |
| 31 | +## Dapr Environment Prerequisites |
| 32 | + |
| 33 | +Before deploying this reaction, ensure the following are in place in your Kubernetes environment: |
| 34 | + |
| 35 | +1. **Application's Dapr State Store**: Your Dapr microservice(s) (e.g., running in `my-app-namespace`) must have a Dapr state store component configured and deployed. This component tells your application's Dapr sidecar how to connect to the underlying state store (e.g., Redis, Cosmos DB). Let's say this component is named `mystatestore`. |
| 36 | + |
| 37 | +2. **Data Store Accessibility**: The actual data store (e.g., your Redis instance) that backs your Dapr state component must be network-accessible from the Kubernetes namespace in which drasi was installed (default: `drasi-system`). This is because the Drasi Reaction pod runs in `drasi-system` (or the namespace chosen during installation) and will need to connect to this data store. |
| 38 | + |
| 39 | +3. **Crucial: Dapr State Store Component for Drasi in `drasi-system` Namespace**: |
| 40 | + This is a key step. The Drasi `SyncDaprStateStore` Reaction runs as a pod in the Kubernetes namespace in which Drasi was installed (default: `drasi-system`). Like any Dapr-enabled application, it relies on its *own* Dapr sidecar (running alongside it in `drasi-system`) to interact with Dapr building blocks, including state stores. |
| 41 | + Therefore, you **must** deploy a Dapr state store component manifest specifically for the Drasi Reaction in the `drasi-system` namespace. This component tells the Reaction's Dapr sidecar how to connect to your *existing* state store. |
| 42 | + |
| 43 | + * **`metadata.name`**: The `name` of this Dapr component in `drasi-system` **must match** the `stateStoreName` you will specify in the Drasi Reaction's configuration (see `spec.queries` later). For example, if your application uses a state store component named `mystatestore`, and you want the Reaction to write to it, you will create a component also named `mystatestore` in the `drasi-system` namespace. |
| 44 | + * **`metadata.namespace`**: This component manifest must specify `namespace: drasi-system`. |
| 45 | + * **`spec` (type, version, metadata/connection details)**: The `spec` section of this component (including `type`, `version`, and all connection `metadata` like `redisHost`, `redisPassword`, etc.) must be **identical** to the Dapr state store component used by your application. It needs to point to the *same underlying physical state store*. |
| 46 | + * **`spec.metadata.keyPrefix`**: **IMPORTANT!** For both the application's Dapr state store component AND the corresponding component in `drasi-system`, it is highly recommended to explicitly set the `keyPrefix` strategy to `"none"`. This ensures that the keys used by the Drasi Reaction (derived directly from the `keyField` in your query results) are stored and retrieved without any automatic Dapr-managed prefixes. This consistency is vital for both the Reaction and your application to access the same data items using the same keys. |
| 47 | + ```yaml |
| 48 | + # Example snippet for spec.metadata in your Dapr component |
| 49 | + # ... |
| 50 | + spec: |
| 51 | + type: state.redis # Or your chosen state store type |
| 52 | + version: v1 |
| 53 | + metadata: |
| 54 | + - name: redisHost |
| 55 | + value: your-shared-redis.default.svc.cluster.local:6379 |
| 56 | + - name: redisPassword |
| 57 | + value: "yourRedisPassword" |
| 58 | + - name: keyPrefix # Add this line |
| 59 | + value: "none" # Explicitly set to "none" |
| 60 | + # ... |
| 61 | + ``` |
| 62 | + |
| 63 | + **Why is `keyPrefix: "none"` important?** |
| 64 | + If `keyPrefix` is not set to `"none"` (e.g., it defaults to `appid` or is explicitly set to another strategy), Dapr will automatically add prefixes (like the Dapr App ID) to the keys. The Drasi Reaction writes keys based purely on your query's `keyField`. If your application's Dapr component expects prefixed keys, it won't find the data written by the Reaction, and vice-versa. Setting `keyPrefix: "none"` on both components ensures that the raw `keyField` value is the actual key in the underlying store, accessible by both. |
| 65 | + |
| 66 | + **Example Structure:** |
| 67 | + |
| 68 | + Let's say your application in `my-app-namespace` uses a Redis state store defined in `my-app-components/app-statestore.yaml`: |
| 69 | + ```yaml |
| 70 | + # filepath: my-app-components/app-statestore.yaml |
| 71 | + apiVersion: dapr.io/v1alpha1 |
| 72 | + kind: Component |
| 73 | + metadata: |
| 74 | + name: mystatestore # Name used by your application |
| 75 | + namespace: my-app-namespace # Your application's namespace |
| 76 | + spec: |
| 77 | + type: state.redis |
| 78 | + version: v1 |
| 79 | + metadata: |
| 80 | + - name: redisHost |
| 81 | + value: your-shared-redis.default.svc.cluster.local:6379 # Points to your actual Redis |
| 82 | + - name: redisPassword |
| 83 | + value: "yourRedisPassword" |
| 84 | + - name: keyPrefix # Explicitly set this to none |
| 85 | + value: "none" |
| 86 | + # ... other configurations |
| 87 | + ``` |
| 88 | + |
| 89 | + You **must** create a corresponding Dapr component for Drasi in the namespace in which drasi was installed (by default, `drasi-system`), for example, in `drasi-components/drasi-statestore-access.yaml`: |
| 90 | + ```yaml |
| 91 | + # filepath: drasi-components/drasi-statestore-access.yaml |
| 92 | + apiVersion: dapr.io/v1alpha1 |
| 93 | + kind: Component |
| 94 | + metadata: |
| 95 | + name: mystatestore # CRITICAL: Same name as your app's component if the Reaction targets it |
| 96 | + namespace: drasi-system # CRITICAL: Must be drasi-system (or the namespace in which drasi was installed) |
| 97 | + spec: |
| 98 | + type: state.redis # Identical spec to your app's component |
| 99 | + version: v1 |
| 100 | + metadata: |
| 101 | + - name: redisHost # Identical connection details |
| 102 | + value: your-shared-redis.default.svc.cluster.local:6379 # Points to the SAME actual Redis |
| 103 | + - name: redisPassword |
| 104 | + value: "yourRedisPassword" |
| 105 | + - name: keyPrefix # Explicitly set this to none |
| 106 | + value: "none" |
| 107 | + # ... other configurations identical to your app's component |
| 108 | + ``` |
| 109 | + Apply this second component definition to your Kubernetes cluster: |
| 110 | + ```bash |
| 111 | + kubectl apply -f drasi-components/drasi-statestore-access.yaml |
| 112 | + ``` |
| 113 | + This allows the Drasi Reaction (via its sidecar in `drasi-system`) to find and use the Dapr component named `mystatestore` to write to your shared Redis instance. |
| 114 | + |
| 115 | +## Registering the Reaction Provider (If Necessary) |
| 116 | + |
| 117 | +The Drasi environment needs to be aware of the `SyncDaprStateStore` reaction type. |
| 118 | + |
| 119 | +1. **Check if registered**: |
| 120 | + ```bash |
| 121 | + drasi list reactionprovider |
| 122 | + ``` |
| 123 | + Look for `SyncDaprStateStore` in the output. |
| 124 | + |
| 125 | +2. **If not listed, register it**: |
| 126 | + Create a `reaction-provider.yaml` file: |
| 127 | + ```yaml |
| 128 | + # filepath: reaction-provider.yaml |
| 129 | + apiVersion: v1 |
| 130 | + kind: ReactionProvider |
| 131 | + name: SyncDaprStateStore |
| 132 | + spec: |
| 133 | + services: |
| 134 | + reaction: |
| 135 | + image: drasi-project/reaction-sync-dapr-statestore:latest # Use the correct image name and tag for your reaction |
| 136 | + ``` |
| 137 | + Apply it: |
| 138 | + ```bash |
| 139 | + drasi apply -f reaction-provider.yaml |
| 140 | + ``` |
| 141 | + |
| 142 | +## Creating the Reaction |
| 143 | + |
| 144 | +To create a Reaction, execute the `drasi apply` command as follows: |
| 145 | + |
| 146 | +```text |
| 147 | +drasi apply -f my-sync-dapr-reaction.yaml |
| 148 | +``` |
| 149 | +The `-f` flag specifies that the definition of the new Reaction is contained in the referenced YAML file `my-sync-dapr-reaction.yaml`. |
| 150 | + |
| 151 | +## Reaction Definition |
| 152 | + |
| 153 | +Here is an example of a `SyncDaprStateStore` Reaction definition: |
| 154 | + |
| 155 | +```yaml |
| 156 | +# filepath: my-sync-dapr-reaction.yaml |
| 157 | +kind: Reaction |
| 158 | +apiVersion: v1 |
| 159 | +name: my-app-state-synchronizer # A unique name for your reaction instance |
| 160 | +spec: |
| 161 | + kind: SyncDaprStateStore # Must match the registered ReactionProvider name |
| 162 | + queries: |
| 163 | + # Example 1: Sync results from 'orders-ready-for-pickup' query |
| 164 | + # This will use the Dapr component named 'mystatestore' in the drasi-system namespace |
| 165 | + orders-ready-for-pickup: '{"stateStoreName": "mystatestore", "keyField": "orderId"}' |
| 166 | + |
| 167 | + # Example 2: Sync results from 'active-user-profiles' query |
| 168 | + # This will use the Dapr component named 'userprofilecache' in the drasi-system namespace |
| 169 | + active-user-profiles: '{"stateStoreName": "userprofilecache", "keyField": "profileId"}' |
| 170 | +``` |
| 171 | + |
| 172 | +In this definition: |
| 173 | +- `apiVersion` must be `v1`. |
| 174 | +- `kind` property tells Drasi to create a `Reaction` resource. |
| 175 | +- `name` property is the unique identity of the Reaction within the Drasi environment. |
| 176 | +- `spec.kind` property tells Drasi the type of Reaction to create, in this case `SyncDaprStateStore`. |
| 177 | + |
| 178 | +This table describes the settings in the `spec` section: |
| 179 | + |
| 180 | +| Property | Description | |
| 181 | +|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| 182 | +| `queries` | An object where each key is the **name** of a Drasi Continuous Query to subscribe to. The value for each key is a JSON string specifying the configuration for that query's synchronization. | |
| 183 | +| | The JSON string for each query must contain: | |
| 184 | +| | - `stateStoreName` (string): The `metadata.name` of the Dapr state store component (e.g., `"mystatestore"`, `"userprofilecache"`) that the Reaction should use. **It is important to note that this refers to the Dapr component defined in the namespace in which drasi was installed (by default - `drasi-system`).** | |
| 185 | +| | - `keyField` (string): The name of the field within each result item from the Drasi Continuous Query that will be used as the unique key when storing that item in the Dapr state store. | |
| 186 | + |
| 187 | +## How Dapr Microservices Access the Synchronized Data |
| 188 | + |
| 189 | +Once the reaction is running and has synchronized data into the shared underlying state store (e.g., your Redis instance): |
| 190 | + |
| 191 | +Your Dapr microservices (running in their own namespace, e.g., `my-app-namespace`) can access this data using their standard Dapr state management client SDKs. They will target their *own* Dapr state store component (e.g., `mystatestore` in `my-app-namespace`), which points to the same underlying physical store that the Drasi Reaction is writing to. |
| 192 | + |
| 193 | +**Example (C# using Dapr.Client):** |
| 194 | +```csharp |
| 195 | +// In your Dapr microservice (e.g., in my-app-namespace) |
| 196 | +using Dapr.Client; |
| 197 | +
|
| 198 | +// ... |
| 199 | +
|
| 200 | +var daprClient = new DaprClientBuilder().Build(); |
| 201 | +
|
| 202 | +// Your app uses its 'mystatestore' component, which points to the same Redis |
| 203 | +// as the 'mystatestore' component in 'drasi-system' used by the Reaction. |
| 204 | +string daprStateStoreNameForApp = "mystatestore"; |
| 205 | +string orderIdToFetch = "some-specific-order-id"; // This key comes from the 'keyField' of a query result |
| 206 | +
|
| 207 | +var orderDetails = await daprClient.GetStateAsync<MyOrderDataType>(daprStateStoreNameForApp, orderIdToFetch); |
| 208 | +
|
| 209 | +if (orderDetails != null) |
| 210 | +{ |
| 211 | + // Process orderDetails |
| 212 | + Console.WriteLine($"Fetched order: {orderDetails.CustomerName}"); |
| 213 | +} |
| 214 | +else |
| 215 | +{ |
| 216 | + Console.WriteLine($"Order with ID {orderIdToFetch} not found in {daprStateStoreNameForApp}."); |
| 217 | +} |
| 218 | +
|
| 219 | +// Define MyOrderDataType according to the structure of your query results |
| 220 | +// public class MyOrderDataType { |
| 221 | +// public string OrderId { get; set; } // Matches 'keyField' if it's part of the data |
| 222 | +// public string CustomerName { get; set; } |
| 223 | +// // ... other fields from your query result |
| 224 | +// } |
| 225 | +``` |
| 226 | + |
| 227 | +## Inspecting the Reaction |
| 228 | + |
| 229 | +As soon as the Reaction is created it will start running. You can check its status: |
| 230 | + |
| 231 | +```text |
| 232 | +drasi list reaction |
| 233 | +``` |
| 234 | +Example output: |
| 235 | +``` |
| 236 | + ID | AVAILABLE |
| 237 | +--------------------------------+------------ |
| 238 | + my-app-state-synchronizer | true |
| 239 | +``` |
| 240 | +If an error occurs, the `AVAILABLE` column will show the error. |
| 241 | + |
| 242 | +For more details: |
| 243 | +```text |
| 244 | +drasi describe reaction my-app-state-synchronizer |
| 245 | +``` |
| 246 | +This returns the full definition and detailed status. |
| 247 | + |
| 248 | +## Modifying the Reaction |
| 249 | + |
| 250 | +To modify the reaction (e.g., change subscribed queries or their configurations), update your YAML file and re-apply it using the same `drasi apply` command with the same reaction name: |
| 251 | +```text |
| 252 | +drasi apply -f my-sync-dapr-reaction.yaml |
| 253 | +``` |
| 254 | + |
| 255 | +## Deleting the Reaction |
| 256 | + |
| 257 | +To delete a Reaction: |
| 258 | +1. By type and name: |
| 259 | + ```text |
| 260 | + drasi delete reaction my-app-state-synchronizer |
| 261 | + ``` |
| 262 | +2. Using the YAML file(s): |
| 263 | + ```text |
| 264 | + drasi delete -f my-sync-dapr-reaction.yaml |
| 265 | + ``` |
| 266 | +This is useful if a single YAML file defines multiple resources. |
0 commit comments