refactoring

This commit is contained in:
2026-09-02 08:38:03 +02:00
parent 649ff0d7c5
commit 38f255c1a4
48 changed files with 6998 additions and 5577 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ Voeg observability incrementeel toe:
1. eerst een request-id en gestandaardiseerde foutregel;
2. daarna tijdmetingen rond trage endpoints;
3. vervolgens database- en graphmetingen wanneer echte belasting dat vereist;
3. vervolgens database- en browsermetingen wanneer echte belasting dat vereist;
4. pas dan een externe metrics- of tracingstack wanneer lokaal loggen onvoldoende blijkt.
## Analyseerbare code
+1 -1
View File
@@ -41,7 +41,7 @@ De huidige vorm rust op een klein aantal bewuste keuzes:
## Kwaliteitsbeeld
De architectuur past goed bij een persoonlijke of teamwiki: weinig processen, een duidelijke databasebron en volledige historie. De sterkste punten zijn de transactionele inhoudsopslag, eenvoudige deployment en lokale frontend-assets. De voornaamste ontwikkelpunten zijn de omvang van `server.rkt` en `static/js/wiki.js`, het ontbreken van een volwaardige geautomatiseerde testsuite, een nieuwe databaseverbinding per opslagbewerking en de N+1-aanpak waarmee de volledige navigatiegraaf wordt opgebouwd.
De architectuur past goed bij een persoonlijke of teamwiki: weinig processen, een duidelijke databasebron en volledige historie. De sterkste punten zijn de transactionele inhoudsopslag, eenvoudige deployment en lokale frontend-assets. De voornaamste ontwikkelpunten zijn de omvang van `server.rkt` en `static/js/wiki.js`, het ontbreken van een volwaardige geautomatiseerde testsuite en een nieuwe databaseverbinding per opslagbewerking.
Deze punten zijn geen reden voor een voorafgaande grote herbouw. De ontwikkelregel is: meet eerst, isoleer het concrete probleem en splits een module wanneer een wijziging daar aantoonbaar eenvoudiger of beter testbaar door wordt.
+1 -1
View File
@@ -42,7 +42,7 @@ De uitzonderingsoptie is uitsluitend bedoeld voor een bewust vertrouwde lokale m
## Beschikbaarheid en misbruik
Er gelden al document- en veldvalidaties, maar een algemeen uploadmaximum en request-rate limiting zijn toekomstige versterkingen. De CMap-opslag begrenst het JSON-document tot 10 MiB. Grote Markdown, bijlagen, graph-opbouw en dure zoekvragen kunnen anders geheugen of verwerkingstijd gebruiken.
Er gelden al document- en veldvalidaties, maar een algemeen uploadmaximum en request-rate limiting zijn toekomstige versterkingen. De CMap-opslag begrenst het JSON-document tot 10 MiB. Grote Markdown, bijlagen en dure zoekvragen kunnen anders geheugen of verwerkingstijd gebruiken.
Voor een publiek bereikbare installatie horen reverse-proxylimits, databaseback-ups, logrotatie en monitoring bij het beveiligingsmodel. Beschikbaarheid is ook een beveiligingseigenschap.
-2
View File
@@ -53,8 +53,6 @@ De prijs is lineaire databasegroei met het aantal versies maal de documentgroott
`pages.search_document`, `todo_items` en `attachment_references.current_reference` zijn afgeleide gegevens. Zij moeten in dezelfde transactie als hun bron worden aangepast. Een los herstelcommando mag ze opnieuw kunnen opbouwen, maar gewone reads mogen niet afhankelijk zijn van een toevallig later achtergrondproces. Concepten met het aspect `TODO` worden daarentegen rechtstreeks uit `concept_definitions` geselecteerd en met de pagina-todo's samengevoegd; daarvoor bestaat bewust geen tweede index.
De gecombineerde navigatiegraaf is momenteel volledig afgeleid in de browser. Zij wordt niet als databasegraaf bewaard.
## CMap-uitwisselingsformaat
Het versieerbare JSON-formaat `racket-wiki-cmap-bundle` volgt dezelfde scheiding als de database. `cmaps[]` bevat per stabiele slug het volledige structuur- en opmaakdocument, `concepts[]` bevat de gedeelde inhoud per UUID en `pages[]` bevat de actuele titel, Markdown, tags en daarin gebruikte attachments van gekoppelde wiki- en uitlegpagina's. De binaire inhoud staat base64-gecodeerd bij de pagina. Daardoor kan één concept op meerdere kaarten dezelfde inhoud houden terwijl positie, formaat, kleuren en typografie per plaatsing behouden blijven. Verbindingszinnen en connectoren verwijzen naar lokale item-id's en blijven dus bij hun diagramcontext.
+1 -3
View File
@@ -72,12 +72,10 @@ conceptkaarten. De hit-test gebruikt dezelfde laagprioriteit, zodat een achterli
klik kan afvangen op de kaart die haar bedekt. Tijdelijke eindpunthendels blijven wel bovenop liggen
zolang een relatie wordt versleept.
## Zoeken, Recent en navigatiegraaf
## Zoeken en Recent
Paginazoeken gebruikt een opgeslagen gewogen `tsvector` en GIN-index. CMap-zoeken verzamelt titel, slug, labels en synopses uit JSONB. Recent voegt de nieuwste pagina- en CMapwijzigingen samen.
De gecombineerde navigatiegraaf leest verwijzingen uit alle huidige pagina's en CMaps. Hij wordt in de browser gecachet totdat een catalogus opnieuw wordt geladen. Dit levert rijke navigatie op, maar is het belangrijkste schaalrisico; zie [Verwachte performance](racket-wiki:performance).
## Wachtwoordherstel en mail
De publieke herstelactie geeft altijd dezelfde reactie, ook als een account niet bestaat. Een echte aanvraag maakt een gehashte, eenmalige code die één uur geldig is en past rate limiting per gebruiker toe. Na succesvol herstel worden bestaande sessies ingetrokken.
+2 -2
View File
@@ -6,7 +6,7 @@ De backend is functioneel opgesplitst. Configuratie, databaseverbinding, migrati
De belangrijkste grens is die tussen `server.rkt` en de opslagmodules. `server.rkt` hoort HTTP te begrijpen; `storage.rkt`, `cmap-storage.rkt` en `auth.rkt` horen domeinbewerkingen en database-invarianten te begrijpen. Geen opslagprocedure mag een webrequest nodig hebben.
De frontend heeft een vergelijkbare grens. `cmap-racket-wiki.js` implementeert een editorcomponent met callbacks voor openen, selectie en wijzigingen. `wiki.js` koppelt die callbacks aan routes, API's en dialoogvensters.
De frontend heeft een vergelijkbare grens. `cmap-racket-wiki.js` implementeert een editorcomponent met callbacks voor openen, selectie en wijzigingen. `wiki.js` koppelt die callbacks aan routes, API's en dialoogvensters. Zuivere referentie-, route-, Markdown- en outlinebewerkingen staan in eigen modules; `BreadcrumbTrail` beheert uitsluitend de per-tab paginahistorie en kent de DOM niet. `routes.js` herkent hashes, terwijl `wiki.js` eigenaar blijft van permissiecontrole en het openen van views. De concrete controllers onder `static/js/wiki/admin/` beheren hun eigen formulier- of lijstinteractie. Zij ontvangen alleen de bestaande API- en vertaalfuncties en, wanneer de gewone catalogus werkelijk verandert, één gerichte herlaadfunctie.
## Sterke punten
@@ -42,7 +42,7 @@ Deze concentraties zijn technische schuld, geen automatische opdracht tot een gr
Wanneer `server.rkt` verder groeit, ligt opsplitsing per adaptergebied voor de hand: sessie/profiel, pagina's, CMaps en beheer. De centrale router kan dan dun blijven. Handlers ontvangen `config` expliciet en roepen dezelfde bestaande diensten aan.
Wanneer `wiki.js` verder groeit, zijn route/state, pagina-editor, CMap-host, graph-view en admin-views natuurlijke grenzen. Splits alleen met native ES-modules wanneer de setup- en cacheversies van alle scripts tegelijk beheerst worden.
Wanneer `wiki.js` verder groeit, zijn route/state, pagina-editor, CMap-host en admin-views natuurlijke grenzen. Splits alleen met native ES-modules wanneer de setup- en cacheversies van alle scripts tegelijk beheerst worden.
Wanneer paginaopslag wordt aangepast, kunnen bookmarks, aliases en uploads later eigen modules krijgen. De pagina-schrijftransactie en afgeleide indices moeten daarbij als één consistente operatie behouden blijven; opsplitsing van bestanden mag geen opsplitsing van de transactie veroorzaken.
+10 -18
View File
@@ -9,8 +9,8 @@ De onderstaande schaalinschatting is een architectuuranalyse, geen benchmark. We
| Omvang | Verwachting met huidige architectuur |
| --- | --- |
| Honderden pagina's, tientallen CMaps | Normale lees-, schrijf- en zoekacties horen ruim voldoende te zijn op een gewone lokale server. |
| Enkele duizenden pagina's, honderden CMaps | Paginaweergave en geïndexeerd paginazoeken blijven waarschijnlijk goed; volledige graph-opbouw, CMap-zoeken, catalogusgrootte en verbindingsopbouw worden zichtbaar. |
| Tienduizenden pagina's of veel grote CMaps | Paginering, server-side graphindex, connectionpooling, geïndexeerd CMap-zoeken en versie-/bijlagebeleid worden waarschijnlijk noodzakelijk. |
| Enkele duizenden pagina's, honderden CMaps | Paginaweergave en geïndexeerd paginazoeken blijven waarschijnlijk goed; CMap-zoeken, catalogusgrootte en verbindingsopbouw worden zichtbaar. |
| Tienduizenden pagina's of veel grote CMaps | Paginering, connectionpooling, geïndexeerd CMap-zoeken en versie-/bijlagebeleid worden waarschijnlijk noodzakelijk. |
Het ontwerp is dus passend voor een persoonlijke of teamwiki. Het is niet zonder aanvullende maatregelen ontworpen als internetbrede kennisbank met zeer veel gelijktijdige gebruikers.
@@ -18,16 +18,10 @@ Het ontwerp is dus passend voor een persoonlijke of teamwiki. Het is niet zonder
De actuele pagina staat direct in `pages`; voor normaal lezen hoeft geen versiegeschiedenis te worden opgebouwd. Titel en Markdown leveren een opgeslagen gewogen zoekvector met GIN-index. PostgreSQL verwerkt transacties en locking dicht bij de data. De browser ontvangt bij de paginacatalogus alleen metadata en haalt Markdown pas op bij gebruik.
Vendor-assets worden lokaal geserveerd en veranderen niet tijdens normaal gebruik. De browser cachet de opgebouwde navigatiegraaf totdat pagina- of CMapcatalogus opnieuw wordt geladen.
Vendor-assets worden lokaal geserveerd en veranderen niet tijdens normaal gebruik.
## Belangrijkste toekomstige knelpunten
### Volledige navigatiegraaf
`loadGraphData()` haalt momenteel iedere pagina en iedere CMap sequentieel via een eigen API-request op en extraheert daarna links in de browser. Voor `P` pagina's en `C` CMaps zijn dat `P + C` inhoudsrequests naast de catalogi. Latency groeit daardoor lineair en netwerkvertraging telt herhaaldelijk op.
De duurzame oplossing is een server-side relationele linkindex die in dezelfde schrijftransactie als pagina of CMap wordt bijgewerkt. Een graph-endpoint kan dan alle nodes en edges in één compacte response leveren.
### CMap-zoeken
Paginazoeken gebruikt een opgeslagen index. CMap-zoeken bouwt per query tekst uit JSONB-items en maakt daar op dat moment een `tsvector` van. Bij veel of grote kaarten wordt dit een volledige scan. Voeg dan een opgeslagen zoektekst/`tsvector` en GIN-index aan `concept_maps` toe, bijgewerkt tijdens CMap-opslag.
@@ -50,7 +44,7 @@ Bijlagebytes staan in PostgreSQL en worden volledig in geheugen gelezen voor de
### Browserrendering
Grote CMaps tekenen veel DOM/SVG-objecten en connectors. Volledige maps en graphs hebben uiteindelijk layoutkosten die niet met een snellere database verdwijnen. Meet nodeaantal, rendertijd en interactieframes. Sub-CMaps, viewportculling of vereenvoudigde read-only rendering zijn dan gerichte opties.
Grote CMaps tekenen veel DOM/SVG-objecten en connectors. Volledige kaarten hebben uiteindelijk layoutkosten die niet met een snellere database verdwijnen. Meet nodeaantal, rendertijd en interactieframes. Sub-CMaps, viewportculling of vereenvoudigde read-only rendering zijn dan gerichte opties.
## Meetplan
@@ -58,9 +52,8 @@ Voeg vóór optimalisatie minimaal deze metingen toe:
- requestduur per endpoint en responsebytes;
- connecttijd versus SQL-tijd;
- aantallen pagina's, CMaps, graph-edges en versies;
- aantallen pagina's, CMaps en versies;
- gemiddelde en p95 Markdown-, JSONB- en bijlagegrootte;
- tijd voor volledige graph-opbouw;
- tijd voor CMap-zoekquery's;
- browserrendertijd bij representatieve CMaps.
@@ -70,11 +63,10 @@ Gebruik datasets met echte linkdichtheid en documentgroottes. Duizend lege pagin
De verwachte volgorde bij groei is:
1. graph-edges tijdens opslag indexeren en in één request leveren;
2. CMap-zoekvector opslaan en indexeren;
3. databaseconnectionpool toevoegen;
4. catalogi pagineren of per namespace laden;
5. expliciet versie- en uploadbeleid ontwerpen;
6. pas daarna horizontale of servicegerichte architectuur overwegen.
1. CMap-zoekvector opslaan en indexeren;
2. databaseconnectionpool toevoegen;
3. catalogi pagineren of per namespace laden;
4. expliciet versie- en uploadbeleid ontwerpen;
5. pas daarna horizontale of servicegerichte architectuur overwegen.
Deze volgorde behoudt de eenvoud van [Structuur en samenhang](racket-wiki:structuur-en-samenhang) zolang die nog waardevol is.
+2 -2
View File
@@ -38,7 +38,7 @@ De map `private/` bevat de backendonderdelen:
`translate.rkt` staat bewust aan de publieke rand: zowel backend als frontend gebruiken dezelfde effectieve vertaaltabel.
De map `static/` bevat de browserapplicatie. `static/index.html` definieert de views en dialogen. `static/js/wiki.js` beheert routing, API-aanroepen, editor- en paginatoestand en speciale views. `static/cmap/cmap.js` levert de grafische basis; `cmap-racket-wiki.js` voegt wiki-items, selectie, relaties, sub-CMaps, historie en documentserialisatie toe. `combobox.js` is een herbruikbaar klein UI-onderdeel. CSS is verdeeld tussen algemene wiki-opmaak en CMap-opmaak.
De map `static/` bevat de browserapplicatie. `static/index.html` definieert de views en dialogen. `static/js/wiki.js` beheert routedispatch, API-aanroepen, editor- en paginatoestand en speciale views. De modules onder `static/js/wiki/` bevatten afzonderlijk de referentiesyntaxis, routeherkenning, Markdowntransformaties, paginakoppen en breadcrumbhistorie. De controllers onder `static/js/wiki/admin/` beheren ieder één administratief formulier of overzicht; routes, permissiecontrole, breadcrumbs en algemene adminnavigatie blijven in `wiki.js`. `static/cmap/cmap.js` levert de grafische basis; `cmap-racket-wiki.js` voegt wiki-items, selectie, relaties, sub-CMaps, historie en documentserialisatie toe. `combobox.js` is een herbruikbaar klein UI-onderdeel. CSS is verdeeld tussen algemene wiki-opmaak en CMap-opmaak.
## Afhankelijkheidsrichting
@@ -56,7 +56,7 @@ Deze richting houdt de belangrijkste domeinregels buiten de UI. Een rolcontrole
Een pagina heeft een database-id, namespace, slug, actuele Markdown en een versieteller. `page_versions` verwijst naar dezelfde pagina-id. Todo's, bookmarks, aliases en bijlageverwijzingen sluiten via die id aan.
Een CMap heeft een stabiele slug, titel, JSONB-document en versieteller. `concept_map_versions` bewaart volledige JSONB-snapshots. CMap-items kunnen via `pageSlug` naar een wikipagina verwijzen en via `cmapSlug` naar een andere CMap. De browser gebruikt deze verwijzingen voor navigatie en de gecombineerde sitegraph.
Een CMap heeft een stabiele slug, titel, JSONB-document en versieteller. `concept_map_versions` bewaart volledige JSONB-snapshots. CMap-items kunnen via `pageSlug` naar een wikipagina verwijzen en via `cmapSlug` naar een andere CMap. De browser gebruikt deze verwijzingen om het gekoppelde doel rechtstreeks te openen.
Zie [Data, transacties en versiebeheer](racket-wiki:data-en-versies) voor de invarianten en [Modulariteit en afhankelijkheden](racket-wiki:modulariteit) voor de gewenste grenzen bij uitbreiding.