Thomas Eizinger
3 years ago
1 changed files with 63 additions and 0 deletions
@ -0,0 +1,63 @@ |
|||||
|
# Architecture |
||||
|
|
||||
|
This document outlines the architecture of this software and the rationale that went into it. |
||||
|
Architectural invariants more often than not defines the _absence_ of something. |
||||
|
Seeing something that is not there is hard, which is why we make an effort to document these things in here. |
||||
|
|
||||
|
We define _components_ as software elements that talk to each other at runtime. |
||||
|
The component split does not necessarily reflect the source code split of the software. |
||||
|
For example, the library is embedded in both daemon but only exists once as a source code element. |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
The application is split into several components: |
||||
|
|
||||
|
- A web frontend for the taker |
||||
|
- A taker daemon |
||||
|
- A web frontend for the maker |
||||
|
- A maker daemon |
||||
|
|
||||
|
On a source-code level, we split into: |
||||
|
|
||||
|
- A library defining the core, cryptographic protocol |
||||
|
- A _crate_ defining the two daemons |
||||
|
- Two React-based frontend projects |
||||
|
|
||||
|
## Invariants |
||||
|
|
||||
|
### Event-based communication between frontend and backend |
||||
|
|
||||
|
Each frontend subscribes to a SSE-based feed from the backend. |
||||
|
Each event notifies the frontend about a change in the backend's state. |
||||
|
This state change can either be a result of a user interaction or incoming network communication. |
||||
|
|
||||
|
User interaction MUST NOT directly change the state displayed to the user. |
||||
|
|
||||
|
Instead, we maintain a cycle of: |
||||
|
|
||||
|
1. User interaction triggers POST request to backend |
||||
|
1. Backend state changes |
||||
|
1. State change emits update on SSE feed |
||||
|
1. Event on SSE triggers re-render in application |
||||
|
|
||||
|
As a result of this invariant, we can be sure that any state change is accurately reflected in the frontend, regardless of how it was triggered. |
||||
|
It also makes the frontend very thin and therefore more predictable. |
||||
|
|
||||
|
### Update local state first |
||||
|
|
||||
|
To keep our UI stateless and responsive, we always update the local state of our daemon first (and emit an event for it). |
||||
|
Only once the local state is updated, we engage with other systems like the maker/taker daemon. |
||||
|
|
||||
|
Applying state changes locally first allows us to record the user's intention and provide instant (UI) feedback that we are working on making it happen. |
||||
|
Even if the user restarts the entire application, we can pick up where we left of and finish what we were meant to be doing. |
||||
|
|
||||
|
### Append only database |
||||
|
|
||||
|
The database structure is append only. |
||||
|
We never update / overwrite existing state to make sure we don't ever loose data due to bugs in state transitions. |
||||
|
|
||||
|
### Library only exposes pure transition functions rather than a state machine |
||||
|
|
||||
|
The protocol implemented in the library can be thought of as a state machine that is pushed forward by each party. |
||||
|
To remain flexible in how the protocol is used, the library MUST only expose pure functions to go from one state to the other rather than representing the actual states itself. |
||||
|
This allows applications on top of shape their states and messages as they wish, only reaching into the library for doing the heavy lifting of cryptography and other protocol-specific functionality. |
Loading…
Reference in new issue