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

Building Moduliths That Last: Patterns for Sust...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Building Moduliths That Last: Patterns for Sustainable Module Integration

A system does not need to be distributed to have clean architectural boundaries. Moduliths (modular monoliths) have emerged as a popular alternative to microservices - But doesn't that mean we're going back to where we started from? Won't today's moduliths end up as Big Balls of Mud again in a few years?

The key to a sustainable modulith is clean integration between modules. The shared runtime offers more integration options but also creates the temptation to take shortcuts and create a hot mess. This talk compares different approaches to integrating modules in a modulith. We will explore how different domains suit specific patterns better than others, and distill that knowledge into practical heuristics. We will also examine tactics for establishing these patterns in existing codebases.

As a developer or architect, you will leave with a toolkit of modulith integration patterns and antipatterns, guidelines for choosing the right ones, and the knowledge to establish them in legacy applications.

Avatar for SQUER Solutions

SQUER Solutions

October 29, 2026

More Decks by SQUER Solutions

Other Decks in Programming

Transcript

  1. We overdid it with microservices ! True! In most cases

    , a modulith would be enough ! I agree …
  2. If it’s so easy to create a well - structured

    modulith … Why are most real - life monoliths BIG BALLS OF MUD?
  3. LEAD SOFTWARE ENGINEER Christoph Kober If it’s so easy to

    create a well - structured modulith … Why are most real - life monoliths BIG BALLS OF MUD? Transformation Engineer Fan of Japan DDD Aficionado
  4. As we dive into the existing codebase , we find

    that all modules are integrated with direct calls . Is that a problem ? How can we improve the situation ?
  5. INTEGRATION PATTERNS Direct Calls @Transactional public void cancelTour(CancelTourCommand command) {

    var now = LocalDateTime.now(); var tour = tourRepository.findRequiredById(command.tourId()); tour.cancel(now); } var paymentPool = paymentService.getPaymentPool(tour.getId().toString()); paymentPool.refund(now);
  6. INTEGRATION PATTERNS The Good Parts Direct Calls - USAGE –

    The Bad Parts Starting Point Tight Coupling Simple Applications Leaking Internals Moving away from DB Integration Implicit Interfaces
  7. INTEGRATION PATTERNS Direct Calls @Transactional public void cancelTour(CancelTourCommand command) {

    var now = LocalDateTime.now(); var tour = tourRepository.findRequiredById(command.tourId()); tour.cancel(now); } var paymentPool = paymentService.getPaymentPool(tour.getId().toString()); paymentPool.refund(now);
  8. INTEGRATION PATTERNS - REFACTORINGS Refactoring – Introduce Facade @Transactional public

    void cancelTour(CancelTourCommand command) { var now = LocalDateTime.now(); var tour = tourRepository.findRequiredById(command.tourId()); tour.cancel(now); payments.refund(new RefundPaymentPoolCommand(tour.getId().toString())); var paymentPool = paymentService.getPaymentPool(tour.getId().toString()); } paymentPool.refund(now); }
  9. INTEGRATION PATTERNS - REFACTORINGS Refactoring – Introduce Mediator @Transactional public

    void cancelTour(CancelTourCommand command) { var now = LocalDateTime.now(); var tour = tourRepository.findRequiredById(command.tourId()); tour.cancel(now); mediator.send(new payments.refund(new RefundPaymentPoolCommand(tour.getId().toString())); var paymentPool = RefundPaymentPoolCommand(tour.getId().toString())); paymentService.getPaymentPool(tour.getId().toString()); } paymentPool.refund(now); }
  10. All integrations now live directly in the modules . It’s

    hard to follow a full business workflow end - to - end. What can we do?
  11. INTEGRATION PATTERNS Orchestration Layer @Component public class CancelTourWorkflow { //

    ... } @Transactional public void cancelTour(UUID tourId) { tours.cancelTour(new CancelTourCommand(tourId)); payments.refund(new RefundPaymentPoolCommand(tourId.toString())); }
  12. INTEGRATION PATTERNS Refactoring – Introduce Orchestration Layer @Component public class

    CancelTourWorkflow { // ... @Transactional public void cancelTour(UUID tourId) { tours.cancelTour(new CancelTourCommand(tourId)); } payments.refund(new RefundPaymentPoolCommand(tourId.toString())); } } }
  13. INTEGRATION PATTERNS Refactoring – Introduce Orchestration Layer @Component public class

    CancelTourWorkflow { // ... } @Transactional public void cancelTour(UUID tourId) { tours.cancelTour(new CancelTourCommand(tourId)); payments.refund(new RefundPaymentPoolCommand(tourId.toString())); }
  14. INTEGRATION PATTERNS The Bad Parts Business Logic creeping in -

    USAGE Orchestration Layer – The Good Parts Valuable for a transition phase Full decoupling of Modules Anemic Models & Modules Full process in one place Easy to implement Hard to distribute later Full process in one place
  15. Direct Calls introduce explicit contract Facade use as foundatio n

    for Orchestration Layer decouple with Mediator replace with
  16. Our orchestration layer grew . It mostly solves integration problems

    ; the business workflows became secondary . Do we have alternative options ?
  17. INTEGRATION PATTERNS - USAGE Process Engines The Bad Parts Business

    Logic creeping in – The Good Parts Adaptable by Business Experts Visualized Process Anemic Models & Modules Supports media discontinuity Rarely touched by non - coders Live Process Observability Code is easier to work with Reusability & Customization
  18. Direct Calls introduce explicit contract Facade use as foundatio n

    for Orchestration Layer decouple with Mediator replace with replace with Process Engine
  19. We want to continuously extend our workflows . But as

    we are going , more and more details are creeping in and coupling increases . How can we improve the evolvability of our workflows ?
  20. INTEGRATION PATTERNS INTEGRATION PATTERNS Events – Publishing Events – Processing

    @Transactional public void cancelTour( CancelTourCommand command) { @EventListener @Transactional public void onTourCancelled( TourCancelledEvent event) { var tour = tourRepository .findRequiredById(command.tourId()); var pool = paymentPoolRepository .findByReference(event.tourId().toString()); tour.cancel(LocalDateTime.now()); } publisher.publish( new TourCancelledEvent(tour.getId())); } pool.refund(LocalDateTime.now());
  21. INTEGRATION PATTERNS Events - USAGE – The Bad Parts Need

    to be versioned Asynchronous by default Eventual consistency
  22. INTEGRATION PATTERNS Events - USAGE – The Bad Parts Need

    to be versioned Asynchronous by default Eventual consistency Does not automatically apply to moduliths !
  23. INTEGRATION PATTERNS The Good Parts Events - USAGE – The

    Bad Parts Pipeline Domains Process is code - only & implicit Complex integrations Observability is harder Evolvability needed Requires rethinking
  24. If you are using Event Sourcing, separate between your Event

    Sourcing events and your Integration events
  25. Direct Calls introduce explicit contract Facade introduc e Events split

    workflow with use as foundatio n for Orchestration Layer decouple with Mediator Integration Events replace with replace with Process Engine
  26. Through events , we can integrate our change workflows and

    make them extendable . But sometimes , we just want to view data from different modules in the same UI. Do we really need to integrate all data through events to achieve this ?
  27. OUR EXAMPLE DOMAIN UI Integration – Towards Self - Contained

    Systems CityTour.now Deployment Monolith Guides Tours Payments Guides DB Tours DB Payments DB
  28. OUR EXAMPLE DOMAIN UI Integration – Towards Self - Contained

    Systems CityTour.now Deployment Monolith GUIDES MODULE TOURS MODULE UI PAYMENT MODULE UI UI Guides Tours Payments BUSINESS LOGIC BUSINESS LOGIC BUSINESS LOGIC Guides DB async Tours DB async Payments DB
  29. INTEGRATION PATTERNS The Bad Parts Business Logic creeping in -

    USAGE UI Integration – The Good Parts Backoffice Use - Cases Transition Phase Nano - Frontend Trap Dashboards Only for User Interactions Diversified Businesses
  30. Direct Calls introduce explicit contract replace read - only data

    integration Facade decouple with introduc e Events split workflow with use as foundatio n for Orchestration Layer replace read - only data integration UI Integration Mediator Integration Events replace with replace with Process Engine
  31. Enabling teams. Transforming IT landscapes. Digitalizing enterprises. Althanstraße 4/3/63 1090

    Vienna (+43) 676 849963 200 August - Everding - Str. 25 81671 Munich (+49) 151 5251 7885 [email protected] www.squer.io www.code - crafts.com