156 lines
8.4 KiB
Markdown
156 lines
8.4 KiB
Markdown
# MijnBesluit — architectuur
|
|
|
|
## Wat het is
|
|
|
|
Een platform om je beslissingen bij leven vast te leggen in een digitale kluis, en om berichten
|
|
te versturen naar familie en vrienden — nu of in de toekomst, en ook pas na je overlijden.
|
|
|
|
## De vorm in één plaatje
|
|
|
|
```
|
|
Website (Blazor) Android / iOS (MAUI)
|
|
| |
|
|
| in-proces | TLS-socket, poort 5200
|
|
v v
|
|
+---------------------------------------------+
|
|
| MijnBesluit.Server (dispatcher, sessies) | <- alleen voor de socketkant
|
|
+---------------------------------------------+
|
|
\ /
|
|
v v
|
|
+---------------------------------------------+
|
|
| MijnBesluit.Application |
|
|
| AccountService, FamilyService, VaultService, |
|
|
| MessageService, LegacyService + crypto |
|
|
+---------------------------------------------+
|
|
|
|
|
+---------------------------------------------+
|
|
| MijnBesluit.Domain (entiteiten + regels) |
|
|
+---------------------------------------------+
|
|
|
|
|
+---------------------------------------------+
|
|
| MijnBesluit.Infrastructure (EF Core) |
|
|
+---------------------------------------------+
|
|
|
|
|
MariaDB + versleutelde bestanden op schijf
|
|
```
|
|
|
|
De regels staan één keer, in Application en Domain. De website en de app zijn allebei niet meer
|
|
dan een manier om diezelfde regels aan te roepen. Wat op de website niet mag, kan dus ook via de
|
|
app niet — dat hoeft nergens tweemaal te worden afgedwongen.
|
|
|
|
## De projecten
|
|
|
|
| Project | Wat het doet | Externe packages |
|
|
|---|---|---|
|
|
| `MijnBesluit.Domain` | Entiteiten en de regels: eigendom van profielen, overlijdensprocedure, vrijgave van berichten | geen |
|
|
| `MijnBesluit.Application` | De handelingen: registreren, uitnodigen, kluis, berichten, nalatenschap. Plus alle versleuteling | geen |
|
|
| `MijnBesluit.Protocol` | Het socketprotocol: framing, de server-luisteraar en de client voor de app | geen |
|
|
| `MijnBesluit.Server` | Vertaalt socketverzoeken naar Application-aanroepen; sessiebeheer | geen |
|
|
| `MijnBesluit.Infrastructure` | EF Core op MariaDB, sleutelbestand, bestandsopslag, e-mail | EF Core, Pomelo |
|
|
| `MijnBesluit.ServiceBeheer` | De Windows-service installeren, verwijderen, starten en stoppen | geen |
|
|
| `MijnBesluit.SocketServer` | De zelfstandige service voor de app, het beheerschermpje en de achtergrondtaken | Hosting, WinForms |
|
|
| `MijnBesluit.Web` | De website in Blazor | geen |
|
|
| `MijnBesluit.SelfCheck` | Controleert de regels zonder database of webserver | geen |
|
|
|
|
Dat de eerste vier geen enkel extern package nodig hebben is met opzet: daardoor zijn de regels
|
|
en het protocol te bouwen en te controleren zonder dat er ook maar iets geïnstalleerd hoeft te
|
|
worden, en is `MijnBesluit.Protocol` zonder gedoe te hergebruiken in de MAUI-app.
|
|
|
|
## Geen webservice, geen IIS
|
|
|
|
De website is Blazor Server en roept de Application-laag rechtstreeks aan, in hetzelfde proces.
|
|
Er zit dus geen webservice tussen die serialiseert, over HTTP praat en weer deserialiseert.
|
|
|
|
De app praat met `MijnBesluit.SocketServer`: een zelfstandig draaiende Windows-service (64 bits)
|
|
met een eigen TCP-luisteraar. Daar komt geen IIS aan te pas.
|
|
|
|
Dezelfde exe is ook zijn eigen installatieprogramma: start Windows hem, dan draait de server;
|
|
start jij hem met de rechtermuisknop als administrator, dan verschijnt een schermpje met een
|
|
poortnummer en een knop **Install** — of **UnInstall** als hij al geïnstalleerd is. Zie
|
|
`docs/SocketServer-installeren.md`.
|
|
|
|
```
|
|
dotnet publish src\MijnBesluit.SocketServer -c Release -r win-x64 --self-contained false -o D:\MijnBesluit\publish
|
|
```
|
|
|
|
Binnen de service:
|
|
|
|
* één thread accepteert verbindingen;
|
|
* elke verbinding krijgt een eigen thread met een blokkerende leeslus;
|
|
* elk verzoek gaat naar zijn eigen thread, zodat een trage databaseactie de volgende verzoeken
|
|
van diezelfde app niet ophoudt. Elk verzoek krijgt daarmee ook zijn eigen databasecontext.
|
|
|
|
## Hoe een profiel van eigenaar wisselt
|
|
|
|
Dit is de kern van hoe MijnBesluit zich verspreidt.
|
|
|
|
1. Jan maakt een account. Hij krijgt een profiel (dat hij meteen zelf bezit) en een familieproject.
|
|
2. Jan voegt zijn dochter Eva toe. Er komt een profiel voor Eva, **beheerd door Jan**. Jan kan
|
|
alles aan dat profiel wijzigen.
|
|
3. Jan stuurt een uitnodiging naar Eva's e-mailadres. In de database staat alleen de SHA-256 van
|
|
het uitnodigingstoken; het token zelf bestaat enkel in die ene e-mail.
|
|
4. Eva klikt op de link en kiest een wachtwoord. Op dat moment wordt het profiel **geclaimd**:
|
|
`claimed_by_user_id` wordt gevuld en `managed_by_user_id` leeggemaakt.
|
|
5. Vanaf nu kan Jan niets meer aan Eva's profiel wijzigen. Hij ziet haar nog steeds in zijn
|
|
familie staan, met het label "geclaimd".
|
|
6. Eva krijgt haar eigen familieproject en nodigt op haar beurt anderen uit.
|
|
|
|
De regel staat op één plek: `ProfileOwnership.CanEdit`. Alles wat een profiel wil wijzigen gaat
|
|
daarlangs.
|
|
|
|
## Overlijden en nalatenschap
|
|
|
|
0. Je wijst beheerders aan, op volgorde. Zij zien dat meteen staan onder *Aan mij toevertrouwd*,
|
|
met hun volgorde erbij — daar reken je tenslotte op als het zover is. Meer dan de aanwijzing
|
|
zien ze niet: geen datum, geen aantallen, geen kluis.
|
|
|
|
Weghalen kan ook (`legacy.remove-manager`). Dat hoort erbij: een beheerder kan zelf overlijden,
|
|
en aanwijzen zonder kunnen terugnemen is geen keuze. Eén geval kan niet — iemand die je kluis
|
|
al heeft gekregen. Dan is de procedure afgerond en heeft hij gezien wat erin stond; die regel
|
|
weghalen zou alleen de administratie opschonen en niet wat er gebeurd is.
|
|
1. **Iedereen uit je kring** kan melden dat je bent overleden: de mensen die jij hebt toegevoegd
|
|
en die hun uitnodiging hebben aangenomen. Eén melding per gebruiker, afgedwongen met een
|
|
unieke sleutel in de database.
|
|
2. **Minstens één van die meldingen komt van een aangewezen beheerder.** Zolang die er niet is,
|
|
begint er niets: geen bericht aan de betrokkene, geen termijn die verloopt.
|
|
|
|
Waarom niet "alleen beheerders mogen melden", wat voor de hand lag: dan heb je evenveel
|
|
beheerders nodig als er meldingen vereist zijn. Wijs je er twee aan terwijl er drie meldingen
|
|
nodig zijn — wat de meeste mensen zullen doen — dan kan de procedure nooit worden afgerond en
|
|
gebeurt er bij een overlijden niets. Stilzwijgend, want niemand merkt het.
|
|
|
|
Zo werkt het nu dus niet. Het *aantal* haal je met je kring, want die merkt een overlijden als
|
|
eerste en is groot genoeg. De *beheerder* is de stem die er in elk geval bij moet zitten. Twee
|
|
verschillende soorten mensen moeten het dus eens zijn voordat er iets in beweging komt.
|
|
|
|
Blijft die beheerdersmelding uit, dan zegt het nalatenschapsscherm dat met zoveel woorden
|
|
(`WachtOpBeheerder`). En wie nog niemand heeft aangewezen, krijgt daar een waarschuwing: zonder
|
|
beheerder blijft de kluis ook na een overlijden dicht.
|
|
3. Zodra aan allebei is voldaan (standaard drie meldingen, per profiel instelbaar), krijgt de
|
|
betrokkene zelf een e-mail: *klopt dit niet, laat het weten*.
|
|
4. Reageert die persoon binnen de bezwaartermijn (standaard 14 dagen), dan vervallen alle
|
|
meldingen en gebeurt er niets.
|
|
5. Reageert die persoon niet, dan wordt het overlijden bevestigd. De aangewezen
|
|
nalatenschapsbeheerders krijgen toegang tot precies die kluisitems die zijn gemarkeerd als
|
|
"vrijgeven na overlijden" — de rest blijft dicht. Berichten met "pas als ik er niet meer ben"
|
|
worden bezorgd.
|
|
|
|
Een **lopende** procedure ziet er bij de beheerder precies zo uit als iemand die springlevend is.
|
|
Dat is met opzet: anders kon hij aan zijn eigen scherm aflezen dat er meldingen binnen zijn over
|
|
iemand die dat nog kan tegenspreken. Pas bij stap 5 verandert de kaart.
|
|
|
|
Later kan een koppeling met de BRP (de opvolger van de GBA) stap 1 tot en met 4 vervangen door
|
|
één betrouwbare bron. De rest van de code verandert dan niet: alleen wie `DeathConfirmedAt`
|
|
vult, verandert.
|
|
|
|
## Achtergrondtaken
|
|
|
|
`AchtergrondTaken` in de SocketServer draait elk kwartier en doet drie dingen: berichten bezorgen
|
|
waarvan de datum is bereikt of de afzender bevestigd is overleden, bezwaartermijnen die zijn
|
|
afgelopen afronden, en verlopen sessies opruimen.
|
|
|
|
## Tijd
|
|
|
|
Alles wordt in UTC opgeslagen en pas op het scherm omgerekend naar lokale tijd. Een bericht dat
|
|
op 25 december bezorgd moet worden, verschuift zo nooit door zomertijd.
|