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