Modularizing the Monolith - Jimmy Bogard - NDC Oslo 2024

Learn practical strategies for breaking down monolithic applications into well-structured modules, with insights on boundaries, data management, and avoiding common pitfalls.

Key takeaways
  • A monolith is a system with a single unit of deployment, where all modules are deployed together. The main challenge with monoliths is not their size but their lack of clear boundaries between different areas of functionality

  • Modularization helps manage complexity by organizing code around business capabilities and enforcing clear boundaries between modules. Modules should:

    • Be independent and interchangeable
    • Have well-defined public contracts/APIs
    • Contain all logic and data needed for their functionality
    • Stretch from UI to database layer
  • Vertical slice architecture helps identify natural module boundaries by organizing code around user actions/features rather than technical layers. This makes it easier to:

    • Understand business processes
    • Identify related functionality
    • Move code between modules
    • Enforce module independence
  • Data management is critical for modularization:

    • Each module should own and control its data
    • Avoid sharing data between modules directly
    • Be careful with data duplication and consistency
    • Consider temporal aspects of data
  • Moving from monolith to microservices is challenging because:

    • Distributed systems are inherently complex
    • Boundary decisions are hard to change later
    • Teams often attempt rewrites instead of incremental changes
    • Technology choices become more constrained
  • Successful modularization strategy:

    • Start with existing monolith
    • Identify business capabilities and processes
    • Create vertical slices of functionality
    • Group related slices into modules
    • Enforce boundaries through explicit contracts
    • Consider async messaging between chatty modules
  • Module independence can be measured by:

    • Number of dependencies on other modules
    • Amount of shared code/data
    • Frequency of communication between modules
    • Ability to change without affecting other modules