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

Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...aditisharan08
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsMehedi Hasan Shohan
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 

Recently uploaded (20)

Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software Solutions
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 

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