Last week I was seeing a video about functional architecture and how it could help projects to be more maintainable:
Functional architecture - The pits of success - Mark Seemann
Although I did not agree on some of the statements and some of the examples on C#, I got curious (even more) about the topic.
In my project we are starting to use RabbitMQ as message broker for our business events, both for services communication and for analytics. For example, we want to make our logger to publish events in case of a message that is above some configured threshold (error by default).
When explaining these ideas to my team, one of my team members was worried about it, since the logger that was already performing side effects (but more or less safe) would start emitting events. He proposed to detach the functionality from the side effects. So again, functional programming was knocking to my door.
A side effect is any change or any action performed out of the boundaries of a given function. By definition modifying a global variable, writing to the database, sending an event through an event bus, writing to a log file, changing the DOM, etc. is a side effect. Any of those things could produce a failure or an unexpected change and therefore any function performing any of those actions becomes non-deterministic and more difficult to test (more on this to come).
Pure vs Impure functions
A pure function is a deterministic function that will always return the same value when getting the same input and that does not perform any side effects. So basically the pure function gets some values, performs some calculations and return a result: always the same one given the same parameters.
As you might suppose then, an impure function is any function that has performs side effects, so it is not deterministic. Its output, success or failure, etc would depend on external factors.
As a result, testing a pure function is easy and testing an impure function not so much; depending on the number or nature of the side effects they can become very complex to test.
Ports and adapters architecture
Ports and adapters or Hexagonal architecture tries so separate the core of the application from all the external elements it needs to connect to. An application will need to access to the Database, send emails, send events through a message broker, receive http requests, write log messages to files, etc.
It proposes four different types of elements:
- Application: the core, the functionality that models the domain behaviours and data.
- Ports: entry (or exit) points to the application. The public interface to the application. What methods it provides for inputting information and what methods it needs to send resulting data to external services.
- Adapters: they are the elements responsible for getting or sending data to specific external elements. For example a module to access to the Database, a module to send events to an event bus, a logger to write the messages to disk, etc.
- External elements: these are elements that are not part of out system but we need to interact with them to be able to perform the complete functionality. These are UI, Database, message brokers, etc.
Adding all together, it is easy to map the concepts from functional programming and from ports and adapters to have an idea on how nicely they fit and how natural it should be to create a functional application with ports and adapters architecture.
- Application shall be composed by only pure functions. It will provide the predictability and testability we need for the most important part of the system and we can reason about it more easily due to its deterministic nature.
- Adapters shall contain all the functions that will access the external systems, i.e. that will perform side effects. So the impure (non-deterministic) functions will be enclosed in the boundaries of the application.
The idea of joining functional programming with ports and adapters seems very reasonable and logic, but still without a clear/real example the concepts cannot be tested.
In my next posts I will create an application in three different flavours so we can materialise these ideas and find out if they really make sense. The application will be a service based on nodejs performing accesses to a database, writing log messages and sending business events to an event broker (all the external systems will be simulated). The flavours will be:
- Layered architecture: HTTP layer will call service layer that will call the repository and the event bus.
- Ports and adapters - Object Oriented: the core application will contain the domain logic and will get the adapters by dependency injections.
- Ports and adapters - Functional: the core application will contain only pure functions, the side effects will be performed by the adapters.
All will contain Unit Tests since I can foresee that in this regard we will see a lot of differences.
Do you have any experience on working with functional programming? What are your thoughts about it?