The microservices honeymoon is over. When starting a new project or revamping a legacy monolith, teams started looking for alternatives to microservices. The Modular Monolith, or 'Modulith', is an architecture that reaps the benefits of (vertical) functional decoupling without the high costs associated with separate deployments. This talk will delve into the advantages and challenges of this progressive architecture, beginning with exploring the concept of a 'module', its internal structure, public API, and inter-module communication patterns. Supported by spring-modulith, the talk provides practical guidance on addressing the main challenges of a Modultith Architecture: finding and guarding module boundaries, data decoupling, and integration module-testing. You should not miss this talk if you are a software architect or tech lead seeking practical, scalable solutions.
About the author
With two decades of experience, Victor is a Java Champion working as a trainer for top companies in Europe. Five thousands developers in 120 companies attended his workshops, so he gets to debate every week the challenges that various projects struggle with. In return, Victor summarizes key points from these workshops in conference talks and online meetups for the European Software Crafters, the world’s largest developer community around architecture, refactoring, and testing. Discover how Victor can help you on victorrentea.ro : company training catalog, consultancy and YouTube playlists.
2. 👋 I'm Victor Rentea 🇷🇴 Java Champion, PhD(CS)
18 years of coding
10 years of training & consul2ng at 130+ companies on:
❤ Refactoring, Architecture, Unit Tes4ng
🛠 Java: Spring, Hibernate, Performance, ReacIve
Lead of European So8ware Cra8ers (7K developers)
Join for free monthly online meeIngs from 1700 CET
Channel: YouTube.com/vrentea
Life += 👰 + 👧 + 🙇 + 🐈 @victorrentea
https://victorrentea.ro
🇷🇴
past events
6. 6 VictorRentea.ro
Big Ball of Mud
(BBoM Pattern)
= a sprawling, sloppy,
duct-tape,
spaghetti-code jungle,
showing signs of unregulated growth,
and repeated expedient repair.
In desperate need of refactoring, but lacking tests.
https://wiki.c2.com/?BigBallOfMud
7. 7 VictorRentea.ro
☢ nano-services
Velocity
⏩
FAST
🐌
SLOW
Microservices
Autonomy of microservices
outweighs their overhead
Development Velocity
☢ BBoM
Monolith My system will be complex,
so, let's start with microservices!
(Next-Ne)lix Syndrome)
Project Age
Early Late
produc3vity drops due to
high complexity & coupling
1-2 years or ≥ 7 dev
8. 8 VictorRentea.ro
You should NOT start a new project with microservices,
even if you're sure your application will be big enough
to make it worthwhile. - Martin Fowler
principal microservice evangelist
h5ps://mar3nfowler.com/bliki/MonolithFirst.html (2015)
Instead: start simple (KISS) and
address the system's natural bottlenecks as they occur
( bugs, delays, dev pain )
😔 😔 😔
Meanwhile: "If we don't start with microservices, THEY (the bosses) won't
give us the .me we need to break it into microservices later" = WASTE
10. 10 VictorRentea.ro
Pain-Driven Architecture
Protect core logic vs ugly API call with an Adapter
Split Complexity by Layers of Abstraction (ie. Facade)
Isolate Persistence if Shared/Legacy storage
Grow a Rich Model in Complex Domains
Refactor to Modulith when team grows
Use Events for cleaner 1:N Decoupling
Go to Microservices to meet NFRs
12. 12 VictorRentea.ro
1) Big-Bang Rewrite of a 12y-old, 3M LOC codebase
- Re-gather all requirements and start from scratch
- Any changes to the old system you (a) reject, (b) delay, or (b) cost x2 (old+new)
- 20-30% success rate & likely to turn into a Distributed Monolith
2) Strangler-Fig Pa4ern (outside-in) ✅
- Rewrite decoupled parts as microservices behind a proxy protecIng clients
3) Refactor to Modules > Extract (inside-out) ✅
- Progressively decouple logic and data in the monolithic codebase
From a BBoM to Microservices
13. 13 VictorRentea.ro
Refactor to Modules
Modulith
Can prove the best
in some domains
Velocity
⏩
FAST
🐌
SLOW
Microservices
Development Velocity
Monolith
Project Age
Early Late
Easy to extract
a microservice
14. 14 VictorRentea.ro
Can we have the best of both?
Deployment architecture of a Monolith
Logical decoupling of Microservices
+ Easy to extract a Microservice
19. 19 VictorRentea.ro
Goal of Modulith
independent teams managing decoupled modules
Example #1 (e-health)
each team {4-5 DEV+FE+PO+QA} owns ~4 modules
x 8 teams
+ 2 x FuncIonal Architects
+ 3 x Core Pladorm Team
total = 60 people
CI build ≤ 15 minutes (tuned)
1 prod deploy /week on 10 instances
Example #2 (pharmacy): 30 people
Example #3 (loan approval): 24 people
24. 24 VictorRentea.ro
= a stand-alone logical applicaEon, having its own:
§business features (user value)
§private implementaIon: domain model + logic
§public API:
- internal, for other modules: via method calls or events
- external, for other systems: via REST, Rabbit, KaOa...
§private tables in database
§micro-frontend: screens & shared components (monorepo)
What is a Module?
25. 25 VictorRentea.ro
Internal API
for other modules
External API
for other systems
MQ
RPC
implementa4on
not accessible
by other modules
Events
publish
<< plugin >>
B
listen
Methods
Interface
26. 26 VictorRentea.ro
§Publisher should NOT expect any effect ⚠
§Publisher is unaware of listeners (poten;ally mul;ple) J
§The order of listeners should not ma>er. If it does: chain a new event ⚠
§Events can carry state to avoid a call back to publisher 🤔
§All listeners run sequen1ally in the publisher's thread and transac;on (if any)
§Listeners can be @Async
§Events can be persisted if sent over KaJa/Rabbit..
- Spring-modulith @Applica3onModuleListener persist events in DB un3l processed
⚠ Events are harder to navigate than method calls è use only between modules
Events 101
Eventual Consistency
29. 29 VictorRentea.ro
§ArchUnit @Test www.archunit.org + ported to most languages
§Spring Modulith @Test spring.io/projects/spring-modulith
§Build Modules (Maven/Gradle)
Enforce Code Boundaries
@Test !// unit test running on CI
public void encapsulatedModules() {
var classes = new ClassFileImporter().importPackages("com.myapp");
var sliceRule = slices().matching("!..myapp.(*)!..*")
.should().notDependOnEachOther()
.ignoreDependency(resideInAnyPackage("!..shared!..", "!..api!.."));
sliceRule.check(classes); !// #1 fail on any deviation
List<String> violations = sliceRule.evaluate(classes)!!...;
assertThat(violations).hasSizeLessThan(33); !// #2 fitness function
}
@Test
void verifyModularity() { !// uses ArchUnit rules under the hood
ApplicationModules.of(ModulithApp.class).verify();
}
30. 30 VictorRentea.ro
⚠ Only a'er boundaries are clear AND the team is about to split
✅ Stronger separa2on
- Impossible to add excep:ons/@Disable to the ArchUnit @Tests 😏
- Impossible to have cycles è
- BeAer IDE sugges:ons (eg: imagine 2 classes named 'Product' in different modules)
✅ Selec2ve dependencies
- A module can decide to use jasperreports, use reactor, or refuse lombok
- ⚠ All modules share the SAME library VERSIONS in the final binary
✅ Par2al release
- Client customiza:ons: invoicing-nhs.jar
- Purchased features: payment-exports.jar
From Packages to Build Modules
(Maven/Gradle)
31. 31 VictorRentea.ro
Cyclic Dependencies
« module »
A
« module »
B
= Tight Coupling (they oEen change together)
⚠ Build fails if modules are separate build units (eg maven/gradle)
use
use
32. 32 VictorRentea.ro
?
?
A B
O
orchestrator
"facade"
Pull OrchestraIon Up
?
?
?
?
Allow the call cycle,
just fix the code cycle
shared
A-impl B-impl
A-api B-api
Extract API out
run5me calls
keep a coarse-grained API,
for a monolithic client/site
Strategical Dependencies between Modules
microservices: shared lib
microservices: events on queue
microservices: interdependent microservices + client.jar
microservices: api gateway/bff/saga microservices: shared lib/3rd service microservices: merge or break
❤
AB
Merge Modules
Tight Coupling
(eg dozens of links)
Dependency Inversion
B allows A to implement logic,
without depending on A
A B
ç event
call
Publish Events (in-mem)
⭐Decoupled
A B
S
Push Shared Down
A B
(code dep) implements è
call
Dependency Inversion
⭐Decoupled
AProviderImpl
runtime call
«interface»
AProvider
Module A must react to changes in B
"shared"
Module:
- StringUQls
- Logging/Audit
- Country list
Wrong boundaries.
Try others? 🤔
33. 33 VictorRentea.ro
The 'Shared' Module
§If large => BBoM ☢
- Possible starIng point coming from BBoM
§Extract business logic
§Extract complex concerns: common-security, common-kaOa..
§Contain internal API of all modules: api.catalog|.inventory 🤔
- ✅ Pros: simple fix to cyclic dependencies
- ⚠ Cons: risk of coupling internal APIs (write an ArchUnit rule)
§Contain staEc reference data: Country, Region, DiagnosEc...
- Be'er than dozens of nano-modules
34. 34 VictorRentea.ro
* the same applies for "Microservice"
Smaller! Nano!
Post-BBoM PTSD ?
How small should modules be?
or microservices
35. 35 VictorRentea.ro
The Fallacy of Nano-Services
The microservices honeymoon is over.
Uber is refactoring thousands of microservices into a more manageable solution;
Kelsey Hightower is predicting monoliths are the future;
Sam Newman is declaring that microservices should never be the default choice,
but rather a last resort.
Monolith can be cheaper (Amazon) and faster (StackOverflow.com)
https://vladikk.com/2020/04/09/untangling-microservices/
Smaller pieces
=> more coupling
36. 36 VictorRentea.ro
order product
fulfillment
place order
cancel order
return
catalog
search
filter
display
compare
stock
inventory
stock
warehouse
supply-chain
hEps://www.michaelnygard.com/blog/2018/01/services-by-lifecycle/
Scales
Be'er
Split by
Data Concept
(aka En*ty Service)
Bu$in€ss Capability / User Value
(aka Feature Service)
40. 40 VictorRentea.ro
Data Decoupling Levels
1. No IsolaGon (BBoM): everyone freely reads/writes any table
⚠ Data CorrupIon: a module could write valid data in a table, but later read bad data.
2. Write IsolaGon: one module writes into a table, but any other can read
🧠 Might require spliong a table (in separate schemas)
INVENTORY.ITEMS vs CATALOG.PRODUCTS, CATALOG.PRODUCT_ATTRIBUTES
⚠ Frozen Table Structure: an ALTER TABLE can break the readers
3. Exclusive Access🤩💖: modules have private schemas
😏 Trick: except read-only VIEWS exposed for others to JOIN : INVENTORY.V_STOCK(id, items)
4. Consistency per Module - tables of two modules:
(a) do NOT share any Foreign Key, and
(b) are NOT updated in the same Transac4on
Defer unIl microservice
extrac4on is imminent
Strong Consistency
Eventual Consistency
41. 41 VictorRentea.ro
(vs a tradiJonal Monolith)?
1. Complexity EncapsulaIon behind clear Module APIs
- Autonomy & Less cogniJve load per module
2. Smaller Domain Model gets specialized, thus more useful
- catalog.Product vs inventory.InventoryItem
3. Module owns its Persistence
- Easy to keep tables in sync with evolving Domain Model
What makes a Modulith maintainable
43. 45 VictorRentea.ro
§End-to-end tests are slow and fragile
- Prefer Module-scoped tests ✅ over fragile unit-tests ❌
§"Always ready to ship" using Feature Flags
- ⚠ Constantly remove unneeded flags
§Parallel Build 😎
§A Monorepo can build mulEple arEfacts:
- Modulith + Frontend + 2 Microservices 🤔
Unified Build
44. 46 VictorRentea.ro
§Shared TransacIons
- An excepIon in a module you call/noIfy can roll back your transacIon
§Resource starvaIon
- Modules share thread/DB connec4on pool è Monitoring ⚠
§Deadlocks
- In-process or out- (DB, Redis)
§Single instance of DI container
- Global seongs, rogue aspects, bean name collision
§Unified OpenAPI
- Single version? Type name collisions?
DifficulFes of "Running Together"
48. 50 VictorRentea.ro
Benefits of Microservices
ü Faster Time-to-Market => 😁 Business
if independently deployable by autonomous teams
ü Lower CogniGve Load => 😁 Developers
if small & aligned with business
ü Scalability for the hot🔥 parts that require it
ü Availability: fault-tolerance to parBal failures
ü Technology Freedom vs language/library version
ü Security / Privacy (GDPR) / Compliance
Modulith also
provides these
49. 51 VictorRentea.ro
Drawbacks of Microservices
§Complex to Develop & Deploy
§Network Latency & Reliability
§Asynchronous CommunicaGon (messages/events)
§Eventual Consistency
§Hard to Monitor, Trace & Debug
$M1 premium
50. 52 VictorRentea.ro
§Separate persistence
- No cross-schema SELECT. 🤔 Think...
- Drop all Foreign Keys to ó from other modules' schemas 🤔
- Stop sharing transac4ons with other modules 🤔
§Separate threads
- Turn method calls into REST localhost:8080 calls 🤔 (+propagate TraceId!)
- Events: @Async, @ApplicaIonModuleListener, or via Rabbit, Kawa 🤔
§Separate build & deploy 🎉
- Extract module X internal API as a standalone library (x-api.jar)
- Then separate Git repo 🤔
Steps to Extract a Module as a Microservice
POSTPONE
UNTIL
EXTRACTING A
MICROSERVICE
IS PLANNED IN
NEAR-FUTURE
if a step makes
business panic
STOP
undo, and rethink
extracQon
51. 54 VictorRentea.ro
§The safest & cheapest way to split a Monolith
- To experiment with boundaries & gradually enforce them
§A valid alternaEve to Microservices
- For complex domains with decent NFRs
Modular Monolith
is
53. I was Victor Rentea,
trainer & coach for experienced teams.
Git: h'ps://github.com/victorrentea/spring-modulith.git
Branch: devoxx-uk-24
Meet me online at: