use crate::{aggregate::AggregateRoot, aggregate_id::AggregateId, sequence_number::SequenceNumber, version::Version};
#[derive(Debug, PartialEq)]
#[must_use]
pub struct VersionedAggregate<T: AggregateRoot> {
aggregate: T,
version: Version,
seq_nr: SequenceNumber,
}
impl<T: AggregateRoot> VersionedAggregate<T> {
pub fn new(aggregate: T, version: Version, seq_nr: SequenceNumber) -> Self {
Self {
aggregate,
version,
seq_nr,
}
}
pub fn id(&self) -> &AggregateId<T::ID> {
self.aggregate.id()
}
pub fn aggregate(&self) -> &T {
&self.aggregate
}
pub fn version(&self) -> Version {
self.version
}
pub fn seq_nr(&self) -> SequenceNumber {
self.seq_nr
}
pub fn set_seq_nr(&mut self, seq_nr: SequenceNumber) {
self.seq_nr = seq_nr;
}
pub fn handle(&mut self, cmd: T::Command) -> Result<Vec<T::DomainEvent>, T::Error> {
let events = self.aggregate.handle(cmd)?;
Ok(events)
}
pub fn apply(&mut self, event: T::DomainEvent) {
self.aggregate.apply(event);
}
pub fn snapshot(&self) -> (&T, Version, SequenceNumber) {
(self.aggregate(), self.version, self.seq_nr)
}
pub fn from_snapshot(aggregate: T, version: Version, seq_nr: SequenceNumber) -> Self {
Self::new(aggregate, version, seq_nr)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
aggregate_id::HasIdPrefix, command::Command, domain_event::DomainEvent, event_id::EventIdType,
integration_event::IntegrationEvent, message, test::TestFramework,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct TestId;
impl HasIdPrefix for TestId {
const PREFIX: &'static str = "test";
}
#[derive(Debug, Clone)]
enum TestCommand {
DoSomething { id: AggregateId<TestId> },
DoSomethingElse { id: AggregateId<TestId> },
CausesError { id: AggregateId<TestId> },
}
impl message::Message for TestCommand {
fn name(&self) -> &'static str {
"TestCommand"
}
}
impl Command for TestCommand {
type ID = TestId;
fn id(&self) -> AggregateId<Self::ID> {
match self {
Self::DoSomething { id } => *id,
Self::DoSomethingElse { id } => *id,
Self::CausesError { id } => *id,
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum TestEvent {
SomethingHappened { id: EventIdType, data: String },
SomethingElseHappened { id: EventIdType, data: String },
}
impl message::Message for TestEvent {
fn name(&self) -> &'static str {
"TestEvent"
}
}
impl DomainEvent for TestEvent {
fn id(&self) -> EventIdType {
match self {
Self::SomethingHappened { id, .. } => *id,
Self::SomethingElseHappened { id, .. } => *id,
}
}
fn event_type(&self) -> &'static str {
match self {
Self::SomethingHappened { .. } => "SomethingHappened",
Self::SomethingElseHappened { .. } => "SomethingElseHappened",
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct TestIntegrationEvent;
impl message::Message for TestIntegrationEvent {
fn name(&self) -> &'static str {
"TestIntegrationEvent"
}
}
impl IntegrationEvent for TestIntegrationEvent {
fn id(&self) -> String {
ulid::Ulid::new().to_string()
}
fn event_type(&self) -> &'static str {
"test.integration.event"
}
}
#[derive(Debug, thiserror::Error)]
enum TestError {
#[error("Something went wrong")]
SomethingWentWrong,
}
#[derive(Debug, Clone, PartialEq)]
struct TestAggregate {
id: AggregateId<TestId>,
state: String,
}
impl AggregateRoot for TestAggregate {
const TYPE: &'static str = "TestAggregate";
type ID = TestId;
type Command = TestCommand;
type DomainEvent = TestEvent;
type Error = TestError;
fn init(id: AggregateId<Self::ID>) -> Self {
Self {
id,
state: "initial".to_string(),
}
}
fn id(&self) -> &AggregateId<Self::ID> {
&self.id
}
fn handle(&mut self, cmd: Self::Command) -> Result<Vec<Self::DomainEvent>, Self::Error> {
match cmd {
TestCommand::DoSomething { .. } => Ok(vec![TestEvent::SomethingHappened {
id: EventIdType::new(),
data: "something".to_string(),
}]),
TestCommand::DoSomethingElse { .. } => Ok(vec![TestEvent::SomethingElseHappened {
id: EventIdType::new(),
data: "something else".to_string(),
}]),
TestCommand::CausesError { .. } => Err(TestError::SomethingWentWrong),
}
}
fn apply(&mut self, event: Self::DomainEvent) {
match event {
TestEvent::SomethingHappened { data, .. } => {
self.state = format!("{} -> {}", self.state, data);
}
TestEvent::SomethingElseHappened { data, .. } => {
self.state = format!("{} -> {}", self.state, data);
}
}
}
}
fn create_test_versioned_aggregate() -> VersionedAggregate<TestAggregate> {
let aggregate = TestAggregate {
id: AggregateId::<TestId>::new(),
state: "initial".to_string(),
};
VersionedAggregate::new(aggregate, 1, 0)
}
#[test]
fn test_versioned_aggregate_creation() {
let versioned = create_test_versioned_aggregate();
assert_eq!(versioned.aggregate.state, "initial");
assert_eq!(versioned.version, 1);
assert_eq!(versioned.seq_nr, 0);
}
#[test]
fn test_handle_command() {
let id = AggregateId::<TestId>::new();
let aggregate = TestAggregate::init(id);
TestFramework::with(aggregate.clone())
.given_no_previous_events()
.when(TestCommand::DoSomething { id })
.then_verify(|result| {
assert!(result.is_ok());
let events = result.unwrap();
assert_eq!(events.len(), 1);
match &events[0] {
TestEvent::SomethingHappened { data, .. } => {
assert_eq!(data, "something");
}
_ => panic!("Expected TestEvent::SomethingHappened"),
}
});
TestFramework::with(aggregate)
.given_no_previous_events()
.when(TestCommand::CausesError { id })
.then_expect_error_matches(|e| matches!(e, TestError::SomethingWentWrong));
}
#[test]
fn test_apply_event() {
let id = AggregateId::<TestId>::new();
let aggregate = TestAggregate::init(id);
TestFramework::with(aggregate)
.given_no_previous_events()
.when(TestCommand::DoSomething { id })
.then_aggregate_state(|agg| {
assert_eq!(agg.state, "initial -> something");
});
}
#[test]
fn test_handle_multiple_commands() {
let mut versioned = create_test_versioned_aggregate();
let cmd1 = TestCommand::DoSomething { id: *versioned.id() };
let cmd2 = TestCommand::DoSomethingElse { id: *versioned.id() };
let events1 = versioned.handle(cmd1).unwrap();
let events2 = versioned.handle(cmd2).unwrap();
assert_eq!(events1.len(), 1);
assert_eq!(events2.len(), 1);
assert!(matches!(events1[0], TestEvent::SomethingHappened { .. }));
assert!(matches!(events2[0], TestEvent::SomethingElseHappened { .. }));
assert_eq!(versioned.aggregate.state, "initial");
}
#[test]
fn test_snapshot() {
let versioned = create_test_versioned_aggregate();
let (aggregate, version, seq_nr) = versioned.snapshot();
assert_eq!(aggregate.state, "initial");
assert_eq!(version, 1);
assert_eq!(seq_nr, 0);
}
#[test]
fn test_aggregate_with_given_events() {
let id = AggregateId::<TestId>::new();
let aggregate = TestAggregate::init(id);
TestFramework::with(aggregate)
.given(vec![
TestEvent::SomethingHappened {
id: EventIdType::new(),
data: "first".to_string(),
},
TestEvent::SomethingElseHappened {
id: EventIdType::new(),
data: "second".to_string(),
},
])
.when(TestCommand::DoSomething { id })
.then_aggregate_state(|agg| {
assert_eq!(agg.state, "initial -> first -> second -> something");
});
}
#[test]
fn test_multiple_commands_sequence() {
let mut versioned = create_test_versioned_aggregate();
let mut events = Vec::new();
for i in 0..3 {
let cmd = if i % 2 == 0 {
TestCommand::DoSomething { id: *versioned.id() }
} else {
TestCommand::DoSomethingElse { id: *versioned.id() }
};
let cmd_events = versioned.handle(cmd).unwrap();
events.extend(cmd_events);
}
assert_eq!(events.len(), 3);
for event in events {
versioned.apply(event);
}
assert_eq!(
versioned.aggregate.state,
"initial -> something -> something else -> something"
);
}
}