Last active
June 22, 2025 10:09
-
-
Save odrotbohm/b9d77bf02f0072b7142e6ad3b9bd63f7 to your computer and use it in GitHub Desktop.
Revisions
-
odrotbohm revised this gist
Jun 4, 2025 . No changes.There are no files selected for viewing
-
odrotbohm revised this gist
Jun 4, 2025 . 1 changed file with 40 additions and 31 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,61 +1,68 @@ = Modulith VS. Microservices :icons: font :docdate: 2025-06-04 This document contains a tabular comparison of the Modulithic and Microservice Architecture approach under certain aspects. Many of them can be achieved or influenced in both styles, but to different degrees and using different means. No claims of exhaustiveness. [options="header", cols="1h,2a,2a"] |=== ||Modulith|Microservices |[[domain-structure]]Domain structure <<domain-structure,#>> |* Complex * Explorative, Bounded Context boundaries likely to shift |* Simple * Well-known |[[meta]]Application meta-work focus <<meta,#>> |Clearly separating the bounded contexts, prevent accidental coupling. |Managing infrastructure concerns and challenges induced by the fact that we're building a distributed system. |[[refactoring]]Ability to refactor <<refactoring,#>> |icon:plus-circle[role="green"] -- In the best case, even breaking API changes can be propagated through the system immediately as the compiler builds a huge part of the system ad hoc. Refactorings guided by IDEs. |icon:minus-circle[role="red"] -- As API provider and consumer are distant, special means of verification need to be deployed to detect breaking changes (e.g. consumer-driven contracts). |[[means-of-modularity]]Means of implementing modularity <<means-of-modularity,#>> |Programming language specific means of encapsulation. In Java: classes, packages, artifacts (i.e. build system modules). For Spring applications: Spring Modulith. |Deployment units and the network APIs they expose. |[[api-abstraction-level]] API abstraction level <<api-abstraction-level,#>> |Platform level APIs (e.g. Java). Modules expose domain types or DTOs, Spring components, and published as well as consumed events. |HTTP / Messaging. Modules expose resources and API definitions via the schema of the representations (usually JSON documents) they exchange. Also: GraphQL. |[[interaction-model]] Module interaction model <<interaction-model,#>> |Direct method invocations and (asynchronous) application events within the same process, which makes them fast and less susceptible to error conditions. The method call can either succeed or throw an exception. Eventual consistency optional (see <<consistency, Consistency>>). |Network calls to other systems via HTTP, any RPC technology or messaging via a broker. That introduces the need for serialization and deserialization of data as well as guards against communication problems on the network like circuit breakers, retries etc. Also, that interaction is significantly slower than direct method invocations and implies the need to embrace and deal with eventual consistency. |[[consistency]]Consistency <<consistency,#>> |Strong consistency preferred, although eventual consistency can be embraced t the degree needed (ideally cross aggregates) and is supported through Spring Modulith's https://docs.spring.io/spring-modulith/reference/events.html[Event Publication Registry]. Transactions can be used by the book, e.g. to apply changes on a single aggregate but also span multiple ones if needed and the consequences are acceptable. The latter should be a conscious decision and made explicit. The ability to "veto" a unit of work by foreign modules (e.g. through an event listener participating in the same transaction) provides a certain level of convenience but also increases coupling. |Eventual consistency _must_ be embraced, adding complexity. Business transactions that include multiple modules need to use sagas and compensating actions. |[[integration]]Integration <<integration,#>> |Focused on infrastructure components that are owned by the application. Integration with 3rd-party systems is rare(r) |Interacting with other systems is a primary concern of the architecture and needs development focus. Designing the system to avoid interaction with other systems while serving user requests needs careful design. |[[dependency-management]] Means of verifying dependencies <<dependency-management, #>> |Build system modules. https://www.archunit.org/[ArchUnit], https://github.com/jqassistant[jQAssistant]. Static code analysis tools like jDepend, Structure 101, https://spring.io/projects/spring-modulith[Spring Modulith]. |Runtime communication analysis through Zipkin etc. |[[technology]] Freedom of technology choice <<technology, #>> |icon:plus-circle[role="red"] -- Limited to the chosen platform. In the case of the JVM, multiple JVM languages can be combined theoretically but significantly increase build complexity. |icon:plus-circle[role="green"] -- Full freedom of choice of technology as the means of operation are individual operating system processes. |[[code-organization]] Source code organization <<code-organization, #>> |Likely a single repository, structured into build system modules as needed on the spectrum between single-module, packages-only to build module per logical module. Constraints: @@ -65,22 +72,24 @@ Constraints: * The overhead induced by the split up into multiple build modules. |Separate repositories per bounded context to be able to individually release and deploy versions of the involved systems. |[[developer-productivity]] Developer productivity <<developer-productivity, #>> |The codebase can usually be imported into the IDE as one or as all build modules individually. |The design of business functionality within a single module is usually simpler than in a monolithic arrangement. The additional technical challenges usually add complexity in that regard. Other systems and infrastructure need to be set up to run the module individually and application as a whole. |[[build-and-deployment]] Build & Deployment <<build-and-deployment, #>> |Usually as a whole. Being able to run a build considering changes of individual modules requires tweaks to the build setup and CI infrastructure. |Usually individually. Building a single module is the default. Requires verification of compatibility though (see <<refactoring>>). |[[testing]] Testing <<testing, #>> |Unit and integration tests. The latter usually run the entire application. Horizontal slices testable via Spring Boot support. Vertical slices via Spring Modulith (https://docs.spring.io/spring-modulith/reference/testing.html[integration test documentation]). Testing the system entirely is the default model in integration tests. Version control system inspection can help detecting which tests need to be run (https://docs.spring.io/spring-modulith/reference/testing.html#change-aware-test-execution[Spring Modulith test optimization documentation]). Challenges: * Running integration tests for individual modules. Spring Modulith's integration tests features help here. |Integration tests run on the individual module level by default. @@ -89,11 +98,11 @@ Challenges: * Unless explicitly designed to avoid this, testing is likely to require other modules and some infrastructure to run as well, complicating the undertaking. * Testing the overall system requires the individual modules to be run. The definitions of the overall system tests also have to live somewhere. |[[scalability]] Scalability <<scalability, #>> |icon:minus-circle[role="red"] -- Individual modules cannot be scaled separately easily. One, limited means to help here is caching which can be applied more aggressively to some parts of the system but the effect that this delivers is limited by the ability to cache that part of the domain in the first place. |icon:plus-circle[role="green"] -- As the individual modules are deployed separately, each of them can be scaled individually. |[[isolation]] Isolation <<isolation, #>> |icon:minus-circle[role="red"] -- Not given by default and only achievable on the dependency level by using technologies like OSGi that provide class loader level isolation of code. Memory and CPU are shared. |icon:plus-circle[role="green"] -- Resources can be assigned by individual process. |=== -
odrotbohm created this gist
Apr 24, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,99 @@ = Moduliths VS. Microservices :icons: font [options="header", cols="1h,2a,2a"] |=== ||Moduliths|Microservices |Domain structure |* Complex * Explorative, Bounded Context boundaries likely to shift |* Simple * Well-known |Application meta-work focus |Clearly separating the bounded contexts, prevent accidental coupling. |Managing infrastructure concerns and challenges induced by the fact that we're building a distributed system. |[[refactoring]]Ability to refactor |icon:plus-circle[role="green"] -- In the best case, even breaking API changes can be propagated through the system immediately as the compiler builds a huge part of the system ad hoc. |icon:minus-circle[role="red"] -- As API provider and consumer are distant, special means of verification need to be deployed to detect breaking changes (e.g. consumer-driven contracts). |Means of implementing modularity |Programming language specific means of encapsulation. In Java: classes, packages, artifacts (i.e. build system modules). |Deployment units and the network APIs they expose. |API abstraction level |Platform level APIs (e.g. Java). Modules expose aggregates, Spring components and publishes as well as consumed events. |HTTP / Messaging. Modules expose resources and API definition via the schema of the representations (usually JSON documents) they exchange. |Module interaction model |Direct method invocations and application events within the same process, which makes them fast and less susceptible to error conditions. The method call can either succeed or throw an exception. |Network calls to other systems via HTTP, any RPC technology or messaging via a broker. That induces the need for serialization and deserialization of data as well as guards against communication problems on the network like circuit breakers, retries etc. Also, that interaction is significantly slower than direct method invocations and implies the need to embrace and deal with eventual consistency. |Consistency |Strong consistency preferred, although eventual consistency can be embraced t the degree needed (ideally cross aggregates). Transactions can be used by the book, e.g. to apply changes on a single aggregate but also span multiple ones if needed and the consequences are acceptable. The latter should be a conscious decision and made explicit. The ability to "veto" a unit of work by foreign modules (e.g. through an event listener participating in the same transaction) provides a certain level of convenience but also increases coupling. |Eventual consistency _must_ be embraced, adding complexity. Business transactions that include multiple modules need to use sagas and compensating actions. |Integration |Focused on infrastructure components that are owned by the application. Integration with 3rd-party systems is rare(r) |Interacting with other systems is a primary concern of the architecture and needs development focus. Designing the system to avoid interaction with other systems while serving user requests needs careful design. |Means of verifying dependencies |Build system modules. Static code analysis tools like jDepend, Structure 101, Moduliths. |Runtime communication analysis through Zipkin etc. |Freedom of technology choice |Limited to the chosen platform. In the case of the JVM, multiple JVM languages can be combined theoretically but significantly increase build complexity. |Given. As the abstraction level of interactions |Source code organization |Likely a single repository, structured into build system modules as needed on the spectrum between single-module, packages-only to build module per logical module. Constraints: * The need to consume and assemble different sets of logical modules into the final runtime. * The question of whether logical dependency management should be applied via build tool dependency management. * The overhead induced by the split up into multiple build modules. |Separate repositories per bounded context to be able to individually release and deploy versions of the involved systems. |Developer productivity |The codebase can usually be imported into the IDE as one or as all build modules individually. |The design of business functionality within a single module is usually simpler than in a monolithic arrangement. The additional technical challenges usually add complexity in that regard. Other systems and infrastructure need to be set up to run the module individually and application as a whole. |Build & Deployment |Usually as a whole. Being able to run a build considering changes of individual modules only requires tweaks to the build setup and CI infrastructure. |Usually individually. Building a single module is the default. Requires verification of compatibility though (see <<refactoring>>). |Testing |Unit and integration tests. The latter usually run the entire application. Horizontal slices testable via Spring Boot support. Horizontal slices via Moduliths. Testing the system entirely is the default model in integration tests. Challenges: * Running integration tests for individual modules. Moduliths helps here. |Integration tests run on the individual module level by default. Challenges: * Unless explicitly designed to avoid this, testing is likely to require other modules and some infrastructure to run as well, complicating the undertaking. * Testing the overall system requires the individual modules to be run. The definitions of the overall system tests also have to live somewhere. |Scalability |icon:minus-circle[role="red"] -- Individual modules cannot be scaled separately easily. One, limited means to help here is caching which can be applied more aggressively to some parts of the system but the effect that this delivers is limited by the ability to cache that part of the domain in the first place. |icon:plus-circle[role="green"] -- As the individual modules are deployed separately, each of them can be scaled individually. |Isolation |icon:minus-circle[role="red"] -- Not given by default and only achievable on the dependency level by using technologies like OSGi that provide class loader level isolation of code. Memory and CPU are shared. |icon:plus-circle[role="green"] -- Resources can be assigned by module. |===