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.
15///
16/// # Examples
17///
18/// See the [crate-level example](crate) for a full walkthrough implementing
19/// `Entity` alongside [`EntityId`] and [`crate::domain::ValueObject`].
20pub trait Entity: Send + Sync {
21    /// This entity's identity type.
22    type Id: EntityId;
23
24    /// This entity's identity.
25    fn id(&self) -> &Self::Id;
26
27    /// Identity comparison: true iff `self` and `other` are the same
28    /// entity, regardless of whether their other fields currently match.
29    fn is_same_as(&self, other: &Self) -> bool {
30        self.id() == other.id()
31    }
32}
33
34#[cfg(test)]
35mod test {
36    use std::fmt::Display;
37
38    use crate::domain::ValueObject;
39
40    use super::*;
41
42    #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
43    struct FooId(String);
44
45    impl ValueObject for FooId {}
46
47    impl Display for FooId {
48        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49            write!(f, "{}", self.0)
50        }
51    }
52
53    impl EntityId for FooId {}
54
55    struct Foo {
56        id: FooId,
57        value: String,
58    }
59
60    impl Entity for Foo {
61        type Id = FooId;
62        fn id(&self) -> &Self::Id {
63            &self.id
64        }
65    }
66
67    #[test]
68    fn id_returns_reference_to_entity_id() {
69        let foo = Foo {
70            id: FooId("foo-1".to_string()),
71            value: "value".to_string(),
72        };
73
74        assert_eq!(foo.id(), &FooId("foo-1".to_string()));
75    }
76
77    #[test]
78    fn is_same_as_returns_true_for_same_id_and_same_value() {
79        let a = Foo {
80            id: FooId("foo-1".to_string()),
81            value: "value".to_string(),
82        };
83        let b = Foo {
84            id: FooId("foo-1".to_string()),
85            value: "value".to_string(),
86        };
87
88        assert!(a.is_same_as(&b));
89    }
90
91    #[test]
92    fn is_same_as_returns_true_for_same_id_and_different_value() {
93        let a = Foo {
94            id: FooId("foo-1".to_string()),
95            value: "value-a".to_string(),
96        };
97        let b = Foo {
98            id: FooId("foo-1".to_string()),
99            value: "value-b".to_string(),
100        };
101
102        assert_ne!(a.value, b.value);
103        assert!(a.is_same_as(&b));
104    }
105
106    #[test]
107    fn is_same_as_returns_false_for_different_id_and_same_value() {
108        let a = Foo {
109            id: FooId("foo-1".to_string()),
110            value: "value".to_string(),
111        };
112        let b = Foo {
113            id: FooId("foo-2".to_string()),
114            value: "value".to_string(),
115        };
116
117        assert!(!a.is_same_as(&b));
118    }
119
120    #[test]
121    fn is_same_as_returns_true_for_self_comparison() {
122        let foo = Foo {
123            id: FooId("foo-1".to_string()),
124            value: "value".to_string(),
125        };
126
127        assert!(foo.is_same_as(&foo));
128    }
129
130    #[test]
131    fn is_same_as_returns_true_for_empty_string_ids() {
132        let a = Foo {
133            id: FooId(String::new()),
134            value: "value-a".to_string(),
135        };
136        let b = Foo {
137            id: FooId(String::new()),
138            value: "value-b".to_string(),
139        };
140
141        assert!(a.is_same_as(&b));
142    }
143
144    #[test]
145    fn entity_impl_satisfies_send_and_sync() {
146        fn assert_impl<T: Send + Sync>() {}
147        assert_impl::<Foo>();
148    }
149}