Skip to main content

client_async_secure/
client_async_secure.rs

1use futures::StreamExt;
2use umadb_client::UmaDbClient;
3use umadb_dcb::{
4    DcbAppendCondition, DcbError, DcbEvent, DcbEventStoreAsync, DcbQuery, DcbQueryItem,
5    TrackingInfo,
6};
7use uuid::Uuid;
8
9#[tokio::main]
10async fn main() -> Result<(), Box<dyn std::error::Error>> {
11    // Connect to the gRPC server
12    let url = "https://localhost:50051".to_string();
13    let client = UmaDbClient::new(url)
14        .ca_path("server.pem".to_string()) // For self-signed server certificates.
15        .api_key("umadb:example-api-key-4f7c2b1d9e5f4a038c1a".to_string())
16        .connect_async()
17        .await?;
18
19    // Define a consistency boundary
20    let cb = DcbQuery {
21        items: vec![DcbQueryItem {
22            types: vec!["example".to_string()],
23            tags: vec!["tag1".to_string(), "tag2".to_string()],
24        }],
25    };
26
27    // Read events for a decision model
28    let mut read_response = client.read(Some(cb.clone()), None, false, None).await?;
29
30    // Build decision model
31    while let Some(result) = read_response.next().await {
32        match result {
33            Ok(event) => {
34                println!(
35                    "Got event at position {}: {:?}",
36                    event.position, event.event
37                );
38            }
39            Err(status) => panic!("gRPC stream error: {}", status),
40        }
41    }
42
43    // Remember the last-known position
44    let last_known_position = read_response.head().await?;
45    println!("Last known position is: {:?}", last_known_position);
46
47    // Produce new event, attaching some metadata (e.g. provenance) that is
48    // stored alongside the event and returned when it is read back.
49    let mut metadata = Vec::new();
50    metadata.push(("source".to_string(), "client_async_secure".to_string()));
51    metadata.push(("correlation_id".to_string(), Uuid::new_v4().to_string()));
52    let event = DcbEvent {
53        event_type: "example".to_string(),
54        tags: vec!["tag1".to_string(), "tag2".to_string()],
55        data: b"Hello, world!".to_vec(),
56        uuid: Some(Uuid::new_v4()),
57        metadata,
58    };
59
60    // Append event in consistency boundary
61    let commit_position1 = client
62        .append(
63            vec![event.clone()],
64            Some(DcbAppendCondition {
65                fail_if_events_match: cb.clone(),
66                after: last_known_position,
67            }),
68            None,
69        )
70        .await?;
71    println!("Appended event at position: {}", commit_position1);
72
73    // Append conflicting event - expect an error
74    let conflicting_event = DcbEvent {
75        event_type: "example".to_string(),
76        tags: vec!["tag1".to_string(), "tag2".to_string()],
77        data: b"Hello, world!".to_vec(),
78        uuid: Some(Uuid::new_v4()), // different UUID
79        metadata: Vec::new(),
80    };
81    let conflicting_result = client
82        .append(
83            vec![conflicting_event.clone()],
84            Some(DcbAppendCondition {
85                fail_if_events_match: cb.clone(),
86                after: last_known_position,
87            }),
88            None,
89        )
90        .await;
91
92    // Expect an integrity error
93    match conflicting_result {
94        Err(DcbError::IntegrityError(integrity_error)) => {
95            println!("Conflicting event was rejected: {:?}", integrity_error);
96        }
97        other => panic!("Expected IntegrityError, got {:?}", other),
98    }
99
100    // Conditional appends with event UUIDs are idempotent.
101    println!(
102        "Retrying to append event at position: {:?}",
103        last_known_position
104    );
105    let commit_position2 = client
106        .append(
107            vec![event.clone()],
108            Some(DcbAppendCondition {
109                fail_if_events_match: cb.clone(),
110                after: last_known_position,
111            }),
112            None,
113        )
114        .await?;
115
116    if commit_position1 == commit_position2 {
117        println!(
118            "Append method returned same commit position: {}",
119            commit_position2
120        );
121    } else {
122        panic!("Expected idempotent retry!")
123    }
124
125    // Subscribe to all events for a projection
126    let mut subscription = client.subscribe(None, None).await?;
127
128    // Build an up-to-date view
129    while let Some(result) = subscription.next().await {
130        match result {
131            Ok(ev) => {
132                println!("Processing event at {}: {:?}", ev.position, ev.event);
133                if ev.position == commit_position2 {
134                    println!("Projection has processed new event!");
135                    break;
136                }
137            }
138            Err(status) => panic!("gRPC stream error: {}", status),
139        }
140    }
141
142    // Track an upstream position
143    let upstream_position = client.get_tracking_info("upstream").await?;
144    let next_upstream_position = upstream_position.unwrap_or(0) + 1;
145    println!("Next upstream position: {next_upstream_position}");
146    client
147        .append(
148            vec![],
149            None,
150            Some(TrackingInfo {
151                source: "upstream".to_string(),
152                position: next_upstream_position,
153            }),
154        )
155        .await?;
156    assert_eq!(
157        next_upstream_position,
158        client.get_tracking_info("upstream").await?.unwrap()
159    );
160    println!("Upstream position tracked okay!");
161
162    // Try recording the same upstream position
163    let conflicting_result = client
164        .append(
165            vec![],
166            None,
167            Some(TrackingInfo {
168                source: "upstream".to_string(),
169                position: next_upstream_position,
170            }),
171        )
172        .await;
173
174    // Expect an integrity error
175    match conflicting_result {
176        Err(DcbError::IntegrityError(integrity_error)) => {
177            println!(
178                "Conflicting upstream position was rejected: {:?}",
179                integrity_error
180            );
181        }
182        other => panic!("Expected IntegrityError, got {:?}", other),
183    }
184
185    Ok(())
186}