Skip to content

Commit 7619738

Browse files
committed
README updated
1 parent 82a9c6f commit 7619738

10 files changed

+176
-6
lines changed

src/application/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod order_materialized_view;
12
pub mod order_restaurant_aggregate;
23
pub mod restaurant_materialized_view;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use crate::domain::api::OrderEvent;
2+
use crate::domain::order_view::{OrderView, OrderViewState};
3+
use crate::framework::application::materialized_view::MaterializedView;
4+
use crate::infrastructure::order_view_state_repository::OrderViewStateRepository;
5+
6+
/// A convenient type alias for the order materialized view.
7+
pub type OrderMeterializedView<'a> =
8+
MaterializedView<Option<OrderViewState>, OrderEvent, OrderViewStateRepository, OrderView<'a>>;

src/domain/api.rs

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ pub struct RestaurantName(pub String);
2424

2525
#[derive(PostgresType, Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
2626
pub struct OrderId(pub Uuid);
27+
impl fmt::Display for OrderId {
28+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29+
// Delegate the formatting to the inner Uuid
30+
write!(f, "{}", self.0)
31+
}
32+
}
2733

2834
#[derive(PostgresType, Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
2935
pub struct Reason(pub String);

src/domain/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,13 @@ pub fn event_to_restaurant_event(event: &Event) -> Option<RestaurantEvent> {
210210
Event::OrderPrepared(_e) => None,
211211
}
212212
}
213+
214+
pub fn event_to_order_event(event: &Event) -> Option<OrderEvent> {
215+
match event {
216+
Event::RestaurantCreated(_e) => None,
217+
Event::RestaurantMenuChanged(_e) => None,
218+
Event::OrderPlaced(_e) => None,
219+
Event::OrderCreated(e) => Some(OrderEvent::Created(e.to_owned())),
220+
Event::OrderPrepared(e) => Some(OrderEvent::Prepared(e.to_owned())),
221+
}
222+
}

src/domain/order_view.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub struct OrderViewState {
1313
}
1414

1515
/// A convenient type alias for the Order view
16-
type OrderView<'a> = View<'a, Option<OrderViewState>, OrderEvent>;
16+
pub type OrderView<'a> = View<'a, Option<OrderViewState>, OrderEvent>;
1717

1818
/// View represents the event handling algorithm. It belongs to the Domain layer.
1919
pub fn order_view<'a>() -> OrderView<'a> {

src/infrastructure/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod order_restaurant_event_repository;
2+
pub mod order_view_state_repository;
23
pub mod restaurant_view_state_repository;

src/infrastructure/order_restaurant_event_repository.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::framework::infrastructure::event_repository::EventOrchestratingReposi
55
pub struct OrderAndRestaurantEventRepository {}
66

77
/// Implementation of the event orchestrating repository for the restaurant and order domain(s).
8+
/// We use default implementation from the trait. How cool is that?
89
impl EventOrchestratingRepository<Command, Event> for OrderAndRestaurantEventRepository {}
910

1011
impl OrderAndRestaurantEventRepository {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::domain::api::OrderEvent;
2+
use crate::domain::order_view::OrderViewState;
3+
use crate::framework::domain::api::Identifier;
4+
use crate::framework::infrastructure::errors::ErrorMessage;
5+
use crate::framework::infrastructure::to_payload;
6+
use crate::framework::infrastructure::view_state_repository::ViewStateRepository;
7+
use pgrx::{IntoDatum, JsonB, PgBuiltInOids, Spi};
8+
9+
/// OrderViewStateRepository struct
10+
/// View state repository is always very specific to the domain. There is no default implementation in the `ViewStateRepository` trait.
11+
pub struct OrderViewStateRepository {}
12+
13+
/// OrderViewStateRepository - struct implementation
14+
impl OrderViewStateRepository {
15+
/// Create a new OrderViewStateRepository
16+
pub fn new() -> Self {
17+
OrderViewStateRepository {}
18+
}
19+
}
20+
21+
/// Implementation of the view state repository for the order `view` state.
22+
impl ViewStateRepository<OrderEvent, Option<OrderViewState>> for OrderViewStateRepository {
23+
/// Fetches current state, based on the event.
24+
fn fetch_state(
25+
&self,
26+
event: &OrderEvent,
27+
) -> Result<Option<Option<OrderViewState>>, ErrorMessage> {
28+
let query = "SELECT data FROM orders WHERE id = $1";
29+
Spi::connect(|client| {
30+
let mut results = Vec::new();
31+
let tup_table = client
32+
.select(
33+
query,
34+
None,
35+
Some(vec![(
36+
PgBuiltInOids::UUIDOID.oid(),
37+
event.identifier().to_string().into_datum(),
38+
)]),
39+
)
40+
.map_err(|err| ErrorMessage {
41+
message: "Failed to fetch the order: ".to_string() + &err.to_string(),
42+
})?;
43+
for row in tup_table {
44+
let data = row["data"].value::<JsonB>().map_err(|err| ErrorMessage {
45+
message: "Failed to fetch the order/payload (map `data` to `JsonB`): ".to_string() + &err.to_string(),
46+
})?.ok_or(ErrorMessage {
47+
message: "Failed to fetch order data/payload (map `data` to `JsonB`): No data/payload found".to_string(),
48+
})?;
49+
50+
results.push(to_payload::<OrderViewState>(data)?);
51+
}
52+
Ok(Some(results.into_iter().last()))
53+
})
54+
}
55+
/// Saves the new state.
56+
fn save(&self, state: &Option<OrderViewState>) -> Result<Option<OrderViewState>, ErrorMessage> {
57+
let state = state.as_ref().ok_or(ErrorMessage {
58+
message: "Failed to save the order: state is empty".to_string(),
59+
})?;
60+
let data = serde_json::to_value(state).map_err(|err| ErrorMessage {
61+
message: "Failed to serialize the order: ".to_string() + &err.to_string(),
62+
})?;
63+
64+
Spi::connect(|mut client| {
65+
client
66+
.update(
67+
"INSERT INTO orders (id, data) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET data = $2 RETURNING data",
68+
None,
69+
Some(vec![
70+
(
71+
PgBuiltInOids::UUIDOID.oid(),
72+
state.identifier.to_string().into_datum(),
73+
),
74+
(
75+
PgBuiltInOids::JSONBOID.oid(),
76+
JsonB(data).into_datum(),
77+
),
78+
]),
79+
)?
80+
.first()
81+
.get_one::<JsonB>().map(|o|{ o.map( |it| to_payload(it).unwrap() )})
82+
})
83+
.map(Some)
84+
.map_err(|err| ErrorMessage {
85+
message: "Failed to save the order: ".to_string() + &err.to_string(),
86+
})
87+
.map(|state| state.unwrap())
88+
}
89+
}

src/infrastructure/restaurant_view_state_repository.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::framework::infrastructure::view_state_repository::ViewStateRepository
77
use pgrx::{IntoDatum, JsonB, PgBuiltInOids, Spi};
88

99
/// RestaurantViewStateRepository struct
10+
/// View state repository is always very specific to the domain. There is no default implementation in the `ViewStateRepository` trait.
1011
pub struct RestaurantViewStateRepository {}
1112

1213
/// RestaurantViewStateRepository - struct implementation
@@ -45,7 +46,7 @@ impl ViewStateRepository<RestaurantEvent, Option<RestaurantViewState>>
4546
let data = row["data"].value::<JsonB>().map_err(|err| ErrorMessage {
4647
message: "Failed to fetch the restaurant/payload (map `data` to `JsonB`): ".to_string() + &err.to_string(),
4748
})?.ok_or(ErrorMessage {
48-
message: "Failed to fetch event data/payload (map `data` to `JsonB`): No data/payload found".to_string(),
49+
message: "Failed to fetch restaurant data/payload (map `data` to `JsonB`): No data/payload found".to_string(),
4950
})?;
5051

5152
results.push(to_payload::<RestaurantViewState>(data)?);
@@ -84,7 +85,7 @@ impl ViewStateRepository<RestaurantEvent, Option<RestaurantViewState>>
8485
.first()
8586
.get_one::<JsonB>().map(|o|{ o.map( |it| to_payload(it).unwrap() )})
8687
})
87-
.map(|state| Some(state))
88+
.map(Some)
8889
.map_err(|err| ErrorMessage {
8990
message: "Failed to save the restaurant: ".to_string() + &err.to_string(),
9091
})

src/lib.rs

+56-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
use crate::application::order_materialized_view::OrderMeterializedView;
12
use crate::application::order_restaurant_aggregate::OrderAndRestaurantAggregate;
23
use crate::application::restaurant_materialized_view::RestaurantMeterializedView;
4+
use crate::domain::order_view::order_view;
35
use crate::domain::restaurant_view::restaurant_view;
46
use crate::domain::{
5-
event_to_restaurant_event, order_restaurant_decider, order_restaurant_saga, Command, Event,
7+
event_to_order_event, event_to_restaurant_event, order_restaurant_decider,
8+
order_restaurant_saga, Command, Event,
69
};
710
use crate::framework::infrastructure::errors::{ErrorMessage, TriggerError};
811
use crate::framework::infrastructure::to_payload;
912
use crate::infrastructure::order_restaurant_event_repository::OrderAndRestaurantEventRepository;
13+
use crate::infrastructure::order_view_state_repository::OrderViewStateRepository;
1014
use crate::infrastructure::restaurant_view_state_repository::RestaurantViewStateRepository;
1115
use pgrx::prelude::*;
1216
use pgrx::JsonB;
@@ -59,7 +63,7 @@ fn handle_all(commands: Vec<Command>) -> Result<Vec<Event>, ErrorMessage> {
5963
.map(|res| res.into_iter().map(|(e, _)| e.clone()).collect())
6064
}
6165

62-
/// Event handler for Restaurant events / Trigger function that handles events and updates the materialized view.
66+
/// Event handler for Restaurant events / Trigger function that handles restaurant related events and updates the materialized view/table.
6367
#[pg_trigger]
6468
fn handle_restaurant_events<'a>(
6569
trigger: &'a PgTrigger<'a>,
@@ -105,6 +109,52 @@ extension_sql!(
105109
requires = [handle_restaurant_events]
106110
);
107111

112+
/// Event handler for Order events / Trigger function that handles order related events and updates the materialized view/table.
113+
#[pg_trigger]
114+
fn handle_order_events<'a>(
115+
trigger: &'a PgTrigger<'a>,
116+
) -> Result<Option<PgHeapTuple<'a, impl WhoAllocated>>, TriggerError> {
117+
let new = trigger
118+
.new()
119+
.ok_or(TriggerError::NullTriggerTuple)?
120+
.into_owned();
121+
let event: JsonB = new
122+
.get_by_name::<JsonB>("data")?
123+
.ok_or(TriggerError::NullTriggerTuple)?;
124+
let materialized_view =
125+
OrderMeterializedView::new(OrderViewStateRepository::new(), order_view());
126+
127+
match event_to_order_event(
128+
&to_payload::<Event>(event)
129+
.map_err(|err| TriggerError::EventHandlingError(err.to_string()))?,
130+
) {
131+
// If the event is not a Restaurant event, we do nothing
132+
None => return Ok(Some(new)),
133+
// If the event is a Restaurant event, we handle it
134+
Some(e) => {
135+
materialized_view
136+
.handle(&e)
137+
.map_err(|err| TriggerError::EventHandlingError(err.message))?;
138+
}
139+
}
140+
Ok(Some(new))
141+
}
142+
143+
// Materialized view / Table for the Order query side model
144+
// This table is updated by the trigger function / event handler `handle_order_events`
145+
extension_sql!(
146+
r#"
147+
CREATE TABLE IF NOT EXISTS orders (
148+
id UUID PRIMARY KEY,
149+
data JSONB
150+
);
151+
152+
CREATE TRIGGER order_event_handler_trigger AFTER INSERT ON events FOR EACH ROW EXECUTE PROCEDURE handle_order_events();
153+
"#,
154+
name = "order_event_handler_trigger",
155+
requires = [handle_order_events]
156+
);
157+
108158
#[cfg(any(test, feature = "pg_test"))]
109159
#[pg_schema]
110160
mod tests {
@@ -115,7 +165,10 @@ mod tests {
115165
VALUES ('RestaurantCreated', '5f8bdf95-c95b-4e4b-8535-d2ac4663bea9', 'Restaurant', 'e48d4d9e-403e-453f-b1ba-328e0ce23737', '{"type": "RestaurantCreated","identifier": "e48d4d9e-403e-453f-b1ba-328e0ce23737", "name": "Pljeska", "menu": {"menu_id": "02f09a3f-1624-3b1d-8409-44eff7708210", "items": [{"id": "02f09a3f-1624-3b1d-8409-44eff7708210","name": "supa","price": 10},{"id": "02f09a3f-1624-3b1d-8409-44eff7708210","name": "sarma","price": 20 }],"cuisine": "Vietnamese"}, "final": false }', 'e48d4d9e-403e-453f-b1ba-328e0ce23737', NULL, FALSE);
116166
"#,
117167
name = "data_insert",
118-
requires = ["restaurant_event_handler_trigger"]
168+
requires = [
169+
"restaurant_event_handler_trigger",
170+
"order_event_handler_trigger"
171+
]
119172
);
120173
use crate::domain::api::{
121174
ChangeRestaurantMenu, CreateRestaurant, OrderCreated, OrderLineItem, OrderPlaced,

0 commit comments

Comments
 (0)