SlideShare a Scribd company logo
PROGRAMS AS
VALUES
WRITE CODE AND DON'T GET LOST
1 — Pierangelo Cecchetto - LambdaConf 2025
THE CODE ARCHEOLOGIST
PROBLEM
▸ "Investigation requests" (not "Bug Reports")
▸ What is the system doing?
▸ What is the system supposed to do?
2 — Pierangelo Cecchetto - LambdaConf 2025
INVESTIGATION REQUEST
#1
System consumes documents from Kafka and
produces processing proofs. We see 100
messages going in, and only 70 are OK.
Why did 30 go to DLQ?
3 — Pierangelo Cecchetto - LambdaConf 2025
INVESTIGATION REQUEST
#2
In a purchase approval system, I see 30
transactions refused out of 100. Why are they
refused?
4 — Pierangelo Cecchetto - LambdaConf 2025
INVESTIGATION REQUEST
#3
"We were supposed to see 8 shop locations on
Google Maps but we see only 6. Why is that?"
5 — Pierangelo Cecchetto - LambdaConf 2025
INVESTIGATION REQUEST
#3
"We were supposed to see 8 shop locations on
Google Maps but we see only 6. Why is that?"
6 — Pierangelo Cecchetto - LambdaConf 2025
AND TO MAKE THINGS
MORE INTERESTING...
▸ No error messages in our logs
7 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
FOCUS ON THE WHAT RATHER THAN THE HOW
// HOW (Procedural)
val x = List(1, 2, 3)
val y = scala.collection.mutable.ArrayBuffer.empty[Int]
for (i <- 0 until x.size) {
y += x(i) * 2
}
// WHAT (Functional)
val x = List(1, 2, 3)
val y = x.map(_ * 2)
8 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
/**
* Check db connectivity
*/
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = {
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
ZIO
.collectAll(
List(
dbCheck(
DbType.MySql,
List(TableName("table1"), TableName("table2"))
),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
}
9 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
10 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = {
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
}
11 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
12 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = {
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
}
13 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
}
14 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = {
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
}
15 — Pierangelo Cecchetto - LambdaConf 2025
THE PROMISE OF FP
IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK
ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
16 — Pierangelo Cecchetto - LambdaConf 2025
PURELY FUNCTIONAL CODE
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = {
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
}
▸ Immutable values
▸ Strongly typed - neotype
▸ Functional effect system -
ZIO
17 — Pierangelo Cecchetto - LambdaConf 2025
PURELY FUNCTIONAL CODE
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = {
def dbCheck(
dbType: DbType,
checkTables: List[TableName]
): ZIO[Transactor[Task], Nothing, List[StatusError]] =
/*
* Doobie code
*/
def kafkaCheck(
topics: List[Topic]
): URIO[AdminClient, Option[StatusError]] =
/*
* ZIO-kafka code
*/
def httpCheck(
url: Url
): URIO[SttpClient, List[StatusError]] =
/*
* Sttp code
*/
ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
}
Is it focused on the how or on the
what?
18 — Pierangelo Cecchetto - LambdaConf 2025
PROBLEM #1: ARBITRARY FUNCTIONS
def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] =
ZIO.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080")),
ZIO.succeed(List(StatusError("Fourth error")))
ZIO.die(new RuntimeException("Error in the fifth check"))
)
)
.map(_.flatten)
19 — Pierangelo Cecchetto - LambdaConf 2025
PROBLEM #2: DIVERGENT DOCUMENTATION
/**
* Check db connectivity
*/
def checkErrors() =
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2")))
20 — Pierangelo Cecchetto - LambdaConf 2025
PROBLEM #2: DIVERGENT DOCUMENTATION
/**
* Check db connectivity
*/
def checkErrors() = ZIO
.collectAll(
List(
dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))),
kafkaCheck(List(Topic("topic1"), Topic("topic2"))),
httpCheck(Url("http://localhost:8080"))
)
)
.map(_.flatten)
21 — Pierangelo Cecchetto - LambdaConf 2025
PROBLEM #3: SOLUTION BOUND TO ZIO
▸ Legacy services use Future and Slick
▸ No guarantee of equivalence between implementations
22 — Pierangelo Cecchetto - LambdaConf 2025
ORTHOGONALITY
ADDRESS ALL THREE PROBLEMS INDEPENDENTLY:
1. Constrain what we can do
2. Keep documentation always aligned with code
3. Make solution stack-agnostic
23 — Pierangelo Cecchetto - LambdaConf 2025
DEFINE A LANGUAGE TO DESCRIBE OUR
SOLUTION
24 — Pierangelo Cecchetto - LambdaConf 2025
MODEL THE SOLUTION
sealed trait ErrorCondition
object ErrorCondition {
case class DBErrorCondition(dbType: DbType, checkTables: List[TableName]) extends ErrorCondition
case class KafkaErrorCondition(topics: List[Topic]) extends ErrorCondition
case class HttpErrorCondition(url: Url) extends ErrorCondition
}
25 — Pierangelo Cecchetto - LambdaConf 2025
ADD COMBINATORS
sealed trait ErrorCondition
object ErrorCondition {
case class Or(left: ErrorCondition, right: ErrorCondition) extends ErrorCondition
case class DBErrorCondition(dbType: DbType, checkTables: List[TableName]) extends ErrorCondition
case class KafkaErrorCondition(topics: List[Topic]) extends ErrorCondition
case class HttpErrorCondition(url: Url) extends ErrorCondition
}
26 — Pierangelo Cecchetto - LambdaConf 2025
DESCRIBE SOLUTIONS THROUGH DATA
STRUCTURES
Or(
DBErrorCondition(
DbType.Postgres,
List(TableName("user"), TableName("access_token"))
),
HttpErrorCondition(
Url("https://license-check.org/check")
)
)
27 — Pierangelo Cecchetto - LambdaConf 2025
MAKE IT ERGONOMIC
sealed trait ErrorCondition { self =>
def ||(other: ErrorCondition): ErrorCondition = Or(self, other)
}
object Dsl:
def dbErrorCondition(dbType: DbType, checkTables: TableName*): ErrorCondition =
DBErrorCondition(dbType, checkTables.toList)
def kafkaErrorCondition(topics: Topic*): ErrorCondition =
KafkaErrorCondition(topics.toList)
def getHttp2xx(url: Url): ErrorCondition =
HttpErrorCondition(url)
28 — Pierangelo Cecchetto - LambdaConf 2025
MAKE IT ERGONOMIC
val errorCondition =
dbErrorCondition(DbType.Postgres,
TableName("user"), TableName("access_token")
) ||
getHttp2xx(Url("https://license-check.org"))
29 — Pierangelo Cecchetto - LambdaConf 2025
FLEXIBILITY
val errorCondition =
dbErrorCondition(DbType.Postgres,
TableName("user"), TableName("access_token")
) ||
getHttp2xx(Url("https://license-check.org")) ||
kafkaErrorCondition("messages-topic")
30 — Pierangelo Cecchetto - LambdaConf 2025
FLEXIBILITY
val errorCondition =
dbErrorCondition(DbType.Postgres,
TableName("user"), TableName("access_token")
) ||
getHttp2xx(Url("https://license-check.org")) ||
kafkaErrorCondition("messages-topic") ||
dbErrorCondition(dbType, TableName("documents"))
31 — Pierangelo Cecchetto - LambdaConf 2025
ORTHOGONALITY
ADDRESS ALL THREE PROBLEMS INDEPENDENTLY:
1.
✅
Constrain what we can do
2.
⬜
Keep documentation always aligned with code
3.
⬜
Make solution stack-agnostic
32 — Pierangelo Cecchetto - LambdaConf 2025
RUN IT: MAKE A CHECKERRORS()
Compile the data structure into a function
object ZIOInterpreter {
def checkErrors(errorCondition: ErrorCondition): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] =
errorCondition match
case ErrorCondition.DBErrorCondition(DbType.Postgres, checkTables) =>
DoobieZIOdBHealthcheck.status(DoobieZIOdBHealthcheck.existsPostgres, checkTables)
case ErrorCondition.DBErrorCondition(DbType.MySql, checkTables) =>
DoobieZIOdBHealthcheck.status(DoobieZIOdBHealthcheck.existsMySql, checkTables)
case ErrorCondition.HttpErrorCondition(url) =>
SttpHealthCheck.check(url)
case ErrorCondition.Or(left, right) =>
interpret(left).zipPar(interpret(right)).map {
case (leftErrors, rightErrors) => leftErrors ++ rightErrors
}
}
33 — Pierangelo Cecchetto - LambdaConf 2025
INTERPRETER: THE BARE
MINIMUM
sealed trait ErrorCondition
def checkErrors(ec: ErrorCondition): UIO[List[Errors]] =
ec match {
//cover all cases
}
34 — Pierangelo Cecchetto - LambdaConf 2025
INTERPRETER: OPTIMIZE
BEFORE RUNNING
sealed trait ErrorCondition
private def optimize(ec: ErrorCondition): ErrorCondition =
/*
* collect all checks of the same type, to minimize the calls
*/
def checkErrors(ec: ErrorCondition): UIO[List[Errors]] =
optimize(ec) match {
//cover all cases
}
35 — Pierangelo Cecchetto - LambdaConf 2025
BENEFITS: TECH STACK INDEPENDENCE
For a Future/Slick implementation:
def checkErrors(
db: slick.jdbc.JdbcBackend#Database,
jdbcProfile: slick.jdbc.JdbcProfile,
httpClient: SttpFutureBackend[Future, Any]
)(
errorCondition: ErrorCondition )(implicit ec: ExecutionContext): Future[List[StatusError]] =
errorCondition match {
// Different implementation, same logic
}
36 — Pierangelo Cecchetto - LambdaConf 2025
BENEFITS: DOCUMENTATION FROM CODE
def interpret(errorCondition: ErrorCondition): String =
errorCondition match {
//render (statefully) all cases
}
_
37 — Pierangelo Cecchetto - LambdaConf 2025
BENEFITS: DOCUMENTATION FROM CODE
def interpret(errorCondition: ErrorCondition): String =
errorCondition match {
//render (statefully) all cases
}
Output:
Either of:
Database Check (Postgres):
Required tables: customer, access_token
OR
Kafka Check:
Required topics: events
OR
HTTP Check:
URL: http://localhost:8080/status/200
are detected for failure
38 — Pierangelo Cecchetto - LambdaConf 2025
ORTHOGONALITY
ADDRESS ALL THREE PROBLEMS INDEPENDENTLY:
1.
✅
Constrain what we can do
2.
✅
Keep documentation always aligned with code
3.
✅
Make solution stack-agnostic
39 — Pierangelo Cecchetto - LambdaConf 2025
EVOLUTION OF THE LANGUAGE
New requirement New term in the algebra
case class RabbitMQErrorCondition(exchanges: List[Exchange]) extends ErrorCondition
def rabbitMQErrorCondition(exchanges: Exchange*): ErrorCondition =
RabbitMQErrorCondition(exchanges.toList)
40 — Pierangelo Cecchetto - LambdaConf 2025
COMPILER-ENFORCED CONSISTENCY
[error] 17 | errorCondition match
[error] | ^^^^^^^^^^^^^^
[error] |match may not be exhaustive.
[error] |
[error] |It would fail on pattern case: domainmodeling.healthcheck.ErrorCondition.RabbitMQErrorCondition(_)
41 — Pierangelo Cecchetto - LambdaConf 2025
A MORE COMPLEX EXAMPLE
PAYMENT AUTHORIZATION RULES
The problem: Authorizing payments based on complex evolving rules
def isBlocked(creditCard: CreditCard, purchase: Purchase): Boolean
42 — Pierangelo Cecchetto - LambdaConf 2025
DOMAIN MODEL
case class CreditCard(
id: Id,
cardNumber: CardNumber,
cardHolderName: CardHolderName,
cardType: CardType,
expiryDate: ExpiryDate,
issuedInCountry: Country
)
case class Purchase(
id: Id,
creditCardId: Id,
amount: Amount,
shopId: ShopId
)
case class Shop(
id: ShopId,
name: ShopName,
country: Country,
categories: List[ShopCategory]
)
43 — Pierangelo Cecchetto - LambdaConf 2025
PAYMENT APPROVAL SYSTEM
def isBlocked(creditCard: CreditCard, purchase: Purchase): Boolean
44 — Pierangelo Cecchetto - LambdaConf 2025
FIRST REQUIREMENT
"Block all electronic purchases in China"
Naive implementation:
def isBlocked(creditCard: CreditCard, purchase: Purchase): Boolean =
purchase.shop.country == Country.China &&
purchase.shop.categories.contains(ShopCategory.Electronics))
45 — Pierangelo Cecchetto - LambdaConf 2025
BUILDING THE BASIC RULES
sealed trait BlockingRule
case class PurchaseOccursInCountry(country: Country) extends BlockingRule
case class PurchaseCategoryEquals(purchaseCategory: ShopCategory) extends BlockingRule
case class PurchaseAmountExceeds(amount: Double) extends BlockingRule
case class FraudProbabilityExceeds(threshold: Probability) extends BlockingRule
case class CreditCardFlagged() extends BlockingRule
case class ShopIsBlacklisted() extends BlockingRule
46 — Pierangelo Cecchetto - LambdaConf 2025
ADDING COMBINATORS
sealed trait BlockingRule { self =>
def &&(other: BlockingRule): BlockingRule = BlockingRule.And(self, other)
def ||(other: BlockingRule): BlockingRule = BlockingRule.Or(self, other)
}
47 — Pierangelo Cecchetto - LambdaConf 2025
USER-FRIENDLY CONSTRUCTORS
object DSL {
private def purchaseInCountry(country: Country): BlockingRule =
BlockingRule.PurchaseOccursInCountry(country)
def purchaseCountryIsOneOf(countries: Country*): BlockingRule =
countries.map(purchaseInCountry).reduce(_ || _)
private def purchaseCategoryEquals(purchaseCategory: ShopCategory): BlockingRule =
BlockingRule.PurchaseCategoryEquals(purchaseCategory)
def purchaseCategoryIsOneOf(purchaseCategories: ShopCategory*): BlockingRule =
purchaseCategories.map(purchaseCategoryEquals).reduce(_ || _)
def purchaseAmountExceeds(amount: Double): BlockingRule = BlockingRule.PurchaseAmountExceeds(amount)
def fraudProbabilityExceeds(threshold: Probability): BlockingRule = BlockingRule.FraudProbabilityExceeds(threshold)
def creditCardFlagged: BlockingRule = BlockingRule.CreditCardFlagged()
def shopIsBlacklisted: BlockingRule = BlockingRule.ShopIsBlacklisted()
}
48 — Pierangelo Cecchetto - LambdaConf 2025
RULE INTERPRETER
def isBlocked(
ccFlaggedService: CreditCardFlaggedService,
fraudScoreService: FraudScoreService,
shopRepository: ShopRepository)(
rule: BlockingRule
)(cc: CreditCard, p: Purchase): UIO[Boolean] = {
def eval(rule: BlockingRule): UIO[Boolean] = rule match {
case BlockingRule.PurchaseOccursInCountry(country) =>
purchaseOccursInCountry(BlockingRule.PurchaseOccursInCountry(country))(input)
case BlockingRule.PurchaseCategoryEquals(purchaseCategory) =>
purchaseCategoryEquals(BlockingRule.PurchaseCategoryEquals(purchaseCategory))(cc, p)
case BlockingRule.PurchaseAmountExceeds(amount) =>
purchaseAmountExceeds(BlockingRule.PurchaseAmountExceeds(amount))(input)
case BlockingRule.CreditCardFlagged() => creditCardFlagged(ccFlaggedService)(cc, p)
case BlockingRule.FraudProbabilityExceeds(threshold) =>
fraudProbability(BlockingRule.FraudProbabilityExceeds(threshold), fraudScoreService)(cc, p)
case BlockingRule.And(l, r) => eval(l).zipWith(eval(r))(_ && _)
case BlockingRule.Or(l, r) => eval(l).zipWith(eval(r))(_ || _)
case BlockingRule.ShopIsBlacklisted() => shopRepository.isBlacklisted(p.shop.id)
}
eval(rule)
}
49 — Pierangelo Cecchetto - LambdaConf 2025
DOCUMENTATION FROM CODE: MERMAID
def toMermaidCode(blockingRule: BlockingRule): UIO[MermaidCode] = {
import MermaidInterpreter.Instances.given
for {
labelledTree <- label(blockingRule)
mermaidCode <- labelledToMermaidCode(labelledTree)
} yield MermaidCode(mermaidCode)
}
50 — Pierangelo Cecchetto - LambdaConf 2025
RULE EVOLUTION: V1
val br1 =
purchaseCountryIsOneOf(Country.China) &&
purchaseCategoryIsOneOf(ShopCategory.Electronics)
51 — Pierangelo Cecchetto - LambdaConf 2025
RULE EVOLUTION: V2
val br1 =
purchaseCountryIsOneOf(Country.China) &&
purchaseCategoryIsOneOf(ShopCategory.Electronics)
val br2 = br1 ||
(purchaseCountryIsOneOf(Country.UK) &&
purchaseCategoryIsOneOf(ShopCategory.Gambling))
52 — Pierangelo Cecchetto - LambdaConf 2025
RULE EVOLUTION: V3
val br1 =
purchaseCountryIsOneOf(Country.China) &&
purchaseCategoryIsOneOf(ShopCategory.Electronics)
val br2 = br1 ||
(purchaseCountryIsOneOf(Country.UK) &&
purchaseCategoryIsOneOf(ShopCategory.Gambling))
val br3 = br2 ||
(
purchaseCountryIsOneOf(Country.Italy) &&
purchaseCategoryIsOneOf(
ShopCategory.Gambling,
ShopCategory.Adult
) &&
purchaseAmountExceeds(1000) &&
fraudProbabilityExceeds(Probability(0.8))
)
53 — Pierangelo Cecchetto - LambdaConf 2025
BENEFITS
▸ Constrain the possibilities- Define a minimum set of things we can do, and
build from there
▸ Self-Documenting- Text/Visual representation directly from code
▸ Technology Independent- Same rules, different implementations
▸ Evolving Safely- Compiler catches missing cases
▸ Optimization- We can transform the rule tree before execution
54 — Pierangelo Cecchetto - LambdaConf 2025
...AND THERE IS MORE!
Not only algorithmic problems:
▸ ETL processing pipelines
▸ Workflows
▸ API requests (Tapir, ZIO-http)
▸ Domain-specific calculations
55 — Pierangelo Cecchetto - LambdaConf 2025
...AND THERE IS MORE!
Further developments:
▸ Serialize/Deserialize your business logic, version, revert
▸ Derive a visual debugger for complex logic
56 — Pierangelo Cecchetto - LambdaConf 2025
SCALA SHINES...
▸ Compiler exhaustive checks
▸ given & phantom types to make unwanted constructions
impossible
57 — Pierangelo Cecchetto - LambdaConf 2025
...BUT ALL YOU NEED IS DATA
▸ Languages with good ADT
▸ Pattern matching (data de-construction)
▸ Data types with methods for ergonomics
58 — Pierangelo Cecchetto - LambdaConf 2025
Keep it Simple
▸ No need for HKTs unless it is really necessary
▸ Restrict operations to a limited set
▸ We don't always need monads in high-level problems
59 — Pierangelo Cecchetto - LambdaConf 2025
Conservation
Principle
Clear
=
easy to understand, document, evolve
60 — Pierangelo Cecchetto - LambdaConf 2025
Conservation
Principle
Constraints Liberate, Liberties Constrain —
Runar Bjarnason
61 — Pierangelo Cecchetto - LambdaConf 2025
THANK YOU!
Questions?
62 — Pierangelo Cecchetto - LambdaConf 2025

More Related Content

Similar to Programs as Values - Write code and don't get lost (20)

Functional Operations - Susan Potter
Functional Operations - Susan PotterFunctional Operations - Susan Potter
Functional Operations - Susan Potter
distributed matters
 
Functional Operations - Susan Potter
Functional Operations - Susan PotterFunctional Operations - Susan Potter
Functional Operations - Susan Potter
distributed matters
 
GUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programmingGUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programming
David Muñoz Díaz
 
GUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programmingGUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programming
David Muñoz Díaz
 
Event Sourcing and Functional Programming
Event Sourcing and Functional ProgrammingEvent Sourcing and Functional Programming
Event Sourcing and Functional Programming
GlobalLogic Ukraine
 
Functional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenFunctional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heaven
Pawel Szulc
 
Event Sourcing and Functional Programming
Event Sourcing and Functional ProgrammingEvent Sourcing and Functional Programming
Event Sourcing and Functional Programming
GlobalLogic Ukraine
 
Functional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenFunctional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heaven
Pawel Szulc
 
Functional Systems @ Twitter
Functional Systems @ TwitterFunctional Systems @ Twitter
Functional Systems @ Twitter
C4Media
 
Functional Systems @ Twitter
Functional Systems @ TwitterFunctional Systems @ Twitter
Functional Systems @ Twitter
C4Media
 
Functional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 DevelopersFunctional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 Developers
Jayaram Sankaranarayanan
 
Functional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 DevelopersFunctional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 Developers
Jayaram Sankaranarayanan
 
ZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in Scala
Wiem Zine Elabidine
 
ZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in Scala
Wiem Zine Elabidine
 
Advanced
AdvancedAdvanced
Advanced
mxmxm
 
Advanced
AdvancedAdvanced
Advanced
mxmxm
 
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVMVoxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Manuel Bernhardt
 
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVMVoxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Manuel Bernhardt
 
A Call for Sanity in NoSQL
A Call for Sanity in NoSQLA Call for Sanity in NoSQL
A Call for Sanity in NoSQL
C4Media
 
A Call for Sanity in NoSQL
A Call for Sanity in NoSQLA Call for Sanity in NoSQL
A Call for Sanity in NoSQL
C4Media
 
Functional Operations - Susan Potter
Functional Operations - Susan PotterFunctional Operations - Susan Potter
Functional Operations - Susan Potter
distributed matters
 
Functional Operations - Susan Potter
Functional Operations - Susan PotterFunctional Operations - Susan Potter
Functional Operations - Susan Potter
distributed matters
 
GUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programmingGUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programming
David Muñoz Díaz
 
GUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programmingGUL UC3M - Introduction to functional programming
GUL UC3M - Introduction to functional programming
David Muñoz Díaz
 
Event Sourcing and Functional Programming
Event Sourcing and Functional ProgrammingEvent Sourcing and Functional Programming
Event Sourcing and Functional Programming
GlobalLogic Ukraine
 
Functional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenFunctional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heaven
Pawel Szulc
 
Event Sourcing and Functional Programming
Event Sourcing and Functional ProgrammingEvent Sourcing and Functional Programming
Event Sourcing and Functional Programming
GlobalLogic Ukraine
 
Functional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenFunctional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heaven
Pawel Szulc
 
Functional Systems @ Twitter
Functional Systems @ TwitterFunctional Systems @ Twitter
Functional Systems @ Twitter
C4Media
 
Functional Systems @ Twitter
Functional Systems @ TwitterFunctional Systems @ Twitter
Functional Systems @ Twitter
C4Media
 
Functional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 DevelopersFunctional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 Developers
Jayaram Sankaranarayanan
 
Functional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 DevelopersFunctional Programming 101 for Java 7 Developers
Functional Programming 101 for Java 7 Developers
Jayaram Sankaranarayanan
 
ZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in Scala
Wiem Zine Elabidine
 
ZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in Scala
Wiem Zine Elabidine
 
Advanced
AdvancedAdvanced
Advanced
mxmxm
 
Advanced
AdvancedAdvanced
Advanced
mxmxm
 
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVMVoxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Manuel Bernhardt
 
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVMVoxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Voxxed Days Vienna - The Why and How of Reactive Web-Applications on the JVM
Manuel Bernhardt
 
A Call for Sanity in NoSQL
A Call for Sanity in NoSQLA Call for Sanity in NoSQL
A Call for Sanity in NoSQL
C4Media
 
A Call for Sanity in NoSQL
A Call for Sanity in NoSQLA Call for Sanity in NoSQL
A Call for Sanity in NoSQL
C4Media
 

Recently uploaded (20)

Essentials of Resource Planning in a Downturn
Essentials of Resource Planning in a DownturnEssentials of Resource Planning in a Downturn
Essentials of Resource Planning in a Downturn
OnePlan Solutions
 
COBOL Programming with VSCode - IBM Certificate
COBOL Programming with VSCode - IBM CertificateCOBOL Programming with VSCode - IBM Certificate
COBOL Programming with VSCode - IBM Certificate
VICTOR MAESTRE RAMIREZ
 
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
Neuralink TemplateeeeeeeeeeeeeeeeeeeeeeeeeeNeuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
alexandernoetzold
 
Porting Qt 5 QML Modules to Qt 6 Webinar
Porting Qt 5 QML Modules to Qt 6 WebinarPorting Qt 5 QML Modules to Qt 6 Webinar
Porting Qt 5 QML Modules to Qt 6 Webinar
ICS
 
Best Inbound Call Tracking Software for Small Businesses
Best Inbound Call Tracking Software for Small BusinessesBest Inbound Call Tracking Software for Small Businesses
Best Inbound Call Tracking Software for Small Businesses
TheTelephony
 
Marketo & Dynamics can be Most Excellent to Each Other – The Sequel
Marketo & Dynamics can be Most Excellent to Each Other – The SequelMarketo & Dynamics can be Most Excellent to Each Other – The Sequel
Marketo & Dynamics can be Most Excellent to Each Other – The Sequel
BradBedford3
 
14 Years of Developing nCine - An Open Source 2D Game Framework
14 Years of Developing nCine - An Open Source 2D Game Framework14 Years of Developing nCine - An Open Source 2D Game Framework
14 Years of Developing nCine - An Open Source 2D Game Framework
Angelo Theodorou
 
Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...
Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...
Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...
Alluxio, Inc.
 
How the US Navy Approaches DevSecOps with Raise 2.0
How the US Navy Approaches DevSecOps with Raise 2.0How the US Navy Approaches DevSecOps with Raise 2.0
How the US Navy Approaches DevSecOps with Raise 2.0
Anchore
 
From Chaos to Clarity - Designing (AI-Ready) APIs with APIOps Cycles
From Chaos to Clarity - Designing (AI-Ready) APIs with APIOps CyclesFrom Chaos to Clarity - Designing (AI-Ready) APIs with APIOps Cycles
From Chaos to Clarity - Designing (AI-Ready) APIs with APIOps Cycles
Marjukka Niinioja
 
Providing Better Biodiversity Through Better Data
Providing Better Biodiversity Through Better DataProviding Better Biodiversity Through Better Data
Providing Better Biodiversity Through Better Data
Safe Software
 
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Wondershare PDFelement Pro 11.4.20.3548 Crack Free DownloadWondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Puppy jhon
 
Who will create the languages of the future?
Who will create the languages of the future?Who will create the languages of the future?
Who will create the languages of the future?
Jordi Cabot
 
How Insurance Policy Management Software Streamlines Operations
How Insurance Policy Management Software Streamlines OperationsHow Insurance Policy Management Software Streamlines Operations
How Insurance Policy Management Software Streamlines Operations
Insurance Tech Services
 
Code and No-Code Journeys: The Coverage Overlook
Code and No-Code Journeys: The Coverage OverlookCode and No-Code Journeys: The Coverage Overlook
Code and No-Code Journeys: The Coverage Overlook
Applitools
 
OpenTelemetry 101 Cloud Native Barcelona
OpenTelemetry 101 Cloud Native BarcelonaOpenTelemetry 101 Cloud Native Barcelona
OpenTelemetry 101 Cloud Native Barcelona
Imma Valls Bernaus
 
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Agentic Techniques in Retrieval-Augmented Generation with Azure AI SearchAgentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Maxim Salnikov
 
IBM Rational Unified Process For Software Engineering - Introduction
IBM Rational Unified Process For Software Engineering - IntroductionIBM Rational Unified Process For Software Engineering - Introduction
IBM Rational Unified Process For Software Engineering - Introduction
Gaurav Sharma
 
Leveraging Foundation Models to Infer Intents
Leveraging Foundation Models to Infer IntentsLeveraging Foundation Models to Infer Intents
Leveraging Foundation Models to Infer Intents
Keheliya Gallaba
 
Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...
Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...
Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...
BradBedford3
 
Essentials of Resource Planning in a Downturn
Essentials of Resource Planning in a DownturnEssentials of Resource Planning in a Downturn
Essentials of Resource Planning in a Downturn
OnePlan Solutions
 
COBOL Programming with VSCode - IBM Certificate
COBOL Programming with VSCode - IBM CertificateCOBOL Programming with VSCode - IBM Certificate
COBOL Programming with VSCode - IBM Certificate
VICTOR MAESTRE RAMIREZ
 
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
Neuralink TemplateeeeeeeeeeeeeeeeeeeeeeeeeeNeuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
alexandernoetzold
 
Porting Qt 5 QML Modules to Qt 6 Webinar
Porting Qt 5 QML Modules to Qt 6 WebinarPorting Qt 5 QML Modules to Qt 6 Webinar
Porting Qt 5 QML Modules to Qt 6 Webinar
ICS
 
Best Inbound Call Tracking Software for Small Businesses
Best Inbound Call Tracking Software for Small BusinessesBest Inbound Call Tracking Software for Small Businesses
Best Inbound Call Tracking Software for Small Businesses
TheTelephony
 
Marketo & Dynamics can be Most Excellent to Each Other – The Sequel
Marketo & Dynamics can be Most Excellent to Each Other – The SequelMarketo & Dynamics can be Most Excellent to Each Other – The Sequel
Marketo & Dynamics can be Most Excellent to Each Other – The Sequel
BradBedford3
 
14 Years of Developing nCine - An Open Source 2D Game Framework
14 Years of Developing nCine - An Open Source 2D Game Framework14 Years of Developing nCine - An Open Source 2D Game Framework
14 Years of Developing nCine - An Open Source 2D Game Framework
Angelo Theodorou
 
Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...
Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...
Meet You in the Middle: 1000x Performance for Parquet Queries on PB-Scale Dat...
Alluxio, Inc.
 
How the US Navy Approaches DevSecOps with Raise 2.0
How the US Navy Approaches DevSecOps with Raise 2.0How the US Navy Approaches DevSecOps with Raise 2.0
How the US Navy Approaches DevSecOps with Raise 2.0
Anchore
 
From Chaos to Clarity - Designing (AI-Ready) APIs with APIOps Cycles
From Chaos to Clarity - Designing (AI-Ready) APIs with APIOps CyclesFrom Chaos to Clarity - Designing (AI-Ready) APIs with APIOps Cycles
From Chaos to Clarity - Designing (AI-Ready) APIs with APIOps Cycles
Marjukka Niinioja
 
Providing Better Biodiversity Through Better Data
Providing Better Biodiversity Through Better DataProviding Better Biodiversity Through Better Data
Providing Better Biodiversity Through Better Data
Safe Software
 
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Wondershare PDFelement Pro 11.4.20.3548 Crack Free DownloadWondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Puppy jhon
 
Who will create the languages of the future?
Who will create the languages of the future?Who will create the languages of the future?
Who will create the languages of the future?
Jordi Cabot
 
How Insurance Policy Management Software Streamlines Operations
How Insurance Policy Management Software Streamlines OperationsHow Insurance Policy Management Software Streamlines Operations
How Insurance Policy Management Software Streamlines Operations
Insurance Tech Services
 
Code and No-Code Journeys: The Coverage Overlook
Code and No-Code Journeys: The Coverage OverlookCode and No-Code Journeys: The Coverage Overlook
Code and No-Code Journeys: The Coverage Overlook
Applitools
 
OpenTelemetry 101 Cloud Native Barcelona
OpenTelemetry 101 Cloud Native BarcelonaOpenTelemetry 101 Cloud Native Barcelona
OpenTelemetry 101 Cloud Native Barcelona
Imma Valls Bernaus
 
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Agentic Techniques in Retrieval-Augmented Generation with Azure AI SearchAgentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Maxim Salnikov
 
IBM Rational Unified Process For Software Engineering - Introduction
IBM Rational Unified Process For Software Engineering - IntroductionIBM Rational Unified Process For Software Engineering - Introduction
IBM Rational Unified Process For Software Engineering - Introduction
Gaurav Sharma
 
Leveraging Foundation Models to Infer Intents
Leveraging Foundation Models to Infer IntentsLeveraging Foundation Models to Infer Intents
Leveraging Foundation Models to Infer Intents
Keheliya Gallaba
 
Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...
Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...
Maintaining + Optimizing Database Health: Vendors, Orchestrations, Enrichment...
BradBedford3
 
Ad

Programs as Values - Write code and don't get lost

  • 1. PROGRAMS AS VALUES WRITE CODE AND DON'T GET LOST 1 — Pierangelo Cecchetto - LambdaConf 2025
  • 2. THE CODE ARCHEOLOGIST PROBLEM ▸ "Investigation requests" (not "Bug Reports") ▸ What is the system doing? ▸ What is the system supposed to do? 2 — Pierangelo Cecchetto - LambdaConf 2025
  • 3. INVESTIGATION REQUEST #1 System consumes documents from Kafka and produces processing proofs. We see 100 messages going in, and only 70 are OK. Why did 30 go to DLQ? 3 — Pierangelo Cecchetto - LambdaConf 2025
  • 4. INVESTIGATION REQUEST #2 In a purchase approval system, I see 30 transactions refused out of 100. Why are they refused? 4 — Pierangelo Cecchetto - LambdaConf 2025
  • 5. INVESTIGATION REQUEST #3 "We were supposed to see 8 shop locations on Google Maps but we see only 6. Why is that?" 5 — Pierangelo Cecchetto - LambdaConf 2025
  • 6. INVESTIGATION REQUEST #3 "We were supposed to see 8 shop locations on Google Maps but we see only 6. Why is that?" 6 — Pierangelo Cecchetto - LambdaConf 2025
  • 7. AND TO MAKE THINGS MORE INTERESTING... ▸ No error messages in our logs 7 — Pierangelo Cecchetto - LambdaConf 2025
  • 8. THE PROMISE OF FP FOCUS ON THE WHAT RATHER THAN THE HOW // HOW (Procedural) val x = List(1, 2, 3) val y = scala.collection.mutable.ArrayBuffer.empty[Int] for (i <- 0 until x.size) { y += x(i) * 2 } // WHAT (Functional) val x = List(1, 2, 3) val y = x.map(_ * 2) 8 — Pierangelo Cecchetto - LambdaConf 2025
  • 9. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK /** * Check db connectivity */ def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = { def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ ZIO .collectAll( List( dbCheck( DbType.MySql, List(TableName("table1"), TableName("table2")) ), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) } 9 — Pierangelo Cecchetto - LambdaConf 2025
  • 10. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ 10 — Pierangelo Cecchetto - LambdaConf 2025
  • 11. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = { def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) } 11 — Pierangelo Cecchetto - LambdaConf 2025
  • 12. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ 12 — Pierangelo Cecchetto - LambdaConf 2025
  • 13. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = { def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) } 13 — Pierangelo Cecchetto - LambdaConf 2025
  • 14. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ } 14 — Pierangelo Cecchetto - LambdaConf 2025
  • 15. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = { def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) } 15 — Pierangelo Cecchetto - LambdaConf 2025
  • 16. THE PROMISE OF FP IN BUSINESS LOGIC PROBLEMS - HEALTH CHECK ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) 16 — Pierangelo Cecchetto - LambdaConf 2025
  • 17. PURELY FUNCTIONAL CODE def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = { def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) } ▸ Immutable values ▸ Strongly typed - neotype ▸ Functional effect system - ZIO 17 — Pierangelo Cecchetto - LambdaConf 2025
  • 18. PURELY FUNCTIONAL CODE def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = { def dbCheck( dbType: DbType, checkTables: List[TableName] ): ZIO[Transactor[Task], Nothing, List[StatusError]] = /* * Doobie code */ def kafkaCheck( topics: List[Topic] ): URIO[AdminClient, Option[StatusError]] = /* * ZIO-kafka code */ def httpCheck( url: Url ): URIO[SttpClient, List[StatusError]] = /* * Sttp code */ ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) } Is it focused on the how or on the what? 18 — Pierangelo Cecchetto - LambdaConf 2025
  • 19. PROBLEM #1: ARBITRARY FUNCTIONS def checkErrors(): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = ZIO.collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")), ZIO.succeed(List(StatusError("Fourth error"))) ZIO.die(new RuntimeException("Error in the fifth check")) ) ) .map(_.flatten) 19 — Pierangelo Cecchetto - LambdaConf 2025
  • 20. PROBLEM #2: DIVERGENT DOCUMENTATION /** * Check db connectivity */ def checkErrors() = dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))) 20 — Pierangelo Cecchetto - LambdaConf 2025
  • 21. PROBLEM #2: DIVERGENT DOCUMENTATION /** * Check db connectivity */ def checkErrors() = ZIO .collectAll( List( dbCheck(DbType.MySql, List(TableName("table1"), TableName("table2"))), kafkaCheck(List(Topic("topic1"), Topic("topic2"))), httpCheck(Url("http://localhost:8080")) ) ) .map(_.flatten) 21 — Pierangelo Cecchetto - LambdaConf 2025
  • 22. PROBLEM #3: SOLUTION BOUND TO ZIO ▸ Legacy services use Future and Slick ▸ No guarantee of equivalence between implementations 22 — Pierangelo Cecchetto - LambdaConf 2025
  • 23. ORTHOGONALITY ADDRESS ALL THREE PROBLEMS INDEPENDENTLY: 1. Constrain what we can do 2. Keep documentation always aligned with code 3. Make solution stack-agnostic 23 — Pierangelo Cecchetto - LambdaConf 2025
  • 24. DEFINE A LANGUAGE TO DESCRIBE OUR SOLUTION 24 — Pierangelo Cecchetto - LambdaConf 2025
  • 25. MODEL THE SOLUTION sealed trait ErrorCondition object ErrorCondition { case class DBErrorCondition(dbType: DbType, checkTables: List[TableName]) extends ErrorCondition case class KafkaErrorCondition(topics: List[Topic]) extends ErrorCondition case class HttpErrorCondition(url: Url) extends ErrorCondition } 25 — Pierangelo Cecchetto - LambdaConf 2025
  • 26. ADD COMBINATORS sealed trait ErrorCondition object ErrorCondition { case class Or(left: ErrorCondition, right: ErrorCondition) extends ErrorCondition case class DBErrorCondition(dbType: DbType, checkTables: List[TableName]) extends ErrorCondition case class KafkaErrorCondition(topics: List[Topic]) extends ErrorCondition case class HttpErrorCondition(url: Url) extends ErrorCondition } 26 — Pierangelo Cecchetto - LambdaConf 2025
  • 27. DESCRIBE SOLUTIONS THROUGH DATA STRUCTURES Or( DBErrorCondition( DbType.Postgres, List(TableName("user"), TableName("access_token")) ), HttpErrorCondition( Url("https://license-check.org/check") ) ) 27 — Pierangelo Cecchetto - LambdaConf 2025
  • 28. MAKE IT ERGONOMIC sealed trait ErrorCondition { self => def ||(other: ErrorCondition): ErrorCondition = Or(self, other) } object Dsl: def dbErrorCondition(dbType: DbType, checkTables: TableName*): ErrorCondition = DBErrorCondition(dbType, checkTables.toList) def kafkaErrorCondition(topics: Topic*): ErrorCondition = KafkaErrorCondition(topics.toList) def getHttp2xx(url: Url): ErrorCondition = HttpErrorCondition(url) 28 — Pierangelo Cecchetto - LambdaConf 2025
  • 29. MAKE IT ERGONOMIC val errorCondition = dbErrorCondition(DbType.Postgres, TableName("user"), TableName("access_token") ) || getHttp2xx(Url("https://license-check.org")) 29 — Pierangelo Cecchetto - LambdaConf 2025
  • 30. FLEXIBILITY val errorCondition = dbErrorCondition(DbType.Postgres, TableName("user"), TableName("access_token") ) || getHttp2xx(Url("https://license-check.org")) || kafkaErrorCondition("messages-topic") 30 — Pierangelo Cecchetto - LambdaConf 2025
  • 31. FLEXIBILITY val errorCondition = dbErrorCondition(DbType.Postgres, TableName("user"), TableName("access_token") ) || getHttp2xx(Url("https://license-check.org")) || kafkaErrorCondition("messages-topic") || dbErrorCondition(dbType, TableName("documents")) 31 — Pierangelo Cecchetto - LambdaConf 2025
  • 32. ORTHOGONALITY ADDRESS ALL THREE PROBLEMS INDEPENDENTLY: 1. ✅ Constrain what we can do 2. ⬜ Keep documentation always aligned with code 3. ⬜ Make solution stack-agnostic 32 — Pierangelo Cecchetto - LambdaConf 2025
  • 33. RUN IT: MAKE A CHECKERRORS() Compile the data structure into a function object ZIOInterpreter { def checkErrors(errorCondition: ErrorCondition): ZIO[Transactor[Task] & AdminClient & SttpClient, Nothing, List[StatusError]] = errorCondition match case ErrorCondition.DBErrorCondition(DbType.Postgres, checkTables) => DoobieZIOdBHealthcheck.status(DoobieZIOdBHealthcheck.existsPostgres, checkTables) case ErrorCondition.DBErrorCondition(DbType.MySql, checkTables) => DoobieZIOdBHealthcheck.status(DoobieZIOdBHealthcheck.existsMySql, checkTables) case ErrorCondition.HttpErrorCondition(url) => SttpHealthCheck.check(url) case ErrorCondition.Or(left, right) => interpret(left).zipPar(interpret(right)).map { case (leftErrors, rightErrors) => leftErrors ++ rightErrors } } 33 — Pierangelo Cecchetto - LambdaConf 2025
  • 34. INTERPRETER: THE BARE MINIMUM sealed trait ErrorCondition def checkErrors(ec: ErrorCondition): UIO[List[Errors]] = ec match { //cover all cases } 34 — Pierangelo Cecchetto - LambdaConf 2025
  • 35. INTERPRETER: OPTIMIZE BEFORE RUNNING sealed trait ErrorCondition private def optimize(ec: ErrorCondition): ErrorCondition = /* * collect all checks of the same type, to minimize the calls */ def checkErrors(ec: ErrorCondition): UIO[List[Errors]] = optimize(ec) match { //cover all cases } 35 — Pierangelo Cecchetto - LambdaConf 2025
  • 36. BENEFITS: TECH STACK INDEPENDENCE For a Future/Slick implementation: def checkErrors( db: slick.jdbc.JdbcBackend#Database, jdbcProfile: slick.jdbc.JdbcProfile, httpClient: SttpFutureBackend[Future, Any] )( errorCondition: ErrorCondition )(implicit ec: ExecutionContext): Future[List[StatusError]] = errorCondition match { // Different implementation, same logic } 36 — Pierangelo Cecchetto - LambdaConf 2025
  • 37. BENEFITS: DOCUMENTATION FROM CODE def interpret(errorCondition: ErrorCondition): String = errorCondition match { //render (statefully) all cases } _ 37 — Pierangelo Cecchetto - LambdaConf 2025
  • 38. BENEFITS: DOCUMENTATION FROM CODE def interpret(errorCondition: ErrorCondition): String = errorCondition match { //render (statefully) all cases } Output: Either of: Database Check (Postgres): Required tables: customer, access_token OR Kafka Check: Required topics: events OR HTTP Check: URL: http://localhost:8080/status/200 are detected for failure 38 — Pierangelo Cecchetto - LambdaConf 2025
  • 39. ORTHOGONALITY ADDRESS ALL THREE PROBLEMS INDEPENDENTLY: 1. ✅ Constrain what we can do 2. ✅ Keep documentation always aligned with code 3. ✅ Make solution stack-agnostic 39 — Pierangelo Cecchetto - LambdaConf 2025
  • 40. EVOLUTION OF THE LANGUAGE New requirement New term in the algebra case class RabbitMQErrorCondition(exchanges: List[Exchange]) extends ErrorCondition def rabbitMQErrorCondition(exchanges: Exchange*): ErrorCondition = RabbitMQErrorCondition(exchanges.toList) 40 — Pierangelo Cecchetto - LambdaConf 2025
  • 41. COMPILER-ENFORCED CONSISTENCY [error] 17 | errorCondition match [error] | ^^^^^^^^^^^^^^ [error] |match may not be exhaustive. [error] | [error] |It would fail on pattern case: domainmodeling.healthcheck.ErrorCondition.RabbitMQErrorCondition(_) 41 — Pierangelo Cecchetto - LambdaConf 2025
  • 42. A MORE COMPLEX EXAMPLE PAYMENT AUTHORIZATION RULES The problem: Authorizing payments based on complex evolving rules def isBlocked(creditCard: CreditCard, purchase: Purchase): Boolean 42 — Pierangelo Cecchetto - LambdaConf 2025
  • 43. DOMAIN MODEL case class CreditCard( id: Id, cardNumber: CardNumber, cardHolderName: CardHolderName, cardType: CardType, expiryDate: ExpiryDate, issuedInCountry: Country ) case class Purchase( id: Id, creditCardId: Id, amount: Amount, shopId: ShopId ) case class Shop( id: ShopId, name: ShopName, country: Country, categories: List[ShopCategory] ) 43 — Pierangelo Cecchetto - LambdaConf 2025
  • 44. PAYMENT APPROVAL SYSTEM def isBlocked(creditCard: CreditCard, purchase: Purchase): Boolean 44 — Pierangelo Cecchetto - LambdaConf 2025
  • 45. FIRST REQUIREMENT "Block all electronic purchases in China" Naive implementation: def isBlocked(creditCard: CreditCard, purchase: Purchase): Boolean = purchase.shop.country == Country.China && purchase.shop.categories.contains(ShopCategory.Electronics)) 45 — Pierangelo Cecchetto - LambdaConf 2025
  • 46. BUILDING THE BASIC RULES sealed trait BlockingRule case class PurchaseOccursInCountry(country: Country) extends BlockingRule case class PurchaseCategoryEquals(purchaseCategory: ShopCategory) extends BlockingRule case class PurchaseAmountExceeds(amount: Double) extends BlockingRule case class FraudProbabilityExceeds(threshold: Probability) extends BlockingRule case class CreditCardFlagged() extends BlockingRule case class ShopIsBlacklisted() extends BlockingRule 46 — Pierangelo Cecchetto - LambdaConf 2025
  • 47. ADDING COMBINATORS sealed trait BlockingRule { self => def &&(other: BlockingRule): BlockingRule = BlockingRule.And(self, other) def ||(other: BlockingRule): BlockingRule = BlockingRule.Or(self, other) } 47 — Pierangelo Cecchetto - LambdaConf 2025
  • 48. USER-FRIENDLY CONSTRUCTORS object DSL { private def purchaseInCountry(country: Country): BlockingRule = BlockingRule.PurchaseOccursInCountry(country) def purchaseCountryIsOneOf(countries: Country*): BlockingRule = countries.map(purchaseInCountry).reduce(_ || _) private def purchaseCategoryEquals(purchaseCategory: ShopCategory): BlockingRule = BlockingRule.PurchaseCategoryEquals(purchaseCategory) def purchaseCategoryIsOneOf(purchaseCategories: ShopCategory*): BlockingRule = purchaseCategories.map(purchaseCategoryEquals).reduce(_ || _) def purchaseAmountExceeds(amount: Double): BlockingRule = BlockingRule.PurchaseAmountExceeds(amount) def fraudProbabilityExceeds(threshold: Probability): BlockingRule = BlockingRule.FraudProbabilityExceeds(threshold) def creditCardFlagged: BlockingRule = BlockingRule.CreditCardFlagged() def shopIsBlacklisted: BlockingRule = BlockingRule.ShopIsBlacklisted() } 48 — Pierangelo Cecchetto - LambdaConf 2025
  • 49. RULE INTERPRETER def isBlocked( ccFlaggedService: CreditCardFlaggedService, fraudScoreService: FraudScoreService, shopRepository: ShopRepository)( rule: BlockingRule )(cc: CreditCard, p: Purchase): UIO[Boolean] = { def eval(rule: BlockingRule): UIO[Boolean] = rule match { case BlockingRule.PurchaseOccursInCountry(country) => purchaseOccursInCountry(BlockingRule.PurchaseOccursInCountry(country))(input) case BlockingRule.PurchaseCategoryEquals(purchaseCategory) => purchaseCategoryEquals(BlockingRule.PurchaseCategoryEquals(purchaseCategory))(cc, p) case BlockingRule.PurchaseAmountExceeds(amount) => purchaseAmountExceeds(BlockingRule.PurchaseAmountExceeds(amount))(input) case BlockingRule.CreditCardFlagged() => creditCardFlagged(ccFlaggedService)(cc, p) case BlockingRule.FraudProbabilityExceeds(threshold) => fraudProbability(BlockingRule.FraudProbabilityExceeds(threshold), fraudScoreService)(cc, p) case BlockingRule.And(l, r) => eval(l).zipWith(eval(r))(_ && _) case BlockingRule.Or(l, r) => eval(l).zipWith(eval(r))(_ || _) case BlockingRule.ShopIsBlacklisted() => shopRepository.isBlacklisted(p.shop.id) } eval(rule) } 49 — Pierangelo Cecchetto - LambdaConf 2025
  • 50. DOCUMENTATION FROM CODE: MERMAID def toMermaidCode(blockingRule: BlockingRule): UIO[MermaidCode] = { import MermaidInterpreter.Instances.given for { labelledTree <- label(blockingRule) mermaidCode <- labelledToMermaidCode(labelledTree) } yield MermaidCode(mermaidCode) } 50 — Pierangelo Cecchetto - LambdaConf 2025
  • 51. RULE EVOLUTION: V1 val br1 = purchaseCountryIsOneOf(Country.China) && purchaseCategoryIsOneOf(ShopCategory.Electronics) 51 — Pierangelo Cecchetto - LambdaConf 2025
  • 52. RULE EVOLUTION: V2 val br1 = purchaseCountryIsOneOf(Country.China) && purchaseCategoryIsOneOf(ShopCategory.Electronics) val br2 = br1 || (purchaseCountryIsOneOf(Country.UK) && purchaseCategoryIsOneOf(ShopCategory.Gambling)) 52 — Pierangelo Cecchetto - LambdaConf 2025
  • 53. RULE EVOLUTION: V3 val br1 = purchaseCountryIsOneOf(Country.China) && purchaseCategoryIsOneOf(ShopCategory.Electronics) val br2 = br1 || (purchaseCountryIsOneOf(Country.UK) && purchaseCategoryIsOneOf(ShopCategory.Gambling)) val br3 = br2 || ( purchaseCountryIsOneOf(Country.Italy) && purchaseCategoryIsOneOf( ShopCategory.Gambling, ShopCategory.Adult ) && purchaseAmountExceeds(1000) && fraudProbabilityExceeds(Probability(0.8)) ) 53 — Pierangelo Cecchetto - LambdaConf 2025
  • 54. BENEFITS ▸ Constrain the possibilities- Define a minimum set of things we can do, and build from there ▸ Self-Documenting- Text/Visual representation directly from code ▸ Technology Independent- Same rules, different implementations ▸ Evolving Safely- Compiler catches missing cases ▸ Optimization- We can transform the rule tree before execution 54 — Pierangelo Cecchetto - LambdaConf 2025
  • 55. ...AND THERE IS MORE! Not only algorithmic problems: ▸ ETL processing pipelines ▸ Workflows ▸ API requests (Tapir, ZIO-http) ▸ Domain-specific calculations 55 — Pierangelo Cecchetto - LambdaConf 2025
  • 56. ...AND THERE IS MORE! Further developments: ▸ Serialize/Deserialize your business logic, version, revert ▸ Derive a visual debugger for complex logic 56 — Pierangelo Cecchetto - LambdaConf 2025
  • 57. SCALA SHINES... ▸ Compiler exhaustive checks ▸ given & phantom types to make unwanted constructions impossible 57 — Pierangelo Cecchetto - LambdaConf 2025
  • 58. ...BUT ALL YOU NEED IS DATA ▸ Languages with good ADT ▸ Pattern matching (data de-construction) ▸ Data types with methods for ergonomics 58 — Pierangelo Cecchetto - LambdaConf 2025
  • 59. Keep it Simple ▸ No need for HKTs unless it is really necessary ▸ Restrict operations to a limited set ▸ We don't always need monads in high-level problems 59 — Pierangelo Cecchetto - LambdaConf 2025
  • 60. Conservation Principle Clear = easy to understand, document, evolve 60 — Pierangelo Cecchetto - LambdaConf 2025
  • 61. Conservation Principle Constraints Liberate, Liberties Constrain — Runar Bjarnason 61 — Pierangelo Cecchetto - LambdaConf 2025
  • 62. THANK YOU! Questions? 62 — Pierangelo Cecchetto - LambdaConf 2025