Building Better Monoliths – Modulithic Applications with Spring Boot

Building Better Monoliths – Modulithic Applications with Spring Boot

Slides of the talk I gave at W-JAX 2018 outlining modularization strategies for monolithic Spring Boot applications and using the Moduliths library to run module tests.

@springcentral

977c74bb044a9d4fa90b305824eda390?s=128

Oliver Drotbohm

November 07, 2018
Tweet

Transcript

  1. Modulithic Applications
 with Spring Boot Building better monoliths @olivergierke Oliver

    Drotbohm ƀ odrotbohm@pivotal.io odrotbohm
  2. 2

  3. Monolith VS. Microservice 3 Module
 A Module
 B Module
 C

    Module
 B Module
 C Module
 A Bounded Context Deployment Unit Internal invocation External invocation
  4. Monolith 4 The application packages the implementation of multiple Bounded

    Contexts as a single deployment unit. Easy to refactor Simple IDE features can be used to move significant parts of the codebase around and update all client code at the same time. Easy to test the overall system An overall test run is a matter of running a Spring (Boot) integration test.
  5. Monolith 5 The application packages the implementation of multiple Bounded

    Contexts as a single deployment unit. Likely to degrade unless explicitly managed Unless explicit means of monitoring the structure are employed, the architecture will degrade over time. Harder to test individual Bounded Contexts Testing individual bounded contexts requires extra effort in setting up Spring configuration to make sure components can be picked up for each module individually.
  6. Microservices 6 A bounded context defines the boundaries of the

    deployment artifact. Often parts of the context are even deployed as separate processes. ◦ Testing individual modules Testing individual modules usually requires collaborating systems to be bootstrapped, stubbed or mocked. ◦ Bounded Context interaction is remote The interaction between BCs involved remote interaction, either by sending messages or invoke other systems directly. This accumulates latency, and requires additional measures against the collaborators being unavailable which increases complexity.
  7. Microservices 7 A bounded context defines the boundaries of the

    deployment artifact. Often parts of the context are even deployed as separate processes. Hard to refactor context boundaries Context boundaries are implemented as published APIs that have to be evolved carefully as clients cannot be updated to server changes immediately. Hard to test the overall system While testing an individual system usually works reasonably well, integration testing multiple systems together is complex and creates additional overhead.
  8. How to preserve the structure in a
 monolithic application? “

    8
  9. Limit active Bounded Context interaction! “ 9

  10. 10 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts

  11. 11 @Component class OrderManagement { @Transactional void completeOrder(Order order) {

    order.complete(); this.orders.save(order); this.inventory.updateStockFor(order); } }
  12. 12 @Component class Inventory { void updateStockFor(Order order) { order.getLineItems().foreach(it

    -> { … }); } }
  13. 13 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts

  14. 14 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts 


    Event 
 Event
  15. 15 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts 


    Event 
 Event
  16. DEMO 16

  17. How to preserve the structure in a
 monolithic application? “

    17
  18. Define and monitor structure violations! “ 18

  19. Singular Artifact 19 Package per module. Usage of Java package

    scope to hide component internals. Package per module is a simple convention Does not need explicit definition. Easy to pick up and remember. Package scope to hide components: the compiler becomes the simplest possible architecture management tool. Easy to refactor Changes in bounded context can be fully applied by using the IDE refactoring tools.
  20. Singular Artifact 20 Package per module. Usage of Java package

    scope to hide component internals. Monolithic bootstrap Integration tests usually start the entire application. More control can be gained by more fine-grained Spring configuration. Layers can be tests using Spring Boot’s test annotations. More complex package setup breaks control Once sub-packages are introduced access control becomes a lot harder as types need to become public to be consumable from other packages of the same component.
  21. Multiple Artifacts 21 Every module port becomes a dedicated build

    artifact. Dedicated control over dependencies Each artifact defines it’s dependencies and thus controls which other artifacts are visible at compile time. Test scope is defined by the artifact tests don’t need to run the entire application but need to select the artifacts on the test runtime classpath.
  22. Multiple Artifacts 22 Every module port becomes a dedicated build

    artifact. Potential explosion of number of artifacts Bounded Context with more than a simple API / implementation structure cause the project to consist of a lot of artifacts which increases build setup complexity. Artifacts still available for consumption Implementation artifacts are still available for others to depend on. No separation of logical module and internal structure.
  23. Java Module System 23 Every module becomes a Java 9+

    module. ◦ Strong coupling between module and artifact JPMS assumes a single module with a single logical public API per artifact and thus cannot be used to imply rules within an artifact. With multiple APIs you’re essentially back to „Multiple Artifacts“. ◦ Semantics of the „opens“ keyword Required to let frameworks instantiate non-public types via reflection. Creates incentives to create „packages that require reflection“. ◦ No test support There’s no concept of running one or more modules in a single test run.
  24. External Tools 24 External tools (e.g. jQAssistant, Sonargraph, jDepend) integrated

    into the build and executed in the CI environment are used to signal structural violations. Most extensive option Tools can not only inspect source code but also other artifacts (architectural descriptions) to verify. Distance to development workflow Feedback is usually delayed as the verification is usually tied to a more extensive test run, which is usually caused by the effort to evaluate the rules.
  25. Moduliths https://github.com/odrotbohm/moduliths Binaries available from https://repo.spring.io 25

  26. Moduliths 26 1. A convention to map contexts to packages

    Packages directly nested underneath the main application package are considered context ones. 2. Simple set of access rules Only access components in module API packages 3. API and test support to bootstrap modules Integration with Spring test context framework and Spring Boot integration test support to limit bootstrap (component & entity scanning) to only the involved packages. 4. Documentation support PlantUML integration via Simon Brown’s Structurizr to document module structure and dependency types.
  27. 27 package com.acme @Modulith // Instead of @SpringBootApplication class MyApplication

    { … } package com.acme.moduleA @ModuleTest // Instead of @SpringBootTest class MyModuleTests { … } Applies package conventions Bootstraps single module for test
  28. 28 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts 


    Event 
 Event Component Dependency Type dependency
  29. 29 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts 


    Event 
 Event Component Dependency Type dependency
  30. DEMO 30

  31. Bootstrap modes 31 1. Standalone Bootstraps the module the test

    is contained in. Components in other modules the bootstrapped module depends on need to be provided through @MockBean. 2. Direct dependencies Bootstraps the module including the modules it directly depends on. Transitive dependencies have to be mocked just like in standalone mode. 3. All dependencies Bootstraps the entire sub-tree of modules starting from the current one.
  32. 32 Orders Business
 Time Catalog Inventory Accountancy User
 Accounts 


    Event 
 Event Component Dependency Type dependency
  33. Some conventions… 33 01 10 M M 01 10 Allowed

    by the compiler Allowed by Moduliths Rejected by the compiler Rejected by Moduliths Public Package protected Bounded Context Java Package
  34. API Package Convention 34 ….modulith ….modulith.moduleA ….modulith.moduleB Visibility modifiers and

    the compiler enough to hide internals.
  35. Module B moduleB Module A moduleA 35 ComponentA ComponentAImpl ComponentB

    ComponentBImpl Public Package protected Bounded Context Java Package 01 10 01 10
  36. API Package Convention 36 ….modulith ….modulith.moduleA ….modulith.moduleA.internal ….modulith.moduleB ….modulith.moduleB.internal Access

    to components
 residing in internal packages
 forbidden and checked
 during tests.
  37. Module A moduleA.foo Module B moduleB moduleA 37 ComponentA ComponentAImpl

    ComponentB ComponentBImpl moduleA.bar SupportA SupportAImpl 01 10 M 01 10 01 10 Public Package protected Bounded Context Java Package
  38. DEMO 38

  39. Thank you! 39 @olivergierke Oliver Drotbohm ƀ odrotbohm@pivotal.io odrotbohm

  40. Resources 40 Moduliths Project website on GitHub Majestic Modular Monoliths

    Axel Fontaine, 8.11. 16:30-17:30 – Raum: Calgary Video on Youtube Modular Monoliths Simon Brown – Video on YouTube Refactoring to a System of Systems Video on YouTube