Docs: Architectural Notes (#1673)

* Docs: Architectural Notes

* including Service Diagram

* minor changes
This commit is contained in:
Kim Neunert 2022-04-25 15:56:57 +02:00 committed by GitHub
parent 732225e846
commit 406dfefe67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 3 deletions

31
docs/architecture.md Normal file
View file

@ -0,0 +1,31 @@
# Architectural Notes
specter-desktop and specter-cloud are flask-applications and follow typical Model-View-Control-principles. The model-part is covered by a number of business-objects and managers who are responsible for maintaining/managing those. Currently we have these managers:
## Managers
* GenericDataManager is abstracting persistence-aspects and most managers which are persisting json-data are derived from this one.
* ConfigManager where the api-docs tells us: "The ConfigManager manages the configuration persisted in config.json. It's not supposed to have any side-effects. Setting and getting only with a lot of validation and computing while setting/getting."
* DeviceManager where the api-docs tells us: "A DeviceManager mainly manages the persistence of a device-json-structures compliant to helper.load_jsons.py".
* NodeManager manages internal and external nodes.
* UserManager manages all stuff regarding Users.
* Similarly the WalletManager
## Views / Blueprints
The controller is covered by "server_endpoints". Those are split up in “blueprints”. This is a way to split the urls in sub-urls. These sub-urls are loosely related to the business-objects: `/welcome`, `/auth`, `/devices`, `/nodes`, `/price`, `/services` (should be renamed "`/plugins`"), `/settings`, `/setup`, `/wallets`.
## Views
The views are effectively jinja2 templates which can extend from higher order ones and have placeholders which templates can then override. The `base.jinja` lays out the basic layout having a block for the `sidebar` and a `main` block (plus `head` and `scripts` ). Some blueprints (`/settings`,`/wallets`) span up a top-level-navigation. In that case, the template extend from its specific blueprint-base-template which in turn provides a block called `content` and the template decides which menu-point is active:
```
{% extends "wallet/components/wallet_tab.jinja" %}
{% set tab = 'receive' %}
{% block content %}
...
```
This is also a pattern used for plugins.
# Plugins
The more specific a functionality became, the more awkward it felt to be integrated in the above core-Architecture. When we started to make exchange specific functionality, we wanted to protect the core-Architecture from deluting. Therefore we created a plugin-concept which allows to have the above concepts replicated in it there own self-contained standalone units.
We try now to implement bigger chunks of functionality in plugins. Maybe even core-functionality might be placed with "core-plugins" in the future. Those are placed in `src/specterext`. But plugins can also live in there own repos, have their own release-lifecycle and be used by Specter like any other dependency as well. For more information about plugins see [Third Party Service Integrations](./services/services.md).

View file

@ -55,7 +55,7 @@ Users can also manually associate an existing `Address` with a `Service` (this i
_Note: TODO: manually un-reserve an `Address` from a `Service`._
### `Service` Configuration
### Service Configuration
In order to separate the service-configuration from the main-configuration, you can specify your config in a file called `config.py`. It's structure is similiar to the specter-wide `config.py`, e.g.:
```
class BaseConfig():
@ -66,7 +66,7 @@ class ProductionConfig(BaseConfig):
```
In your code, you can access the correct value as in any other flask-code, like `api_url = app.config.get("SWAN_API_URL")`. If the instance is running a config (e.g. `DevelopmentConfig`) which is not available in your service-specific config (as above), the inheritance-hirarchy from the mainconfig will get traversed and the first hit will get get configured. In this example, it would be `BaseConfig`.
### `ServiceEncryptedStorage`
### ServiceEncryptedStorage
Most `Service`s will require user secrets (e.g. API key and secret). Each Specter `User` will have their own on-disk encrypted `ServiceEncryptedStorage` with filename `<username>_services.json`. Note that the user's secrets for all `Service`s will be stored in this one file.
This is built upon the `GenericDataManager` class which supports optional encrypted fields. In this case all fields are encrypted. The `GenericDataManager` encryption can only be unlocked by each `User`'s individual `user_secret` that itself is stored encrypted on-disk; it is decrypted to memory when the `User` logs in.
@ -79,7 +79,7 @@ It is up to each `Service` implementation to decide what data is stored; the `Se
This is also where `Service`-wide configuration or other information should be stored, _**even if it is not secret**_ (see above intro about not polluting other existing data stores).
### `ServiceEncryptedStorageManager`
### ServiceEncryptedStorageManager
Because the `ServiceEncryptedStorage` is specific to each individual user, this manager provides convenient access to automatically retrieve the `current_user` from the Flask context and provide the correct user's `ServiceEncryptedStorage`. It is implemented as a `Singleton` which can be retrieved simply by importing the class and calling `get_instance()`.
This simplifies code to just asking for:
@ -110,6 +110,12 @@ Annotations are stored on a per-wallet and per-`Service` basis as _unencrypted_
_Note: current `Service` implementations have not yet needed this feature so displaying annotations is not yet implemented._
## Data Storage Class Diagram
Unfortunately, the two unencrypted classes are derived from the encrypted one rather than having it the other way around or having abstract classes. This makes the diagram maybe a bit confusing.
[![](https://mermaid.ink/img/pako:eNqVVMFuwjAM_ZUqJzaVw66IIU0D7bRd0G6VItO4LFvqoCRlqhj_PpeWASLduhyiqH7v-dlOsxO5VSgmIjfg_VzD2kGZUcKr3Z-Q0Ol8DgGegWCNLpl-jcfJEt1W57ig3NWbgGoZrONoS-oJXjBfCaPcPxI-ENkAQVvyF7RHS4VeVw5WBpea1gaDpZbZZ6eT_9VyzMK18_8oZeIuE8l4fMsn4tOQRvZm7FPra24XZgLjK4--38BFTe1-uCORAe3acLOk1KSDlCOPpkgTxSBZWKPQpUlniUcnP7C-f7GENycmQYnSFvLdc7zQBk-hn1r4OxrlTxFjQY3ORKSHbUfcn3vuqTFm_MIyt4h3pX1zraTCA_0sX0b9ya5varxPB7DUKk0-wfC1HSh_PeJdFB7_Mc6sTKf--Hk2G96769lHhJrlW7xc1bJp538qGpQjJnVqhUhFia4ErfiROwhlIrxhiZmY8FFhAZUJmciogVYbHj8ulOb8YlKA8ZgKqIJd1pSLSXAVHkHdW9mh9t9YNMxZ)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqVVMFuwjAM_ZUqJzaVw66IIU0D7bRd0G6VItO4LFvqoCRlqhj_PpeWASLduhyiqH7v-dlOsxO5VSgmIjfg_VzD2kGZUcKr3Z-Q0Ol8DgGegWCNLpl-jcfJEt1W57ig3NWbgGoZrONoS-oJXjBfCaPcPxI-ENkAQVvyF7RHS4VeVw5WBpea1gaDpZbZZ6eT_9VyzMK18_8oZeIuE8l4fMsn4tOQRvZm7FPra24XZgLjK4--38BFTe1-uCORAe3acLOk1KSDlCOPpkgTxSBZWKPQpUlniUcnP7C-f7GENycmQYnSFvLdc7zQBk-hn1r4OxrlTxFjQY3ORKSHbUfcn3vuqTFm_MIyt4h3pX1zraTCA_0sX0b9ya5varxPB7DUKk0-wfC1HSh_PeJdFB7_Mc6sTKf--Hk2G96769lHhJrlW7xc1bJp538qGpQjJnVqhUhFia4ErfiROwhlIrxhiZmY8FFhAZUJmciogVYbHj8ulOb8YlKA8ZgKqIJd1pSLSXAVHkHdW9mh9t9YNMxZ)
### callback methods
Your service-class will inherit a callback-method which will get called for various reasons with the "reason" being a string as the first parameter. Checkout the `cryptoadvance.specter.services.callbacks` file for the specific callbacks.

View file

@ -28,6 +28,7 @@ nav:
- 'Continuous Integration': continuous-integration.md
- cypress-testing.md
- services/services.md
- 'Architectural Notes': architecture.md
- 'Some Random Dev Thoughts': archblog.md
- API:
- api/README.md