Throughout the years, the Concentric Architectures (Onion, Hexagonal, Clean-..) have grown into the undisputed leader among backend systems architectures. With the rise of Domain-Driven Design, keeping your Domain ring 'agnostic' to the outside world has become the norm today. But history proved that any 'norm' in software architectures will cause overengineering if applied without criticism.
After a brief recap of these architectures, their pitfalls, and weaknesses, we'll see two alternatives that segregate code not in 'layers' or 'rings' but in vertical slices: Feature Slicing and Modular Monolith.
[Feature Slicing](vertical Slice Architecture) (aka *UseCase) has its own pitfalls and weaknesses, that we'll briefly review. But this will just warm us up for the next style.
Modular Monolith (aka Modulith) is an architecture style that helped many companies break their legacy codebases, and smoothly move to microservices. Most of the techniques discussed here can also come handy when one single microservice grew big and needs to be broken down.
Even more, greenfield projects today opt for this architecture instead of microservices, to avoid paying the high cost of distributability. Imagine cohesive but decoupled modules living in the same code base & deployment, but on which different teams work in harmony, delivering more value much faster than an equivalent microservice ecosystem.🦄
On the agenda:
- patterns to break data structures
- how to protect Domains inside modules
- communication patterns between modules
- breaking cyclic dependencies
4. 4 VictorRentea.ro
a training by
Chapter 1. Concentric Architectures
= Onion, Clean, Hexagonal, Ports-and-Adapters
5. 5 VictorRentea.ro
a training by
IAdapter
Adapter
Onion/Clean Architecture Recap
Outer layers depend on inner layers
Persistence concerns pushed out
Dependency Inversion
(call logic in an outer circle via an interface)
Services + Model
Flow control
(method call direction)
Domain is agnostic to
UI, DB, APIs, Frameworks
(protect the business rules)
Onion Architecture Primer: https://marcoatschaefer.medium.com/onion-architecture-explained-building-maintainable-software-54996ff8e464
7. 7 VictorRentea.ro
a training by
vs interface
Hexagonal aka Port-and-Adapters Architecture
Original Article: https://alistair.cockburn.us/hexagonal-architecture/
Bad cost-benefit
Useless
interface
implements
DepENDENCY INvERSION
Flow control
(method call direction)
8. 9 VictorRentea.ro
a training by
https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
9. 10 VictorRentea.ro
a training by
Sources of overengineering in Concentric Architectures
Too many interfaces
"any layer must only expose and consume interfaces"
Mandated layers
"a Controller MUST call a Service that MUST call a Repo"
Mock-full tests 🧅
"each layer is tested by mocking the layer below"
Separate Domain from Persistence 😭
"CustomerModel should be mapped CustomerEntity in Repo layer"
Application-layer structures
"Application layer should have its own data structures ≠Domain, ≠Dto"
Rigid dependency management
"domain MUST NEVER depend on anything outside"
Monolithic in nature
"the (one) Domain is in the center"
Are these still problems
in our pragmatic variation?
Delete interfaces with only 1 implementation, unless Dep Inversion
Extract complexity into Domain Services; challenge Controllers
More integration tests (honeycomb) or squash layers
ArchUnit allows exceptions 😬
Only required if UC exposed via 2+ channels
Gulp... 😬 Indeed
Only required if legacy/enemy DB
11. 12 VictorRentea.ro
a training by
Layers
Controller
Repository + Infrastructure
Application Service
Vertical SLICING
DOMAIN Service + Model
Change
12. 13 VictorRentea.ro
a training by
How "wide" should an Application Service be?
= how many use-cases?
Multiple use-cases 👈 what we did
class OrderApplicationService {
public getOrderById() {/*trivial logic🥱*/}
public placeOrder() {..extract..extract...}
// eg. 10 more
}
One class / use-case = Slice by Feature
class GetOrderByIdUseCase{/* 5 useful lines*/}
class PlaceOrderUseCase {/* 400 lines */}
risk: god class
https://jimmybogard.com/vertical-slice-architecture/
continuously extract logic ou
accidental coupling
between use-cases
change 1 use-case=
change 1 (less) files
Group code
by axis of change
13. 14 VictorRentea.ro
a training by
One class / use-case = Slice by Feature
class GetOrderByIdUseCase{/* 5 useful lines*/}
class PlaceOrderUseCase {/* 400 lines */} risk: god classes
https://jimmybogard.com/vertical-slice-architecture/
The stricter the rules are,
the less creative we think
risk: too small classes
risk: repeating logic (DR
extract shared logic to Domain
extract complex logic to Domain
Start Put everything related to a UseCase in a single file,
from Controller down to any custom Repository methods
14. 15 VictorRentea.ro
a training by
One class / use-case = Slice by Feature
class GetOrderByIdUseCase{/* 5 useful lines*/}
class PlaceOrderUseCase {/* 400 lines */}
Query
Command
read data
change data
CQRS
15. 16 VictorRentea.ro
a training by
Encapsulate DTOs
whenever possible
class GetOrderByIdUseCase{ // query 👁
private static class Request { ... } // JSON
private static class Response { ... } // JSON
public Response handle(Request request) {...}
}
class PlaceOrderUseCase { // command ✍️
private static class Request { ... } // JSON
private static class Response { ... } // JSON
public Response handle(Request request) {...}
}
allows different architecture styles per use-case
16. 17 VictorRentea.ro
a training by
Queries 👁
class GetOrderById {
private static class Request { ... }
private static class Response { ....<large>...... }
@GetMapping
public Response handle(Request request) {...}
}
- Can select projections ("select new Dto" JPQL) = different READ Model (CQRS)
- Can use raw SQL, or whatever ...
- If ≥2 Query UC share logic (rarely) extract it (eg. into a Util) = DRY
17. 18 VictorRentea.ro
a training by
Commands ✍️
• Start simple
• Can get veeeery complex. Don't PANIC! Continuously refactor:
Extract Method
Extract Class
Move Methods (into a Domain Model Object or a Domain Service)
class PlaceOrder {
private static class Request { ....<large>.... } // + @NotNull, @Size..
private static class Response { ... }
@PostMapping
@Transactional
public Response handle(@Validated Request request) {
*read stuff*
*interesting domain logic to extract*
*save stuff*
}
}
Perfect Fit for
Task-Based UI
18. 19 VictorRentea.ro
a training by
Layers
Controller
Repository + Infrastructure
Application Service
SLICE BY FEATURE
DOMAIN Service/Model
Logic Shared by 2 Features
extracted into a Domain Service/Model
Complex business logic of a Feature
extracted into a Domain Service/Model
19. 20 VictorRentea.ro
a training by
Application / Infrastructure
Domain
👁 UC-1
External API
✍️UC-3 ✍️UC-4
Domain
Service
Vertical-Slicing vs Concentric Architecture Equivalence
✍️UC-2
Domain
Model
👁 UC-19
👁 UC-17
Not enough domain complexity:
they don't deserve
to be part of Domain
20. 21 VictorRentea.ro
a training by
Microservice (if you must)
Module in a Modulith 👑
Large Slice
Shippin
g
Invoicin
g
Catalo
g
NO: Each Module can decide
how many layers/rings has
its internal structure
Slice by Feature
(aka UseCase, previous slides)
Vertical Slicing Small Slice
What
Size?
No
layers?
= Modular Monolith
21. 22 VictorRentea.ro
a training by
A large codebase is more likely to suffer
from internal coupling (scope creep, domain model dilution),
than external coupling (to external APIs)
24. 25 VictorRentea.ro
a training by
.controller
.service
.repo
.infra
com.myorg.myapp
.order
.product
Too keep packages small,
we grouped classes
in subpackages
Package Names
.entity
.order
.product
ll started with...
technical layers names
(not domain concepts)
25. 26 VictorRentea.ro
a training by
.service
com.myorg.myapp .order
.product .service
The Screaming Architecture
=organize code first by subdomain, not by technical layers
https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html
.entity
.repo
…
…
separate microservices
teams
modules
26. 27 VictorRentea.ro
a training by
Naming
(again)
.order .product
.fulfillment .catalog
.invoice
.invoicing
Group by Business Capability👌
(what it does, its value)
Group by Concept (Data)
28. 29 VictorRentea.ro
a training by
The Fallacy of Nano-Services
• If you break the code in too many modules (eg 20+)
• The local complexity of each piece is ↓ ✅
• But the global complexity gets 🔝 🚫🚫🚫
or Module
https://vladikk.com/2020/04/09/untangling-microservices/
29. 30 VictorRentea.ro
a training by
The Fallacy of Nano-Services
or Module
Local Complexity
how much complexity is
in one microservice/module
Global Complexity
how hard it is to integrate
the microservices/modules
Increase cohesiveness of modules (=break complexity)
while reducing coupling between them (=micro-APIs)
https://vladikk.com/2020/04/09/untangling-microservices/
30. 31 VictorRentea.ro
a training by
A Service is NOT
• Just behavior
Example: calculation, validation
• Just CRUD data
Example: Country, Tenant, Clinic
or Module
Modulith: repos in commons
Microservices: static data or
entity microservice
31. 32 VictorRentea.ro
a training by
A Service is ...
the technical authority for a business capability
or Module
.invoice .invoicing
32. 33 VictorRentea.ro
a training by
Cognitive🧅 Load: able to specialize in one area
Code Ownership & Control: free to refactor the Domain Model
Bounded Contexts: sharp concepts, less attributes & more constraints
Ideally 1 module = 1 Team = Two-Pizza Team
Align with Organization Boundaries (stakeholders) - Conway's Law
Has a sound responsibility ?
Finding Module Boundaries🏆
Changing its internal structures and business rules affects other modules?
Its API changes when other modules change? (=coupling)
Is it replaceable with off-the-shelf software?
Is it easy to remove when the business need is gone?
Is it testable alone, ie without creating many entities from other modules?
36. 40 VictorRentea.ro
a training by
What's faster to build?
(from scratch)
Microservices
A Monolith or
But soon complexity and
coupling
start slowing us down
37. 41 VictorRentea.ro
a training by
41
From a Big Ball of Mud Microservices
1) Rewrite (big-bang)
No new features for 1 year
Requires re-specifying everything
20-30% success chance (studies)
2) Modularize => Extract
Experiment and prototype
Be ready to stop any time
3) Strangler-Fig Pattern
Reimplement any all features
in fresh microservices
👑
Modulith
38. 42 VictorRentea.ro
a training by
A new greenfield project
Proxy
The Strangler Fig Pattern
😁
When a feature is
reimplemented,
route the requests for it
to the new project
Prerequisite: clear requirements
39. 43 VictorRentea.ro
a training by
order
product
Unlink Domain Entities
class OrderLine {
...
Product product;
Long productId;1
String productName;2
}
class Product {
id
name
...
}
1) Keep only the ID ✅
👌 Preserve the FK in Modulith
+ then later: fetch on-demand
2) Snapshot immutable data
+ according to domain rules
3) Keep mutable data in sync 😨
+ replicate changes (eg via events)
⚠️Eventual Consistency (iff distributed)
object
reference
40. 44 VictorRentea.ro
a training by
complexity
complexity
booking appointment
Split Domain Entities
class BookingRequest {
id
date + time
specialtyId
...
}
class Appointment {
id
medical report
followup date
date + time
...
}
class Appointment {
id
date + time
specialtyId
... +20 fields
appointed:boolean
medical report
followup date
... 40 more
}
41. 45 VictorRentea.ro
a training by
👌
The owner of some data
is the module who changes it
The modules that need the data must ask the owner
42. 46 VictorRentea.ro
a training by
0 Writers | * Readers = Reference Data 'shared' module
- eg. Country list
1 Writer | n Readers = Owned Readers get data from Writer
- eg. Catalog Stock.getStock()
n Writers | 0 Readers = Notifications commons via events
- eg. Audit, sendSMS
2+ Writers | * Readers Split Entity 😩 or Merge modules ❤️
- eg. break Order PlacedOrder + ShippedOrder
Data Ownership
# modules
43. 48 VictorRentea.ro
a training by
Calls between Modules
module
A
module
B
call any class inside B
Lack of
encapsulation
External API
(eg HTTP, gRPC,...)
dev riot
Why so many
structures?!!😡
Goal = Decoupling
- Review the coupling
- Detect cyclic dependencies
- Detect "Module in the middle"
DTO
Internal API
("Door")
DDO
Data Decoupling Objects
calls only via B's "Door"
44. 49 VictorRentea.ro
a training by
Cyclic Dependencies between Modules
A B
call
call
Bad news: Maven/Gradle build fails
<dependency>
<dependency>
45. 50 VictorRentea.ro
a training by
Ways to fix Cyclic Dependencies between Modules
Key Questions:
- massive interdependence?
- need to reuse technical/infra stuff?
- any existing monolithical clients / frozen API?
- are modules exposing external APIs?
46. 51 VictorRentea.ro
a training by
A B
A-api B-api
Extract API modules
No single orchestrator
runtime calls
A B
O
orchestrator
Keep Orchestration Up
= Facade
(eg site?, mobile)
Ways to fix Cyclic Dependencies between Modules
A B
event
call
Fire Events Upstream
⭐️Decoupled
A B
implements
call
Dependency Inversion
⭐️Decoupled
AProvider
AProviderImpl
call
microservices: N/A
microservices: events on queue
microservices: interdependent microservices
microservices: api gateway/saga microservices: shared lib/3rd service microservices: merge?
A B
C
commons
Push Commons Down
Technical:
emails
country
audit, ...
❤️
AB
Merge Modules
if tight
coupling
47. 52 VictorRentea.ro
a training by
B
Surface of a Module
publish events
call
interface
implementation
A
command
(change)
query (read)
(a) notification {id}
(b) fat-event {data}
listen to events
48. 53 VictorRentea.ro
a training by
Segregate in baby-steps
Prepare to stop/undo when needed - incorrect boundary?
Too many tiny modules will 🔝 the Global Complexity
Focus on Data ownership & Flow control
Introduce Events for one-way signals, w/o response
Do NOT sacrifice consistency too early (Transactions, FKs)
Measure progress (eg. ArchUnit, custom tool)
Modulith Lessons
51. 56 VictorRentea.ro
a training by
Reasons to extract a Module into a Microservice
• Different Resource Requirements (eg NOSQL vs RDB)
• Scalability, or go Reactive🔥
• Availability (not impacted by failures in other modules)
• Requires different programming language / exotic framework
• High rate of delivery => independent deployability
• Security posture (vs Wild Wild Web)
• 🛑 STOP if Strong Consistency Requirements: terrible business consequences
• ...
52. 57 VictorRentea.ro
a training by
Module or Microservice?
https://www.confluent.io/resources/event-driven-microservices/
53. 59 VictorRentea.ro
a training by
4) Distributed Systems
1) code decoupling 3) ❌ DB Consistency
2) ❌ Transactions
A
B
A
B
Shared
Transaction
TX
method calls and events
only via doors ✅
External REST API
Foreign Key
Data Replication
(events, CDC, ...)
🔥
MICROSERVICES
🔥
- retry
- idempotency
- queues
- load balancing
- 503
- Network Delay
- k8s, Docker
- rate limiter
- distrib. cache
- circuit breaker
- 😱😱😱😱
TX
>@TransactionEventListener(AFTER_COMMIT)
>@Async @EventListener (worse!)
Tx not shared between modules ✅ Separate Schemas ✅
😱 😱 😱 Eventual Consistency 😱 😱 😱 => Questions to BIZ
Monolith => Modulith => Microservices
Blue Team 👕
Pink Team👚
Partially inspired by Axel Fontaine's talk about the "Majestic Modular Monolith": https://www.youtube.com/watch?v=vV34-3NZkh4
54. VictorRentea.ro
60
Final poll, what architecture have you used by now?
A) Layers: 13/
B) Onion (domain module) 6/
C) Port-adapters (*Port) 4/
D) Feature Slices (*UseCase) 5/
E) Modulith (functional modules in a monolith) 10/
F) Event Sourcing + CQRS 4/
(Total answers: 16/)
55. 61 VictorRentea.ro
a training by
📚
Hexagonal Architecture aka. Ports-and-Adapters
- Original article by Alistair Cockburn, 2005: https://alistair.cockburn.us/hexagonal-architecture/
- Colorful explanation, 8th light: https://8thlight.com/insights/a-color-coded-guide-to-ports-and-adapters
- Input Port Interface are useless: https://softwareengineering.stackexchange.com/q/438705
Onion Architecture
- Clean Architecture Uncle Bob: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Onion Architecture Primer: https://marcoatschaefer.medium.com/onion-architecture-explained-building-maintainable-software-54996ff8e464
- Original article, Jeffrey Palermo, https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/
Vertical Slice Architecture (aka -UseCase)
- Feature slicing Jimmy Bogard talk: https://youtu.be/5kOzZz2vj2o and short intro article: https://jimmybogard.com/vertical-slice-architecture/
- Task-based UI: https://cqrs.wordpress.com/documents/task-based-ui/
Finding Module/Service Boundaries
- Heuristics to find boundaries: https://www.dddheuristics.com/design-heuristics/
- Organize by features, not by entities, Adam Ralph: https://youtu.be/tVnIUZbsxWI
Modular Monoliths
- Majestic Modular Monolith, Axel Fontaine: https://youtu.be/BOvxJaklcr0
- Local Complexity vs Global Complexity in microservices: https://vladikk.com/2020/04/09/untangling-microservices/
Comparisons:
- Comparison of Layered, Onion, vs Vertical Slices, Simon Brown: https://youtu.be/5OjqD-ow8GE
- Nice overview + picture ⭐️ https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
Task-based UI:
- https://codeopinion.com/decomposing-crud-to-a-task-based-ui/
Books:
- Software Architecture: Fundamentals & The Hard Parts by Neal Ford & Mark Richardson
Architecture Reading
Notes de l'éditeur
Salut! Eu sunt Victor si am 14 ani… de lucru cu Java, desi in ultimii ani am fost impins sa invat si PHP, Scala si C#.
Lucrez la IBM ca Lead Archirect, in Bucuresti. Stiati voi ca aveti un IBM chiar aici in Cluj, intr-un sediu nou-nout. In….
De fapt, cu ajorul IBM sunt astazi aici cu voi.
Stiu deja ce ganditi… aa.. E architect. Sta undeva langa un ficus si deseneaza pe pereti diagrame UML..
-NU . Eu bag la cod. Lucrez pe cele mai dificile proiecte pe care le avem.
La sfarsh:Sala mare -