SlideShare a Scribd company logo
1 of 154
Download to read offline
Railway Oriented Programming
A functional approach to error handling
What do railways
have to do with
programming?
Railway Oriented Programming
A functional approach to error handling
Scott Wlaschin
@ScottWlaschin
fsharpforfunandprofit.com
FPbridge.co.uk...but OCaml and Haskell
are very similar.
Examples will be
in F#...
Overview
Topics covered:
• Happy path programming
• Straying from the happy path
• Introducing "Railway Oriented Programming"
• Using the model in practice
• Extending and improving the design
Happy path programming
Implementing a simple use case
A simple use case
Receive request
Validate and canonicalize request
Update existing user record
Send verification email
Return result to user
type Request = {
userId: int;
name: string;
email: string }
"As a user I want to update my name and email address"
Imperative code
string ExecuteUseCase()
{
var request = receiveRequest();
validateRequest(request);
canonicalizeEmail(request);
db.updateDbFromRequest(request);
smtpServer.sendEmail(request.Email);
return "Success";
}
Functional flow
let executeUseCase =
receiveRequest
>> validateRequest
>> canonicalizeEmail
>> updateDbFromRequest
>> sendEmail
>> returnMessage
F# left-to-right
composition operator
Straying from the happy path...
What do you do when
something goes wrong?
Straying from the happy path
“A program is a spell cast over a
computer, turning input
into error messages”
“A program is a spell cast over a
computer, turning input
into error messages”
Straying from the happy path
Name is blank
Email not valid
Receive request
Validate and canonicalize request
Update existing user record
Send verification email
Return result to user
User not found
Db error
Authorization error
Timeout
"As a user I want to update my name and email address"
type Request = {
userId: int;
name: string;
email: string }
- and see sensible error messages when something goes wrong!
Imperative code with error handling
string UpdateCustomerWithErrorHandling()
{
var request = receiveRequest();
validateRequest(request);
canonicalizeEmail(request);
db.updateDbFromRequest(request);
smtpServer.sendEmail(request.Email)
return "OK";
}
Imperative code with error handling
string UpdateCustomerWithErrorHandling()
{
var request = receiveRequest();
var isValidated = validateRequest(request);
if (!isValidated) {
return "Request is not valid"
}
canonicalizeEmail(request);
db.updateDbFromRequest(request);
smtpServer.sendEmail(request.Email)
return "OK";
}
Imperative code with error handling
string UpdateCustomerWithErrorHandling()
{
var request = receiveRequest();
var isValidated = validateRequest(request);
if (!isValidated) {
return "Request is not valid"
}
canonicalizeEmail(request);
var result = db.updateDbFromRequest(request);
if (!result) {
return "Customer record not found"
}
smtpServer.sendEmail(request.Email)
return "OK";
}
Imperative code with error handling
string UpdateCustomerWithErrorHandling()
{
var request = receiveRequest();
var isValidated = validateRequest(request);
if (!isValidated) {
return "Request is not valid"
}
canonicalizeEmail(request);
try {
var result = db.updateDbFromRequest(request);
if (!result) {
return "Customer record not found"
}
} catch {
return "DB error: Customer record not updated"
}
smtpServer.sendEmail(request.Email)
return "OK";
}
Imperative code with error handling
string UpdateCustomerWithErrorHandling()
{
var request = receiveRequest();
var isValidated = validateRequest(request);
if (!isValidated) {
return "Request is not valid"
}
canonicalizeEmail(request);
try {
var result = db.updateDbFromRequest(request);
if (!result) {
return "Customer record not found"
}
} catch {
return "DB error: Customer record not updated"
}
if (!smtpServer.sendEmail(request.Email)) {
log.Error "Customer email not sent"
}
return "OK";
}
Imperative code with error handling
string UpdateCustomerWithErrorHandling()
{
var request = receiveRequest();
var isValidated = validateRequest(request);
if (!isValidated) {
return "Request is not valid"
}
canonicalizeEmail(request);
try {
var result = db.updateDbFromRequest(request);
if (!result) {
return "Customer record not found"
}
} catch {
return "DB error: Customer record not updated"
}
if (!smtpServer.sendEmail(request.Email)) {
log.Error "Customer email not sent"
}
return "OK";
}
Functional flow with error handling
let updateCustomer =
receiveRequest
>> validateRequest
>> canonicalizeEmail
>> updateDbFromRequest
>> sendEmail
>> returnMessage
Before
Functional flow with error handling
let updateCustomerWithErrorHandling =
receiveRequest
>> validateRequest
>> canonicalizeEmail
>> updateDbFromRequest
>> sendEmail
>> returnMessage
Does this look familiar?
Don't believe me? Stay tuned!
After
Designing the unhappy path
Request/response (non-functional) design
Request
Response
Validate Update Send
Request handling service
Request
Response
Validate Update Send
Request handling service
Request
Errors
Response
Validate Update Send
Request handling service
Imperative code can return early
Data flow (functional) design
ResponseValidate Update SendA single function representing the use caseRequest
Request ResponseValidate Update Send
A single function representing the use case
Request
Errors
Success
Response
Validate Update Send
Error
Response
A single function representing the use case
Q: How can you bypass downstream
functions when an error happens?
Functional design
How can a function have more than one output?
type Result =
| Success
| ValidationError
| UpdateError
| SmtpError
Request
Errors
SuccessValidate Update Send
Failure
A single function representing the use case
Functional design
How can a function have more than one output?
type Result =
| Success
| Failure
Request
Errors
SuccessValidate Update Send
Failure
A single function representing the use case
Functional design
How can a function have more than one output?
type Result<'TEntity> =
| Success of 'TEntity
| Failure of string
Request
Errors
SuccessValidate Update Send
Failure
A single function representing the use case
Functional design
Request
Errors
SuccessValidate Update Send
Failure
A single function representing the use case
• Each use case will be equivalent to a single function
• The function will return a sum type with two cases:
"Success" and "Failure".
• The use case function will be built from a series of smaller functions,
each representing one step in a data flow.
• The errors from each step will be combined into a single "failure" path.
How do I work with errors
in a functional way?
Monad dialog
v
Monads are confusing?
Monads are not really that confusing
http://bit.ly/monad-paper
Railway oriented programming
This has absolutely nothing to do with monads.
A railway track analogy
The Tunnel of
Transformation
Function
apple -> banana
A railway track analogy
Function 1
apple -> banana
Function 2
banana -> cherry
A railway track analogy
>>
Function 1
apple -> banana
Function 2
banana -> cherry
A railway track analogy
New Function
apple -> cherry
Can't tell it was built from
smaller functions!
An error generating function
Request SuccessValidate
Failure
let validateInput input =
if input.name = "" then
Failure "Name must not be blank"
else if input.email = "" then
Failure "Email must not be blank"
else
Success input // happy path
Introducing switches
Success!
Failure
Input ->
Connecting switches
Validate UpdateDbon success
bypass
Connecting switches
Validate UpdateDb
Connecting switches
Validate UpdateDb SendEmail
Connecting switches
Validate UpdateDb SendEmail
The two-track model in practice
Composing switches
Validate UpdateDb SendEmail
Composing switches
Validate UpdateDb SendEmail
Composing switches
Validate UpdateDb SendEmail
>> >>
Composing one-track functions is fine...
Composing switches
Validate UpdateDb SendEmail
>> >>
... and composing two-track functions is fine...
Composing switches
Validate UpdateDb SendEmail
 
... but composing switches is not allowed!
Composing switches
Validate
Two-track input Two-track input
Validate
One-track input Two-track input


Building an adapter block
Two-track input
Slot for switch function
Two-track output
Building an adapter block
Two-track input Two-track output
Validate Validate
let adapt switchFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
Building an adapter block
Two-track input Two-track output
let adapt switchFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
Building an adapter block
Two-track input Two-track output
let adapt switchFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
Building an adapter block
Two-track input Two-track output
let adapt switchFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
Building an adapter block
Two-track input Two-track output
Bind as an adapter block
Two-track input Two-track output
let bind switchFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
bind : ('a -> TwoTrack<'b>) -> TwoTrack<'a> -> TwoTrack<'b>
Bind as an adapter block
Two-track input Two-track output
let bind switchFunction twoTrackInput =
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
bind : ('a -> TwoTrack<'b>) -> TwoTrack<'a> -> TwoTrack<'b>
name50
Bind example
let nameNotBlank input =
if input.name = "" then
Failure "Name must not be blank"
else Success input
let name50 input =
if input.name.Length > 50 then
Failure "Name must not be longer than 50 chars"
else Success input
let emailNotBlank input =
if input.email = "" then
Failure "Email must not be blank"
else Success input
nameNotBlank
emailNotBlank
Bind example
nameNotBlank (combined with)
name50 (combined with)
emailNotBlank
nameNotBlank name50 emailNotBlank
Bind example
bind nameNotBlank
bind name50
bind emailNotBlank
nameNotBlank name50 emailNotBlank
Bind example
bind nameNotBlank
>> bind name50
>> bind emailNotBlank
nameNotBlank name50 emailNotBlank
Bind example
let validateRequest =
bind nameNotBlank
>> bind name50
>> bind emailNotBlank
// validateRequest : TwoTrack<Request> -> TwoTrack<Request>
validateRequest
Bind example
let (>>=) twoTrackInput switchFunction =
bind switchFunction twoTrackInput
let validateRequest twoTrackInput =
twoTrackInput
>>= nameNotBlank
>>= name50
>>= emailNotBlank
validateRequest
Bind doesn't stop transformations
FunctionB
type TwoTrack<'TEntity> =
| Success of 'TEntity
| Failure of string
FunctionA
Composing switches - review
Validate UpdateDb SendEmail
Validate UpdateDb SendEmail
Comic Interlude
What do you call a
train that eats toffee? I don't know, what do you
call a train that eats toffee?
A chew,
chew train!
More fun with railway tracks...
Working with other functions
More fun with railway tracks...
Fitting other functions into this framework:
• Single track functions
• Dead-end functions
• Functions that throw exceptions
• Supervisory functions
Converting one-track functions
Fitting other functions into this framework:
• Single track functions
• Dead-end functions
• Functions that throw exceptions
• Supervisory functions
Converting one-track functions
// trim spaces and lowercase
let canonicalizeEmail input =
{ input with email = input.email.Trim().ToLower() }
canonicalizeEmail
Converting one-track functions
UpdateDb SendEmailValidate
canonicalizeEmail
Won't compose
Converting one-track functions
Two-track input
Slot for one-track function
Two-track output
Converting one-track functions
Two-track input Two-track output
CanonicalizeCanonicalize
Converting one-track functions
Two-track input Two-track output
let map singleTrackFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> Success (singleTrackFunction s)
| Failure f -> Failure f
map : ('a -> 'b) -> TwoTrack<'a> -> TwoTrack<'b>`
Single track
function
Converting one-track functions
Two-track input Two-track output
let map singleTrackFunction =
bind (singleTrackFunction >> Success)
map : ('a -> 'b) -> TwoTrack<'a> -> TwoTrack<'b>
Single track
function
Converting one-track functions
UpdateDb SendEmailValidate
canonicalizeEmail
Will compose
Converting dead-end functions
Fitting other functions into this framework:
• Single track functions
• Dead-end functions
• Functions that throw exceptions
• Supervisory functions
Converting dead-end functions
let updateDb request =
// do something
// return nothing at all
updateDb
Converting dead-end functions
SendEmailValidate UpdateDb
Won't compose
Converting dead-end functions
One-track input
Slot for dead end function
One-track output
Converting dead-end functions
One-track input One-track output
let tee deadEndFunction oneTrackInput =
deadEndFunction oneTrackInput
oneTrackInput
tee : ('a -> unit) -> 'a -> 'a
Dead end
function
Converting dead-end functions
SendEmailValidate
UpdateDb
Will compose
Functions that throw exceptions
Fitting other functions into this framework:
• Single track functions
• Dead-end functions
• Functions that throw exceptions
• Supervisory functions
Functions that throw exceptions
One-track input
Two-track output
SendEmail SendEmail
Add try/catch to
handle timeouts, say
Looks innocent, but might
throw an exception
Functions that throw exceptions
EvenYoda recommends
not to use exception
handling for control flow:
Guideline: Convert exceptions into Failures
"Do or do not, there is
no try".
Supervisory functions
Fitting other functions into this framework:
• Single track functions
• Dead-end functions
• Functions that throw exceptions
• Supervisory functions
Supervisory functions
Two-track input Two-track output
Slot for one-track function for
Success case
Slot for one-track function for
Failure case
Putting it all together
Putting it all together
Validate
UpdateDb
SendEmail
Canonicalize
Input
Output??
Putting it all together
Validate
UpdateDb
SendEmail
Canonicalize
returnMessage
Input Output
let returnMessage result =
match result with
| Success obj -> OK obj.ToJson()
| Failure msg -> BadRequest msg
Putting it all together - review
The "two-track" framework is a useful approach for
most use-cases.
You can fit most functions into
this model.
Putting it all together - review
The "two-track" framework is a useful approach for
most use-cases.
let updateCustomer =
receiveRequest
>> validateRequest
>> updateDbFromRequest
>> sendEmail
>> returnMessage
let updateCustomer =
receiveRequest
>> validateRequest
>> updateDbFromRequest
>> sendEmail
>> returnMessage
Let's look at the code -- before and after adding error handling
Comic Interlude
Why can't a steam
locomotive sit down?
I don't know,
why can't a steam
locomotive sit down?
Because it
has a tender
behind!
Extending the framework:
• Designing for errors
• Parallel tracks
• Domain events
Designing for errors
Unhappy paths are requirements too
Designing for errors
let validateInput input =
if input.name = "" then
Failure "Name must not be blank"
else if input.email = "" then
Failure "Email must not be blank"
else
Success input // happy path
type TwoTrack<'TEntity> =
| Success of 'TEntity
| Failure of string
Using strings is not good
Designing for errors
let validateInput input =
if input.name = "" then
Failure NameMustNotBeBlank
else if input.email = "" then
Failure EmailMustNotBeBlank
else
Success input // happy path
type TwoTrack<'TEntity> =
| Success of 'TEntity
| Failure of ErrorMessage
type ErrorMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
Special type rather
than string
Designing for errors
let validateInput input =
if input.name = "" then
Failure NameMustNotBeBlank
else if input.email = "" then
Failure EmailMustNotBeBlank
else if (input.email doesn't match regex) then
Failure EmailNotValid input.email
else
Success input // happy path
type ErrorMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
Add invalid
email as data
Designing for errors
type ErrorMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
// database errors
| UserIdNotValid of UserId
| DbUserNotFoundError of UserId
| DbTimeout of ConnectionString
| DbConcurrencyError
| DbAuthorizationError of ConnectionString * Credentials
// SMTP errors
| SmtpTimeout of SmtpConnection
| SmtpBadRecipient of EmailAddress
Documentation of everything
that can go wrong --
And it's type-safe
documentation that can't go
out of date!
Designing for errors
type ErrorMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
// database errors
| UserIdNotValid of UserId
| DbUserNotFoundError of UserId
| DbTimeout of ConnectionString
| DbConcurrencyError
| DbAuthorizationError of ConnectionString * Credentials
// SMTP errors
| SmtpTimeout of SmtpConnection
| SmtpBadRecipient of EmailAddress
Documentation of everything
that can go wrong --
And it's type-safe
documentation that can't go
out of date!
As we develop the code, we can build up a complete
list of everything that could go wrong
Designing for errors – converting to strings
No longer works – each case must now
be explicitly converted to a string
returnMessage
let returnMessage result =
match result with
| Success _ -> "Success"
| Failure msg -> msg
Designing for errors – converting to strings
let returnMessage result =
match result with
| Success _ -> "Success"
| Failure err ->
match err with
| NameMustNotBeBlank -> "Name must not be blank"
| EmailMustNotBeBlank -> "Email must not be blank"
| EmailNotValid (EmailAddress email) ->
sprintf "Email %s is not valid" email
// database errors
| UserIdNotValid (UserId id) ->
sprintf "User id %i is not a valid user id" id
| DbUserNotFoundError (UserId id) ->
sprintf "User id %i was not found in the database" id
| DbTimeout (_,TimeoutMs ms) ->
sprintf "Could not connect to database within %i ms" ms
| DbConcurrencyError ->
sprintf "Another user has modified the record. Please resubmit"
| DbAuthorizationError _ ->
sprintf "You do not have permission to access the database"
// SMTP errors
| SmtpTimeout (_,TimeoutMs ms) ->
sprintf "Could not connect to SMTP server within %i ms" ms
| SmtpBadRecipient (EmailAddress email) ->
sprintf "The email %s is not a valid recipient" email
Each case must be converted to a
string – but this is only needed
once, and only at the last step.
All strings are in one place,
so translations are easier.
returnMessage
(or use resource file)
Designing for errors - review
type ErrorMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
// database errors
| UserIdNotValid of UserId
| DbUserNotFoundError of UserId
| DbTimeout of ConnectionString
| DbConcurrencyError
| DbAuthorizationError of ConnectionString * Credentials
// SMTP errors
| SmtpTimeout of SmtpConnection
| SmtpBadRecipient of EmailAddress
Documentation of everything that
can go wrong.
Type-safe -- can't go out of date!
Parallel tracks
Parallel validation
nameNotBlank name50 emailNotBlank
Problem: Validation done in series.
So only one error at a time is returned
Parallel validation
nameNotBlank
name50
emailNotBlank
Split
input
Combine
output
Now we do get all
errors at once!
... But how to combine?
Combining switches
+
Trick: if we create an operation that combines pairs
into a new switch, we can repeat to combine as many
switches as we like.
Combining switches
+
+
Trick: if we create an operation that combines pairs
into a new switch, we can repeat to combine as many
switches as we like.
Combining switches
+
+
Trick: if we create an operation that combines pairs
into a new switch, we can repeat to combine as many
switches as we like.
Domain events
Communicating information to
downstream functions
Events are not errors
Validate
UpdateDb
SendEmail
Tell CRM that
email was sent
Events are not errors
Validate
UpdateDb
SendEmail
Tell CRM that
email was sent
type MyUseCaseMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
// database errors
| UserIdNotValid of UserId
// SMTP errors
| SmtpTimeout of SmtpConnection
// Domain events
| UserSaved of AuditInfo
| EmailSent of EmailAddress * MsgId
Events are not errors
Validate
UpdateDb
SendEmail
Tell CRM that
email was sent
type MyUseCaseMessage =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
// database errors
| UserIdNotValid of UserId
// SMTP errors
| SmtpTimeout of SmtpConnection
// Domain events
| UserSaved of AuditInfo
| EmailSent of EmailAddress * MsgId
type TwoTrack<'TEntity> =
| Success of 'TEntity * Message list
| Failure of Message list
Comic Interlude
Why can't a train
driver be electrocuted? I don't know,
why can't a train driver
be electrocuted?
Because he's not
a conductor!
Some topics not covered...
... but could be handled
in an obvious way.
Topics not covered
• Errors across service boundaries
• Async on success path (instead of sync)
• Compensating transactions
(instead of two phase commit)
• Logging (tracing, app events, etc.)
Summary
A recipe for handling errors in a
functional way
Recipe for handling errors in a functional way
type TwoTrack<'TEntity> =
| Success of 'TEntity * Message list
| Failure of Message list
Validate UpdateDb SendEmail
type Message =
| NameMustNotBeBlank
| EmailMustNotBeBlank
| EmailNotValid of EmailAddress
Demo
http://bit.ly/rop-example
I don’t always have errors...
I don’t always have errors...
Railway Oriented Programming
@ScottWlaschin
fsharpforfunandprofit.com
FPbridge.co.uk
http://bit.ly/rop-example

More Related Content

What's hot

Clean code slide
Clean code slideClean code slide
Clean code slideAnh Huan Miu
 
The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)Scott Wlaschin
 
Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Scott Wlaschin
 
Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)Scott Wlaschin
 
The Functional Programmer's Toolkit (NDC London 2019)
The Functional Programmer's Toolkit (NDC London 2019)The Functional Programmer's Toolkit (NDC London 2019)
The Functional Programmer's Toolkit (NDC London 2019)Scott Wlaschin
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Chris Richardson
 
Railway Orientated Programming In C#
Railway Orientated Programming In C#Railway Orientated Programming In C#
Railway Orientated Programming In C#Tama000
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APIMario Fusco
 
Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Roman Elizarov
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code SmellsMario Sangiorgio
 
From object oriented to functional domain modeling
From object oriented to functional domain modelingFrom object oriented to functional domain modeling
From object oriented to functional domain modelingMario Fusco
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8RichardWarburton
 
Advanced Javascript
Advanced JavascriptAdvanced Javascript
Advanced JavascriptAdieu
 
PHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsPHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsGuilherme Blanco
 
Clean Code I - Best Practices
Clean Code I - Best PracticesClean Code I - Best Practices
Clean Code I - Best PracticesTheo Jungeblut
 

What's hot (20)

Clean code
Clean codeClean code
Clean code
 
Clean code slide
Clean code slideClean code slide
Clean code slide
 
The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)
 
Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)
 
Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)
 
JavaScript Promises
JavaScript PromisesJavaScript Promises
JavaScript Promises
 
The Functional Programmer's Toolkit (NDC London 2019)
The Functional Programmer's Toolkit (NDC London 2019)The Functional Programmer's Toolkit (NDC London 2019)
The Functional Programmer's Toolkit (NDC London 2019)
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
 
Railway Orientated Programming In C#
Railway Orientated Programming In C#Railway Orientated Programming In C#
Railway Orientated Programming In C#
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java API
 
Clean code
Clean codeClean code
Clean code
 
Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017Deep dive into Coroutines on JVM @ KotlinConf 2017
Deep dive into Coroutines on JVM @ KotlinConf 2017
 
Clean Code
Clean CodeClean Code
Clean Code
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code Smells
 
From object oriented to functional domain modeling
From object oriented to functional domain modelingFrom object oriented to functional domain modeling
From object oriented to functional domain modeling
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
 
Advanced Javascript
Advanced JavascriptAdvanced Javascript
Advanced Javascript
 
PHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsPHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object Calisthenics
 
jQuery PPT
jQuery PPTjQuery PPT
jQuery PPT
 
Clean Code I - Best Practices
Clean Code I - Best PracticesClean Code I - Best Practices
Clean Code I - Best Practices
 

Similar to Railway Oriented Programming

Patterns and practices for real-world event-driven microservices by Rachel Re...
Patterns and practices for real-world event-driven microservices by Rachel Re...Patterns and practices for real-world event-driven microservices by Rachel Re...
Patterns and practices for real-world event-driven microservices by Rachel Re...Codemotion Dubai
 
Patterns and practices for real-world event-driven microservices
Patterns and practices for real-world event-driven microservicesPatterns and practices for real-world event-driven microservices
Patterns and practices for real-world event-driven microservicesRachel Reese
 
Build your website with angularjs and web apis
Build your website with angularjs and web apisBuild your website with angularjs and web apis
Build your website with angularjs and web apisChalermpon Areepong
 
A First Date With Scala
A First Date With ScalaA First Date With Scala
A First Date With ScalaFranco Lombardo
 
What's new in Rails 5 - API Mode & Action Cable overview
What's new in Rails 5 - API Mode & Action Cable overviewWhat's new in Rails 5 - API Mode & Action Cable overview
What's new in Rails 5 - API Mode & Action Cable overviewMaxim Veksler
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasminePaulo Ragonha
 
Tdd iPhone For Dummies
Tdd iPhone For DummiesTdd iPhone For Dummies
Tdd iPhone For DummiesGiordano Scalzo
 
EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2
EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2
EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2Rob Tweed
 
TPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxTPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxJirat Kijlerdpornpailoj
 
One does not simply "Upgrade to Rails 3"
One does not simply "Upgrade to Rails 3"One does not simply "Upgrade to Rails 3"
One does not simply "Upgrade to Rails 3"testflyjets
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 
Working with Javascript in Rails
Working with Javascript in RailsWorking with Javascript in Rails
Working with Javascript in RailsSeungkyun Nam
 
Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0Claire Townend Gee
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 
Good karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaGood karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaExoLeaders.com
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSam Brannen
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training Patrick Schroeder
 
Exploring Angular 2 - Episode 2
Exploring Angular 2 - Episode 2Exploring Angular 2 - Episode 2
Exploring Angular 2 - Episode 2Ahmed Moawad
 

Similar to Railway Oriented Programming (20)

Patterns and practices for real-world event-driven microservices by Rachel Re...
Patterns and practices for real-world event-driven microservices by Rachel Re...Patterns and practices for real-world event-driven microservices by Rachel Re...
Patterns and practices for real-world event-driven microservices by Rachel Re...
 
Patterns and practices for real-world event-driven microservices
Patterns and practices for real-world event-driven microservicesPatterns and practices for real-world event-driven microservices
Patterns and practices for real-world event-driven microservices
 
Build your website with angularjs and web apis
Build your website with angularjs and web apisBuild your website with angularjs and web apis
Build your website with angularjs and web apis
 
A First Date With Scala
A First Date With ScalaA First Date With Scala
A First Date With Scala
 
What's new in Rails 5 - API Mode & Action Cable overview
What's new in Rails 5 - API Mode & Action Cable overviewWhat's new in Rails 5 - API Mode & Action Cable overview
What's new in Rails 5 - API Mode & Action Cable overview
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
Tdd iPhone For Dummies
Tdd iPhone For DummiesTdd iPhone For Dummies
Tdd iPhone For Dummies
 
EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2
EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2
EWD 3 Training Course Part 38: Building a React.js application with QEWD, Part 2
 
Structure on a freeform world
Structure on a freeform worldStructure on a freeform world
Structure on a freeform world
 
TPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxTPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and Flux
 
One does not simply "Upgrade to Rails 3"
One does not simply "Upgrade to Rails 3"One does not simply "Upgrade to Rails 3"
One does not simply "Upgrade to Rails 3"
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
Working with Javascript in Rails
Working with Javascript in RailsWorking with Javascript in Rails
Working with Javascript in Rails
 
Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
Good karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaGood karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with Karma
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. REST
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training
 
Cocoa heads 09112017
Cocoa heads 09112017Cocoa heads 09112017
Cocoa heads 09112017
 
Exploring Angular 2 - Episode 2
Exploring Angular 2 - Episode 2Exploring Angular 2 - Episode 2
Exploring Angular 2 - Episode 2
 

More from Scott Wlaschin

Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)Scott Wlaschin
 
Building confidence in concurrent code with a model checker: TLA+ for program...
Building confidence in concurrent code with a model checker: TLA+ for program...Building confidence in concurrent code with a model checker: TLA+ for program...
Building confidence in concurrent code with a model checker: TLA+ for program...Scott Wlaschin
 
Reinventing the Transaction Script (NDC London 2020)
Reinventing the Transaction Script (NDC London 2020)Reinventing the Transaction Script (NDC London 2020)
Reinventing the Transaction Script (NDC London 2020)Scott Wlaschin
 
The Power Of Composition (DotNext 2019)
The Power Of Composition (DotNext 2019)The Power Of Composition (DotNext 2019)
The Power Of Composition (DotNext 2019)Scott Wlaschin
 
Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)Scott Wlaschin
 
Four Languages From Forty Years Ago (NewCrafts 2019)
Four Languages From Forty Years Ago (NewCrafts 2019)Four Languages From Forty Years Ago (NewCrafts 2019)
Four Languages From Forty Years Ago (NewCrafts 2019)Scott Wlaschin
 
Four Languages From Forty Years Ago
Four Languages From Forty Years AgoFour Languages From Forty Years Ago
Four Languages From Forty Years AgoScott Wlaschin
 
F# for C# Programmers
F# for C# ProgrammersF# for C# Programmers
F# for C# ProgrammersScott Wlaschin
 
Designing with capabilities (DDD-EU 2017)
Designing with capabilities (DDD-EU 2017)Designing with capabilities (DDD-EU 2017)
Designing with capabilities (DDD-EU 2017)Scott Wlaschin
 
Thirteen ways of looking at a turtle
Thirteen ways of looking at a turtleThirteen ways of looking at a turtle
Thirteen ways of looking at a turtleScott Wlaschin
 
Designing with Capabilities
Designing with CapabilitiesDesigning with Capabilities
Designing with CapabilitiesScott Wlaschin
 
Dr Frankenfunctor and the Monadster
Dr Frankenfunctor and the MonadsterDr Frankenfunctor and the Monadster
Dr Frankenfunctor and the MonadsterScott Wlaschin
 
Enterprise Tic-Tac-Toe
Enterprise Tic-Tac-ToeEnterprise Tic-Tac-Toe
Enterprise Tic-Tac-ToeScott Wlaschin
 
An introduction to property based testing
An introduction to property based testingAn introduction to property based testing
An introduction to property based testingScott Wlaschin
 
Swift vs. Language X
Swift vs. Language XSwift vs. Language X
Swift vs. Language XScott Wlaschin
 
Doge-driven design
Doge-driven designDoge-driven design
Doge-driven designScott Wlaschin
 
The Theory of Chains
The Theory of ChainsThe Theory of Chains
The Theory of ChainsScott Wlaschin
 

More from Scott Wlaschin (17)

Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)
 
Building confidence in concurrent code with a model checker: TLA+ for program...
Building confidence in concurrent code with a model checker: TLA+ for program...Building confidence in concurrent code with a model checker: TLA+ for program...
Building confidence in concurrent code with a model checker: TLA+ for program...
 
Reinventing the Transaction Script (NDC London 2020)
Reinventing the Transaction Script (NDC London 2020)Reinventing the Transaction Script (NDC London 2020)
Reinventing the Transaction Script (NDC London 2020)
 
The Power Of Composition (DotNext 2019)
The Power Of Composition (DotNext 2019)The Power Of Composition (DotNext 2019)
The Power Of Composition (DotNext 2019)
 
Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)
 
Four Languages From Forty Years Ago (NewCrafts 2019)
Four Languages From Forty Years Ago (NewCrafts 2019)Four Languages From Forty Years Ago (NewCrafts 2019)
Four Languages From Forty Years Ago (NewCrafts 2019)
 
Four Languages From Forty Years Ago
Four Languages From Forty Years AgoFour Languages From Forty Years Ago
Four Languages From Forty Years Ago
 
F# for C# Programmers
F# for C# ProgrammersF# for C# Programmers
F# for C# Programmers
 
Designing with capabilities (DDD-EU 2017)
Designing with capabilities (DDD-EU 2017)Designing with capabilities (DDD-EU 2017)
Designing with capabilities (DDD-EU 2017)
 
Thirteen ways of looking at a turtle
Thirteen ways of looking at a turtleThirteen ways of looking at a turtle
Thirteen ways of looking at a turtle
 
Designing with Capabilities
Designing with CapabilitiesDesigning with Capabilities
Designing with Capabilities
 
Dr Frankenfunctor and the Monadster
Dr Frankenfunctor and the MonadsterDr Frankenfunctor and the Monadster
Dr Frankenfunctor and the Monadster
 
Enterprise Tic-Tac-Toe
Enterprise Tic-Tac-ToeEnterprise Tic-Tac-Toe
Enterprise Tic-Tac-Toe
 
An introduction to property based testing
An introduction to property based testingAn introduction to property based testing
An introduction to property based testing
 
Swift vs. Language X
Swift vs. Language XSwift vs. Language X
Swift vs. Language X
 
Doge-driven design
Doge-driven designDoge-driven design
Doge-driven design
 
The Theory of Chains
The Theory of ChainsThe Theory of Chains
The Theory of Chains
 

Recently uploaded

Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 

Recently uploaded (20)

Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 

Railway Oriented Programming

  • 1. Railway Oriented Programming A functional approach to error handling What do railways have to do with programming?
  • 2. Railway Oriented Programming A functional approach to error handling Scott Wlaschin @ScottWlaschin fsharpforfunandprofit.com FPbridge.co.uk...but OCaml and Haskell are very similar. Examples will be in F#...
  • 3. Overview Topics covered: • Happy path programming • Straying from the happy path • Introducing "Railway Oriented Programming" • Using the model in practice • Extending and improving the design
  • 5. A simple use case Receive request Validate and canonicalize request Update existing user record Send verification email Return result to user type Request = { userId: int; name: string; email: string } "As a user I want to update my name and email address"
  • 6. Imperative code string ExecuteUseCase() { var request = receiveRequest(); validateRequest(request); canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email); return "Success"; }
  • 7. Functional flow let executeUseCase = receiveRequest >> validateRequest >> canonicalizeEmail >> updateDbFromRequest >> sendEmail >> returnMessage F# left-to-right composition operator
  • 8. Straying from the happy path... What do you do when something goes wrong?
  • 9. Straying from the happy path
  • 10. “A program is a spell cast over a computer, turning input into error messages”
  • 11. “A program is a spell cast over a computer, turning input into error messages”
  • 12. Straying from the happy path Name is blank Email not valid Receive request Validate and canonicalize request Update existing user record Send verification email Return result to user User not found Db error Authorization error Timeout "As a user I want to update my name and email address" type Request = { userId: int; name: string; email: string } - and see sensible error messages when something goes wrong!
  • 13. Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); validateRequest(request); canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }
  • 14. Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }
  • 15. Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } smtpServer.sendEmail(request.Email) return "OK"; }
  • 16. Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } smtpServer.sendEmail(request.Email) return "OK"; }
  • 17. Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } if (!smtpServer.sendEmail(request.Email)) { log.Error "Customer email not sent" } return "OK"; }
  • 18. Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } if (!smtpServer.sendEmail(request.Email)) { log.Error "Customer email not sent" } return "OK"; }
  • 19. Functional flow with error handling let updateCustomer = receiveRequest >> validateRequest >> canonicalizeEmail >> updateDbFromRequest >> sendEmail >> returnMessage Before
  • 20. Functional flow with error handling let updateCustomerWithErrorHandling = receiveRequest >> validateRequest >> canonicalizeEmail >> updateDbFromRequest >> sendEmail >> returnMessage Does this look familiar? Don't believe me? Stay tuned! After
  • 22. Request/response (non-functional) design Request Response Validate Update Send Request handling service Request Response Validate Update Send Request handling service Request Errors Response Validate Update Send Request handling service Imperative code can return early
  • 23. Data flow (functional) design ResponseValidate Update SendA single function representing the use caseRequest Request ResponseValidate Update Send A single function representing the use case Request Errors Success Response Validate Update Send Error Response A single function representing the use case Q: How can you bypass downstream functions when an error happens?
  • 24. Functional design How can a function have more than one output? type Result = | Success | ValidationError | UpdateError | SmtpError Request Errors SuccessValidate Update Send Failure A single function representing the use case
  • 25. Functional design How can a function have more than one output? type Result = | Success | Failure Request Errors SuccessValidate Update Send Failure A single function representing the use case
  • 26. Functional design How can a function have more than one output? type Result<'TEntity> = | Success of 'TEntity | Failure of string Request Errors SuccessValidate Update Send Failure A single function representing the use case
  • 27. Functional design Request Errors SuccessValidate Update Send Failure A single function representing the use case • Each use case will be equivalent to a single function • The function will return a sum type with two cases: "Success" and "Failure". • The use case function will be built from a series of smaller functions, each representing one step in a data flow. • The errors from each step will be combined into a single "failure" path.
  • 28. How do I work with errors in a functional way?
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48. v
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64. Monads are confusing? Monads are not really that confusing http://bit.ly/monad-paper
  • 65. Railway oriented programming This has absolutely nothing to do with monads.
  • 66. A railway track analogy The Tunnel of Transformation Function apple -> banana
  • 67. A railway track analogy Function 1 apple -> banana Function 2 banana -> cherry
  • 68. A railway track analogy >> Function 1 apple -> banana Function 2 banana -> cherry
  • 69. A railway track analogy New Function apple -> cherry Can't tell it was built from smaller functions!
  • 70. An error generating function Request SuccessValidate Failure let validateInput input = if input.name = "" then Failure "Name must not be blank" else if input.email = "" then Failure "Email must not be blank" else Success input // happy path
  • 76. The two-track model in practice
  • 79. Composing switches Validate UpdateDb SendEmail >> >> Composing one-track functions is fine...
  • 80. Composing switches Validate UpdateDb SendEmail >> >> ... and composing two-track functions is fine...
  • 81. Composing switches Validate UpdateDb SendEmail   ... but composing switches is not allowed!
  • 82. Composing switches Validate Two-track input Two-track input Validate One-track input Two-track input  
  • 83. Building an adapter block Two-track input Slot for switch function Two-track output
  • 84. Building an adapter block Two-track input Two-track output Validate Validate
  • 85. let adapt switchFunction = fun twoTrackInput -> match twoTrackInput with | Success s -> switchFunction s | Failure f -> Failure f Building an adapter block Two-track input Two-track output
  • 86. let adapt switchFunction = fun twoTrackInput -> match twoTrackInput with | Success s -> switchFunction s | Failure f -> Failure f Building an adapter block Two-track input Two-track output
  • 87. let adapt switchFunction = fun twoTrackInput -> match twoTrackInput with | Success s -> switchFunction s | Failure f -> Failure f Building an adapter block Two-track input Two-track output
  • 88. let adapt switchFunction = fun twoTrackInput -> match twoTrackInput with | Success s -> switchFunction s | Failure f -> Failure f Building an adapter block Two-track input Two-track output
  • 89. Bind as an adapter block Two-track input Two-track output let bind switchFunction = fun twoTrackInput -> match twoTrackInput with | Success s -> switchFunction s | Failure f -> Failure f bind : ('a -> TwoTrack<'b>) -> TwoTrack<'a> -> TwoTrack<'b>
  • 90. Bind as an adapter block Two-track input Two-track output let bind switchFunction twoTrackInput = match twoTrackInput with | Success s -> switchFunction s | Failure f -> Failure f bind : ('a -> TwoTrack<'b>) -> TwoTrack<'a> -> TwoTrack<'b>
  • 91. name50 Bind example let nameNotBlank input = if input.name = "" then Failure "Name must not be blank" else Success input let name50 input = if input.name.Length > 50 then Failure "Name must not be longer than 50 chars" else Success input let emailNotBlank input = if input.email = "" then Failure "Email must not be blank" else Success input nameNotBlank emailNotBlank
  • 92. Bind example nameNotBlank (combined with) name50 (combined with) emailNotBlank nameNotBlank name50 emailNotBlank
  • 93. Bind example bind nameNotBlank bind name50 bind emailNotBlank nameNotBlank name50 emailNotBlank
  • 94. Bind example bind nameNotBlank >> bind name50 >> bind emailNotBlank nameNotBlank name50 emailNotBlank
  • 95. Bind example let validateRequest = bind nameNotBlank >> bind name50 >> bind emailNotBlank // validateRequest : TwoTrack<Request> -> TwoTrack<Request> validateRequest
  • 96. Bind example let (>>=) twoTrackInput switchFunction = bind switchFunction twoTrackInput let validateRequest twoTrackInput = twoTrackInput >>= nameNotBlank >>= name50 >>= emailNotBlank validateRequest
  • 97. Bind doesn't stop transformations FunctionB type TwoTrack<'TEntity> = | Success of 'TEntity | Failure of string FunctionA
  • 98. Composing switches - review Validate UpdateDb SendEmail Validate UpdateDb SendEmail
  • 99. Comic Interlude What do you call a train that eats toffee? I don't know, what do you call a train that eats toffee? A chew, chew train!
  • 100. More fun with railway tracks... Working with other functions
  • 101. More fun with railway tracks... Fitting other functions into this framework: • Single track functions • Dead-end functions • Functions that throw exceptions • Supervisory functions
  • 102. Converting one-track functions Fitting other functions into this framework: • Single track functions • Dead-end functions • Functions that throw exceptions • Supervisory functions
  • 103. Converting one-track functions // trim spaces and lowercase let canonicalizeEmail input = { input with email = input.email.Trim().ToLower() } canonicalizeEmail
  • 104. Converting one-track functions UpdateDb SendEmailValidate canonicalizeEmail Won't compose
  • 105. Converting one-track functions Two-track input Slot for one-track function Two-track output
  • 106. Converting one-track functions Two-track input Two-track output CanonicalizeCanonicalize
  • 107. Converting one-track functions Two-track input Two-track output let map singleTrackFunction = fun twoTrackInput -> match twoTrackInput with | Success s -> Success (singleTrackFunction s) | Failure f -> Failure f map : ('a -> 'b) -> TwoTrack<'a> -> TwoTrack<'b>` Single track function
  • 108. Converting one-track functions Two-track input Two-track output let map singleTrackFunction = bind (singleTrackFunction >> Success) map : ('a -> 'b) -> TwoTrack<'a> -> TwoTrack<'b> Single track function
  • 109. Converting one-track functions UpdateDb SendEmailValidate canonicalizeEmail Will compose
  • 110. Converting dead-end functions Fitting other functions into this framework: • Single track functions • Dead-end functions • Functions that throw exceptions • Supervisory functions
  • 111. Converting dead-end functions let updateDb request = // do something // return nothing at all updateDb
  • 112. Converting dead-end functions SendEmailValidate UpdateDb Won't compose
  • 113. Converting dead-end functions One-track input Slot for dead end function One-track output
  • 114. Converting dead-end functions One-track input One-track output let tee deadEndFunction oneTrackInput = deadEndFunction oneTrackInput oneTrackInput tee : ('a -> unit) -> 'a -> 'a Dead end function
  • 116. Functions that throw exceptions Fitting other functions into this framework: • Single track functions • Dead-end functions • Functions that throw exceptions • Supervisory functions
  • 117. Functions that throw exceptions One-track input Two-track output SendEmail SendEmail Add try/catch to handle timeouts, say Looks innocent, but might throw an exception
  • 118. Functions that throw exceptions EvenYoda recommends not to use exception handling for control flow: Guideline: Convert exceptions into Failures "Do or do not, there is no try".
  • 119. Supervisory functions Fitting other functions into this framework: • Single track functions • Dead-end functions • Functions that throw exceptions • Supervisory functions
  • 120. Supervisory functions Two-track input Two-track output Slot for one-track function for Success case Slot for one-track function for Failure case
  • 121. Putting it all together
  • 122. Putting it all together Validate UpdateDb SendEmail Canonicalize Input Output??
  • 123. Putting it all together Validate UpdateDb SendEmail Canonicalize returnMessage Input Output let returnMessage result = match result with | Success obj -> OK obj.ToJson() | Failure msg -> BadRequest msg
  • 124. Putting it all together - review The "two-track" framework is a useful approach for most use-cases. You can fit most functions into this model.
  • 125. Putting it all together - review The "two-track" framework is a useful approach for most use-cases. let updateCustomer = receiveRequest >> validateRequest >> updateDbFromRequest >> sendEmail >> returnMessage let updateCustomer = receiveRequest >> validateRequest >> updateDbFromRequest >> sendEmail >> returnMessage Let's look at the code -- before and after adding error handling
  • 126. Comic Interlude Why can't a steam locomotive sit down? I don't know, why can't a steam locomotive sit down? Because it has a tender behind!
  • 127. Extending the framework: • Designing for errors • Parallel tracks • Domain events
  • 128. Designing for errors Unhappy paths are requirements too
  • 129. Designing for errors let validateInput input = if input.name = "" then Failure "Name must not be blank" else if input.email = "" then Failure "Email must not be blank" else Success input // happy path type TwoTrack<'TEntity> = | Success of 'TEntity | Failure of string Using strings is not good
  • 130. Designing for errors let validateInput input = if input.name = "" then Failure NameMustNotBeBlank else if input.email = "" then Failure EmailMustNotBeBlank else Success input // happy path type TwoTrack<'TEntity> = | Success of 'TEntity | Failure of ErrorMessage type ErrorMessage = | NameMustNotBeBlank | EmailMustNotBeBlank Special type rather than string
  • 131. Designing for errors let validateInput input = if input.name = "" then Failure NameMustNotBeBlank else if input.email = "" then Failure EmailMustNotBeBlank else if (input.email doesn't match regex) then Failure EmailNotValid input.email else Success input // happy path type ErrorMessage = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress Add invalid email as data
  • 132. Designing for errors type ErrorMessage = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress // database errors | UserIdNotValid of UserId | DbUserNotFoundError of UserId | DbTimeout of ConnectionString | DbConcurrencyError | DbAuthorizationError of ConnectionString * Credentials // SMTP errors | SmtpTimeout of SmtpConnection | SmtpBadRecipient of EmailAddress Documentation of everything that can go wrong -- And it's type-safe documentation that can't go out of date!
  • 133. Designing for errors type ErrorMessage = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress // database errors | UserIdNotValid of UserId | DbUserNotFoundError of UserId | DbTimeout of ConnectionString | DbConcurrencyError | DbAuthorizationError of ConnectionString * Credentials // SMTP errors | SmtpTimeout of SmtpConnection | SmtpBadRecipient of EmailAddress Documentation of everything that can go wrong -- And it's type-safe documentation that can't go out of date! As we develop the code, we can build up a complete list of everything that could go wrong
  • 134. Designing for errors – converting to strings No longer works – each case must now be explicitly converted to a string returnMessage let returnMessage result = match result with | Success _ -> "Success" | Failure msg -> msg
  • 135. Designing for errors – converting to strings let returnMessage result = match result with | Success _ -> "Success" | Failure err -> match err with | NameMustNotBeBlank -> "Name must not be blank" | EmailMustNotBeBlank -> "Email must not be blank" | EmailNotValid (EmailAddress email) -> sprintf "Email %s is not valid" email // database errors | UserIdNotValid (UserId id) -> sprintf "User id %i is not a valid user id" id | DbUserNotFoundError (UserId id) -> sprintf "User id %i was not found in the database" id | DbTimeout (_,TimeoutMs ms) -> sprintf "Could not connect to database within %i ms" ms | DbConcurrencyError -> sprintf "Another user has modified the record. Please resubmit" | DbAuthorizationError _ -> sprintf "You do not have permission to access the database" // SMTP errors | SmtpTimeout (_,TimeoutMs ms) -> sprintf "Could not connect to SMTP server within %i ms" ms | SmtpBadRecipient (EmailAddress email) -> sprintf "The email %s is not a valid recipient" email Each case must be converted to a string – but this is only needed once, and only at the last step. All strings are in one place, so translations are easier. returnMessage (or use resource file)
  • 136. Designing for errors - review type ErrorMessage = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress // database errors | UserIdNotValid of UserId | DbUserNotFoundError of UserId | DbTimeout of ConnectionString | DbConcurrencyError | DbAuthorizationError of ConnectionString * Credentials // SMTP errors | SmtpTimeout of SmtpConnection | SmtpBadRecipient of EmailAddress Documentation of everything that can go wrong. Type-safe -- can't go out of date!
  • 138. Parallel validation nameNotBlank name50 emailNotBlank Problem: Validation done in series. So only one error at a time is returned
  • 140. Combining switches + Trick: if we create an operation that combines pairs into a new switch, we can repeat to combine as many switches as we like.
  • 141. Combining switches + + Trick: if we create an operation that combines pairs into a new switch, we can repeat to combine as many switches as we like.
  • 142. Combining switches + + Trick: if we create an operation that combines pairs into a new switch, we can repeat to combine as many switches as we like.
  • 143. Domain events Communicating information to downstream functions
  • 144. Events are not errors Validate UpdateDb SendEmail Tell CRM that email was sent
  • 145. Events are not errors Validate UpdateDb SendEmail Tell CRM that email was sent type MyUseCaseMessage = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress // database errors | UserIdNotValid of UserId // SMTP errors | SmtpTimeout of SmtpConnection // Domain events | UserSaved of AuditInfo | EmailSent of EmailAddress * MsgId
  • 146. Events are not errors Validate UpdateDb SendEmail Tell CRM that email was sent type MyUseCaseMessage = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress // database errors | UserIdNotValid of UserId // SMTP errors | SmtpTimeout of SmtpConnection // Domain events | UserSaved of AuditInfo | EmailSent of EmailAddress * MsgId type TwoTrack<'TEntity> = | Success of 'TEntity * Message list | Failure of Message list
  • 147. Comic Interlude Why can't a train driver be electrocuted? I don't know, why can't a train driver be electrocuted? Because he's not a conductor!
  • 148. Some topics not covered... ... but could be handled in an obvious way.
  • 149. Topics not covered • Errors across service boundaries • Async on success path (instead of sync) • Compensating transactions (instead of two phase commit) • Logging (tracing, app events, etc.)
  • 150. Summary A recipe for handling errors in a functional way
  • 151. Recipe for handling errors in a functional way type TwoTrack<'TEntity> = | Success of 'TEntity * Message list | Failure of Message list Validate UpdateDb SendEmail type Message = | NameMustNotBeBlank | EmailMustNotBeBlank | EmailNotValid of EmailAddress
  • 153. I don’t always have errors...
  • 154. I don’t always have errors... Railway Oriented Programming @ScottWlaschin fsharpforfunandprofit.com FPbridge.co.uk http://bit.ly/rop-example