Software architecture is the practice of modularizing and organizing software development from large to small. Functional programming on the other hand is a style of programming centered around functions and, among others, concepts like Pure functions or avoiding State and Side-Effects as far as possible. Functional software architecture is thus the transfer of ideas from functional programming to software architecting. In the following I will describe my point of view of such functional architecture.
I note, that there are already quite a few contributions to this topic, for example:
- https://increment.com/software-architecture/primer-on-functional-architecture/
- https://www.infoq.com/news/2023/04/late-arch-functional-programming
- https://www.functional-architecture.org
- as well as various talks available on Youtube.
However, I often miss the practical correspondence to functional programming. So, here is my take on functional architecture.
To avoid a lengthy introduction to functional ideas, I assume you have some very basic understanding.
Software Architecture
Every software is in essence a transformation of data, and software architecture is, to me, first of all a bird's eye view of this transformation. Thus the most basic depiction of software can be given by:
- Inputs
- Outputs
- State
- Side-Effects
which describe the core functional requirements of a software project (or component).
Inputs and Outputs
Inputs control a process, while outputs result from a process. Jointly, inputs and outputs are called ports, and are specified by a:
- Protocol
- Media type
- Structure
- Format
- Content
which need to be defined on the architecture level to allow interoperability. An example for a web-service is:
- HTTP as protocol
- JSON as media type
- OpenAPI to define the structure of a HTTP API
- JSON:API to format each message
- JSON-schema to define templates for request and response contents.
Generally, HTTP and JSON are a widespread choice, facilitating interfacing. HTTP is easily testable with a browser or standard tools like wget
or curl
. JSON is more compact than XML, not white-space sensitive like YAML, and natively parsable in Javascript, which has relevance for frontends.
On a lower level, a definition could be linking type (protocol), data types (media type), header file (structure), function signatures (format), argument and return value constraints (content).
State vs Side-Effects
State is a remberance of past effects, while side-effects may alter state outside the current scope. Hence, it is important if an effect affects inside or outside the project's (or component's) scope.
Component View
Every non-trivial software project consists of components. Each component again is defined by a bird's eye view consisting of inputs, outputs, state, and side-effects.
In my opinion, in functional architecture, functions in programming generalize to components in architecture. Hence, the characteristic of components one should aim for is purity, which means in component terms idempotence or statelessness. If a component has to carry state to be useful, such as a database, then it should be isolated from other components.
To correctly architect a component's statefulness and side-effects, dependency management is paramount. This refers to dependent components as well as external dependencies, ie libraries, services, etc.
Components and Containers
As a practical approach to isolate top-level components, I think, containers (process virtualization via Docker or Podman) are particularly suitable for functional architectures, since typically containers do not retain state after restarts. Using containers to isolate components has the additional benefits of reproducible deployments, and in case of stateless containers, easier replication and scaling.
Note that state, in terms of containers, can be obvious in case permanent storage in form of an external volume is needed, but state can also come in terms of temporary files or memory-based. However, state can be discardable, for example certain types of lock files are only relevant to a currently running instance.
Furthermore in larger projects, components may have sub-components; here the same characterization of a sub-component, ie inputs, outputs, state, and side-effects apply, maybe not in terms of containers but in terms of libraries.
Side-Effect or Not?
The more self-sufficent components are, the more care needs to be taken on their self-management. For example each component likely contributes to a log. Writing to a log can be considered state in case of an internal (local) log or a side-effect in case of a central (global) log. Importance of such an effect determines if it induces state, side-effect or can be discarded.
At some level of granularity one has to disregard effects, otherwise one could consider just running the software causing heating of the environment surrounding the host system computer as a side-effect.
Technology Stack
Classically, a technology stack is defined for a whole project to have some degree of homgeneity across components. Using isolated components, like containers, technology stacks can be set up individually per component while retaining homogenous ports. Individuality can refer practically to the container image, programming languages, and dependencies.
Functional Languages?
A software architecture can be functional even though no functional language or functional programming paradigm is explicitly used. This is due to the focus on the ports and effects of a project and its components. However, strict discipline is required from architects with regard to those ports. Furthermore, developers need to take care, that no (non-discardable) disk- or memory-based state is introduced by using non-functional means of programming or utilized external dependencies; not to forget dependencies' dependencies.
Summary
To use this flavor of functional architecture, internalize that its functions all the way down: A software project (main function) and each of its components correspond to functions and have inputs, outputs, state and side-effects. And while additional views maybe needed to document component interaction, these can be based off the presented component characterization.
Bonus: Documenting Functional Architecture
There a various standards for documenting software architecture, like arc42 or C4. While useful and somewhat well-known (there is certainly a correlation here), here architecture documentation can be further simplified, particularly due to the self-similarity of project and component. Following is a small template, that can also serve as a project's and component's README:
# {Project/Component name}
What: {One-line executive summary of project/component}
Lead: {Main responsible person(s) for this project/component}
This: {URL, version pinpointing project/component change}
Repo: {URL to project/component repository}
Docu: {URL to project/component documentation}
Ports:
- {Summarize an input, output or pair of input and output}
- ...
State:
- {Summarize how if at all state is carried}
- ...
Side Effects:
- {Summarize how a side effect is caused}
- ...
Tech:
- Image
- Software
- Language
- Library
- ...
Internal Deps:
- {Name/URL of component dependency}
External Deps:
- {Name/URL to an external software depedency}
References:
- {Title/URL to a relevant reference}
Note, that this file is a Markdown and YAML file at the same time, and as such human- and machine-readable, if the fields are filled carefully.
Lastly, I note using such a README.md
for each component (and sub-component) induces a hierarchical structure (for example of directories) in a project.
Top comments (0)