├── .editorconfig ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── ddd-rs-derive ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── aggregate_root.rs │ ├── entity.rs │ ├── lib.rs │ └── value_object.rs └── ddd-rs ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── application ├── mod.rs ├── repository.rs └── service.rs ├── domain ├── aggregate.rs ├── entity.rs ├── mod.rs └── value_object.rs ├── error.rs ├── infrastructure ├── memory │ ├── mod.rs │ └── repository.rs └── mod.rs └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/git,rust,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=git,rust,visualstudiocode 3 | 4 | ### Git ### 5 | # Created by git for backups. To disable backups in Git: 6 | # $ git config --global mergetool.keepBackup false 7 | *.orig 8 | 9 | # Created by git when using merge tools for conflicts 10 | *.BACKUP.* 11 | *.BASE.* 12 | *.LOCAL.* 13 | *.REMOTE.* 14 | *_BACKUP_*.txt 15 | *_BASE_*.txt 16 | *_LOCAL_*.txt 17 | *_REMOTE_*.txt 18 | 19 | ### Rust ### 20 | # Generated by Cargo 21 | # will have compiled files and executables 22 | debug/ 23 | target/ 24 | 25 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 26 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 27 | Cargo.lock 28 | 29 | # These are backup files generated by rustfmt 30 | **/*.rs.bk 31 | 32 | # MSVC Windows builds of rustc generate these, which store debugging information 33 | *.pdb 34 | 35 | ### VisualStudioCode ### 36 | .vscode/* 37 | !.vscode/settings.json 38 | !.vscode/tasks.json 39 | !.vscode/launch.json 40 | !.vscode/extensions.json 41 | !.vscode/*.code-snippets 42 | 43 | # Local History for Visual Studio Code 44 | .history/ 45 | 46 | # Built Visual Studio Code Extensions 47 | *.vsix 48 | 49 | ### VisualStudioCode Patch ### 50 | # Ignore all local history of files 51 | .history 52 | .ionide 53 | 54 | # End of https://www.toptal.com/developers/gitignore/api/git,rust,visualstudiocode 55 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bungcip.better-toml", 4 | "serayuzgur.crates", 5 | "usernamehw.errorlens", 6 | "rust-lang.rust-analyzer", 7 | "vadimcn.vscode-lldb" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.rulers": [ 4 | 100 5 | ], 6 | "rust-analyzer.check.command": "clippy", 7 | } 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["ddd-rs", "ddd-rs-derive"] 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gabriel Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddd-rs 2 | 3 | Domain-Driven Design (DDD) building blocks, for Rust applications. 4 | 5 | ## Usage 6 | 7 | ```toml 8 | [dependencies] 9 | ddd-rs = "1" 10 | ``` 11 | 12 | ### More 13 | 14 | See the [documentation](https://docs.rs/ddd-rs) for more usage information. 15 | 16 | ## License 17 | 18 | `ddd-rs` is provided under the MIT license. See [LICENSE](LICENSE). 19 | -------------------------------------------------------------------------------- /ddd-rs-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ddd-rs-derive" 3 | version = "1.1.0" 4 | edition = "2021" 5 | authors = ["Gabriel Kim "] 6 | license = "MIT" 7 | description = "`ddd-rs`'s proc macros." 8 | repository = "https://github.com/gabrielkim13/ddd-rs" 9 | homepage = "https://github.com/gabrielkim13/ddd-rs" 10 | categories = ["rust-patterns"] 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | darling = "0.20" 17 | quote = "1" 18 | syn = "2" 19 | -------------------------------------------------------------------------------- /ddd-rs-derive/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ddd-rs-derive/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /ddd-rs-derive/src/aggregate_root.rs: -------------------------------------------------------------------------------- 1 | use darling::FromDeriveInput; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | 5 | #[derive(darling::FromDeriveInput)] 6 | #[darling(attributes(aggregate_root), supports(struct_named))] 7 | struct AggregateRoot { 8 | ident: syn::Ident, 9 | generics: syn::Generics, 10 | data: darling::ast::Data, 11 | } 12 | 13 | #[derive(darling::FromMeta)] 14 | struct DomainEventsMarker; 15 | 16 | #[derive(darling::FromField)] 17 | #[darling(attributes(aggregate_root))] 18 | struct AggregateRootField { 19 | ident: Option, 20 | ty: syn::Type, 21 | domain_events: Option, 22 | } 23 | 24 | pub fn derive(input: TokenStream) -> TokenStream { 25 | let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); 26 | 27 | let AggregateRoot { 28 | ident, 29 | generics, 30 | data, 31 | .. 32 | } = match AggregateRoot::from_derive_input(&derive_input) { 33 | Ok(receiver) => receiver, 34 | Err(e) => return TokenStream::from(e.write_errors()), 35 | }; 36 | 37 | let fields = data.take_struct().unwrap(); 38 | 39 | derive_aggregate_root(ident, generics, fields) 40 | } 41 | 42 | fn derive_aggregate_root( 43 | ident: syn::Ident, 44 | generics: syn::Generics, 45 | fields: darling::ast::Fields, 46 | ) -> TokenStream { 47 | let aggregate_root_ex = fields 48 | .into_iter() 49 | .find_map(|f| { 50 | f.domain_events.map(|_| { 51 | let domain_events_ident = f.ident.unwrap(); 52 | let domain_events_ty = map_domain_event_ty(f.ty); 53 | 54 | quote! { 55 | impl #generics #ident #generics { 56 | fn register_domain_event( 57 | &mut self, 58 | domain_event: ::DomainEvent 59 | ) { 60 | self.#domain_events_ident.push(domain_event); 61 | } 62 | } 63 | 64 | impl #generics ddd_rs::domain::AggregateRootEx for #ident #generics { 65 | type DomainEvent = #domain_events_ty; 66 | 67 | fn take_domain_events(&mut self) -> Vec { 68 | self.#domain_events_ident.drain(..).collect() 69 | } 70 | } 71 | } 72 | }) 73 | }) 74 | .unwrap_or_default(); 75 | 76 | quote! { 77 | impl #generics ddd_rs::domain::AggregateRoot for #ident #generics {} 78 | 79 | #aggregate_root_ex 80 | } 81 | .into() 82 | } 83 | 84 | fn map_domain_event_ty(ty: syn::Type) -> syn::Type { 85 | use syn::{GenericArgument, PathArguments, Type}; 86 | 87 | let mut ty_path = match ty { 88 | Type::Path(syn::TypePath { path, .. }) => path, 89 | _ => panic!("Domain events field type must be of kind `syn::Path`"), 90 | }; 91 | 92 | let syn::PathSegment { 93 | ident, arguments, .. 94 | } = ty_path.segments.pop().unwrap().into_value(); 95 | 96 | if ident != "Vec" { 97 | panic!("Domain events field must be a `Vec` when deriving this trait"); 98 | } 99 | 100 | let arg = match arguments { 101 | PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { mut args, .. }) => { 102 | args.pop().unwrap().into_value() 103 | } 104 | _ => unreachable!(), 105 | }; 106 | 107 | match arg { 108 | GenericArgument::Type(ty) => ty, 109 | _ => unreachable!(), 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ddd-rs-derive/src/entity.rs: -------------------------------------------------------------------------------- 1 | use darling::FromDeriveInput; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | 5 | #[derive(darling::FromDeriveInput)] 6 | #[darling(attributes(entity), supports(struct_named))] 7 | struct Entity { 8 | ident: syn::Ident, 9 | generics: syn::Generics, 10 | data: darling::ast::Data, 11 | } 12 | 13 | #[derive(darling::FromMeta)] 14 | struct IdMarker; 15 | 16 | #[derive(darling::FromField)] 17 | #[darling(attributes(entity))] 18 | struct EntityField { 19 | ident: Option, 20 | ty: syn::Type, 21 | id: Option, 22 | } 23 | 24 | pub fn derive(input: TokenStream) -> TokenStream { 25 | let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); 26 | 27 | let Entity { 28 | ident, 29 | generics, 30 | data, 31 | .. 32 | } = match Entity::from_derive_input(&derive_input) { 33 | Ok(entity) => entity, 34 | Err(e) => return TokenStream::from(e.write_errors()), 35 | }; 36 | 37 | let fields = data.take_struct().unwrap(); 38 | 39 | derive_entity(ident, generics, fields) 40 | } 41 | 42 | fn derive_entity( 43 | ident: syn::Ident, 44 | generics: syn::Generics, 45 | fields: darling::ast::Fields, 46 | ) -> TokenStream { 47 | let id_field = fields 48 | .into_iter() 49 | .find(|f| f.id.is_some()) 50 | .expect("Missing `id` field"); 51 | 52 | let id_ident = id_field.ident.unwrap(); 53 | let id_ty = id_field.ty; 54 | 55 | quote! { 56 | impl #generics ddd_rs::domain::Entity for #ident #generics { 57 | type Id = #id_ty; 58 | 59 | fn id(&self) -> &Self::Id { 60 | &self.#id_ident 61 | } 62 | } 63 | 64 | impl #generics PartialEq for #ident #generics { 65 | fn eq(&self, other: &Self) -> bool { 66 | use ddd_rs::domain::Entity; 67 | 68 | self.id() == other.id() 69 | } 70 | } 71 | 72 | impl #generics Eq for #ident #generics {} 73 | } 74 | .into() 75 | } 76 | -------------------------------------------------------------------------------- /ddd-rs-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # ddd-rs-derive 2 | //! 3 | //! `ddd-rs`'s proc macros. 4 | 5 | #![warn(missing_docs)] 6 | 7 | mod aggregate_root; 8 | mod entity; 9 | mod value_object; 10 | 11 | use proc_macro::TokenStream; 12 | 13 | /// Proc macro for deriving the `AggregateRoot` trait. 14 | /// 15 | /// Use the `#[aggregate_root(domain_events)]` attribute to tag the domain events field of the 16 | /// aggregate root, which is assumed to be a `Vec`. 17 | #[proc_macro_derive(AggregateRoot, attributes(aggregate_root))] 18 | pub fn derive_aggregate_root(input: TokenStream) -> TokenStream { 19 | aggregate_root::derive(input) 20 | } 21 | 22 | /// Proc macro for deriving the `Entity` trait. 23 | /// 24 | /// Use the `#[entity(id)]` attribute to tag the identity (ID) field of the entity. 25 | #[proc_macro_derive(Entity, attributes(entity))] 26 | pub fn derive_entity(input: TokenStream) -> TokenStream { 27 | entity::derive(input) 28 | } 29 | 30 | /// Proc macro for deriving the `ValueObject` trait. 31 | /// 32 | /// Use the `#[value_object(eq)]` attribute to tag which fields should be considered as equality 33 | /// components when comparing value objects. 34 | #[proc_macro_derive(ValueObject, attributes(value_object))] 35 | pub fn derive_value_object(input: TokenStream) -> TokenStream { 36 | value_object::derive(input) 37 | } 38 | -------------------------------------------------------------------------------- /ddd-rs-derive/src/value_object.rs: -------------------------------------------------------------------------------- 1 | use darling::FromDeriveInput; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | 5 | #[derive(darling::FromDeriveInput)] 6 | #[darling(attributes(value_object), supports(struct_named))] 7 | struct ValueObject { 8 | ident: syn::Ident, 9 | generics: syn::Generics, 10 | data: darling::ast::Data, 11 | } 12 | 13 | #[derive(darling::FromMeta)] 14 | struct EqMarker; 15 | 16 | #[derive(darling::FromField)] 17 | #[darling(attributes(value_object))] 18 | struct ValueObjectField { 19 | ident: Option, 20 | eq: Option, 21 | } 22 | 23 | pub fn derive(input: TokenStream) -> TokenStream { 24 | let derive_input = syn::parse_macro_input!(input as syn::DeriveInput); 25 | 26 | let ValueObject { 27 | ident, 28 | generics, 29 | data, 30 | .. 31 | } = match ValueObject::from_derive_input(&derive_input) { 32 | Ok(receiver) => receiver, 33 | Err(e) => return TokenStream::from(e.write_errors()), 34 | }; 35 | 36 | let fields = data.take_struct().unwrap(); 37 | 38 | derive_value_object(ident, generics, fields) 39 | } 40 | 41 | fn derive_value_object( 42 | ident: syn::Ident, 43 | generics: syn::Generics, 44 | fields: darling::ast::Fields, 45 | ) -> TokenStream { 46 | let fields = fields 47 | .into_iter() 48 | .map(|f| (f.eq, f.ident.as_ref().map(|ident| quote!(#ident)).unwrap())) 49 | .collect::>(); 50 | 51 | let field = fields.iter().map(|(_, f)| f); 52 | let eq_field = fields.iter().filter_map(|(eq, f)| eq.as_ref().map(|_| f)); 53 | 54 | quote! { 55 | impl #generics ddd_rs::domain::ValueObject for #ident #generics {} 56 | 57 | impl #generics Clone for #ident #generics { 58 | fn clone(&self) -> Self { 59 | Self { 60 | #(#field: self.#field.clone(),)* 61 | } 62 | } 63 | } 64 | 65 | impl #generics PartialEq for #ident #generics { 66 | fn eq(&self, other: &Self) -> bool { 67 | true #( && self.#eq_field == other.#eq_field)* 68 | } 69 | } 70 | } 71 | .into() 72 | } 73 | -------------------------------------------------------------------------------- /ddd-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ddd-rs" 3 | version = "1.2.1" 4 | edition = "2021" 5 | authors = ["Gabriel Kim "] 6 | license = "MIT" 7 | description = "Domain-Driven Design (DDD) building blocks, for Rust applications." 8 | repository = "https://github.com/gabrielkim13/ddd-rs" 9 | homepage = "https://github.com/gabrielkim13/ddd-rs" 10 | categories = ["rust-patterns"] 11 | 12 | [dependencies] 13 | async-trait = "0.1" 14 | ddd-rs-derive = { version = "=1.1.0", optional = true, path = "../ddd-rs-derive" } 15 | 16 | [dev-dependencies] 17 | tokio-test = "0.4" 18 | 19 | [features] 20 | default = ["derive"] 21 | 22 | # Provides `derive` macros. 23 | derive = ["ddd-rs-derive"] 24 | -------------------------------------------------------------------------------- /ddd-rs/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ddd-rs/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /ddd-rs/src/application/mod.rs: -------------------------------------------------------------------------------- 1 | mod repository; 2 | pub use repository::*; 3 | 4 | mod service; 5 | pub use service::*; 6 | -------------------------------------------------------------------------------- /ddd-rs/src/application/repository.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::domain::{AggregateRoot, AggregateRootEx, Entity}; 4 | 5 | use super::DomainEventHandler; 6 | 7 | /// Trait for representing a **Repository**. 8 | /// 9 | /// > Therefore, use a Repository, the purpose of which is to encapsulate all the logic needed to 10 | /// > obtain object references. The domain objects won’t have to deal with the infrastructure to get 11 | /// > the needed references to other objects of the domain. They will just get them from the 12 | /// > Repository and the model is regaining its clarity and focus. 13 | /// 14 | /// # Examples 15 | /// 16 | /// This example uses the [InMemoryRepository](crate::infrastructure::memory::InMemoryRepository) 17 | /// which is a sample implementation of this trait. 18 | /// 19 | /// ``` 20 | /// use ddd_rs::{ 21 | /// application::{ReadRepository, Repository}, 22 | /// infrastructure::InMemoryRepository 23 | /// }; 24 | /// 25 | /// // By definition, only `AggregateRoot`s have repositories. 26 | /// // 27 | /// // Common entities must be retrieved and persisted through their associated aggregate roots. 28 | /// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity, Clone)] 29 | /// struct MyEntity { 30 | /// #[entity(id)] 31 | /// id: u32, 32 | /// my_field: String, 33 | /// } 34 | /// 35 | /// impl MyEntity { 36 | /// pub fn new(id: u32, my_field: impl ToString) -> Self { 37 | /// Self { 38 | /// id, 39 | /// my_field: my_field.to_string(), 40 | /// } 41 | /// } 42 | /// } 43 | /// 44 | /// # tokio_test::block_on(async { 45 | /// let repository: InMemoryRepository = InMemoryRepository::new(); 46 | /// 47 | /// // Add some entities to the repository. 48 | /// repository.add(MyEntity::new(1, "foo")).await.unwrap(); 49 | /// repository.add(MyEntity::new(2, "bar")).await.unwrap(); 50 | /// repository.add(MyEntity::new(3, "baz")).await.unwrap(); 51 | /// 52 | /// // Attempt to retrieve an entity by its ID. 53 | /// let my_entity_2 = repository.get_by_id(2).await.unwrap(); 54 | /// 55 | /// assert!(my_entity_2.is_some()); 56 | /// assert_eq!(my_entity_2.as_ref().map(|e| e.my_field.as_str()), Some("bar")); 57 | /// 58 | /// let mut my_entity_2 = my_entity_2.unwrap(); 59 | /// 60 | /// // Update the entity, then persist its changes. 61 | /// my_entity_2.my_field = "qux".to_string(); 62 | /// 63 | /// let my_entity_2 = repository.update(my_entity_2).await.unwrap(); 64 | /// 65 | /// assert_eq!(my_entity_2.my_field.as_str(), "qux"); 66 | /// 67 | /// // Delete the entity permanently. 68 | /// repository.delete(my_entity_2).await.unwrap(); 69 | /// 70 | /// // Assert it no longer exists. 71 | /// assert!(!repository.exists(2).await.unwrap()); 72 | /// # }) 73 | /// ``` 74 | #[async_trait::async_trait] 75 | pub trait Repository: ReadRepository { 76 | /// Adds an entity to the repository. 77 | async fn add(&self, entity: T) -> crate::Result; 78 | 79 | /// Updates an entity on the repository. 80 | async fn update(&self, entity: T) -> crate::Result; 81 | 82 | /// Deletes the entity from the repository. 83 | async fn delete(&self, entity: T) -> crate::Result<()>; 84 | 85 | /// Adds the given entities to the repository. 86 | async fn add_range(&self, entities: Vec) -> crate::Result> { 87 | let mut added_entities = Vec::new(); 88 | 89 | for entity in entities { 90 | self.add(entity).await.map(|e| added_entities.push(e))?; 91 | } 92 | 93 | Ok(added_entities) 94 | } 95 | 96 | /// Updates the given entities on the repository. 97 | async fn update_range(&self, entities: Vec) -> crate::Result> { 98 | let mut updated_entities = Vec::new(); 99 | 100 | for entity in entities { 101 | self.update(entity) 102 | .await 103 | .map(|e| updated_entities.push(e))?; 104 | } 105 | 106 | Ok(updated_entities) 107 | } 108 | 109 | /// Deletes the given entities from the repository. 110 | async fn delete_range(&self, entities: Vec) -> crate::Result<()> { 111 | for entity in entities { 112 | self.delete(entity).await?; 113 | } 114 | 115 | Ok(()) 116 | } 117 | } 118 | 119 | /// Trait for representing a read-only **Repository**. 120 | /// 121 | /// See the [Repository] trait for the definition of a repository and a sample of its usage. 122 | #[async_trait::async_trait] 123 | pub trait ReadRepository: Send + Sync { 124 | /// Gets an entity with the given ID. 125 | async fn get_by_id(&self, id: ::Id) -> crate::Result>; 126 | 127 | /// Lists all entities within a given page. 128 | async fn list(&self, skip: usize, take: usize) -> crate::Result>; 129 | 130 | /// Returns the total number of entities in the repository. 131 | async fn count(&self) -> crate::Result; 132 | 133 | /// Checks whether an entity with the given ID exists in the repository. 134 | async fn exists(&self, id: ::Id) -> crate::Result { 135 | self.get_by_id(id).await.map(|e| e.is_some()) 136 | } 137 | 138 | /// Checks if the repository is empty. 139 | async fn is_empty(&self) -> crate::Result { 140 | self.count().await.map(|c| c == 0) 141 | } 142 | } 143 | 144 | /// Repository extension abstraction, for performing operations over aggregates that implement the 145 | /// [AggregateRootEx] trait. 146 | /// 147 | /// # Examples 148 | /// 149 | /// Building upon the [Repository] sample, this example shows how a repository object can be 150 | /// extended in order to support concepts from the [AggregateRootEx] trait. 151 | /// 152 | /// ``` 153 | /// use std::sync::Arc; 154 | /// 155 | /// use ddd_rs::{ 156 | /// application::{DomainEventHandler, ReadRepository, Repository, RepositoryEx}, 157 | /// infrastructure::InMemoryRepository 158 | /// }; 159 | /// 160 | /// // The aggregate below requires an action to be performed asynchronously, but doing so directly 161 | /// // would require the aggregate root to: 162 | /// // 163 | /// // - Have a reference to one or many application services, thus breaching the isolation between 164 | /// // the Application and Domain layers; 165 | /// // - Expect an async runtime, which is generally associated with I/O operations and 166 | /// // long-running tasks, to be available for the implementation of business rules. 167 | /// // 168 | /// // These can be seem as contrary to the modeling principles of DDD, since the domain model 169 | /// // should be self-sufficient when enforcing its own business rules. 170 | /// // 171 | /// // Instead, the aggregate will register a domain event requesting the async action to be 172 | /// // performed prior to being persisted to the repository. 173 | /// #[derive(Clone, Debug, PartialEq)] 174 | /// enum MyDomainEvent { 175 | /// AsyncActionRequested { action: String }, 176 | /// } 177 | /// 178 | /// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity, Clone)] 179 | /// struct MyEntity { 180 | /// #[entity(id)] 181 | /// id: u32, 182 | /// last_performed_action: Option, 183 | /// #[aggregate_root(domain_events)] 184 | /// domain_events: Vec, 185 | /// } 186 | /// 187 | /// impl MyEntity { 188 | /// pub fn new(id: u32) -> Self { 189 | /// Self { 190 | /// id, 191 | /// last_performed_action: None, 192 | /// domain_events: Default::default(), 193 | /// } 194 | /// } 195 | /// 196 | /// pub fn request_async_action(&mut self, action: impl ToString) { 197 | /// let domain_event = MyDomainEvent::AsyncActionRequested { action: action.to_string() }; 198 | /// 199 | /// self.register_domain_event(domain_event); 200 | /// } 201 | /// 202 | /// pub fn confirm_async_action_performed(&mut self, action: impl ToString) { 203 | /// self.last_performed_action.replace(action.to_string()); 204 | /// } 205 | /// } 206 | /// 207 | /// // The domain event handler will usually be a context that holds references to all necessary 208 | /// // services and providers to handle domain events. 209 | /// struct MyDomainEventHandler { 210 | /// repository: Arc>, 211 | /// } 212 | /// 213 | /// impl MyDomainEventHandler { 214 | /// pub fn new(repository: Arc>) -> Self { 215 | /// Self { repository } 216 | /// } 217 | /// } 218 | /// 219 | /// #[async_trait::async_trait] 220 | /// impl DomainEventHandler for MyDomainEventHandler { 221 | /// async fn handle(&self, mut entity: MyEntity, event: MyDomainEvent) -> ddd_rs::Result { 222 | /// let action = match event { 223 | /// MyDomainEvent::AsyncActionRequested { action, .. } => action, 224 | /// }; 225 | /// 226 | /// // Perform the async action... 227 | /// 228 | /// entity.confirm_async_action_performed(action); 229 | /// 230 | /// self.repository.update(entity).await 231 | /// } 232 | /// } 233 | /// 234 | /// # tokio_test::block_on(async { 235 | /// 236 | /// // Extend the basic repository to enable processing of domain events registered by the 237 | /// // aggregate, upon persistence. 238 | /// let repository = Arc::new(InMemoryRepository::new()); 239 | /// let domain_event_handler = Arc::new(MyDomainEventHandler::new(repository.clone())); 240 | /// 241 | /// let repository_ex = RepositoryEx::new(domain_event_handler, repository); 242 | /// 243 | /// // Create a new entity and request an async action. 244 | /// let mut entity = MyEntity::new(42); 245 | /// 246 | /// entity.request_async_action("foo"); 247 | /// 248 | /// // Assert that the action was not performed yet, but registered a domain event. 249 | /// assert!(entity.last_performed_action.is_none()); 250 | /// assert_eq!(entity.domain_events.len(), 1); 251 | /// 252 | /// // Persist the entity and assert that the action was performed as a result. 253 | /// repository_ex.add(entity).await.unwrap(); 254 | /// 255 | /// let entity = repository_ex.get_by_id(42).await.unwrap().unwrap(); 256 | /// 257 | /// assert_eq!(entity.last_performed_action.unwrap(), "foo"); 258 | /// assert!(entity.domain_events.is_empty()); 259 | /// # }) 260 | /// ``` 261 | pub struct RepositoryEx { 262 | domain_event_handler: Arc>, 263 | repository: Arc>, 264 | } 265 | 266 | impl RepositoryEx { 267 | /// Creates a new instance of the extended repository. 268 | pub fn new( 269 | domain_event_handler: Arc>, 270 | repository: Arc>, 271 | ) -> Self { 272 | Self { 273 | domain_event_handler, 274 | repository, 275 | } 276 | } 277 | } 278 | 279 | #[async_trait::async_trait] 280 | impl ReadRepository for RepositoryEx { 281 | async fn get_by_id(&self, id: ::Id) -> crate::Result> { 282 | self.repository.get_by_id(id).await 283 | } 284 | 285 | async fn list(&self, skip: usize, take: usize) -> crate::Result> { 286 | self.repository.list(skip, take).await 287 | } 288 | 289 | async fn count(&self) -> crate::Result { 290 | self.repository.count().await 291 | } 292 | } 293 | 294 | #[async_trait::async_trait] 295 | impl Repository for RepositoryEx { 296 | async fn add(&self, mut entity: T) -> crate::Result { 297 | let domain_events = entity.take_domain_events(); 298 | 299 | let mut entity = self.repository.add(entity).await?; 300 | 301 | for event in domain_events { 302 | entity = self.domain_event_handler.handle(entity, event).await?; 303 | } 304 | 305 | Ok(entity) 306 | } 307 | 308 | async fn update(&self, mut entity: T) -> crate::Result { 309 | let domain_events = entity.take_domain_events(); 310 | 311 | let mut entity = self.repository.update(entity).await?; 312 | 313 | for event in domain_events { 314 | entity = self.domain_event_handler.handle(entity, event).await?; 315 | } 316 | 317 | Ok(entity) 318 | } 319 | 320 | async fn delete(&self, mut entity: T) -> crate::Result<()> { 321 | let domain_events = entity.take_domain_events(); 322 | 323 | let mut entity = entity; 324 | 325 | for event in domain_events { 326 | entity = self.domain_event_handler.handle(entity, event).await?; 327 | } 328 | 329 | self.repository.delete(entity).await 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /ddd-rs/src/application/service.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::AggregateRootEx; 2 | 3 | /// Trait for representing a **Request**. 4 | /// 5 | /// Requests are usually [Commands](Command) or [Queries](Query), and their sole requirement is to 6 | /// have an associated `Response` type. 7 | pub trait Request: Send { 8 | /// Request response type. 9 | type Response: Send; 10 | } 11 | 12 | /// Trait for a [Request] that represents a **Command**. 13 | /// 14 | /// > Change the state of a system but do not return a value. 15 | pub trait Command: Send {} 16 | 17 | impl Request for T { 18 | type Response = (); 19 | } 20 | 21 | /// Alias for a [Request] that represents a **Query**. 22 | /// 23 | /// > Return a result and do not change the observable state of the system (are free of side 24 | /// > effects). 25 | pub use Request as Query; 26 | 27 | /// Trait for representing a **Request Handler**. 28 | /// 29 | /// See [Request] for more information about [Commands](Command) and [Queries](Query). 30 | /// 31 | /// # Examples 32 | /// 33 | /// ``` 34 | /// use std::sync::Mutex; 35 | /// 36 | /// use ddd_rs::application::{Command, CommandHandler, Query, QueryHandler}; 37 | /// 38 | /// // Error type for the Fibonacci service. 39 | /// // 40 | /// // It is required to implement the `std::error::Error` trait. 41 | /// 42 | /// #[derive(Debug, PartialEq)] 43 | /// struct FibonacciError(&'static str); 44 | /// 45 | /// impl std::fmt::Display for FibonacciError { 46 | /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | /// self.0.fmt(f) 48 | /// } 49 | /// } 50 | /// 51 | /// impl std::error::Error for FibonacciError {} 52 | /// 53 | /// // Fibonacci service. 54 | /// // 55 | /// // For demonstration purposes, it is implemented as a stateful service. 56 | /// 57 | /// #[derive(Default)] 58 | /// struct FibonacciService { 59 | /// n: Mutex, 60 | /// } 61 | /// 62 | /// impl FibonacciService { 63 | /// fn set(&self, n: u32) { 64 | /// let mut current = self.n.lock().unwrap(); 65 | /// 66 | /// *current = n; 67 | /// } 68 | /// 69 | /// fn get_next(&self) -> u32 { 70 | /// let mut current = self.n.lock().unwrap(); 71 | /// 72 | /// let next = Self::fibonacci(*current); 73 | /// 74 | /// *current = *current + 1; 75 | /// 76 | /// next 77 | /// } 78 | /// 79 | /// fn fibonacci(n: u32) -> u32 { 80 | /// match n { 81 | /// 0 => 0, 82 | /// 1 => 1, 83 | /// _ => Self::fibonacci(n - 1) + Self::fibonacci(n - 2), 84 | /// } 85 | /// } 86 | /// } 87 | /// 88 | /// // Sets the current 0-based element of the Fibonacci sequence. 89 | /// // 90 | /// // `Command`s usually do not return a value, so their `Response` type is automatically `()`. 91 | /// 92 | /// struct SetFibonacciCommand { 93 | /// n: u32, 94 | /// } 95 | /// 96 | /// impl Command for SetFibonacciCommand {} 97 | /// 98 | /// #[async_trait::async_trait] 99 | /// impl CommandHandler for FibonacciService { 100 | /// type Error = FibonacciError; 101 | /// 102 | /// async fn handle(&self, command: SetFibonacciCommand) -> Result<(), Self::Error> { 103 | /// self.set(command.n); 104 | /// 105 | /// Ok(()) 106 | /// } 107 | /// } 108 | /// 109 | /// // Gets the next element of the Fibonacci sequence. 110 | /// // 111 | /// // `Query`s are issued in order to retrieve a value, but without causing any side-effects to the 112 | /// // underlying state of the system. 113 | /// // 114 | /// // The more general `Request` trait can be used for actions that have side-effects but also 115 | /// // require a value to be returned as its result. 116 | /// 117 | /// struct GetNextFibonacciQuery; 118 | /// 119 | /// impl Query for GetNextFibonacciQuery { 120 | /// type Response = u32; 121 | /// } 122 | /// 123 | /// #[async_trait::async_trait] 124 | /// impl QueryHandler for FibonacciService { 125 | /// type Error = FibonacciError; 126 | /// 127 | /// async fn handle(&self, _query: GetNextFibonacciQuery) -> Result { 128 | /// Ok(self.get_next()) 129 | /// } 130 | /// } 131 | /// 132 | /// // Finally, instantiate and perform `Request`s to the `FibonacciService`. 133 | /// 134 | /// # tokio_test::block_on(async { 135 | /// let fibonacci = FibonacciService::default(); 136 | /// 137 | /// assert_eq!(fibonacci.handle(SetFibonacciCommand { n: 10 }).await, Ok(())); 138 | /// assert_eq!(fibonacci.handle(GetNextFibonacciQuery).await, Ok(55)); 139 | /// assert_eq!(fibonacci.handle(GetNextFibonacciQuery).await, Ok(89)); 140 | /// assert_eq!(fibonacci.handle(GetNextFibonacciQuery).await, Ok(144)); 141 | /// # }) 142 | /// ``` 143 | #[async_trait::async_trait] 144 | pub trait RequestHandler: Send + Sync { 145 | /// Request handler error type. 146 | type Error: std::error::Error; 147 | 148 | /// Handles the incoming [Request], returning its [Response](Request::Response) as a [Result]. 149 | async fn handle(&self, request: T) -> Result<::Response, Self::Error>; 150 | } 151 | 152 | /// Alias for a [RequestHandler] specific to [Commands](Command). 153 | pub use RequestHandler as CommandHandler; 154 | 155 | /// Alias for a [RequestHandler] specific to [Queries](Query). 156 | pub use RequestHandler as QueryHandler; 157 | 158 | /// Trait for representing a **Domain Event Handler**. 159 | /// 160 | /// See [AggregateRootEx] for more information about **Domain Events** and the example for 161 | /// [RepositoryEx](super::RepositoryEx) about this trait's usage. 162 | #[async_trait::async_trait] 163 | pub trait DomainEventHandler: Send + Sync { 164 | /// Handles the incoming domain event, applying any necessary changes to the entity. 165 | async fn handle(&self, entity: T, event: T::DomainEvent) -> crate::Result; 166 | } 167 | -------------------------------------------------------------------------------- /ddd-rs/src/domain/aggregate.rs: -------------------------------------------------------------------------------- 1 | /// Trait for representing an **Aggregate Root**. 2 | /// 3 | /// > An Aggregate is a group of associated objects which are considered as one unit with regard to 4 | /// > data changes. The Aggregate is demarcated by a boundary which separates the objects inside 5 | /// > from those outside. Each Aggregate has one root. The root is an Entity, and it is the only 6 | /// > object accessible from outside. The root can hold references to any of the aggregate objects, 7 | /// > and the other objects can hold references to each other, but an outside object can hold 8 | /// > references only to the root object. If there are other Entities inside the boundary, the 9 | /// > identity of those entities is local, making sense only inside the aggregate. 10 | /// 11 | /// # Examples 12 | /// 13 | /// Derive its implementation using the [ddd_rs::AggregateRoot](crate::AggregateRoot) macro: 14 | /// 15 | /// ``` 16 | /// // The `AggregateRoot` usually holds references to other entities, acting as a means to access 17 | /// // and even modify them. 18 | /// // 19 | /// // Note that we also need to derive the `Entity` trait, since an `AggregateRoot` is an `Entity`. 20 | /// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity)] 21 | /// struct MyAggregateRoot { 22 | /// #[entity(id)] 23 | /// id: u32, 24 | /// foo: Foo, 25 | /// bars: Vec, 26 | /// } 27 | /// 28 | /// #[derive(ddd_rs::Entity)] 29 | /// struct Foo { 30 | /// #[entity(id)] 31 | /// id: u32, 32 | /// foo: String, 33 | /// } 34 | /// 35 | /// #[derive(ddd_rs::Entity)] 36 | /// struct Bar { 37 | /// #[entity(id)] 38 | /// id: String, 39 | /// bar: u32, 40 | /// } 41 | /// ``` 42 | pub trait AggregateRoot: super::Entity + Send + Sync + 'static {} 43 | 44 | /// Extensions to the [AggregateRoot] behavior. 45 | /// 46 | /// # Examples 47 | /// 48 | /// Implement this trait explicitly when you need non-trivial use-cases, such as registering domain 49 | /// events on immutable instances of your entity: 50 | /// 51 | /// ``` 52 | /// use std::sync::Mutex; 53 | /// 54 | /// use ddd_rs::domain::AggregateRootEx; 55 | /// 56 | /// // The `DomainEvent` will usually be an arithmetic enum type, in order to allow for multiple 57 | /// // distinguishable event kinds within a single type. 58 | /// #[derive(Debug, PartialEq)] 59 | /// enum MyDomainEvent { 60 | /// DidSomething { something: String }, 61 | /// DidSomethingElse { something_else: String }, 62 | /// } 63 | /// 64 | /// // The `AggregateRoot` owns a list of its own `DomainEvent`s. 65 | /// // 66 | /// // The [Interior Mutability](https://doc.rust-lang.org/reference/interior-mutability.html) 67 | /// // pattern may be relevant when semantically immutable actions need to register domain events. 68 | /// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity)] 69 | /// struct MyAggregateRoot { 70 | /// #[entity(id)] 71 | /// id: u32, 72 | /// domain_events: Mutex>, 73 | /// } 74 | /// 75 | /// // The aggregate root's methods may register domain events upon different actions. 76 | /// impl MyAggregateRoot { 77 | /// pub fn new(id: u32) -> Self { 78 | /// Self { 79 | /// id, 80 | /// domain_events: Default::default(), 81 | /// } 82 | /// } 83 | /// 84 | /// pub fn do_something(&self, something: impl ToString) { 85 | /// let something = something.to_string(); 86 | /// 87 | /// // Do something... 88 | /// 89 | /// self.register_domain_event(MyDomainEvent::DidSomething { something }); 90 | /// } 91 | /// 92 | /// pub fn do_something_else(&mut self, something_else: impl ToString) { 93 | /// let something_else = something_else.to_string(); 94 | /// 95 | /// // Do something else... 96 | /// 97 | /// self.register_domain_event(MyDomainEvent::DidSomethingElse { something_else }); 98 | /// } 99 | /// 100 | /// fn register_domain_event(&self, domain_event: ::DomainEvent) { 101 | /// let mut domain_events = self.domain_events.lock().unwrap(); 102 | /// 103 | /// domain_events.push(domain_event); 104 | /// } 105 | /// } 106 | /// 107 | /// impl AggregateRootEx for MyAggregateRoot { 108 | /// type DomainEvent = MyDomainEvent; 109 | /// 110 | /// fn take_domain_events(&mut self) -> Vec { 111 | /// let mut domain_events = self.domain_events.lock().unwrap(); 112 | /// 113 | /// domain_events.drain(..).collect() 114 | /// } 115 | /// } 116 | /// 117 | /// let aggregate_root = MyAggregateRoot::new(42); 118 | /// 119 | /// // This registers a `MyDomainEvent::DidSomething` event. 120 | /// // 121 | /// // Note that this happens under an immutable reference to the aggregate. 122 | /// aggregate_root.do_something("foo"); 123 | /// 124 | /// let mut aggregate_root = aggregate_root; 125 | /// 126 | /// // This registers a `MyDomainEvent::DidSomethingElse` event. 127 | /// aggregate_root.do_something_else("bar"); 128 | /// 129 | /// // Take the domain events and assert that they are gone afterwards. 130 | /// let domain_events = aggregate_root.take_domain_events(); 131 | /// 132 | /// assert_eq!( 133 | /// domain_events[0], 134 | /// MyDomainEvent::DidSomething { 135 | /// something: "foo".to_string() 136 | /// } 137 | /// ); 138 | /// assert_eq!( 139 | /// domain_events[1], 140 | /// MyDomainEvent::DidSomethingElse { 141 | /// something_else: "bar".to_string() 142 | /// } 143 | /// ); 144 | /// 145 | /// assert!(aggregate_root.take_domain_events().is_empty()); 146 | /// ``` 147 | /// 148 | /// Otherwise, derive its implementation using the [ddd_rs::AggregateRoot](crate::AggregateRoot) 149 | /// macro and the `#[aggregate_root(domain_events)]` attribute: 150 | /// 151 | /// ``` 152 | /// use ddd_rs::domain::AggregateRootEx; 153 | /// 154 | /// #[derive(Debug, PartialEq)] 155 | /// enum MyDomainEvent { 156 | /// DidSomething { something: String }, 157 | /// } 158 | /// 159 | /// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity)] 160 | /// struct MyAggregateRoot { 161 | /// #[entity(id)] 162 | /// id: u32, 163 | /// #[aggregate_root(domain_events)] 164 | /// domain_events: Vec, 165 | /// } 166 | /// 167 | /// impl MyAggregateRoot { 168 | /// pub fn new(id: u32) -> Self { 169 | /// Self { 170 | /// id, 171 | /// domain_events: Default::default(), 172 | /// } 173 | /// } 174 | /// 175 | /// pub fn do_something(&mut self, something: impl ToString) { 176 | /// let something = something.to_string(); 177 | /// 178 | /// // Do something... 179 | /// 180 | /// // The `register_domain_event` method is automatically derived. 181 | /// self.register_domain_event(MyDomainEvent::DidSomething { something }); 182 | /// } 183 | /// } 184 | /// 185 | /// // This time around, the aggregate may only register domain events on mutable methods. 186 | /// let mut aggregate_root = MyAggregateRoot::new(42); 187 | /// 188 | /// aggregate_root.do_something("foo"); 189 | /// 190 | /// let domain_events = aggregate_root.take_domain_events(); 191 | /// 192 | /// assert_eq!( 193 | /// domain_events[0], 194 | /// MyDomainEvent::DidSomething { 195 | /// something: "foo".to_string() 196 | /// } 197 | /// ); 198 | /// 199 | /// assert!(aggregate_root.take_domain_events().is_empty()); 200 | /// ``` 201 | pub trait AggregateRootEx: AggregateRoot { 202 | /// Domain event type. 203 | /// 204 | /// > Use domain events to explicitly implement side effects of changes within your domain. 205 | /// > In other words, and using DDD terminology, use domain events to explicitly implement side 206 | /// > effects across multiple aggregates. 207 | type DomainEvent: Send; 208 | 209 | /// Clears all domain events from the aggregate, returning them in order of occurrence. 210 | fn take_domain_events(&mut self) -> Vec; 211 | } 212 | -------------------------------------------------------------------------------- /ddd-rs/src/domain/entity.rs: -------------------------------------------------------------------------------- 1 | /// Trait for representing an **Entity**. 2 | /// 3 | /// > There is a category of objects which seem to have an identity, which remains the same 4 | /// > throughout the states of the software. For these objects it is not the attributes which 5 | /// > matter, but a thread of continuity and identity, which spans the life of a system and can 6 | /// > extend beyond it. Such objects are called Entities. 7 | /// 8 | /// # Examples 9 | /// 10 | /// Derive its implementation using the [ddd_rs::Entity](crate::Entity) macro: 11 | /// 12 | /// ``` 13 | /// use ddd_rs::domain::Entity; 14 | /// 15 | /// // Annotate the identity field with the `#[entity(id)]` attribute. 16 | /// #[derive(ddd_rs::Entity, Debug)] 17 | /// struct MyEntity { 18 | /// #[entity(id)] 19 | /// code: u32, 20 | /// my_field: String, 21 | /// } 22 | /// 23 | /// impl MyEntity { 24 | /// pub fn new(code: u32, my_field: impl ToString) -> Self { 25 | /// Self { 26 | /// code, 27 | /// my_field: my_field.to_string(), 28 | /// } 29 | /// } 30 | /// } 31 | /// 32 | /// let a = MyEntity::new(1, "foo"); 33 | /// let b = MyEntity::new(1, "bar"); 34 | /// let c = MyEntity::new(2, "foo"); 35 | /// 36 | /// // By definition, `Entity` equality is based exclusively on their identity. 37 | /// assert_eq!(a, b); 38 | /// assert_eq!(a.id(), b.id()); 39 | /// 40 | /// assert_ne!(a, c); 41 | /// assert_ne!(a.id(), c.id()); 42 | /// ``` 43 | pub trait Entity: Eq + PartialEq { 44 | /// Identity type. 45 | type Id: Clone + PartialEq + Send + Sync; 46 | 47 | /// Identity. 48 | fn id(&self) -> &Self::Id; 49 | } 50 | -------------------------------------------------------------------------------- /ddd-rs/src/domain/mod.rs: -------------------------------------------------------------------------------- 1 | mod aggregate; 2 | pub use aggregate::*; 3 | 4 | mod entity; 5 | pub use entity::*; 6 | 7 | mod value_object; 8 | pub use value_object::*; 9 | -------------------------------------------------------------------------------- /ddd-rs/src/domain/value_object.rs: -------------------------------------------------------------------------------- 1 | /// Trait for representing a **Value Object**. 2 | /// 3 | /// > There are cases when we need to contain some attributes of a domain element. We are not 4 | /// > interested in which object it is, but what attributes it has. An object that is used to 5 | /// > describe certain aspects of a domain, and which does not have identity, is named Value Object. 6 | /// 7 | /// # Examples 8 | /// 9 | /// Derive its implementation using the [ddd_rs::ValueObject](crate::ValueObject) macro: 10 | /// 11 | /// ``` 12 | /// // Annotate the equality components with the `#[value_object(eq)]` attribute. 13 | /// #[derive(ddd_rs::ValueObject, Debug)] 14 | /// struct MyValueObject { 15 | /// #[value_object(eq)] 16 | /// x: bool, 17 | /// y: Option, 18 | /// #[value_object(eq)] 19 | /// z: String, 20 | /// } 21 | /// 22 | /// let a = MyValueObject { x: true, y: Some(42), z: String::from("foo") }; 23 | /// let b = MyValueObject { x: true, y: Some(-1), z: String::from("foo") }; 24 | /// let c = MyValueObject { x: false, y: Some(42), z: String::from("bar") }; 25 | /// 26 | /// // `ValueObject`s are equal if all their equality components are equal. 27 | /// assert_eq!(a, b); 28 | /// assert_ne!(a, c); 29 | /// 30 | /// // They are also cloneable, by definition. 31 | /// let a_clone = a.clone(); 32 | /// 33 | /// assert_eq!(a.x, a_clone.x); 34 | /// assert_eq!(a.y, a_clone.y); 35 | /// assert_eq!(a.z, a_clone.z); 36 | /// ``` 37 | pub trait ValueObject: Clone + PartialEq {} 38 | -------------------------------------------------------------------------------- /ddd-rs/src/error.rs: -------------------------------------------------------------------------------- 1 | /// Alias for a type-erased error type. 2 | pub type BoxError = Box; 3 | 4 | /// `Result` type with a pre-defined [BoxError] error variant. 5 | pub type Result = std::result::Result; 6 | -------------------------------------------------------------------------------- /ddd-rs/src/infrastructure/memory/mod.rs: -------------------------------------------------------------------------------- 1 | mod repository; 2 | pub use repository::*; 3 | -------------------------------------------------------------------------------- /ddd-rs/src/infrastructure/memory/repository.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::application::{ReadRepository, Repository}; 4 | use crate::domain::{AggregateRoot, Entity}; 5 | 6 | /// An in-memory implementation of [Repository], using a [HashMap]. 7 | /// 8 | /// See the example on [Repository] for usage information of this repository implementation. 9 | pub struct InMemoryRepository { 10 | entities: std::sync::RwLock::Id, T>>, 11 | } 12 | 13 | impl InMemoryRepository { 14 | /// Creates a new [InMemoryRepository]. 15 | pub fn new() -> Self { 16 | Self { 17 | entities: std::sync::RwLock::new(HashMap::new()), 18 | } 19 | } 20 | } 21 | 22 | impl Default for InMemoryRepository { 23 | fn default() -> Self { 24 | Self::new() 25 | } 26 | } 27 | 28 | #[async_trait::async_trait] 29 | impl ReadRepository for InMemoryRepository 30 | where 31 | ::Id: std::hash::Hash + Eq, 32 | { 33 | async fn get_by_id(&self, id: ::Id) -> crate::Result> { 34 | let ro_entities = self.entities.read().unwrap(); 35 | 36 | let entity = ro_entities.get(&id).cloned(); 37 | 38 | Ok(entity) 39 | } 40 | 41 | async fn list(&self, skip: usize, take: usize) -> crate::Result> { 42 | let ro_entities = self.entities.read().unwrap(); 43 | 44 | let entities = ro_entities 45 | .values() 46 | .skip(skip) 47 | .take(take) 48 | .cloned() 49 | .collect(); 50 | 51 | Ok(entities) 52 | } 53 | 54 | async fn count(&self) -> crate::Result { 55 | let ro_entities = self.entities.read().unwrap(); 56 | 57 | Ok(ro_entities.len()) 58 | } 59 | } 60 | 61 | #[async_trait::async_trait] 62 | impl Repository for InMemoryRepository 63 | where 64 | ::Id: std::hash::Hash + Eq, 65 | { 66 | async fn add(&self, entity: T) -> crate::Result { 67 | let mut wo_entities = self.entities.write().unwrap(); 68 | 69 | wo_entities.insert(entity.id().clone(), entity.clone()); 70 | 71 | Ok(entity) 72 | } 73 | 74 | async fn update(&self, entity: T) -> crate::Result { 75 | self.add(entity).await 76 | } 77 | 78 | async fn delete(&self, entity: T) -> crate::Result<()> { 79 | let mut wo_entities = self.entities.write().unwrap(); 80 | 81 | wo_entities.remove(entity.id()); 82 | 83 | Ok(()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ddd-rs/src/infrastructure/mod.rs: -------------------------------------------------------------------------------- 1 | /// In-memory (i.e. in-process) infrastructure 2 | pub mod memory; 3 | pub use memory::*; 4 | -------------------------------------------------------------------------------- /ddd-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # ddd-rs 2 | //! 3 | //! Domain-Driven Design (DDD) building blocks, for Rust applications. 4 | //! 5 | //! > Most of the definitions on these docs are taken from Eric Evan's 6 | //! > [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://www.oreilly.com/library/view/domain-driven-design-tackling/0321125215/). 7 | //! 8 | //! ## Application layer 9 | //! 10 | //! - [Repository](application::Repository) 11 | //! - Service: 12 | //! - [Command](application::Command) / [Query](application::Query) 13 | //! - [Request](application::Request) 14 | //! - [RequestHandler](application::RequestHandler) 15 | //! 16 | //! ## Domain layer 17 | //! 18 | //! - [AggregateRoot](domain::AggregateRoot) 19 | //! - [Entity](domain::Entity) 20 | //! - [ValueObject](domain::ValueObject) 21 | //! 22 | //! ## Infrastructure layer 23 | //! 24 | //! - In-memory: 25 | //! - [InMemoryRepository](infrastructure::InMemoryRepository) 26 | 27 | #![warn(missing_docs)] 28 | 29 | /// Application layer 30 | pub mod application; 31 | 32 | /// Domain layer 33 | pub mod domain; 34 | 35 | /// Infrastructure layer 36 | pub mod infrastructure; 37 | 38 | mod error; 39 | pub use error::*; 40 | 41 | #[cfg(feature = "derive")] 42 | pub use ddd_rs_derive::*; 43 | --------------------------------------------------------------------------------