SlideShare a Scribd company logo
1 of 65
Download to read offline
Cooking your Ravioli "al dente"
with Hexagonal Architecture
Jeroen Rosenberg
● Software Consultant @ Xebia
● Founder of Amsterdam Scala
● Currently doing Kotlin & Java projects
● Father of three
● I like Italian food :)
@jeroenrosenberg
jeroenr
https://jeroenrosenberg.medium.com
Agenda
● What is Hexagonal Architecture?
● Why should I care?
● How is cooking Ravioli “al dente” relevant?
Classic Layered
Architecture
Classic Layered Architecture
Classic Layered Architecture
Classic Layered Architecture
Classic Layered Architecture
Domain gets cluttered
● By 3rd party integrations (dependency on API version)
● By dependency on persistence layer and framework
● By using application frameworks or SDKs
@Service
class DepositService(
val userRepo: UserRepository,
val userAccountRepo: UserAccountRepository,
val exchangeApiClient: ExchangeApiClient,
val eventBus: EventBus
) {
fun deposit(userId: String, amount: BigDecimal, currency: String){ ... }
}
@Service
class DepositService(…) {
@Transactional
fun deposit(userId: String, amount: BigDecimal, currency: String){
require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” }
require(Currencies.isSupported(currency)) { “$currency is not supported”}
userRepo.findById(userId)?.let { user ->
userAccountRepo.findByAccountId(user.accountId)?.let { account ->
val rateToUsd = if (currency != “USD”) {
exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate
} else { 1.0 }
val rateToPref = if (account.currency != “USD”) {
exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate
} else { 1.0 }
val oldBalance = account.balance
account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal()
userAccountRepo.save(account)
val update = BalanceUpdate(
userId, oldBalance, account.balance
)
eventBus.publish(ProducerRecord(“balance-updates”, update))
}
}
}
}
@Service
class DepositService(…) {
@Transactional
fun deposit(userId: String, amount: BigDecimal, currency: String){
require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” }
require(Currencies.isSupported(currency)) { “$currency is not supported”}
userRepo.findById(userId)?.let { user ->
userAccountRepo.findByAccountId(user.accountId)?.let { account ->
val rateToUsd = if (currency != “USD”) {
exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate
} else { 1.0 }
val rateToPref = if (account.currency != “USD”) {
exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate
} else { 1.0 }
val oldBalance = account.balance
account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal()
userAccountRepo.save(account)
val update = BalanceUpdate(
userId, oldBalance, account.balance
)
eventBus.publish(ProducerRecord(“balance-updates”, update))
}
}
}
}
Entity (Proxy)
@Service
class DepositService(…) {
@Transactional
fun deposit(userId: String, amount: BigDecimal, currency: String){
require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” }
require(Currencies.isSupported(currency)) { “$currency is not supported”}
userRepo.findById(userId)?.let { user ->
userAccountRepo.findByAccountId(user.accountId)?.let { account ->
val rateToUsd = if (currency != “USD”) {
exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate
} else { 1.0 }
val rateToPref = if (account.currency != “USD”) {
exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate
} else { 1.0 }
val oldBalance = account.balance
account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal()
userAccountRepo.save(account)
val update = BalanceUpdate(
userId, oldBalance, account.balance
)
eventBus.publish(ProducerRecord(“balance-updates”, update))
}
}
}
}
Dependency on API version
@Service
class DepositService(…) {
@Transactional
fun deposit(userId: String, amount: BigDecimal, currency: String){
require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” }
require(Currencies.isSupported(currency)) { “$currency is not supported”}
userRepo.findById(userId)?.let { user ->
userAccountRepo.findByAccountId(user.accountId)?.let { account ->
val rateToUsd = if (currency != “USD”) {
exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate
} else { 1.0 }
val rateToPref = if (account.currency != “USD”) {
exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate
} else { 1.0 }
val oldBalance = account.balance
account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal()
userAccountRepo.save(account)
val update = BalanceUpdate(
userId, oldBalance, account.balance
)
eventBus.publish(ProducerRecord(“balance-updates”, update))
}
}
}
}
Orchestration
@Service
class DepositService(…) {
@Transactional
fun deposit(userId: String, amount: BigDecimal, currency: String){
require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” }
require(Currencies.isSupported(currency)) { “$currency is not supported”}
userRepo.findById(userId)?.let { user ->
userAccountRepo.findByAccountId(user.accountId)?.let { account ->
val rateToUsd = if (currency != “USD”) {
exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate
} else { 1.0 }
val rateToPref = if (account.currency != “USD”) {
exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate
} else { 1.0 }
val oldBalance = account.balance
account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal()
userAccountRepo.save(account)
val update = BalanceUpdate(
userId, oldBalance, account.balance
)
eventBus.publish(ProducerRecord(“balance-updates”, update))
}
}
}
}
Issues
● Testability: not unit testable or requires lots of mocking
Issues
● Testability: not unit testable or requires lots of mocking
● API/Platform version dependency
○ What if you need to support multiple platform versions?
○ What if API versions change?
Issues
● Testability: not unit testable or requires lots of mocking
● API/Platform version dependency
○ What if you need to support multiple platform versions?
○ What if API versions change?
● Orchestration mixed with business logic is hard to maintain
Issues
● Testability: not unit testable or requires lots of mocking
● API/Platform version dependency
○ What if you need to support multiple platform versions?
○ What if API versions change?
● Orchestration mixed with business logic is hard to maintain
● Mutability leads to mistakes
Hexagonal Architecture
Classic Layered Architecture
Inversion of Control
Application
Programmable
Interface
Service
Provider
Interface
Ports and Adapters
Ports
● Interface
● Tech agnostic
● Designed around
purpose of
interaction
Adapters
● Allow interaction
through a
particular port
● Use a particular
technology
“so as to be still firm when bitten”
Al dente
/al ˈdɛnteɪ,al ˈdɛnti/
adverb
When you push modularity too far...
● Coupling is too loose
● Low cohesion
● Bloated call stacks
● Navigation through code will be more difficult
● Transaction management will be hard
How do we determine our domain?
“A particular field of thought, activity or interest”
domain
/də(ʊ)ˈmeɪn/
noun
“A particular field of thought, activity or interest”
What an organisation does
“A particular field of thought, activity or interest”
How an organisation does it
Bounded Context
● A distinct part of the domain
● Split up domain in smaller, independent models with clear boundaries
● No ambiguity
● Could be separate module, jar, microservice
● Context mapping DDD pattern (https://www.infoq.com/articles/ddd-contextmapping/)
Using the building blocks of DDD
● Value objects
● Entities
● Domain services
● Application services
Value objects
● Defined by their value
● Immutable
● Thread-safe and side-effect free
● Small and coherent
● Contain business logic that can be
applied on the object itself
● Contain validation to ensure its value
is valid
● You will likely have many
Value objects
● Defined by their value
● Immutable
● Thread-safe and side-effect free
● Small and coherent
● Contain business logic that can be
applied on the object itself
● Contain validation to ensure its value
is valid
● You will likely have many
enum class Currency { USD, EUR }
data class Money(
val amount: BigDecimal,
val currency: Currency,
) {
fun add(o: Money): Money {
if(currency != o.currency)
throw IllegalArgumentException()
return Money(
amount.add(o.amount),
currency
)
}
}
Entities
● Defined by their identifier
● Mutable
● You will likely have a few
Entities
● Defined by their identifier
● Mutable
● You will likely have a few
@Document
data class UserAccount(
@Id
val _id: ObjectId = ObjectId(),
var name: String,
var createdAt: Instant = Instant.now(),
var updatedAt: Instant
) {
var auditTrail: List<String> = listOf()
fun updateName(name: String) {
this.auditTrail = auditTrail.plus(
“${this.name} -> ${name}”
)
this.name = name
this.updatedAt = Instant.now()
}
}
Domain Services
● Stateless
● Highly cohesive
● Contain business logic that doesn’t naturally fit in value objects
Domain Services
● Stateless
● Highly cohesive
● Contain business logic that doesn’t naturally fit in value objects
// in Domain Module
interface CurrencyExchangeService {
fun exchange(money: Money, currency: Currency): Money
}
Domain Services
● Stateless
● Highly cohesive
● Contain business logic that doesn’t naturally fit in value objects
// in Infrastructure Module
class CurrencyExchangeServiceImpl : CurrencyExchangeService {
fun exchange(money: Money, currency: Currency): Money {
val amount = moneta.Money.of(money.amount, money.currency.toString())
val conversion = MonetaryConversions.getConversion(currency.toString())
val converted = amount.with(conversion)
return Money(
converted.number.numberValueExact(BigDecimal::class.java),
Currency.valueOf(converted.currency.currencyCode)
)
}
Application Services
● Stateless
● Orchestrates business operations (no business logic)
○ Transaction control
○ Enforce security
● Communicates through ports
● Use DTOs for communication
○ Little conversion overhead
○ Domain can evolve without having to change clients
Application Services
● Stateless
● Orchestrates business operations (no business logic)
○ Transaction control
○ Enforce security
● Implements a port in the case an external system wants to access your app
● Uses a port (implemented by an adapter) to access an external system
● Use DTOs for communication
○ Little conversion overhead
○ Domain can evolve without having to change clients
// in Infrastructure Module
@Service
class UserAccountAdminServiceImpl(
private val userRepository: UserRepository
) : UserAccountAdminService {
@Transactional
fun resetPassword(userId: Long) {
val user = userRepository.findById(userId)
user.resetPassword()
userRepository.save()
}
}
Domain Module
● Use simple, safe and consistent value objects to model
your domain
○ Generate with Immutables/Lombok/AutoValue
○ Use Java 14 record types or Kotlin data classes
● Implement core business logic and functional (unit) tests
● No dependencies except itself (and 3rd party libraries with
low impact on domain)
● Expose a clear API / “ports”
○ Communicate using value objects / DTOs
● Could be a separate artifact (maven)
Infrastructure Modules
● Separate module that depends on Core Domain Module
● Specific for an application platform / library version
○ Easy to write version specific adapters
● Write adapters
○ Converting to/from entities, DTOs or proxy objects
○ 3rd party integrations
○ REST endpoints
○ DAO’s
● Integration tests if possible
@Service
class DepositService(…) {
@Transactional
fun deposit(userId: String, amount: BigDecimal, currency: String){
require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” }
require(Currencies.isSupported(currency)) { “$currency is not supported”}
userRepo.findById(userId)?.let { user ->
userAccountRepo.findByAccountId(user.accountId)?.let { account ->
val rateToUsd = if (currency != “USD”) {
exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate
} else { 1.0 }
val rateToPref = if (account.currency != “USD”) {
exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate
} else { 1.0 }
val oldBalance = account.balance
account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal()
userAccountRepo.save(account)
val update = BalanceUpdate(
userId, oldBalance, account.balance
)
eventBus.publish(ProducerRecord(“balance-updates”, update))
}
}
}
}
enum class Currency { USD, EUR }
data class ExchangeRate(val rate: BigDecimal, val currency: Currency)
data class Money(val amount: BigDecimal, val currency: Currency) {
val largerThanZero = amount > BigDecimal.ZERO
fun add(o: Money): Money {
if(currency != o.currency) throw IllegalArgumentException()
return Money(amount.add(o.amount), currency)
}
fun convert(exchangeRate: ExchangeRate): Money {
return Money(amount.multiply(exchangeRate.rate), exchangeRate.currency)
}
}
data class UserAccountDTO(val balance: Money)
data class UserDTO(val userAccountId: String, val preferredCurrency: Currency)
// in Domain Module
interface ExchangeRateService {
fun getRate(source: Currency, target: Currency): ExchangeRate
}
// in Infrastructure Module
class ExchangeRateServiceImpl : ExchangeRateService {
override fun getRate(source: Currency, target: Currency): ExchangeRate {
val rate = MonetaryConversions
.getConversion(source.toString())
.getExchangeRate(moneta.Money.of(1, target.toString()))
return ExchangeRate(
rate.factor.numberValueExact(BigDecimal::class.java),
Currency.valueOf(rate.currency.currencyCode)
)
}
}
// in Domain Module
@Service
class DepositService(val exchangeRateService: ExchangeRateService) {
fun deposit(user: UserDTO, account: UserAccountDTO, amount: Money): UserAccountDTO{
require(amount.largerThanZero) { “Amount must be larger than 0” }
val rateToUsd = if (amount.currency != Currency.USD) {
exchangeRateService.getRate(amount.currency, Currency.USD)
} else { ExchangeRate(BigDecimal.ONE, Currency.USD) }
val rateToPref = if (user.preferredCurrency != Currency.USD) {
exchangeRateService.getRate(Currency.USD, user.preferredCurrency)
} else { ExchangeRate(BigDecimal.ONE, Currency.USD) }
return account.copy(
balance = account.balance.add(
amount
` .convert(rateToUsd)
.convert(rateToPreferred)
)
}
}
}
// in Domain Module
@Service
class DepositOrchestrationService(
val depositService: DepositService,
val userService: UserService,
val userAccountService: UserAccountService,
val eventPublisherService: EventPublisherService,
) {
@Transactional
fun deposit(request: DepositRequest): DepositResponse {
val userDTO = userService.getUser(request.userId)
val accountDTO = userAccountService.getUserAccount(userDTO.userAccountId)
val oldBalance = accountDTO.balance
val updated = depositService.deposit(userDTO, accountDTO, request.amount)
userAccountService.save(accountDTO.copy(balance = updated.balance))
val update = BalanceUpdate(
request.userId, oldBalance, updated.balance
)
eventPublisherService.publish(update)
return DepositResponse(request.userId, oldBalance, updated.balance)
}
}
Summary
● Start with isolated and tech agnostic domain
○ Bring value early
○ Delay choices on technical implementation
Summary
● Start with isolated and tech agnostic domain
○ Bring value early
○ Delay choices on technical implementation
● The domain as a stand-alone module with embedded functional tests
Summary
● Start with isolated and tech agnostic domain
○ Bring value early
○ Delay choices on technical implementation
● The domain as a stand-alone module with embedded functional tests
● Modularity
○ As much adapters as needed w/o impacting other parts of the software
○ Tech stack can be changed independently and with low impact on the business
Summary
● Start with isolated and tech agnostic domain
○ Bring value early
○ Delay choices on technical implementation
● The domain as a stand-alone module with embedded functional tests
● Modularity
○ As much adapters as needed w/o impacting other parts of the software
○ Tech stack can be changed independently and with low impact on the business
● Only suitable if you have a real domain
○ overkill when merely transforming data from one format to another

More Related Content

What's hot

2016 - Easing Your Way Into Docker: Lessons From a Journey to Production
2016 - Easing Your Way Into Docker: Lessons From a Journey to Production2016 - Easing Your Way Into Docker: Lessons From a Journey to Production
2016 - Easing Your Way Into Docker: Lessons From a Journey to Productiondevopsdaysaustin
 
Kapil Thangavelu - Cloud Custodian
Kapil Thangavelu - Cloud CustodianKapil Thangavelu - Cloud Custodian
Kapil Thangavelu - Cloud CustodianServerlessConf
 
How to Build a Big Data Application: Serverless Edition
How to Build a Big Data Application: Serverless EditionHow to Build a Big Data Application: Serverless Edition
How to Build a Big Data Application: Serverless EditionLecole Cole
 
Infrastructure as Code: Manage your Architecture with Git
Infrastructure as Code: Manage your Architecture with GitInfrastructure as Code: Manage your Architecture with Git
Infrastructure as Code: Manage your Architecture with GitDanilo Poccia
 
Building a serverless company on AWS lambda and Serverless framework
Building a serverless company on AWS lambda and Serverless frameworkBuilding a serverless company on AWS lambda and Serverless framework
Building a serverless company on AWS lambda and Serverless frameworkLuciano Mammino
 
Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20
Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20
Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20CodeValue
 
MongoDB.local Berlin: App development in a Serverless World
MongoDB.local Berlin: App development in a Serverless WorldMongoDB.local Berlin: App development in a Serverless World
MongoDB.local Berlin: App development in a Serverless WorldMongoDB
 
AWS Lambda from the Trenches
AWS Lambda from the TrenchesAWS Lambda from the Trenches
AWS Lambda from the TrenchesYan Cui
 
2010 07-20 TDD with ActiveResource
2010 07-20 TDD with ActiveResource2010 07-20 TDD with ActiveResource
2010 07-20 TDD with ActiveResourceWolfram Arnold
 
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and TypescriptMongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and TypescriptMongoDB
 
AWS Lambda and Serverless framework: lessons learned while building a serverl...
AWS Lambda and Serverless framework: lessons learned while building a serverl...AWS Lambda and Serverless framework: lessons learned while building a serverl...
AWS Lambda and Serverless framework: lessons learned while building a serverl...Luciano Mammino
 
Reactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDaysReactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDaysManuel Bernhardt
 
Azure Durable Functions (2018-06-13)
Azure Durable Functions (2018-06-13)Azure Durable Functions (2018-06-13)
Azure Durable Functions (2018-06-13)Paco de la Cruz
 
Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)Yan Cui
 
(CMP302) Amazon ECS: Distributed Applications at Scale
(CMP302) Amazon ECS: Distributed Applications at Scale(CMP302) Amazon ECS: Distributed Applications at Scale
(CMP302) Amazon ECS: Distributed Applications at ScaleAmazon Web Services
 
Serverless Architecture Patterns - Manoj Ganapathi
Serverless Architecture Patterns - Manoj GanapathiServerless Architecture Patterns - Manoj Ganapathi
Serverless Architecture Patterns - Manoj GanapathiCodeOps Technologies LLP
 
Working with LoopBack Models
Working with LoopBack ModelsWorking with LoopBack Models
Working with LoopBack ModelsRaymond Feng
 

What's hot (20)

2016 - Easing Your Way Into Docker: Lessons From a Journey to Production
2016 - Easing Your Way Into Docker: Lessons From a Journey to Production2016 - Easing Your Way Into Docker: Lessons From a Journey to Production
2016 - Easing Your Way Into Docker: Lessons From a Journey to Production
 
Kapil Thangavelu - Cloud Custodian
Kapil Thangavelu - Cloud CustodianKapil Thangavelu - Cloud Custodian
Kapil Thangavelu - Cloud Custodian
 
Cqrs api v2
Cqrs api v2Cqrs api v2
Cqrs api v2
 
How to Build a Big Data Application: Serverless Edition
How to Build a Big Data Application: Serverless EditionHow to Build a Big Data Application: Serverless Edition
How to Build a Big Data Application: Serverless Edition
 
Infrastructure as Code: Manage your Architecture with Git
Infrastructure as Code: Manage your Architecture with GitInfrastructure as Code: Manage your Architecture with Git
Infrastructure as Code: Manage your Architecture with Git
 
Building a serverless company on AWS lambda and Serverless framework
Building a serverless company on AWS lambda and Serverless frameworkBuilding a serverless company on AWS lambda and Serverless framework
Building a serverless company on AWS lambda and Serverless framework
 
Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20
Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20
Alon Fliess: APM – What Is It, and Why Do I Need It? - Architecture Next 20
 
MongoDB.local Berlin: App development in a Serverless World
MongoDB.local Berlin: App development in a Serverless WorldMongoDB.local Berlin: App development in a Serverless World
MongoDB.local Berlin: App development in a Serverless World
 
AWS Lambda from the Trenches
AWS Lambda from the TrenchesAWS Lambda from the Trenches
AWS Lambda from the Trenches
 
2010 07-20 TDD with ActiveResource
2010 07-20 TDD with ActiveResource2010 07-20 TDD with ActiveResource
2010 07-20 TDD with ActiveResource
 
Olist Architecture v2.0
Olist Architecture v2.0Olist Architecture v2.0
Olist Architecture v2.0
 
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and TypescriptMongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
 
AWS Lambda and Serverless framework: lessons learned while building a serverl...
AWS Lambda and Serverless framework: lessons learned while building a serverl...AWS Lambda and Serverless framework: lessons learned while building a serverl...
AWS Lambda and Serverless framework: lessons learned while building a serverl...
 
Reactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDaysReactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDays
 
Azure Durable Functions (2018-06-13)
Azure Durable Functions (2018-06-13)Azure Durable Functions (2018-06-13)
Azure Durable Functions (2018-06-13)
 
Orchestrating the Cloud
Orchestrating the CloudOrchestrating the Cloud
Orchestrating the Cloud
 
Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)
 
(CMP302) Amazon ECS: Distributed Applications at Scale
(CMP302) Amazon ECS: Distributed Applications at Scale(CMP302) Amazon ECS: Distributed Applications at Scale
(CMP302) Amazon ECS: Distributed Applications at Scale
 
Serverless Architecture Patterns - Manoj Ganapathi
Serverless Architecture Patterns - Manoj GanapathiServerless Architecture Patterns - Manoj Ganapathi
Serverless Architecture Patterns - Manoj Ganapathi
 
Working with LoopBack Models
Working with LoopBack ModelsWorking with LoopBack Models
Working with LoopBack Models
 

Similar to Cooking your Ravioli "al dente" with Hexagonal Architecture

Intravert Server side processing for Cassandra
Intravert Server side processing for CassandraIntravert Server side processing for Cassandra
Intravert Server side processing for CassandraEdward Capriolo
 
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"DataStax Academy
 
Design Summit - UI Roadmap - Dan Clarizio, Martin Povolny
Design Summit - UI Roadmap - Dan Clarizio, Martin PovolnyDesign Summit - UI Roadmap - Dan Clarizio, Martin Povolny
Design Summit - UI Roadmap - Dan Clarizio, Martin PovolnyManageIQ
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJSWei Ru
 
Connecting to a Webservice.pdf
Connecting to a Webservice.pdfConnecting to a Webservice.pdf
Connecting to a Webservice.pdfShaiAlmog1
 
Ice mini guide
Ice mini guideIce mini guide
Ice mini guideAdy Liu
 
Services Drupalcamp Stockholm 2009
Services Drupalcamp Stockholm 2009Services Drupalcamp Stockholm 2009
Services Drupalcamp Stockholm 2009hugowetterberg
 
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...Miguel Gallardo
 
JSR354 Utrecht JUG 20171027
JSR354 Utrecht JUG 20171027JSR354 Utrecht JUG 20171027
JSR354 Utrecht JUG 20171027Wim van Haaren
 
First impression of the new cloud native programming language ballerina
First impression of the new cloud native programming language ballerinaFirst impression of the new cloud native programming language ballerina
First impression of the new cloud native programming language ballerinaRichárd Kovács
 
SiestaTime - Defcon27 Red Team Village
SiestaTime - Defcon27 Red Team VillageSiestaTime - Defcon27 Red Team Village
SiestaTime - Defcon27 Red Team VillageAlvaro Folgado Rueda
 
JSR 354: Money and Currency API - Short Overview
JSR 354: Money and Currency API - Short OverviewJSR 354: Money and Currency API - Short Overview
JSR 354: Money and Currency API - Short OverviewWerner Keil
 
Andrea Lattuada, Gabriele Petronella - Building startups on Scala
Andrea Lattuada, Gabriele Petronella - Building startups on ScalaAndrea Lattuada, Gabriele Petronella - Building startups on Scala
Andrea Lattuada, Gabriele Petronella - Building startups on ScalaScala Italy
 
Apache Spark Streaming: Architecture and Fault Tolerance
Apache Spark Streaming: Architecture and Fault ToleranceApache Spark Streaming: Architecture and Fault Tolerance
Apache Spark Streaming: Architecture and Fault ToleranceSachin Aggarwal
 

Similar to Cooking your Ravioli "al dente" with Hexagonal Architecture (20)

Intravert Server side processing for Cassandra
Intravert Server side processing for CassandraIntravert Server side processing for Cassandra
Intravert Server side processing for Cassandra
 
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
 
Introduction to Angular JS
Introduction to Angular JSIntroduction to Angular JS
Introduction to Angular JS
 
Design Summit - UI Roadmap - Dan Clarizio, Martin Povolny
Design Summit - UI Roadmap - Dan Clarizio, Martin PovolnyDesign Summit - UI Roadmap - Dan Clarizio, Martin Povolny
Design Summit - UI Roadmap - Dan Clarizio, Martin Povolny
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJS
 
Unit 4(it workshop)
Unit 4(it workshop)Unit 4(it workshop)
Unit 4(it workshop)
 
Connecting to a Webservice.pdf
Connecting to a Webservice.pdfConnecting to a Webservice.pdf
Connecting to a Webservice.pdf
 
Siddhi - cloud-native stream processor
Siddhi - cloud-native stream processorSiddhi - cloud-native stream processor
Siddhi - cloud-native stream processor
 
Ice mini guide
Ice mini guideIce mini guide
Ice mini guide
 
Swift meetup22june2015
Swift meetup22june2015Swift meetup22june2015
Swift meetup22june2015
 
Services Drupalcamp Stockholm 2009
Services Drupalcamp Stockholm 2009Services Drupalcamp Stockholm 2009
Services Drupalcamp Stockholm 2009
 
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
 
JSR354 Utrecht JUG 20171027
JSR354 Utrecht JUG 20171027JSR354 Utrecht JUG 20171027
JSR354 Utrecht JUG 20171027
 
First impression of the new cloud native programming language ballerina
First impression of the new cloud native programming language ballerinaFirst impression of the new cloud native programming language ballerina
First impression of the new cloud native programming language ballerina
 
SiestaTime - Defcon27 Red Team Village
SiestaTime - Defcon27 Red Team VillageSiestaTime - Defcon27 Red Team Village
SiestaTime - Defcon27 Red Team Village
 
JSR 354: Money and Currency API - Short Overview
JSR 354: Money and Currency API - Short OverviewJSR 354: Money and Currency API - Short Overview
JSR 354: Money and Currency API - Short Overview
 
Andrea Lattuada, Gabriele Petronella - Building startups on Scala
Andrea Lattuada, Gabriele Petronella - Building startups on ScalaAndrea Lattuada, Gabriele Petronella - Building startups on Scala
Andrea Lattuada, Gabriele Petronella - Building startups on Scala
 
Adopt JSR 354
Adopt JSR 354Adopt JSR 354
Adopt JSR 354
 
Node.js Workshop
Node.js WorkshopNode.js Workshop
Node.js Workshop
 
Apache Spark Streaming: Architecture and Fault Tolerance
Apache Spark Streaming: Architecture and Fault ToleranceApache Spark Streaming: Architecture and Fault Tolerance
Apache Spark Streaming: Architecture and Fault Tolerance
 

More from Jeroen Rosenberg

More from Jeroen Rosenberg (8)

Apache Solr lessons learned
Apache Solr lessons learnedApache Solr lessons learned
Apache Solr lessons learned
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
 
Websocket on Rails
Websocket on RailsWebsocket on Rails
Websocket on Rails
 
Provisioning with Vagrant & Puppet
Provisioning with Vagrant & PuppetProvisioning with Vagrant & Puppet
Provisioning with Vagrant & Puppet
 
Spring AOP
Spring AOPSpring AOP
Spring AOP
 
Dynamic System Configuration using SOA
Dynamic System Configuration using SOADynamic System Configuration using SOA
Dynamic System Configuration using SOA
 
Dynamic Lighting with OpenGL
Dynamic Lighting with OpenGLDynamic Lighting with OpenGL
Dynamic Lighting with OpenGL
 
Git the fast version control system
Git the fast version control systemGit the fast version control system
Git the fast version control system
 

Recently uploaded

%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrandmasabamasaba
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Bert Jan Schrijver
 
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...chiefasafspells
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is insideshinachiaurasa2
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...SelfMade bd
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...masabamasaba
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyviewmasabamasaba
 
tonesoftg
tonesoftgtonesoftg
tonesoftglanshi9
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in sowetomasabamasaba
 
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...Shane Coughlan
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareJim McKeeth
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastPapp Krisztián
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrainmasabamasaba
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisamasabamasaba
 

Recently uploaded (20)

%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 

Cooking your Ravioli "al dente" with Hexagonal Architecture

  • 1. Cooking your Ravioli "al dente" with Hexagonal Architecture
  • 2. Jeroen Rosenberg ● Software Consultant @ Xebia ● Founder of Amsterdam Scala ● Currently doing Kotlin & Java projects ● Father of three ● I like Italian food :) @jeroenrosenberg jeroenr https://jeroenrosenberg.medium.com
  • 3.
  • 4. Agenda ● What is Hexagonal Architecture? ● Why should I care? ● How is cooking Ravioli “al dente” relevant?
  • 10. Domain gets cluttered ● By 3rd party integrations (dependency on API version) ● By dependency on persistence layer and framework ● By using application frameworks or SDKs
  • 11. @Service class DepositService( val userRepo: UserRepository, val userAccountRepo: UserAccountRepository, val exchangeApiClient: ExchangeApiClient, val eventBus: EventBus ) { fun deposit(userId: String, amount: BigDecimal, currency: String){ ... } }
  • 12. @Service class DepositService(…) { @Transactional fun deposit(userId: String, amount: BigDecimal, currency: String){ require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” } require(Currencies.isSupported(currency)) { “$currency is not supported”} userRepo.findById(userId)?.let { user -> userAccountRepo.findByAccountId(user.accountId)?.let { account -> val rateToUsd = if (currency != “USD”) { exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate } else { 1.0 } val rateToPref = if (account.currency != “USD”) { exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate } else { 1.0 } val oldBalance = account.balance account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal() userAccountRepo.save(account) val update = BalanceUpdate( userId, oldBalance, account.balance ) eventBus.publish(ProducerRecord(“balance-updates”, update)) } } } }
  • 13. @Service class DepositService(…) { @Transactional fun deposit(userId: String, amount: BigDecimal, currency: String){ require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” } require(Currencies.isSupported(currency)) { “$currency is not supported”} userRepo.findById(userId)?.let { user -> userAccountRepo.findByAccountId(user.accountId)?.let { account -> val rateToUsd = if (currency != “USD”) { exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate } else { 1.0 } val rateToPref = if (account.currency != “USD”) { exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate } else { 1.0 } val oldBalance = account.balance account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal() userAccountRepo.save(account) val update = BalanceUpdate( userId, oldBalance, account.balance ) eventBus.publish(ProducerRecord(“balance-updates”, update)) } } } } Entity (Proxy)
  • 14. @Service class DepositService(…) { @Transactional fun deposit(userId: String, amount: BigDecimal, currency: String){ require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” } require(Currencies.isSupported(currency)) { “$currency is not supported”} userRepo.findById(userId)?.let { user -> userAccountRepo.findByAccountId(user.accountId)?.let { account -> val rateToUsd = if (currency != “USD”) { exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate } else { 1.0 } val rateToPref = if (account.currency != “USD”) { exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate } else { 1.0 } val oldBalance = account.balance account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal() userAccountRepo.save(account) val update = BalanceUpdate( userId, oldBalance, account.balance ) eventBus.publish(ProducerRecord(“balance-updates”, update)) } } } } Dependency on API version
  • 15.
  • 16. @Service class DepositService(…) { @Transactional fun deposit(userId: String, amount: BigDecimal, currency: String){ require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” } require(Currencies.isSupported(currency)) { “$currency is not supported”} userRepo.findById(userId)?.let { user -> userAccountRepo.findByAccountId(user.accountId)?.let { account -> val rateToUsd = if (currency != “USD”) { exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate } else { 1.0 } val rateToPref = if (account.currency != “USD”) { exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate } else { 1.0 } val oldBalance = account.balance account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal() userAccountRepo.save(account) val update = BalanceUpdate( userId, oldBalance, account.balance ) eventBus.publish(ProducerRecord(“balance-updates”, update)) } } } } Orchestration
  • 17. @Service class DepositService(…) { @Transactional fun deposit(userId: String, amount: BigDecimal, currency: String){ require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” } require(Currencies.isSupported(currency)) { “$currency is not supported”} userRepo.findById(userId)?.let { user -> userAccountRepo.findByAccountId(user.accountId)?.let { account -> val rateToUsd = if (currency != “USD”) { exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate } else { 1.0 } val rateToPref = if (account.currency != “USD”) { exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate } else { 1.0 } val oldBalance = account.balance account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal() userAccountRepo.save(account) val update = BalanceUpdate( userId, oldBalance, account.balance ) eventBus.publish(ProducerRecord(“balance-updates”, update)) } } } }
  • 18. Issues ● Testability: not unit testable or requires lots of mocking
  • 19. Issues ● Testability: not unit testable or requires lots of mocking ● API/Platform version dependency ○ What if you need to support multiple platform versions? ○ What if API versions change?
  • 20. Issues ● Testability: not unit testable or requires lots of mocking ● API/Platform version dependency ○ What if you need to support multiple platform versions? ○ What if API versions change? ● Orchestration mixed with business logic is hard to maintain
  • 21. Issues ● Testability: not unit testable or requires lots of mocking ● API/Platform version dependency ○ What if you need to support multiple platform versions? ○ What if API versions change? ● Orchestration mixed with business logic is hard to maintain ● Mutability leads to mistakes
  • 22.
  • 23.
  • 29. Ports ● Interface ● Tech agnostic ● Designed around purpose of interaction
  • 30. Adapters ● Allow interaction through a particular port ● Use a particular technology
  • 31.
  • 32.
  • 33.
  • 34.
  • 35. “so as to be still firm when bitten” Al dente /al ˈdɛnteɪ,al ˈdɛnti/ adverb
  • 36. When you push modularity too far... ● Coupling is too loose ● Low cohesion ● Bloated call stacks ● Navigation through code will be more difficult ● Transaction management will be hard
  • 37. How do we determine our domain?
  • 38. “A particular field of thought, activity or interest” domain /də(ʊ)ˈmeɪn/ noun
  • 39. “A particular field of thought, activity or interest” What an organisation does
  • 40. “A particular field of thought, activity or interest” How an organisation does it
  • 41.
  • 42. Bounded Context ● A distinct part of the domain ● Split up domain in smaller, independent models with clear boundaries ● No ambiguity ● Could be separate module, jar, microservice ● Context mapping DDD pattern (https://www.infoq.com/articles/ddd-contextmapping/)
  • 43. Using the building blocks of DDD ● Value objects ● Entities ● Domain services ● Application services
  • 44. Value objects ● Defined by their value ● Immutable ● Thread-safe and side-effect free ● Small and coherent ● Contain business logic that can be applied on the object itself ● Contain validation to ensure its value is valid ● You will likely have many
  • 45. Value objects ● Defined by their value ● Immutable ● Thread-safe and side-effect free ● Small and coherent ● Contain business logic that can be applied on the object itself ● Contain validation to ensure its value is valid ● You will likely have many enum class Currency { USD, EUR } data class Money( val amount: BigDecimal, val currency: Currency, ) { fun add(o: Money): Money { if(currency != o.currency) throw IllegalArgumentException() return Money( amount.add(o.amount), currency ) } }
  • 46. Entities ● Defined by their identifier ● Mutable ● You will likely have a few
  • 47. Entities ● Defined by their identifier ● Mutable ● You will likely have a few @Document data class UserAccount( @Id val _id: ObjectId = ObjectId(), var name: String, var createdAt: Instant = Instant.now(), var updatedAt: Instant ) { var auditTrail: List<String> = listOf() fun updateName(name: String) { this.auditTrail = auditTrail.plus( “${this.name} -> ${name}” ) this.name = name this.updatedAt = Instant.now() } }
  • 48. Domain Services ● Stateless ● Highly cohesive ● Contain business logic that doesn’t naturally fit in value objects
  • 49. Domain Services ● Stateless ● Highly cohesive ● Contain business logic that doesn’t naturally fit in value objects // in Domain Module interface CurrencyExchangeService { fun exchange(money: Money, currency: Currency): Money }
  • 50. Domain Services ● Stateless ● Highly cohesive ● Contain business logic that doesn’t naturally fit in value objects // in Infrastructure Module class CurrencyExchangeServiceImpl : CurrencyExchangeService { fun exchange(money: Money, currency: Currency): Money { val amount = moneta.Money.of(money.amount, money.currency.toString()) val conversion = MonetaryConversions.getConversion(currency.toString()) val converted = amount.with(conversion) return Money( converted.number.numberValueExact(BigDecimal::class.java), Currency.valueOf(converted.currency.currencyCode) ) }
  • 51. Application Services ● Stateless ● Orchestrates business operations (no business logic) ○ Transaction control ○ Enforce security ● Communicates through ports ● Use DTOs for communication ○ Little conversion overhead ○ Domain can evolve without having to change clients
  • 52. Application Services ● Stateless ● Orchestrates business operations (no business logic) ○ Transaction control ○ Enforce security ● Implements a port in the case an external system wants to access your app ● Uses a port (implemented by an adapter) to access an external system ● Use DTOs for communication ○ Little conversion overhead ○ Domain can evolve without having to change clients // in Infrastructure Module @Service class UserAccountAdminServiceImpl( private val userRepository: UserRepository ) : UserAccountAdminService { @Transactional fun resetPassword(userId: Long) { val user = userRepository.findById(userId) user.resetPassword() userRepository.save() } }
  • 53.
  • 54. Domain Module ● Use simple, safe and consistent value objects to model your domain ○ Generate with Immutables/Lombok/AutoValue ○ Use Java 14 record types or Kotlin data classes ● Implement core business logic and functional (unit) tests ● No dependencies except itself (and 3rd party libraries with low impact on domain) ● Expose a clear API / “ports” ○ Communicate using value objects / DTOs ● Could be a separate artifact (maven)
  • 55. Infrastructure Modules ● Separate module that depends on Core Domain Module ● Specific for an application platform / library version ○ Easy to write version specific adapters ● Write adapters ○ Converting to/from entities, DTOs or proxy objects ○ 3rd party integrations ○ REST endpoints ○ DAO’s ● Integration tests if possible
  • 56. @Service class DepositService(…) { @Transactional fun deposit(userId: String, amount: BigDecimal, currency: String){ require(amount > BigDecimal.ZERO) { “Amount must be larger than 0” } require(Currencies.isSupported(currency)) { “$currency is not supported”} userRepo.findById(userId)?.let { user -> userAccountRepo.findByAccountId(user.accountId)?.let { account -> val rateToUsd = if (currency != “USD”) { exchangeApiClient.getRate(RateRequest(currency, “USD”)).rate } else { 1.0 } val rateToPref = if (account.currency != “USD”) { exchangeApiClient.getRate(RateRequest(“USD”, account.currency)).rate } else { 1.0 } val oldBalance = account.balance account.balance += amount * rateToUsd.toBigDecimal() * rateToPref.toBigDecimal() userAccountRepo.save(account) val update = BalanceUpdate( userId, oldBalance, account.balance ) eventBus.publish(ProducerRecord(“balance-updates”, update)) } } } }
  • 57. enum class Currency { USD, EUR } data class ExchangeRate(val rate: BigDecimal, val currency: Currency) data class Money(val amount: BigDecimal, val currency: Currency) { val largerThanZero = amount > BigDecimal.ZERO fun add(o: Money): Money { if(currency != o.currency) throw IllegalArgumentException() return Money(amount.add(o.amount), currency) } fun convert(exchangeRate: ExchangeRate): Money { return Money(amount.multiply(exchangeRate.rate), exchangeRate.currency) } } data class UserAccountDTO(val balance: Money) data class UserDTO(val userAccountId: String, val preferredCurrency: Currency)
  • 58. // in Domain Module interface ExchangeRateService { fun getRate(source: Currency, target: Currency): ExchangeRate } // in Infrastructure Module class ExchangeRateServiceImpl : ExchangeRateService { override fun getRate(source: Currency, target: Currency): ExchangeRate { val rate = MonetaryConversions .getConversion(source.toString()) .getExchangeRate(moneta.Money.of(1, target.toString())) return ExchangeRate( rate.factor.numberValueExact(BigDecimal::class.java), Currency.valueOf(rate.currency.currencyCode) ) } }
  • 59. // in Domain Module @Service class DepositService(val exchangeRateService: ExchangeRateService) { fun deposit(user: UserDTO, account: UserAccountDTO, amount: Money): UserAccountDTO{ require(amount.largerThanZero) { “Amount must be larger than 0” } val rateToUsd = if (amount.currency != Currency.USD) { exchangeRateService.getRate(amount.currency, Currency.USD) } else { ExchangeRate(BigDecimal.ONE, Currency.USD) } val rateToPref = if (user.preferredCurrency != Currency.USD) { exchangeRateService.getRate(Currency.USD, user.preferredCurrency) } else { ExchangeRate(BigDecimal.ONE, Currency.USD) } return account.copy( balance = account.balance.add( amount ` .convert(rateToUsd) .convert(rateToPreferred) ) } } }
  • 60. // in Domain Module @Service class DepositOrchestrationService( val depositService: DepositService, val userService: UserService, val userAccountService: UserAccountService, val eventPublisherService: EventPublisherService, ) { @Transactional fun deposit(request: DepositRequest): DepositResponse { val userDTO = userService.getUser(request.userId) val accountDTO = userAccountService.getUserAccount(userDTO.userAccountId) val oldBalance = accountDTO.balance val updated = depositService.deposit(userDTO, accountDTO, request.amount) userAccountService.save(accountDTO.copy(balance = updated.balance)) val update = BalanceUpdate( request.userId, oldBalance, updated.balance ) eventPublisherService.publish(update) return DepositResponse(request.userId, oldBalance, updated.balance) } }
  • 61.
  • 62. Summary ● Start with isolated and tech agnostic domain ○ Bring value early ○ Delay choices on technical implementation
  • 63. Summary ● Start with isolated and tech agnostic domain ○ Bring value early ○ Delay choices on technical implementation ● The domain as a stand-alone module with embedded functional tests
  • 64. Summary ● Start with isolated and tech agnostic domain ○ Bring value early ○ Delay choices on technical implementation ● The domain as a stand-alone module with embedded functional tests ● Modularity ○ As much adapters as needed w/o impacting other parts of the software ○ Tech stack can be changed independently and with low impact on the business
  • 65. Summary ● Start with isolated and tech agnostic domain ○ Bring value early ○ Delay choices on technical implementation ● The domain as a stand-alone module with embedded functional tests ● Modularity ○ As much adapters as needed w/o impacting other parts of the software ○ Tech stack can be changed independently and with low impact on the business ● Only suitable if you have a real domain ○ overkill when merely transforming data from one format to another