Skip to content

Commit 688534e

Browse files
Merge pull request #108 from amansinghoriginal/syncstate
Docs for sync-statestore reaction
2 parents dba1b32 + 18f09e7 commit 688534e

File tree

2 files changed

+274
-1
lines changed
  • .github/config
  • docs/content/how-to-guides/configure-reactions/configure-sync-dapr-statestore-reaction

2 files changed

+274
-1
lines changed

.github/config/en-custom.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1484,4 +1484,11 @@ MatchedOrders
14841484
binlog
14851485
PollingApproach
14861486
EventStreamingApproach
1487-
TraceQL
1487+
TraceQL
1488+
filepath
1489+
lookups
1490+
keyPrefix
1491+
mystatestore
1492+
redisHost
1493+
redisPassword
1494+
yourRedisPassword
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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

Comments
 (0)