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

Bottom-Up Architecture – Bridging the Achitectu...

Bottom-Up Architecture – Bridging the Achitecture Code Gap

Hard to change code bases often suffer from two primary problems: a lack of alignment with domain boundaries and failure to effectively express architectural ideas in code. To address that critical issue, developers have turned to Separation of Concerns Architectures, such as Onion-, Clean and Hexagonal Architecture. However, these approaches typically yield mixed results, as they primarily focus on separating technical and business code, without addressing the structural aspects of the domain.

This presentation explores strategies for transferring architectural ideas and design pattern languages into code at various levels of abstraction. We will discuss how different frameworks and libraries in the Java ecosystem can aid in this process, leveraging the presence of meta-information within the code to support critical aspects such as structural verification, testability, and documentation. By employing these approaches and tools, developers can write more maintainable code that is less susceptible to degradation over time.

Version:
- Java Forum Nord (current) – https://static.odrotbohm.de/slides/2024/09/bottom-up-architecture-jfn.pdf
- GOTO Amsterdam 2024 – https://static.odrotbohm.de/slides/2024/06/bottom-up-architecture-goto-ams.pdf

Oliver Drotbohm

June 11, 2024
Tweet

More Decks by Oliver Drotbohm

Other Decks in Programming

Transcript

  1. "Architecture is a property of a system, not a description

    of its intended design." — Stefan Tilkov in "Good Enough Architecture"
  2. Abstraction Vocabulary Pattern languages Level of detail Encapsulation Domain terms

    Concepts & Rules Extensional Intensional Enumerated Specified
  3. Deployables / Build modules / Packages Classes, methods, fields Extensional

    Intensional Naming conventions What else? " Invoicing, Shipment Components / Modules EmailAddress, ZipCode Domain language Concepts ValueObject, Entity, Aggregate Layers, Rings Concepts & Rules
  4. Architecture Design DDD Events Strategic Bounded Contexts Context Maps Modules

    Tactical Repositories Aggregates Entities Value Objects Architecture Event Listeners Events Layers Rings Ports Adapters Commands Queries
  5. A simple Aggregate arrangement Orders Customers «Aggregate» Order id: OrderId

    lineItems: List<LineItem> customer: Customer «Entity» LineItem amount: int price: MonetaryAmount «Aggregate» Customer id: CustomerId contains 1 1..* belongs to * 1
  6. A simple Aggregate arrangement Orders Customers «Aggregate» Order id: OrderId

    lineItems: List<LineItem> customer: Customer «Entity» LineItem amount: int price: MonetaryAmount «Aggregate» Customer id: CustomerId contains 1 1..* belongs to * 1 This and that imply that this is wrong! #
  7. MATCH (repo:Java:Type) -[:IMPLEMENTS_GENERIC]-# (superType) -[:OF_RAW_TYPE]-# (:Java:Type { fqn: "o.s.d.r.Repository"}), (superType)

    -[:HAS_ACTUAL_TYPE_ARGUMENT { index: 0 }]-# () -[:OF_RAW_TYPE]-# (aggregateType) SET aggregateType:Aggregate RETURN repo, aggregateType Establishing an Aggregate… in jQAssistant MATCH (aggregate:Aggregate) -[:DECLARES]-# (f:Field) -[:OF_TYPE]-# (fieldType:Aggregate) WHERE aggregate <& fieldType RETURN aggregate, fieldType Establishes the concept Establishes the rule Reference to tech stack $
  8. @AnalyzeClasses(packagesOf = Application.class) public class ArchitectureTest { @ArchTest void verifyAggregates(JavaClasses

    types) { var aggregates = new AggregatesExtractor(); var aggregateTypes = aggregates.doTransform(types); all(aggregates) .should(notReferToOtherAggregates(aggregateTypes)) .check(types); } } Establishing an Aggregate… in ArchUnit Establishes the concept Establishes the rule
  9. Architectural Goal Architectural Concept Supporting Technology Development Practice supports drives

    selection drives selection enables supports supported by Evolvability DDD Building Blocks jMolecules
  10. @Entity @NoArgsConstructor(force = true) @EqualsAndHashCode(of = "id") @Table(name = "SAMPLE_ORDER")

    @Getter public class Order { private final @EmbeddedId OrderId id; @OneToMany(cascade = CascadeType.ALL) private List<LineItem> lineItems; private CustomerId customerId; public Order(CustomerId customerId) { this.id = OrderId.of(UUID.randomUUID()); this.customerId = customerId; } @Value @RequiredArgsConstructor(staticName = "of") @NoArgsConstructor(force = true) public static class OrderId implements Serializable { private static final long serialVersionUID = …; private final UUID orderId; } }
  11. @Entity @NoArgsConstructor(force = true) @EqualsAndHashCode(of = "id") @Table(name = "SAMPLE_ORDER")

    @Getter public class Order implements o.j.d.t.AggregateRoot<Order, OrderId> { private final @EmbeddedId OrderId id; @OneToMany(cascade = CascadeType.ALL) private List<LineItem> lineItems; private CustomerId customerId; public Order(CustomerId customerId) { this.id = OrderId.of(UUID.randomUUID()); this.customerId = customerId; } @Value @RequiredArgsConstructor(staticName = "of") @NoArgsConstructor(force = true) public static class OrderId implements o.j.d.t.Identifier { private static final long serialVersionUID = …; private final UUID orderId; } }
  12. @Entity @NoArgsConstructor(force = true) @EqualsAndHashCode(of = "id") @Table(name = "SAMPLE_ORDER")

    @Getter public class Order { private final @EmbeddedId OrderId id; @OneToMany(cascade = CascadeType.ALL) private List<LineItem> lineItems; private CustomerId customerId; public Order(CustomerId customerId) { this.id = OrderId.of(UUID.randomUUID()); this.customerId = customerId; } @Value @RequiredArgsConstructor(staticName = "of") @NoArgsConstructor(force = true) public static class OrderId implements Serializable { private static final long serialVersionUID = …; private final UUID orderId; } } JPA-induced boilerplate Model characteristics expressed implicitly or through technical means
  13. @Entity @NoArgsConstructor(force = true) @EqualsAndHashCode(of = "id") @Table(name = "SAMPLE_ORDER")

    @Getter public class Order implements AggregateRoot<Order, OrderId> { private final @EmbeddedId OrderId id; @OneToMany(cascade = CascadeType.ALL) private List<LineItem> lineItems; private Association<Customer, CustomerId> customer; public Order(CustomerId customerId) { this.id = OrderId.of(UUID.randomUUID()); this.customer = Association.forId(customerId); } @Value @RequiredArgsConstructor(staticName = "of") @NoArgsConstructor(force = true) public static class OrderId implements Identifier { private static final long serialVersionUID = …; private final UUID orderId; } }
  14. @Entity @NoArgsConstructor(force = true) @EqualsAndHashCode(of = "id") @Table(name = "SAMPLE_ORDER")

    @Getter public class Order implements AggregateRoot<Order, OrderId> { private final @EmbeddedId OrderId id; @OneToMany(cascade = CascadeType.ALL) private List<LineItem> lineItems; private Association<Customer, CustomerId> customer; public Order(CustomerId customerId) { this.id = OrderId.of(UUID.randomUUID()); this.customer = Association.forId(customerId); } @Value @RequiredArgsConstructor(staticName = "of") @NoArgsConstructor(force = true) public static class OrderId implements Identifier { private static final long serialVersionUID = …; private final UUID orderId; } }
  15. [INFO] □─ example.order.Order [INFO] ├─ JPA - Adding @j.p.Entity. [INFO]

    ├─ JPA - Adding default constructor. [INFO] ├─ JPA - Adding nullability verification using new callback methods. [INFO] ├─ JPA - Defaulting id mapping to @j.p.EmbeddedId(). [INFO] ├─ JPA - Defaulting lineItems mapping to @j.p.JoinColumn(…). [INFO] ├─ JPA - Defaulting lineItems mapping to @j.p.OneToMany(…). [INFO] ├─ Spring Data JPA - Implementing o.s.d.d.Persistable<e.o.Order$OrderIdentifier>. [INFO] └─ Spring JPA - customer - Adding @j.p.Convert(converter=…). Meanwhile in your IDE…
  16. @Entity @NoArgsConstructor(force = true) @EqualsAndHashcode(of = "id") @Table(name = "SAMPLE_ORDER")

    @Getter public class Order { private final @EmbeddedId OrderId id; @OneToMany(cascade = CascadeType.ALL) private List<LineItem> lineItems; private CustomerId customerId; public Order(Customer customer) { this.id = OrderId.of(UUID.randomUUID()); this.customerId = customer.getId(); } @Value @RequiredArgsConstructor(staticName = "of") @NoArgsConstructor(force = true) public static class OrderId implements Serializable { private static final long serialVersionUID = …; private final UUID orderId; } } @Table(name = "SAMPLE_ORDER") @Getter public class Order implements AggregateRoot<Order, OrderId> { private final OrderId id; private List<LineItem> lineItems; private Association<Customer, CustomerId> customer; public Order(CustomerId customerId) { this.id = OrderId.of(UUID.randomUUID()); this.customer = Association.forId(customerId); } record OrderId(UUID orderId) implements Identifier {} }
  17. Architectural Goal Architectural Concept Supporting Technology Development Practice supports drives

    selection drives selection enables supports supported by Evolvability Modules Spring Modulith
  18. package com.acme.modulith @SpringBootApplication class MyApplication { … } var modules

    = ApplicationModules.of(MyApplication.class); modules.verify(…); Standard Spring Boot Application Verifies rules for MyApplication
  19. A B C D E Unit of… - Understanding -

    Consistency - Testing - Documentation - Observation
  20. Web Business logic Data access Module A Module B Module

    C @Data…Test @WebMvcTest @ApplicationModuleTest
  21. A B C D E Unit of… - Understanding -

    Consistency - Testing - Documentation - Observation
  22. A B C D E Unit of… - Understanding -

    Consistency - Testing - Documentation - Observation
  23. A B C D E Unit of… - Understanding -

    Consistency - Testing - Documentation - Observation
  24. A B C D E Change detected in C. We

    need to test C and A!
  25. A B C D E Unit of… - Understanding -

    Consistency - Testing - Documentation - Observation
  26. My Component Provided Interface Exposed Service API Spring Beans available

    for DI Exposed Aggregates Primary elements of the domain and constraints Published Events Events the component emits Required Interface Consumed Service API External dependencies of Spring beans Configuration Spring Boot configuration properties Consumed Events Events that the component reacts to
  27. Salespoint «Component: Module» Salespoint :: Accountancy «Component: Module» Salespoint ::

    Catalog «Component: Module» Salespoint :: Inventory «Component: Module» Salespoint :: Order «Component: Module» Salespoint :: Payment «Component: Module» Salespoint :: Quantity «Component: Module» Salespoint :: Storage «Component: Module» Salespoint :: Time «Component: Module» Salespoint :: User Account listens t o depends on uses depends on depends on depends on listens t o depends on depends on depends on depends on uses depends on depends on
  28. Salespoint «Component: Module» Salespoint :: Accountancy «Component: Module» Salespoint ::

    Catalog «Component: Module» Salespoint :: Inventory «Component: Module» Salespoint :: Order «Component: Module» Salespoint :: Payment «Component: Module» Salespoint :: Quantity «Component: Module» Salespoint :: Storage «Component: Module» Salespoint :: Time «Component: Module» Salespoint :: User Account listens t o depends on uses depends on depends on depends on listens t o depends on depends on depends on depends on uses depends on depends on Salespoint :: Inventory «Component: Module» Salespoint :: Catalog «Component: Module» Salespoint :: Inventory «Component: Module» Salespoint :: Order «Component: Module» Salespoint :: Quantity depends on depends on listens t o depends on depends on depends on
  29. Salespoint «Component: Module» Salespoint :: Accountancy «Component: Module» Salespoint ::

    Catalog «Component: Module» Salespoint :: Inventory «Component: Module» Salespoint :: Order «Component: Module» Salespoint :: Payment «Component: Module» Salespoint :: Quantity «Component: Module» Salespoint :: Storage «Component: Module» Salespoint :: Time «Component: Module» Salespoint :: User Account listens t o depends on uses depends on depends on depends on listens t o depends on depends on depends on depends on uses depends on depends on Salespoint :: Inventory «Component: Module» Salespoint :: Catalog «Component: Module» Salespoint :: Inventory «Component: Module» Salespoint :: Order «Component: Module» Salespoint :: Quantity depends on depends on listens t o depends on depends on depends on
  30. Summary Find means to represent architectural and design concepts in

    your codebase. Align software engineering practices with architectural abstractions. Favor architecturally aware technologies over allegedly agnostic ones. Understand how technology choices affect the overall architecture of the system.
  31. Resources Software Architecture for Developers Simon Brown – Books Just

    Enough Software Architecture George Fairbanks – Book Architecture, Design, Implementation Ammon H. Eden, Rick Kazman – Paper Sustainable Software Architecture Carola Lilienthal – Book The Programmer's Brain Felienne Hermans – Book