Structuring functional programs
Tips for structuring code into stable and easy to understand patterns.
Database access patterns
Extract important business logic out of application dependencies and "wrap" it with database calls or calls to other external resources.
- Load data
- Process data
- Save data based on processed result
Example code structure:
# logic with side effects
data = select_data_from_db_or_external_source()
# pure logic. no side effects
result = business_logic.process(data)
# logic with side effects based on result
if result.has_records {
save(result.records)
}
if result.should_notify_others {
notify_listeners(result.message)
}
With this business logic is independent of application technologies (database, file system or third parties) in design and runtime.
If loading all required data isn't possible because of performance issues, we can "stack" layers of application and business logic.
# load minimum required data
data = load_minimum_data()
# pure logic. no side effects
next_steps = business_logic.detect_next_steps(data)
if next_steps.need_more_data? {
# load more data
data = data + load_more_data()
}
# pure logic without side effects
result = next_steps.process(data)
# logic with side effects based on result
if result.has_records {
save(result.records)
}
Architecture
Use Onion architecture
- Dependencies go inwards. That is, the Core domain doesn't know about outside layers
Use pipeline model to implement workflows/use-cases/stories
- Business logic makes decisions
- IO does storage with minimal logic
- Keep Business logic and IO separate
- Keep IO at edges
Testing
- Only need to unit test the business logic
- IO is tested as part of integration tests
Design
- Decide on the inputs and outputs first, then implement based on that. Then break that down into smaller functions than can be composed in a pipeline.
- Functions are first class citizens. Pass functions as parameters to pass around behavior.
Grouping code:
- Types and functions that change together should live together (e.g. in same module)
- Avoid grouping things together by type e.g. controllers, models, views