Skip to content

Commit

Permalink
Merge pull request #54 from fraktalio/feature/view_merge
Browse files Browse the repository at this point in the history
Added new `merge`/`combine` function for the View
  • Loading branch information
idugalic authored Jan 25, 2025
2 parents 145a99a + 676f030 commit 06b9ff0
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ impl<'a, S, E> View<'a, S, E> {

/// Combines two views into one.
/// Creates a new instance of a View by combining two views of type `S`, `E` and `S2`, `E2` into a new view of type `(S, S2)`, `Sum<E, E2>`
/// Combines two views that operate on different event types (`E`` and `E2``) into a new view operating on `Sum<E, E2>`
pub fn combine<S2, E2>(self, view2: View<'a, S2, E2>) -> View<'a, (S, S2), Sum<E, E2>>
where
S: Clone,
Expand Down Expand Up @@ -156,6 +157,36 @@ impl<'a, S, E> View<'a, S, E> {
initial_state: new_initial_state,
}
}

/// Merges two views into one.
/// Creates a new instance of a View by merging two views of type `S`, `E` and `S2`, `E` into a new view of type `(S, S2)`, `E`
/// Similar to `combine`, but the event type is the same for both views.
/// Composes two views that operate on the same/shared event type (`E`) into a new view operating on `E`
pub fn merge<S2>(self, view2: View<'a, S2, E>) -> View<'a, (S, S2), E>
where
S: Clone,
S2: Clone,
{
let new_evolve = Box::new(move |s: &(S, S2), e: &E| {
let s1 = &s.0;
let s2 = &s.1;

let new_state = (self.evolve)(s1, e);
let new_state2 = (view2.evolve)(s2, e);
(new_state, new_state2)
});

let new_initial_state = Box::new(move || {
let s1 = (self.initial_state)();
let s2 = (view2.initial_state)();
(s1, s2)
});

View {
evolve: new_evolve,
initial_state: new_initial_state,
}
}
}

/// Formalizes the `State Computation` algorithm for the `view` to handle events based on the current state, and produce new state.
Expand Down
9 changes: 9 additions & 0 deletions tests/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ pub struct OrderViewState {
pub is_cancelled: bool,
}

/// A second version of the ViewOrder entity / It represents the Query Model
#[derive(Debug, Clone, PartialEq)]
pub struct OrderView2State {
pub order_id: u32,
pub customer_name: String,
pub items: Vec<String>,
pub is_cancelled: bool,
}

/// All variants of Order commands
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
Expand Down
52 changes: 52 additions & 0 deletions tests/view_test.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use api::OrderView2State;
use fmodel_rust::view::{View, ViewStateComputation};

use crate::api::{
Expand Down Expand Up @@ -61,14 +62,45 @@ fn shipment_view<'a>() -> View<'a, ShipmentViewState, ShipmentEvent> {
}
}

fn order_view_second<'a>() -> View<'a, OrderView2State, OrderEvent> {
View {
evolve: Box::new(|state, event| {
let mut new_state = state.clone();
match event {
OrderEvent::Created(evt) => {
new_state.order_id = evt.order_id;
new_state.customer_name = evt.customer_name.to_owned();
new_state.items = evt.items.to_owned();
}
OrderEvent::Updated(evt) => {
new_state.items = evt.updated_items.to_owned();
}
OrderEvent::Cancelled(_) => {
new_state.is_cancelled = true;
}
}
new_state
}),
initial_state: Box::new(|| OrderView2State {
order_id: 0,
customer_name: "".to_string(),
items: Vec::new(),
is_cancelled: false,
}),
}
}

#[test]
fn test() {
let order_view: View<OrderViewState, OrderEvent> = order_view();
let order_view2: View<OrderViewState, OrderEvent> = crate::order_view();
let order_view3: View<OrderViewState, OrderEvent> = crate::order_view();
let order_view_second: View<OrderView2State, OrderEvent> = order_view_second();
let shipment_view: View<ShipmentViewState, ShipmentEvent> = shipment_view();
let combined_view = order_view2
.combine(shipment_view)
.map_event(&event_from_sum);
let merged_view = order_view3.merge(order_view_second);

let order_created_event = OrderEvent::Created(OrderCreatedEvent {
order_id: 1,
Expand All @@ -91,6 +123,7 @@ fn test() {
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
});

let new_combined_state2 = combined_view.compute_new_state(None, &[&order_created_event2]);
assert_eq!(
new_combined_state2,
Expand All @@ -110,6 +143,25 @@ fn test() {
)
);

let new_combined_state_2 = merged_view.compute_new_state(None, &[&order_created_event]);
assert_eq!(
new_combined_state_2,
(
OrderViewState {
order_id: 1,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
is_cancelled: false,
},
OrderView2State {
order_id: 1,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
is_cancelled: false,
}
)
);

let shipment_created_event2 = Event::ShipmentCreated(ShipmentCreatedEvent {
shipment_id: 1,
order_id: 1,
Expand Down

0 comments on commit 06b9ff0

Please sign in to comment.