fj_kernel/services/
service.rs

1use std::ops::Deref;
2
3/// A service that controls access to some state
4///
5/// `Service` is a generic wrapper around some state, as well as code that knows
6/// how to operate on that state. It processes commands, changes the state based
7/// on those command, and produces events that capture these changes. These
8/// events are stored, providing a log of all changes to the state, and can be
9/// replayed later to re-create the state at any point in time.
10///
11/// The wrapped state must implement [`State`], which defines the type of
12/// command that this service processes, and the type of event that captures
13/// state changes. It also defines methods that operate on the state, commands,
14/// and events.
15///
16/// Implementations of [`State`] might also define an extension trait for a
17/// specific `Service<MyState>`, to provide a convenient API to callers.
18///
19/// This design takes inspiration from, and uses the nomenclature of, this
20/// article:
21/// <https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider>
22pub struct Service<S: State> {
23    state: S,
24}
25
26impl<S: State> Service<S> {
27    /// Create an instance of `Service`
28    pub fn new(state: S) -> Self {
29        Self { state }
30    }
31
32    /// Execute a command
33    ///
34    /// The command is executed synchronously. When this method returns, the
35    /// state has been updated and any events have been logged.
36    pub fn execute(&mut self, command: S::Command, events: &mut Vec<S::Event>) {
37        self.state.decide(command, events);
38
39        for event in events {
40            self.state.evolve(event);
41        }
42    }
43
44    /// Replay the provided events on the given state
45    pub fn replay<'event>(
46        state: &mut S,
47        events: impl IntoIterator<Item = &'event S::Event>,
48    ) where
49        <S as State>::Event: 'event,
50    {
51        for event in events {
52            state.evolve(event);
53        }
54    }
55}
56
57impl<S: State> Deref for Service<S> {
58    type Target = S;
59
60    fn deref(&self) -> &Self::Target {
61        &self.state
62    }
63}
64
65impl<S: State> Default for Service<S>
66where
67    S: Default,
68{
69    fn default() -> Self {
70        Self::new(S::default())
71    }
72}
73
74/// Implemented for state that can be wrapped by a [`Service`]
75///
76/// See [`Service`] for a detailed explanation.
77pub trait State {
78    /// A command that relates to the state
79    ///
80    /// Commands are processed by [`State::decide`].
81    type Command;
82
83    /// An event that captures modifications to this state
84    ///
85    /// Events are produced by [`State::decide`] and processed by
86    /// [`State::evolve`].
87    type Event;
88
89    /// Decide how to react to the provided command
90    ///
91    /// If the command must result in changes to the state, any number of events
92    /// that describe these state changes can be produced.
93    fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>);
94
95    /// Evolve the state according to the provided event
96    ///
97    /// This is the only method gets mutable access to the state, making sure
98    /// that all changes to the state are captured as events.
99    ///
100    /// Implementations of this method are supposed to be relatively dumb. Any
101    /// decisions that go into updating the state should be made in
102    /// [`State::decide`], and encoded into the event.
103    fn evolve(&mut self, event: &Self::Event);
104}