1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use crate::models::AggregateRoot;
use std::error::Error;
use crate::event::DomainEvent;

/// A trait that provides a collection like abstraction over database access.
///
/// Generic `T` is some struct that implements `Entity<K>` where `K` is used as the key in the repository methods.  In other words
/// it's expected that an entities id is used as the key for insert and retrieval.
pub trait Repository<T: AggregateRoot> {
    /// An error that communicates that something went wrong at the database level.
    type Error: std::error::Error + std::fmt::Display + 'static;

    /// Inserts an entity into the underlying persistent storage (MySQL, Postgres, Mongo etc.).
    ///
    /// Entity should be inserted at it's globally unique id. It implements the [`Entity`] interface,
    /// so it's globally unique id can be accessed by calling [`id()`].
    ///
    /// If the underlying storage did not have this key present, then insert is successful and the primary key is returned.
    /// This allows for auto-generated ids to be returned after insert.
    ///
    /// If the underlying storage does have the key present, then [`None`] is returned.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    ///
    /// [`Entity`]: ./trait.Entity.html
    /// [`id()`]: ./trait.Entity.html#tymethod.id
    /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
    /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html
    /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
    fn insert(&mut self, entity: &T) -> Result<Option<String>, Self::Error>;

    /// Returns the entity corresponding to the supplied key as an owned type.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    fn get(&mut self, key: &String) -> Result<Option<T>, Self::Error>;


    /// Returns a `Vec<T>` of entities, based on the supplied `page_num` and `page_size`.
    /// The page_num should start at 1, but is up to the implementer to design as they see fit.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    fn get_paged(&mut self, page_num: usize, page_size: usize) -> Result<Option<Vec<T>>, Self::Error>;

    /// Returns `true` if the underlying storage contains an entity at the specified key,
    /// and otherwise returns `false`.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    ///
    /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html
    /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
    fn contains_key(&mut self, key: &String) -> Result<bool, Self::Error> {
        Ok(self.get(key)?.is_some())
    }

    /// Updates the entity in the underlying storage mechanism and if successful returns the primary
    /// key to the caller, which because it implements `Entity` can be had for free by calling `.id()`
    /// on the entity.  If the entity does not exist in the database (it's unique
    /// id is not in use), then we return [`None`].
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    ///
    /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html
    /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
    fn update(&mut self, entity: &T) -> Result<Option<String>, Self::Error>;

    /// Removes an entity from the underlying storage at the given key,
    /// returning the entity key if it was in the database and deleted, and otherwise returning [`None`]
    /// if the entity was not found (no rows effected by the operation).
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    ///
    /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
    /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html
    /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
    fn remove(&mut self, key: &String) -> Result<Option<String>, Self::Error>;
}

/// A trait that provides a collection like abstraction over read only database access.
///
/// Generic `T` is some struct that implements `Entity<K>` where `K` is used as the key in the repository methods.  In other words
/// it's expected that an entities id is used as the key for insert and retrieval.
pub trait ReadRepository<T: AggregateRoot> {
    /// An error that communicates that something went wrong at the database level.
    type Error: std::error::Error + std::fmt::Display + 'static;

    /// Returns the entity corresponding to the supplied key as an owned type.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    fn get(&mut self, key: &String) -> Result<Option<T>, Self::Error>;


    /// Returns a `Vec<T>` of entities, based on the supplied `page_num` and `page_size`.
    /// The page_num should start at 1, but is up to the implementer to design as they see fit.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    fn get_paged(&mut self, page_num: usize, page_size: usize) -> Result<Option<Vec<T>>, Self::Error>;

    /// Returns `true` if the underlying storage contains an entity at the specified key,
    /// and otherwise returns `false`.
    ///
    /// # Failure case
    ///
    /// If we fail to communicate with the underlying storage, then an error is returned.
    ///
    /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html
    /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
    fn contains_key(&mut self, key: &String) -> Result<bool, Self::Error> {
        Ok(self.get(key)?.is_some())
    }
}

/// EventRepository is a trait that provides collection like semantics over event storage and retrival.  The
/// implementor may choose to persist and retrieve events from any storage mechanism of their choosing.
pub trait EventRepository {
    /// Events should likely be pointed at an enum that holds domain event variants.  All variants should
    /// implement DomainEvent trait, and the enum itself should also implement DomainEvent trait.
    /// This is trivial if you're using the domain_derive crate, in which case you can simply use the
    /// DomainEvent macro on each DomainEvent, and then the DomainEvents macro on the enum holding
    /// you DomainEvent variants.
    type Events: DomainEvent;

    /// events_by_aggregate returns a vector of pointers to events filtered by the supplied
    /// aggregate id.
    fn events_by_aggregate(&self, aggregate_id: &String) -> Option<Vec<Self::Events>>;

    /// events_since_version will give the caller all the events that have occurred for the given
    /// aggregate id since the version number supplied.
    fn events_since_version(&self, aggregate_id: &String, version: u64) -> Option<Vec<Self::Events>>;

    /// num_events_since_version provides a vector of events of a length equal to the supplied `num_events`
    /// integer, starting from version + 1, and going up to version + num_events in sequential order.
    ///
    /// Used for re-hydrating aggregates, where the aggregate root can ask for chunks of events that occurred
    /// after it's current version number.
    fn num_events_since_version(&self, aggregate_id: &String, version: u64, num_events: u64) -> Option<Vec<Self::Events>>;


    /// Returns the event if it exists that corresponds to the supplied event_id as an owned type.
    fn get(&self, event_id: &String) -> Option<Self::Events>;

    /// Returns a boolean indicating whether the event repository contains the event by the supplied id.
    fn contains_event(&self, event_id: &String) -> bool {
        self.get(event_id).is_some()
    }

    /// Returns a bool letting the caller know if the event repository contains any events associated with the aggregate id.
    fn contains_aggregate(&self, aggregate_id: &String) -> bool;

    /// Inserts a new domain event into the event store.
    fn insert(&mut self, event: &Self::Events) -> Option<Self::Events>;
}