Slide 1

Slide 1 text

#Devoxx @ramtop Error Handling and Logging in a Functional World Uberto Barbini @ramtop medium.com/@ramtop

Slide 2

Slide 2 text

#Devoxx @ramtop Tweet about this talk mentioning me to win a copy of my book (courtesy of PragProg) Why Another Book?

Slide 3

Slide 3 text

#Devoxx @ramtop In this talk you will learn: 1. Exceptions are overused 2. Log levels are redundant 3. Static loggers… just say no!

Slide 4

Slide 4 text

#Devoxx @ramtop All Courtesy of Functional Programming (whatever it is...)

Slide 5

Slide 5 text

#Devoxx @ramtop Functional Programming is a tool to do a job!

Slide 6

Slide 6 text

#Devoxx @ramtop

Slide 7

Slide 7 text

#Devoxx @ramtop Functional Programming misconceptions: 1-It consists in using list.map{} and lambdas in your objects 2-It can only be done with special libraries or languages 3-It’s not clearly defined (I blogged about it https://medium.com/@ramtop/c317f857bcea)

Slide 8

Slide 8 text

#Devoxx @ramtop Functional Programming is a paradigm inspired by Lambda Calculus Programs are constructed by composing pure functions. (the name is a bit of giveaway) Alonzo Church

Slide 9

Slide 9 text

#Devoxx @ramtop Pure functions follow two rules: 1.don’t use any other input that their arguments (no global variables/singleton) 2.don’t produce any other output that their result (no mutable state) They must be referentially transparent

Slide 10

Slide 10 text

#Devoxx @ramtop A Webserver is a function that transform a Request to a Response.

Slide 11

Slide 11 text

#Devoxx @ramtop Thinking in Morphisms "/todo/{user}/{list}" bind GET to ::getToDoList fun getToDoList(request: Request): Response = request.let(::extractListData) .let(::fetchListContent) .let(::renderHtml) .let(::createResponse) // createResponse( renderHtml( fetchListContent( extractListData(request) )))

Slide 12

Slide 12 text

#Devoxx @ramtop What about impure actions/computations? (i.e. FileSystem or Network IO) Functional Effects instead of Side Effects! Monads

Slide 13

Slide 13 text

#Devoxx @ramtop

Slide 14

Slide 14 text

#Devoxx @ramtop is that once you get the epiphany, once you understand - "oh that's what it is" you lose the ability to explain it to anybody. Doug Crockford The Curse of the Monad

Slide 15

Slide 15 text

#Devoxx @ramtop First Hint:A Monad is a Functor (an Endofunctor to be precise) Functors are Transformers! A DataStructure that can be transformed by a function T U → into a DataStructure

Slide 16

Slide 16 text

#Devoxx @ramtop The most useful functor is the ContextReader data class ContextReader(val runWith: (CTX) -> T) { fun transform(f: (T) -> U): ContextReader = ContextReader { ctx -> f(runWith(ctx)) } } A Reader can be used to work with a Cache, a File, a Socket, a Database, a Thread etc. All different kind of functional effects.

Slide 17

Slide 17 text

#Devoxx @ramtop How to use a Reader data class ContextReader(val runWith: (CTX) -> T) { fun transform(f: (T) -> U): ContextReader = ContextReader { ctx -> f(runWith(ctx)) } } fun openConn(conn: DbConn): DbSession = //some code fun readList(sess: DbSession, id: ListId): ToDoList fun renderListToHtml(list: ToDoList): HtmlPage fun listRepository() = ContextReader(::connectDb) val page = listRepository() //ContextReader .transform{ readList(it, listId) } //ContextReader .transform(::renderListToHtml) //ContextReader .runWith(dbConn) //HtmlPage

Slide 18

Slide 18 text

#Devoxx @ramtop Second Hint:

Slide 19

Slide 19 text

#Devoxx @ramtop A monad is a Functor, but not any Functor are Monads, only Functors that can be bound together. (they have a monoid instance)

Slide 20

Slide 20 text

#Devoxx @ramtop fun readList(sess: DbSession, id: ListId): ToDoList fun writeList(sess: DbSession, list: ToDoList): ToDoList val page = listRepository() //ContextReader .transform{ readList(it, listId) } //ContextReader .transform{ renameList(“newName”) } //ContextReader .transform{ connectToDb().writeList(it) } //ContextReader> !!!! error !!! You cannot use transform() to bind Functors

Slide 21

Slide 21 text

#Devoxx @ramtop data class ContextReader(val runWith: (CTX) -> T) { fun transform(f: (T) -> U): ContextReader = ContextReader { ctx -> f(runWith(ctx)) } fun bind(f: (T) -> ContextReader): ContextReader = ContextReader { ctx -> f(runWith(ctx)).runWith(ctx) } } fun readList(sess: DbSession, id: ListId): ToDoList fun writeList(sess: DbSession, list: ToDoList): ToDoList val page = listRepository() //ContextReader .transform{ readList(it, listId) } //ContextReader .transform{ renameList(“newName”) } //ContextReader .bind{ listRepository().writeList(it) } //ContextReader .transform(::renderListToHtml) //ContextReader .runWith(dbConn) //HtmlPage

Slide 22

Slide 22 text

#Devoxx @ramtop Ok, now back to Exceptions...

Slide 23

Slide 23 text

#Devoxx @ramtop Unchecked Exceptions break the flow unexpectedly lose the original context Checked Exceptions verbose to handle force us to handle the result twice

Slide 24

Slide 24 text

#Devoxx @ramtop Functional solution: a Monad that can be Either a Result or an Error listRepository .retrieveList(listName) .transform { list -> renameList(list, newName)} .bind(::storeList) .wrapFailure("Failure while renaming $listName to $newName") sealed class Outcome data class Success(val value: T): Outcome() data class Failure(val error: E): Outcome()

Slide 25

Slide 25 text

#Devoxx @ramtop

Slide 26

Slide 26 text

#Devoxx @ramtop And finally we arrived to Logging! And finally we arrived to Logging!

Slide 27

Slide 27 text

#Devoxx @ramtop Kirk Pepperdine Log Principle Log as much as you can when things go wrong, log as little as you dare when things go well.

Slide 28

Slide 28 text

#Devoxx @ramtop Outcome has the information we want to log typealias Logger = (Outcome<*, *>, LogContext) -> Unit class StreamLogger: Logger {…} class ConsoleLogger: Logger {…} listRepository .retrieveList(listName) .transform { list -> renameList(list, newName)} .bind(::storeList) .also{logger(it, logContext(user, “list rename”)}

Slide 29

Slide 29 text

#Devoxx @ramtop What about debug statements? Exception in thread "main" java.lang.NullPointerException at java.util.HashMap.merge(HashMap.java:1216) at java.util.stream.Collectors.lambda$toMap$168(Collectors.java:1320) at java.util.stream.Collectors$$Lambda$5/1528902577.accept(Unknown Source) at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) at Main.main(Main.java:48)

Slide 30

Slide 30 text

#Devoxx @ramtop No logger factories No log levels Log structured data No configuration files Better performance Easier to debug Easier to test

Slide 31

Slide 31 text

#Devoxx @ramtop Towards useful error messages Error on operation “getAllLists” with user "Carol" and message "Transaction rolled back because org.postgresql.util.PSQLException: ERROR: column \"row_data\" does not exist” processing request “GET /todo/Carol” with headers: “HTTP/1.1\r\nUser-Agent: Jetty/11.0.6\r\nHost: localhost:8000\r\nAccept-Encoding: gzip\r\nContent-Length: 0\r\nContent-Type: application/octet-stream\r\n\r\n)"

Slide 32

Slide 32 text

#Devoxx @ramtop Can we do better? Let’s separate the Domain from technical layers with the Ports And Adapter Architecture

Slide 33

Slide 33 text

#Devoxx @ramtop Loggers belong to adapters, not domain! Logger Logger Logger Logger Logger Logger

Slide 34

Slide 34 text

#Devoxx @ramtop Writing logs is an adapter concern, not a domain one! - logs all the domain calls - avoid duplicates - consistency and DRY - easier to test

Slide 35

Slide 35 text

#Devoxx @ramtop Thanks! https://www.pexels.com for the pictures If interested follow me on Twitter and Medium (did I mention the book?) Uberto Barbini @ramtop medium.com/@ramtop

Slide 36

Slide 36 text

#Devoxx @ramtop https://twitter.com/search?q=(%23Devoxx)%20(%40ramtop)%20(since%3A2022-10-13)&src=typed_query https://random.org And the winners are: https://pragprog.com/titles/uboop/from-objects-to-functions/