Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Value and Record Types

Value and Record Types

Slides from my talk “Better Domain Models with Value and Record Types.”

575ca492bac55e895d0e1c86f7d709fe?s=128

Henning Schwentner

June 09, 2022
Tweet

More Decks by Henning Schwentner

Other Decks in Programming

Transcript

  1. Starring With vs Special Appearances by

  2. None
  3. None
  4. Java old! C# ABAP PHP Python

  5. None
  6. Buy it on Amazon.com: https://amzn.to/3nF34nI

  7. None
  8. @hschwentner Software != End in itself

  9. None
  10. Software Domäne

  11. Foto: Rosie the Riveter/Wikimedia/CC-PD-Mark

  12. @hschwentner Programming is Model Building

  13. @hschwentner Rules of thumb:

  14. Objects from real world turn into objects in software

  15. Actions from real world turn into operations in software

  16. => Object-orientation

  17. None
  18. to deposit to withdraw bank account money amount IBAN to

    pay interest
  19. Object-Oriented Programming Ole-Johan Dahl Photo: Stein Krogdahl, University of Oslo

    Kristen Nygaard Photo: Jorge Stolfi/Wikipedia 🇳🇴‍
  20. Photo: P. Campbell/Wikipedia Alan Kay Everything is an object 🇺🇸‍

  21. None
  22. Foto: P. Campbell/Wikipedia James Gosling Everything is an object 🇨🇦‍

  23. Anders Hejlsberg Everything is an object

  24. @hschwentner In the real world not everything is an object

  25. @hschwentner Domain-Driven Design Eric Evans 🇺🇸‍ Entity Value Object Aggregate

    Service Factory Repository
  26. @hschwentner Entity vs. Value

  27. Foto: P. Campbell/Wikipedia James Gosling Everything is an object int

    short long byte char float double boolean … except:
  28. Anders Hejlsberg Everything is an object …except structs

  29. Values from real world turn into values in software

  30. @hschwentner What is an object/entity?

  31. None
  32. Identity

  33. None
  34. None
  35. Foto: Th.omas G.Graf/Wikipedia oto: H. Schwentner

  36. Grafik: Pixabay

  37. Foto: Bernd Schwabe in Hannover/Wikipedia

  38. @hschwentner Object: *Identity *Lifecycle *(mutable state)

  39. @hschwentner What is a value?

  40. Grafik: Public Domain/Pixabay

  41. Foto: A. Markiewicz

  42. None
  43. @hschwentner

  44. @hschwentner Value: *No identity *immutable

  45. @hschwentner Example

  46. 👬 👭 👫

  47. None
  48. 👫

  49. 👫 👫

  50. None
  51. 👬 👭 👫

  52. 👫 👫 👭 👫 👭 👫

  53. None
  54. Foto: Bibi Saint-Pol/Wikipedia

  55. @hschwentner Objects in Languages like Java or C#

  56. None
  57. None
  58. None
  59. None
  60. None
  61. Foto: Tiia Monto/Wikipedia

  62. None
  63. None
  64. None
  65. None
  66. None
  67. None
  68. 3

  69. 3 3

  70. 3

  71. @hschwentner Where does an object live actually?

  72. None
  73. None
  74. None
  75. None
  76. None
  77. None
  78. None
  79. compile time

  80. None
  81. run time

  82. @hschwentner Where does an object live actually?

  83. @hschwentner Values in Languages like Java or C#

  84. built-in value types primitive types simple types

  85. int boolean/bool double char

  86. @hschwentner Where does such a value live actually?

  87. 3

  88. 3 3

  89. None
  90. LocalDate/DateTime String

  91. object types that behave like value types value-based class (class

    with) value equality
  92. User-defined value types

  93. @hschwentner Values in Java

  94. int boolean double char

  95. unsigned complex

  96. iban postcode amount

  97. Foto: Public Domain/Pixabay

  98. Grafik: Gemeinfrei/Wikipedia

  99. value-based class

  100. @hschwentner Only final fields

  101. @hschwentner No state-changing methods

  102. @hschwentner equals() does not depend on identity

  103. value-based class

  104. LocalDate String

  105. @hschwentner Values in C#

  106. int bool double char

  107. struct <= 7.1

  108. @hschwentner Only readonly fields

  109. @hschwentner No state-changing methods

  110. readonly struct >= 7.2

  111. @hschwentner equals() 👍 == 🙈

  112. record <= 9

  113. @hschwentner Value equality

  114. @hschwentner equals() 👍 == 👍

  115. @hschwentner Beware: Can be mutable

  116. @hschwentner On the heap

  117. record struct <= 10

  118. @hschwentner Beware: Is mutable

  119. readonly record struct <= 10 😍

  120. @hschwentner Why aren’t int and string enough?

  121. @hschwentner Values in Java — Example

  122. public class Amount { }

  123. import org.jmolecules.ddd.annotation.ValueObject; @ValueObject public class Amount { }

  124. @ValueObject public class Amount { private int _amount; private Currency

    _currency; }
  125. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; }
  126. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
  127. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; private Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public static Amount of(int amount, Currency currency) { return new Amount(amount, currency); } }
  128. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } @Override public boolean equals(Object other) { return _amount == ((Amount) other)._amount && _currency.equals(((Amount) other)._currency); }
  129. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } @Override public boolean equals(Object other) { return _amount == ((Amount) other)._amount && _currency.equals(((Amount) other)._currency); }
  130. @ValueObject public record Amount(int amount, Currency currency) {}

  131. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount otherAmount) { return Amount.of(_amount + otherAmount._amount, _currency); } } Der neue Typ hat richtiges fachliches Verhalten
  132. @ValueObject public class Amount { private final int _amount; private

    final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount otherAmount) { assert hasSameCurrency(otherAmount); return Amount.of(_amount + otherAmount._amount, _currency); } … und Verträge, die vor falschen Währungen schützen
  133. @ValueObject public record Amount(int amount, Currency currency) { public Amount

    add(Amount otherAmount) { assert hasSameCurrency(otherAmount); return new Amount(_amount + otherAmount.amount(), currency); } public boolean hasSameCurrency(Amount otherAmount) { return otherAmount.currency() == currency; } }
  134. @hschwentner Values in C# — Example

  135. public class Amount { }

  136. using NMolecules.DDD; [ValueObject] public class Amount { }

  137. [ValueObject] public class Amount { private int _amount; private Currency

    _currency; }
  138. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; }
  139. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
  140. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public override bool Equals(object other) { return other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); } }
  141. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public override bool Equals(object other) => other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); }
  142. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public override bool Equals(object other) => other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); // GetHashCode() }
  143. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public static bool operator ==(Amount a, Amount b) => a.Equals(b); }
  144. [ValueObject] public class Amount { private readonly int _amount; private

    readonly Currency _currency; // ... public static bool operator ==(Amount a, SignDate b) => a.Equals(b); public static bool operator !=(Amount a, SignDate b) => !a.Equals(b); }
  145. None
  146. 3

  147. 3

  148. [ValueObject] public struct Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
  149. [ValueObject] public struct Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } // Equals() does not have to be overridden }
  150. [ValueObject] public struct Amount { private readonly int _amount; private

    readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } // but the operators have to be overridden }
  151. @hschwentner equals() 👍 == 🙈

  152. [ValueObject] public record Amount(int amount, Currency currency);

  153. [ValueObject] public record Amount(int amount, Currency currency); // automatically readonly

    // Equals(), GetHashCode(), ToString(), operators // do not have to be overloaded and work as expected
  154. [ValueObject] public record struct Amount(int amount, Currency currency);

  155. [ValueObject] public readonly record struct Amount(int amount, Currency currency);

  156. @hschwentner !!! If C# jump to Conclusion or Ubiquitous Langague

    !!!
  157. But

  158. 3 3

  159. @hschwentner equals() 👍 == 🙈

  160. None
  161. 3

  162. by value

  163. by reference

  164. 3

  165. None
  166. Memory*2

  167. 3

  168. 3

  169. None
  170. None
  171. int[]

  172. 3 5 10 5 0

  173. Object[]

  174. 3 5 10 5 0

  175. 3 10 5 0

  176. @hschwentner !!!Ab hier muss es neu!!!

  177. @hschwentner The answer

  178. Foto: Donald24/Wikipedia

  179. None
  180. @hschwentner “Works like an int – codes like a class”

  181. @hschwentner Example

  182. y x

  183. y x

  184. @hschwentner (x, y)

  185. None
  186. None
  187. Bild: Gemeinfrei

  188. Bild: Rursusi/Wikipedia

  189. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  190. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  191. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  192. Bild: Rursusi/Wikipedia

  193. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  194. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  195. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  196. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  197. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  198. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  199. final __ByValue class Point { private final int _x; private

    final int _y; public Point(int x, int y) { _x = x; _y = y; } public boolean equals(Point other) { return _x == other._x && _y == other._y; } }
  200. @hschwentner “Works like an int – codes like a class”

  201. @hschwentner “Works like an int – codes like a class”

  202. Point p = __MakeValue(4, 7);

  203. Point p = __MakeValue(4, 7);

  204. Point p = new Point(4, 7);

  205. Point p = Point(4, 7);

  206. Point p = (4, 7);

  207. 4 7

  208. 4 7

  209. None
  210. None
  211. 4 7

  212. 4 7

  213. 4 7

  214. None
  215. None
  216. Point p = __MakeValue Point(4, 7);

  217. @hschwentner ==

  218. @hschwentner !=

  219. Default value

  220. None
  221. None
  222. @hschwentner What else is there?

  223. List<Point>

  224. List<int>

  225. Grafik: Gemeinfrei/Wikimedia Commons

  226. List<Point>

  227. @hschwentner Ubiquitous Language?

  228. None
  229. None
  230. None
  231. Software != End

  232. None
  233. ?

  234. IMPORTANT!

  235. None
  236. None
  237. None
  238. None
  239. Value Object

  240. @hschwentner Domain values

  241. Postal code GPS coordinate IBAN Container number IATA code Amount

  242. Ubiquitous Language

  243. Fach- sprache

  244. None
  245. Ludwig Wittgenstein “Whereof one cannot speak, thereof one must be

    silent”
  246. Ludwig Wittgenstein »Wovon man nicht sprechen kann, darüber muss man

    schweigen.«
  247. @hschwentner Example

  248. public class BankAccount { private int _balance; public void withdraw(int

    amount) { _balance -= amount; } // ... }
  249. public class BankAccount { private int _balance; public void withdraw(int

    amount) { _balance -= amount; } // ... }
  250. public class BankAccount { private int _balance; public void withdraw(int

    amount) { _balance -= amount; } // ... }
  251. public class BankAccount { private int _balance; public void withdraw(int

    amount) { _balance -= amount; } // ... }
  252. public class BankAccount { private int _balance; public void withdraw(int

    amount) { _balance -= amount; } // ... }
  253. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } }
  254. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } }
  255. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } }
  256. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } }
  257. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } public void deposit(int amount, String currency) { assert currency.equals(_currency); _balance += amount; } }
  258. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } public void deposit(int amount, String currency) { assert currency.equals(_currency); _balance += amount; } }
  259. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } public void deposit(int amount, String currency) { assert currency.equals(_currency); _balance += amount; } }
  260. public class BankAccount { private int _balance; private String _currency;

    public void withdraw(int amount, String currency) { assert currency.equals(_currency); _balance -= amount; } public void deposit(int amount, String currency) { assert currency.equals(_currency); _balance += amount; } }
  261. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  262. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  263. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  264. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  265. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  266. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  267. public class Amount { private final int _amount; private final

    Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount other) { assert hasSameCurrency(other); return new Amount(_amount + other._amount, _currency); } public boolean hasSameCurrency(Amount other) { return other._currency == _currency; } }
  268. public class BankAccount { private Amount _balance; public void withdraw(Amount

    amount) { _balance.subtract(amount); } public void deposit(Amount amount) { _balance.add(amount); } }
  269. public class BankAccount { private Amount _balance; public void withdraw(Amount

    amount) { _balance.subtract(amount); } public void deposit(Amount amount) { _balance.add(amount); } }
  270. public class BankAccount { private Amount _balance; public void withdraw(Amount

    amount) { _balance.subtract(amount); } public void deposit(Amount amount) { _balance.add(amount); } }
  271. public class BankAccount { private Amount _balance; public void withdraw(Amount

    amount) { assert amount.isGreaterThan(_amount); _balance.subtract(amount); } public void deposit(Amount amount) { _balance.add(amount); } }
  272. Memory*2

  273. None
  274. None
  275. None
  276. public class Amount { // ... }

  277. public __ByValue class Amount { // ... }

  278. Bild: Rursusi/Wikipedia

  279. public __ByValue class Amount { // ... }

  280. @hschwentner eat the cake and have it too

  281. @hschwentner real domain models and good performance

  282. @hschwentner And in other programming languages?

  283. C++

  284. None
  285. Point p;

  286. None
  287. Point p = Point(4, 7);

  288. 4 7

  289. Point * p_pointer;

  290. None
  291. Point * p_pointer = new Point(4, 7);

  292. 4 7

  293. Point & p_reference = new Point(4, 7);

  294. C++

  295. None
  296. struct

  297. 4 7

  298. 4 7

  299. None
  300. None
  301. None
  302. @hschwentner But

  303. @hschwentner Mutable state

  304. Foto: Th.omas G.Graf/Wikipedia oto: H. Schwentner

  305. Interesting concept: Nullable<T>

  306. None
  307. Scala

  308. Case Class

  309. @hschwentner Pattern matching

  310. @hschwentner Generated equals() based on the members

  311. @hschwentner Immutable

  312. value-based class

  313. None
  314. Data class

  315. @hschwentner In the end

  316. @hschwentner Key take aways: *value/object are different *only records yet

    * value types are still to come (*domain model and performance)
  317. Bild: Gemeinfrei

  318. @hschwentner Further Reading

  319. project valhalla

  320. https://domainstorytelling.org

  321. Workshop Domain-Driven Design concrete wps.de/ddd

  322. None
  323. None
  324. None
  325. None
  326. None
  327. Henning Schwentner ⌂ https://hschwentner.io @hschwentner ✉ hs@wps.de

  328. @hschwentner Bibliography Beck, Kent et al. Manifesto for Agile Software

    Development. 2001. Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Boston: Addison-Wesley, 2004. Hofer, Stefan and Henning Schwentner. Domain Storytelling: a Collaborative, Visual, and Agile Way to Develop Domain-Driven Software. Boston: Addison- Wesley, 2022.