From 16b0f5145d1c9a160720b7ef6575b5d0803ebfd1 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 29 Sep 2023 11:44:44 +1000 Subject: [PATCH] Keep the entity declaration in the backend The front end simply needs to understand how to event source state, and we want to keep the entity management at the backend. --- .../iot-service/backend/src/temperature.rs | 50 ++++++++++++- examples/iot-service/frontend/src/app.rs | 6 +- .../iot-service/frontend/src/temperature.rs | 4 +- examples/iot-service/model/src/temperature.rs | 74 +++++-------------- 4 files changed, 72 insertions(+), 62 deletions(-) diff --git a/examples/iot-service/backend/src/temperature.rs b/examples/iot-service/backend/src/temperature.rs index cee7584..1c30465 100644 --- a/examples/iot-service/backend/src/temperature.rs +++ b/examples/iot-service/backend/src/temperature.rs @@ -2,6 +2,10 @@ use std::{num::NonZeroUsize, sync::Arc}; +use akka_persistence_rs::{ + effect::{emit_event, reply, unhandled, Effect, EffectExt}, + entity::{Context, EventSourcedBehavior}, +}; use akka_persistence_rs::{ entity_manager::{self}, EntityId, Message, @@ -14,9 +18,51 @@ use chrono::{DateTime, Utc}; use streambed::commit_log::{ConsumerRecord, Key, ProducerRecord, Topic}; use streambed_confidant::FileSecretStore; use streambed_logged::{compaction::NthKeyBasedRetention, FileLog}; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; + +pub use iot_service_model::temperature::{Event, SecretDataValue, State}; + +// Declare temperature sensor entity concerns + +pub enum Command { + Get { reply_to: oneshot::Sender> }, + Post { temperature: u32 }, + Register { secret: SecretDataValue }, +} + +pub struct Behavior; + +impl EventSourcedBehavior for Behavior { + type State = State; + + type Command = Command; -pub use iot_service_model::temperature::{Behavior, Command, Event, SecretDataValue, State}; + type Event = Event; + + fn for_command( + _context: &Context, + state: &Self::State, + command: Self::Command, + ) -> Box> { + match command { + Command::Get { reply_to } if !state.secret.is_empty() => { + reply(reply_to, state.history.clone().into()).boxed() + } + + Command::Post { temperature } if !state.secret.is_empty() => { + emit_event(Event::TemperatureRead { temperature }).boxed() + } + + Command::Register { secret } => emit_event(Event::Registered { secret }).boxed(), + + _ => unhandled(), + } + } + + fn on_event(context: &Context, state: &mut Self::State, event: Self::Event) { + state.on_event(context, event); + } +} // Declare how we marshal our data with FileLog. This is essentially // down to encoding our event type and entity id to a key used for diff --git a/examples/iot-service/frontend/src/app.rs b/examples/iot-service/frontend/src/app.rs index b920468..74610a7 100644 --- a/examples/iot-service/frontend/src/app.rs +++ b/examples/iot-service/frontend/src/app.rs @@ -1,6 +1,5 @@ use akka_persistence_rs::{ - entity::Context as EntityContext, entity::EventSourcedBehavior, entity_manager::EventEnvelope, - EntityId, + entity::Context as EntityContext, entity_manager::EventEnvelope, EntityId, }; use tokio::sync::{broadcast, mpsc}; use web_sys::HtmlInputElement; @@ -88,11 +87,10 @@ impl Component for App { Message::UpdateTemperature { envelope } => { if envelope.entity_id == self.entity_id { - temperature::Behavior::on_event( + self.temperature.on_event( &EntityContext { entity_id: &envelope.entity_id, }, - &mut self.temperature, envelope.event, ); true diff --git a/examples/iot-service/frontend/src/temperature.rs b/examples/iot-service/frontend/src/temperature.rs index 5285128..74b71bc 100644 --- a/examples/iot-service/frontend/src/temperature.rs +++ b/examples/iot-service/frontend/src/temperature.rs @@ -1,4 +1,4 @@ -// Declare the entity +// Declare and query the temperature event source use akka_persistence_rs::{entity_manager::EventEnvelope, EntityId}; use gloo_net::eventsource::futures::EventSource; @@ -6,7 +6,7 @@ use log::{error, warn}; use tokio::sync::{broadcast, mpsc}; use tokio_stream::StreamExt; -pub use iot_service_model::temperature::{Behavior, Command, Event, SecretDataValue, State}; +pub use iot_service_model::temperature::{Event, SecretDataValue, State}; // Execute the event source query for commanded entity ids diff --git a/examples/iot-service/model/src/temperature.rs b/examples/iot-service/model/src/temperature.rs index a2eded8..2a6477d 100644 --- a/examples/iot-service/model/src/temperature.rs +++ b/examples/iot-service/model/src/temperature.rs @@ -1,16 +1,12 @@ -// Handle temperature sensor entity concerns +// Handle temperature sensor state concerns use std::collections::VecDeque; -use akka_persistence_rs::{ - effect::{emit_event, reply, unhandled, Effect, EffectExt}, - entity::{Context, EventSourcedBehavior}, -}; +use akka_persistence_rs::entity::Context; use serde::{Deserialize, Serialize}; use smol_str::SmolStr; -use tokio::sync::oneshot; -// Declare the entity and its behavior +// Declare the state and how it is to be sourced from events #[derive(Default)] pub struct State { @@ -18,62 +14,32 @@ pub struct State { pub secret: SecretDataValue, } -pub type SecretDataValue = SmolStr; - -pub enum Command { - Get { reply_to: oneshot::Sender> }, - Post { temperature: u32 }, - Register { secret: SecretDataValue }, -} - -#[derive(Clone, Deserialize, Serialize)] -pub enum Event { - Registered { secret: SecretDataValue }, - TemperatureRead { temperature: u32 }, -} - -pub struct Behavior; - const MAX_HISTORY_EVENTS: usize = 10; -impl EventSourcedBehavior for Behavior { - type State = State; - - type Command = Command; - - type Event = Event; - - fn for_command( - _context: &Context, - state: &Self::State, - command: Self::Command, - ) -> Box> { - match command { - Command::Get { reply_to } if !state.secret.is_empty() => { - reply(reply_to, state.history.clone().into()).boxed() - } +impl State { + // We provide an event sourcing function so that we can + // source state from events whether we are using the entity + // manager or not. - Command::Post { temperature } if !state.secret.is_empty() => { - emit_event(Event::TemperatureRead { temperature }).boxed() - } - - Command::Register { secret } => emit_event(Event::Registered { secret }).boxed(), - - _ => unhandled(), - } - } - - fn on_event(_context: &Context, state: &mut Self::State, event: Self::Event) { + pub fn on_event(&mut self, _context: &Context, event: Event) { match event { Event::Registered { secret } => { - state.secret = secret; + self.secret = secret; } Event::TemperatureRead { temperature } => { - if state.history.len() == MAX_HISTORY_EVENTS { - state.history.pop_front(); + if self.history.len() == MAX_HISTORY_EVENTS { + self.history.pop_front(); } - state.history.push_back(temperature); + self.history.push_back(temperature); } } } } + +pub type SecretDataValue = SmolStr; + +#[derive(Clone, Deserialize, Serialize)] +pub enum Event { + Registered { secret: SecretDataValue }, + TemperatureRead { temperature: u32 }, +}