Architecting a web application in a functional style
Chris Birchall
Agenda
- Hand waving
- Write some code
- Summarise
http://lambdale.org/
@lambd_ale
1st September 2018
Functional style?
Obstacles to writing in functional style
- Disconnect between theory and practice
- Decision paralysis
- Nagging doubts
Disconnect between theory and practice
Disconnect between theory and practice
theory
practice
Kan
extensions
corepresentable functors
Monads
Yoneda lemma
Type classes
Kleisli
StateT
"build a working, well-tested, maintainable piece of software to deliver business value"
Decision paralysis
Somebody told me to use free monads,
but somebody else told they were passé
Should this thing be a class or an object?
Is it ok to use traits?
I don't even know what my package structure
should look like
Nagging doubt #1
Am I doing it right?
Nagging doubt #2
Is FP actually better than the alternatives?
Benefits of FP
- Local reasoning
- Abstraction
- Composition
Nagging doubt #3
Don't FP and OOP boil down to the same thing anyway?
Yes!
But one or the other might still be
a better tool for the job
Worked example
Photo storage service
Requirements
User can upload a photo and get back a unique ID
User can retrieve a photo by ID
Detailed requirements: upload photo
- Check that uploaded data is a valid photo
- If validation fails, log it and return a 400 error
- Store to a durable filestore service
- If this fails, log a warning and return a 500 error
- Also store to a remote cache
- If this fails, just log a warning
- Send a "photo uploaded" Kafka event
- Return a 201 response containing the photo ID
Detailed requirements: retrieve photo
- First lookup the file in the cache
- If this fails, log a warning and treat as cache miss
- On cache hit, return a 200 response
- On cache miss:
- Retrieve file from filestore
- If file exists:
- Write it to the cache
- If this fails, just log a warning
- Return a 200 response
- Write it to the cache
- Else, return a 404 response
- If file exists:
- Retrieve file from filestore
Non-functional requirements
- Request ID should be extracted from request header (or generated if not present)
- Should be propagated to all backend requests
- Should be included in all logs
- We expect a lot of concurrent requests, so all I/O must be asynchronous
Domain-driven design
Domain
Translation
External world
Functional design
Pure
Impure
External world
Functional design
Abstract
Concrete
External world
"DDD-inspired" package structure
api
application
domain
infrastructure
HTTP controllers
Concrete effects, interpreters
Domain models and algebras
HTTP clients, config loading, ...
Functional domain modelling
Data Behaviour = Domain
ADTs Algebras = Domain
+
+
Errors
-
Expected errors
- Part of the domain
- Model with Either
-
Unexpected errors
- Network errors, cosmic rays, ...
- Handle with Try, IO, Task, ...
- In general you need to handle both
- EitherT can be handy
def validate(input: Foo): IO[Either[ValidationError, Bar]]
Enough talk!
Let's write some code
Summary
✓ Local reasoning
✓ Abstraction
✓ Composition
Architecting a web application in a functional style
By Chris Birchall
Architecting a web application in a functional style
- 2,804