Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Majestic Modular Monoliths

Axel Fontaine
February 22, 2018

Majestic Modular Monoliths

Microservices are everywhere. Everyone seems to be either going into that direction or is talking about doing so. But are they really the best choice for you?

Developers! Architects! Buckle up as we're going to cut through the hype. Instead of going all-in on microservices or all-in on big ball of mud, we'll introduce a third choice: the Majestic Modular Monolith! We'll look at what this brings to the table, when this may be a good fit and how it compares to the other two approaches in terms of code organization, productivity, scalability and much more. We'll look at how this can be designed and implemented in practice, we won't shy away from the hard questions.

Video recording: https://vimeo.com/233980163

Axel Fontaine

February 22, 2018
Tweet

More Decks by Axel Fontaine

Other Decks in Technology

Transcript

  1. Majestic Modular Monoliths Photo by Jon Clark from Darwin, NT,

    Australia - Balls Pyramid pano, CC BY 2.0, https://commons.wikimedia.org/w/index.php?curid=41160475 @axelfontaine
  2. 10110111 00101011 01011011 11001... MyClass{ do() { run(); ... As

    code size increases, where is the bottleneck?
  3. 10110111 00101011 01011011 11001... MyClass{ do() { run(); ... As

    code size increases, where is the bottleneck? 95% / 5% Read/Write Ratio
  4. MyClass{ do() { run(); ... As code size increases, where

    is the bottleneck? 95% / 5% Read/Write Ratio Optimize for this!
  5. MyClass{ do() { run(); ... MyClass{ do() { run(); ...

    MyClass{ do() { run(); ... As code size increases, where is the bottleneck? 95% / 5% Read/Write Ratio ✓ Add Structure ✓ Raise Abstraction ✓ Reduce Cognitive Load
  6. MyClass{ do() { run(); ... MyClass{ do() { run(); ...

    MyClass{ do() { run(); ... As code size increases, where is the bottleneck? 95% / 5% Read/Write Ratio Seeing the forest through the trees!
  7. MyClass{ do() { run(); ... MyClass{ do() { run(); ...

    MyClass{ do() { run(); ... As code size increases, where is the bottleneck? 95% / 5% Read/Write Ratio ✓ Methods ✓ Classes ✓ Packages ✓ Modules ✓ Applications
  8. Architecture = Interactions & Relationships between these ✓ Methods ✓

    Classes ✓ Packages ✓ Modules ✓ Applications
  9. Monolith Microservices • One artifact • Entanglement risk • Simple

    method calls • All parts always up and available • Easy interface refactoring • Application scales as a unit • One database • Transactions • One platform • JAR hell risk (forced dependency convergence) • Limited team parallelization • Many individual services • Focus on clear small units • Unreliable network calls • Service discovery + internal load balancing + circuit breakers • Difficult to refactor • Services scale individually • Polyglot persistence • Eventual consistency • Platform choice • Works with incompatible library versions • Easy team parallelization
  10. Very large organizations Very complex applications Very small organizations Very

    simple applications Monolith Microservices Is there room for something in the middle?
  11. Module Boundaries and Structure API ✓Look for highly cohesive blocks

    of functionality ✓Aim for low coupling to other modules ✓Expose a small, well-defined, stable API ✓Only expose aggregate root ✓Make implementation and other entities private ✓Only depend on API (not impl) of other modules Impl (private) Reduces cognitive load!
  12. Code Isolation Separate Package Maven Module Separate Repo External Service

    different JAR same version same app different JAR different version same app same JAR same version same app different JAR different version different app Microservices Monolith Great for sharing technical & infra. modules between apps!
  13. Maven Module Separate Repo different JAR same version same app

    different JAR different version same app ✓Restricts compile classpath (but not reflection!) ✓Enforces directed acyclic graph (Maven module build order) ✓Only rebuild & test module and its dependents mvn clean install -pl mymodule -amd ✓Easy overview diagrams with IntelliJ or Maven Graph Plugin Seeing the forest through the trees!
  14. Avoiding Jar Hell myapp libxyz 5.3 libabc 4.0 mymodule libabc

    2.0 ✓Limit your dependencies to absolute minimum ✓Prefer libraries with zero transitive dependencies ✓Enforce dependency convergence ✓Shade if conflict is not resolvable
  15. <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>3.0.0-M1</version> <executions> <execution> <id>enforce</id> <configuration> <rules> <dependencyConvergence>

    <uniqueVersions>true</uniqueVersions> </dependencyConvergence> </rules> </configuration> <goals> <goal>enforce</goal> </goals> </execution> </executions> Enforcing Dependency Convergence
  16. Enforcing Package Usage (Java 9 with named modules) module com.hello.payment

    { exports com.hello.payment.api; requires com.hello.invoice.api; requires java.sql; }
  17. Enforcing Package Usage (Java 8 with code-assert) @Test public void

    dependency() { class ComHello extends DependencyRuler { DependencyRule paymentApi, paymentImpl, invoiceApi; public void defineRules() { paymentApi().mayUse(invoiceApi); paymentImpl().mayUse(paymentApi, invoiceApi); } } AnalyzerConfig config = AnalyzerConfig.maven().main(); DependencyRules rules = DependencyRules.denyAll() .withRelativeRules(new ComHello()).withExternals("java.sql"); DependencyResult result = new DependencyAnalyzer(config).rules(rules).analyze(); assertThat(result, matchesRulesExactly()); } https://github.com/nidi3/code-assert
  18. Relational Databases ✓Ensure each module only accesses its own tables

    ✓No sharing of tables between modules ✓Joins only between tables of same module (not across modules!) ✓Maintain referential integrity and transactions across modules
  19. Data Isolation Separate Table Separate Schema Separate Database Other Persistence

    different schema same database same RDBMS different schema different database same RDBMS same schema same database same RDBMS different schema different database different (R)DBMS Same flyway_schema_history table
  20. Scaling Asymmetrically Executor Service different capacity per module ExecutorService paymentExecutor

    = Executors.newFixedThreadPool(10); ExecutorService invoiceExecutor = Executors.newFixedThreadPool(25);
  21. Scaling Asymmetrically Executor Service RDBMS Queue different number of consumers

    per module, persistent and participates in RDBMS transaction different capacity per module id message 1 {“customer”:”abc”} 2 {“other”:”xyz”} INSERT INTO queue (message) VALUES (?) DELETE FROM queue WHERE id = (SELECT id FROM queue ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED) RETURNING message
  22. Scaling Asymmetrically Executor Service RDBMS Queue Dedicated Queue different number

    of consumers per module, persistent and participates in RDBMS transaction different number of consumers per module, persistent and very high performance different capacity per module
  23. boxfuse.com Continuous Deployment as a Service for JVM, Node.js and

    Go apps on AWS Deploying ✓ Up and running in minutes ✓ Deploy with 1 command ✓ Focus on development ✓ Immutable Infrastructure as Code ✓ Minimal images ✓ Zero downtime blue/green deployments boxfuse run my-majestic-monolith.jar –env=prod
  24. flywaydb.org Evolve your relational database schemas reliably across all your

    environments for each of your modules and services with pleasure and plain SQL ✓ Supports all popular RDBMS ✓ Millions of users ✓ Designed for Continuous Delivery ✓ Open-source Community Edition and commercial Pro and Enterprise Editions ✓ Highly focused and very easy to get started Database Schema Evolution
  25. Best, pragmatic, choice for most companies combining the simplicity of

    monoliths with structure and focus of microservices Summary Majestic Modular Monolith