Skip to main content

ddd_toolkit_core/domain/
entity.rs

1use crate::domain::EntityId;
2
3/// A domain object whose equality is defined by its identity, not its
4/// attributes - the same conceptual entity can change all of its other
5/// fields over time and still be "the same" entity.
6///
7/// This trait deliberately does not require [`PartialEq`]/[`Eq`]: nothing
8/// stops an implementor from also deriving `PartialEq`, but a derived
9/// `PartialEq` compares *every* field structurally, which is almost never
10/// the identity comparison a DDD Entity is supposed to have. Use
11/// [`Entity::is_same_as`] wherever "is this the same entity" is the
12/// question being asked; if you do implement `PartialEq`/`Eq` for an
13/// `Entity`, implement it in terms of `is_same_as` rather than deriving it,
14/// so `==` and `is_same_as` cannot silently disagree.
15pub trait Entity: Send + Sync {
16    /// This entity's identity type.
17    type Id: EntityId;
18
19    /// This entity's identity.
20    fn id(&self) -> &Self::Id;
21
22    /// Identity comparison: true iff `self` and `other` are the same
23    /// entity, regardless of whether their other fields currently match.
24    fn is_same_as(&self, other: &Self) -> bool {
25        self.id() == other.id()
26    }
27}
28
29#[cfg(test)]
30mod test {
31    use std::fmt::Display;
32
33    use crate::domain::ValueObject;
34
35    use super::*;
36
37    #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
38    struct FooId(String);
39
40    impl ValueObject for FooId {}
41
42    impl Display for FooId {
43        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44            write!(f, "{}", self.0)
45        }
46    }
47
48    impl EntityId for FooId {}
49
50    struct Foo {
51        id: FooId,
52        value: String,
53    }
54
55    impl Entity for Foo {
56        type Id = FooId;
57        fn id(&self) -> &Self::Id {
58            &self.id
59        }
60    }
61
62    #[test]
63    fn id_returns_reference_to_entity_id() {
64        let foo = Foo {
65            id: FooId("foo-1".to_string()),
66            value: "value".to_string(),
67        };
68
69        assert_eq!(foo.id(), &FooId("foo-1".to_string()));
70    }
71
72    #[test]
73    fn is_same_as_returns_true_for_same_id_and_same_value() {
74        let a = Foo {
75            id: FooId("foo-1".to_string()),
76            value: "value".to_string(),
77        };
78        let b = Foo {
79            id: FooId("foo-1".to_string()),
80            value: "value".to_string(),
81        };
82
83        assert!(a.is_same_as(&b));
84    }
85
86    #[test]
87    fn is_same_as_returns_true_for_same_id_and_different_value() {
88        let a = Foo {
89            id: FooId("foo-1".to_string()),
90            value: "value-a".to_string(),
91        };
92        let b = Foo {
93            id: FooId("foo-1".to_string()),
94            value: "value-b".to_string(),
95        };
96
97        assert_ne!(a.value, b.value);
98        assert!(a.is_same_as(&b));
99    }
100
101    #[test]
102    fn is_same_as_returns_false_for_different_id_and_same_value() {
103        let a = Foo {
104            id: FooId("foo-1".to_string()),
105            value: "value".to_string(),
106        };
107        let b = Foo {
108            id: FooId("foo-2".to_string()),
109            value: "value".to_string(),
110        };
111
112        assert!(!a.is_same_as(&b));
113    }
114
115    #[test]
116    fn is_same_as_returns_true_for_self_comparison() {
117        let foo = Foo {
118            id: FooId("foo-1".to_string()),
119            value: "value".to_string(),
120        };
121
122        assert!(foo.is_same_as(&foo));
123    }
124
125    #[test]
126    fn is_same_as_returns_true_for_empty_string_ids() {
127        let a = Foo {
128            id: FooId(String::new()),
129            value: "value-a".to_string(),
130        };
131        let b = Foo {
132            id: FooId(String::new()),
133            value: "value-b".to_string(),
134        };
135
136        assert!(a.is_same_as(&b));
137    }
138
139    #[test]
140    fn entity_impl_satisfies_send_and_sync() {
141        fn assert_impl<T: Send + Sync>() {}
142        assert_impl::<Foo>();
143    }
144}