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>; }