Slides of for the talk "Towards Functional Programming through Hexagonal Architecture" delivered at the Software Crafters Barcelona 2018 conference #scbcn18 by Juanma Serrano from Habla Computing and Javier Ferrer from CodelyTV
5. Session goal
🤔
Rethinking time
Functional Programming intro based on Hexagonal Architecture
Reviewing different technics to solve the same problems with more modularity
15. Acceptance test
Unit test Integration test
CONTROLLER
REPOS
MODELS
SERVICES
APPLICATION SERVICE
D
A
I
Request flow
IMPLEMENTATION
🗺 Design principles > 🎯 Hexagonal Architecture
Port
Adapter
22. What is a functional architecture?
DSL1
DSL2
DSLN
…
🗺 Design principles > 🦄 Functional Architectures
A FUNCTIONAL LANGUAGE IS A DOMAIN-
SPECIFIC LANGUAGE FOR DEFINING
DOMAIN-SPECIFIC LANGUAGES
23. The DSLs of your application
• Infrastructure DSLs
• HTTP
• Databases
• Messaging
• Application-specific DSLs
• Use cases
• Repos
• …
HTTP
SERVICE DSL
SQL
REPO DSL
🗺 Design principles > 🦄 Functional Architectures
24. Are hexagonal and functional architectures like apples and oranges?
HTTP
SERVICE DSL
SQL
REPO DSL
CONTROLLER
SERVICE IMPL.
ORM
DB
ADAPTER/INTERP.
PORT/DSL
🗺 Design principles > 🦄 Functional Architectures
25. There are no exceptions • Everything is either a port or an adapter
• Every adapter implements a given port
• Adapters are implemented on top of other ports
VideoPostController
VideoRepositoryVideoRepoC
DoobieMySqlVideoRepo
VideoCreator
HTTP
🗺 Design principles > 🦄 Functional Architectures
28. 🗺 Design principles > 🤔 Rethinking time
Final goals
• Decouple from infrastructure
• Testing easiness (test doubles when testing out use cases)
• Change tolerance
• ¡Same goals!
29. 🗺 Design principles > 🤔 Rethinking time
Differences: More decoupling and indirection levels
• Controllers:
• Consistency (APIs for all DSLs) vs. Specific needs
• Conclusion: We need industry standards. PHP-FIG as example to follow
• Application Services:
• Consistency (APIs for all DSLs) vs. Specific needs
• Reuse use case APIs in clients (e.g. user clients)
33. final case class Video(
id: VideoId,
title: VideoTitle,
quality: VideoQuality
)
👤 Domain models > 🎯 OOP > Video Entity
34. object Video {
def apply(id: String, title: String, quality: Int): Video =
Video(
VideoId(id),
VideoTitle(title),
VideoQuality(quality)
)
}
final case class Video(
id: VideoId,
title: VideoTitle,
quality: VideoQuality
)
👤 Domain models > 🎯 OOP > Video Entity
35. object Video {
def apply(id: String, title: String, quality: Int): Video =
Video(
VideoId(id),
VideoTitle(title),
VideoQuality(quality)
)
}
final case class Video(
id: VideoId,
title: VideoTitle,
quality: VideoQuality
)
👤 Domain models > 🎯 OOP > Video Entity
36. object VideoQuality {
val maxQuality = 50
val minQuality = 0
}
final case class VideoQuality(value: Int) {
require(value <= maxQuality, s"Video quality value greater than $maxQuality")
require(value >= minQuality, s"Video quality value less than $minQuality")
def increase(): VideoQuality = {
if (value >= maxQuality) this
else copy(value + 1)
}
}
OOP: Data+Behaviour
👤 Domain models > 🎯 OOP > VideoQuality Value Object
37. object VideoQuality {
val maxQuality = 50
val minQuality = 0
}
final case class VideoQuality(value: Int) {
require(value <= maxQuality, s"Video quality value greater than $maxQuality")
require(value >= minQuality, s"Video quality value less than $minQuality")
def increase(): VideoQuality = {
if (value >= maxQuality) this
else copy(value + 1)
}
}
Behaviour
Simpler API
+ domain semantics
+ immutability
👤 Domain models > 🎯 OOP > VideoQuality Value Object
38. case class Video(
id: VideoId,
title: VideoTitle,
quality: VideoQuality
) {
def increaseQuality(): Video =
copy(quality = quality.increase())
}
VideoQualityIncreaser Video
increaseQuality()
Behaviour
Rich domain model - Tell don’t ask
👤 Domain models > 🎯 OOP > Video Entity
39. public final class Video {
private VideoId id;
private VideoQuality quality;
// …
public VideoQuality getQuality() {
return quality;
}
public void setQuality(
VideoQuality quality
) {
this.quality = quality;
}
}
VideoQualityIncreaser Video
getQuality()
increaseQuality()
setQuality()
Behaviour
👤 Domain models > 🎯 OOP > Video Entity
48. & Implementation > 👤 Domain models > 🤔 Rethinking time
Differences on Domain Modelling goals
• 🦄 FP Type classes: APIs with steroids
• Goal: Coupling to behaviours (not to state)
• Decouple behaviour and data
• Support rich domain layers
• 🎯 OOP: High cohesion (behaviour close to related data)
• Modularity because abstractions => More indirection levels (complexity)
• Goal: Rich domain models (data+behaviour)
INSTANTIATION
TYPECLASS: API
COMPOSITION
LOGIC
69. Differences on layers implementation
• Same goal: Decouple from infrastructure
• 🦄 FP:
• One step forward to avoid infrastructure leaks
• Again, through type classes: a better API
• More complexity: monads, applicatives, etc.
& Implementation > 🏗 Layers > 🤔 Rethinking time
73. object VideoQuality {
val maxQuality = 50
val minQuality = 0
}
final case class VideoQuality(value: Int) {
require(value <= maxQuality, s"Video quality value greater than $maxQuality")
require(value >= minQuality, s"Video quality value less than $minQuality")
def increase(): VideoQuality = {
if (value >= maxQuality) this
else copy(value + 1)
}
}
VO: Raise exceptions
for invalid states
⚡Error Handling > 🎯 OOP > Raise exceptions
86. Differences on Error handling
• 🦄 FP:
• FP tell no lies (or, rather, doesn’t hide anything): more robust contracts using
type system
• Declarative: more abstract about how the error will be raised (exceptions,
Option, Either, etc)
& Implementation > ⚡Error Handling > 🤔 Rethinking time