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

Better Modeling Through Records

Better Modeling Through Records

"Better Modeling Through Records" is the presentation of my talk that I gave at JavaDay Istanbul, 2022.

Avatar for Akin Kaldiroglu

Akin Kaldiroglu

May 28, 2022

Other Decks in Programming

Transcript

  1. 2 • I work as a consultant and trainer for

    Selsoft since 2008. • My interest areas are software development and design, programming languages, OOP and FP. • I’ve been working with Java since its arrival. • You can reach me through various ways: • Email: [email protected] & [email protected] • LinkedIn: www.linkedin.com/in/akinkaldiroglu/ & Twitter: @kaldiroglu About Me
  2. Record Classes • Record classes and record contextual keyword have

    fi rst been introduced as a preview feature in Java 14 and then became standard as part of Java 16. • Record is a speci fi c type of class with some restrictions. 4 • It provides nice opportunities for modeling and coding.
  3. record Rectangle (double length, double width){} final class Rectangle {

    private final double length; private final double width; public Rectangle(double length, double width){ this.length = length; this.width = width; } public double length() { return length; } public double width() { return width; } public boolean equals() { ... } public int hashCode() { ... } public String toString() { ... } }
  4. Declaring A Record - I • De fi ning a

    record requires only a name and list of components as JLS says are declared and rest is handled by the Java compiler: • A private and final fi eld for each component so records are all immutable objects! • Notice that a record can have a component of a mutable object type, so it should be avoided for truly immutable records 6
  5. Declaring A Record - II • For each component a

    public accessor method with the name of the component • State of a record is always reachable! • A canonical constructor that receives arguments for components • equals(), hashCode() and toString() method implementations based on the components 7
  6. Restrictions • Records are implicitly final and they can’t have

    extends clause but they can implement interfaces • They can’t be abstract, sealed or non-sealed • Other than declared components no more fi elds except static ones can be de fi ned, • Even components themselves can’t be rede fi ned as fi elds 8
  7. Possibilities - I • New constructors can be added, •

    But they need to call the canonical constructor, though • New methods can be added but they can’t change the state of the object: • So records should always have an immutable interface! 9
  8. Possibilities - II • equals(), hashCode() and toString() methods can

    be overridden but the implementations should not violate the semantics of the record • equals() and hashCode() should always work on the components of the record 10
  9. Why Records? • Main role of records looks like data

    abstraction, JLS de fi nes a record as a simple aggregate of values. • And information hiding is not applied, components are always reachable! • So records can be used as carrier of data like DTOs 11
  10. Records Only As DTO? • So are records only for

    DTOs? • No, some people tend to regard records as • Lombok-standardized as part of Java 😃 • A way to make Java less verbose? 😃 12
  11. Opportunities • JLS de fi nition of a record: a

    simple aggregate of values. • Records open lots of opportunities that help us create better models through: • Value semantics • Modularity • Naming 14
  12. Value Semantics An object has ValueSemantics when it can be

    treated as a single value. wiki.c2.com
  13. Value Semantics • Sometimes an object stands only for its

    values i.e. if the value of any of its member variables changes the object becomes another object. • That’s not a state change! Values don’t change! • So the differentiating aspect of those objects is their state i.e. totality of the values in their fi elds. 16 record Name(String firstName, String middleName, String lastName){}
  14. Value Objects • Objects with value semantics are also called

    value objects. • Entities vs. value objects! • Value objects are frequently smaller, don’t have a complex lifecycle as entities and are mostly used to model immutable things such as numbers as money or date or strings as name. • They don’t have any identity as entities and thus are compared by their values. 17
  15. record Tckn(String tckn){} record Name(String firstName, String middleName, String lastName){}

    record Address(String zipCode, String city, String neighbourhood, String street, String building){} class Employee{ private final Tckn tckn; private final Name name; private final Address address; private double salary; … public void promote(double promotionPercentage){ salary *= (1 + promotionPercentage); } … }
  16. Value Semantics in Classes • Why not model value semantics

    in a class with all fi nal fi elds? 19
  17. class Employee{ private int tckn; private final String firstName; private

    final String middleName; private final String lastName; private final String zipCode; private final String city; private final String neighbourhood; private final String street; private final String building; private double salary; … public void promote(double promotionPercentage){ salary *= (1 + promotionPercentage); } ... }
  18. Value vs. Reference • We don’t notice values when it

    is embedded in a larger non-value semantics: a confusing semantics! • We tend to think in terms of reference semantics so noticing value semantics is kind of hard. • Entities through their reference semantics mostly overweight and shadow value semantics. • Data-oriented thinking also emphasizes objects with ids. 21
  19. Confusing Semantics • What happens when we have a confusing

    semantics? • i.e. when we treat values with non-value or reference semantics? 22
  20. Date retirementDate = new Date(Date.parse("Tue 1 Nov 2016")); // this

    means we need a retirement party Date partyDate = retirementDate; // but that date is a Tuesday, let's party on the weekend partyDate.setDate(5); assertEquals(new Date(Date.parse("Sat 5 Nov 2016")), retirementDate); // oops, now I have to work three more days :-( https://martinfowler.com/bliki/AliasingBug.html
  21. Avoiding Confusing Semantics • Some ways to avoid confusing semantics:

    • Defensive copy • Immutable collections • A re-write as in the case of date and time API of Java 8 24
  22. OO and Functional Thinking • So we need a richer

    vocabulary for better mental models: • More functional thinking • OOPLs tend to become more functional and their programmers should too! • Better object-oriented thinking • DDD has entity, value and service objects 25
  23. Records as a Compromise • So records is a good

    compromise between OOP and FP! 26
  24. Modularity constructed with standardized units or dimensions for flexibility and

    variety in use www.merriam-webster.com/dictionary/modular
  25. Modularity • Modularity is mainly about two things: • How

    things are brought together as a single unit: cohesion • How units are interrelated: coupling 28
  26. Modularity by Extraction • Strongly-cohesive and loosely-coupled models are almost

    always created by extracting details and keeping related, minimal core together. • Extracting parts of an entity that are used together into another object always helps keep objects small and cohesive. 29
  27. Modularity by Records • Records create motivation to have more

    modular structures. • Extracting parts of an entity that are used together but don’t change into records helps achieve modularity too. 30
  28. class Employee{ private int tckn; private final String firstName; private

    final String middleName; private final String lastName; private final String zipCode; private final String city; private final String neighbourhood; private final String street; private final String building; ... } class Employee{ private final Tckn tckn; private final Name name; private final Address address; ... }
  29. Behaviorally-Rich Records • Although records are mainly data abstraction, nobody

    holds back us from adding behavior related to that data. • So creating behaviorally-rich records helps more modular structures. 32
  30. record Tckn(String tckn) { public Tckn(String tckn) { boolean valid

    = validate(tckn); if(!valid) throw new InvalidTcknException(tckn); this.tckn = tckn; } private boolean validate(String tckn){ … } } record Name(String firstName, String middleName, String lastName) { public String fullName(){ return firstName + " " + middleName.substring(0, 1) + “. " + lastName; } } record Tckn(String tckn) { public static boolean validate(String tckn){ … } }
  31. class Employee{ private int tckn; private final String firstName; private

    final String middleName; private final String lastName; private final String zipCode; private final String city; private final String neighbourhood; private final String street; private final String building; ... } • Where do you think the logic in records would be placed if the records were not used?
  32. Avoiding Scattered-Logic • This approach is also called “primitive obsession”

    in clean code circles. • Primitives lacks cohesion and causes coupling by allowing logic about themselves to get scattered. • So adding behaviors to records increases the cohesion and reduces the coupling of the system by avoiding scattered logic. 35
  33. Rich Ontology • Complex phenomena require rich ontology. • Rich

    ontology can only be achieved by rich naming. • When number of names increases our language gets richer and in turn our world enlarges. There are only two hard things in Computer Science: cache invalidation and naming things. Phil Karlton 37
  34. Date startDate int level Department department double baseSalary double educationPayment

    double handicappedPayment int numberOfChildren … } class Employee{ int tckn; String firstName; String middleName; String lastName; String zipCode; String city; String neighbourhood; String street; String building; String homePhone; String cellPhone;
  35. record Employment (Date startDate, Level level Department department){…} record PhoneInfo(String

    homePhone, String cellPhone{…} record Tckn(String tckn){…} record Name(String firstName, String middleName, String lastName){…} record Address(String zipCode, String city, String neighbourhood, String street, String building){…} enum Level {…} record Salary (double baseSalary double educationPayment double handicappedPayment){…}
  36. class Employee{ Tckn tckn Name name Address address PhoneInfo phoneInfo

    Employment employment Salary salary FamilyInfo familyInfo … }
  37. class Employee{ int tckn; String firstName; String middleName; String lastName;

    String zipCode; String city; String neighbourhood; String street; String building; String homePhone; String cellPhone; Date startDate int level Department department double baseSalary double educationPayment double handicappedPayment … } class Employee{ Tckn tckn Name name Address address PhoneInfo phoneInfo Employment employment FamilyInfo family Salary salary … }
  38. Records increase modularity. Records increase functional portion of code through

    immutability thus reduce complexity. Records help better naming.