Architectuur
Dit hoofdstuk beschrijft de doelarchitectuur van I See You. Backend én frontend worden vanaf 0 gebouwd tegen het domeinmodel als spec. Het bestaande prototype in bron/ is uitsluitend referentie/uitgangspunt — niet de basis waarop wordt voortgebouwd.
I See You is een standalone, single-tenant applicatie waarin een gebruiker een korte opname (een video van maximaal 10 seconden óf een foto) anoniem voorlegt aan opiniegevers; elke vraag krijgt het vaste, via Beheer ingestelde aantal beoordelingen, en online gebruikers krijgen bij de matching voorrang. Na de opname kiest de maker via een Beoordelingsselectie welke secties beoordeeld worden — standaard gezicht, kleding en accessoires; alleen de gekozen secties worden gescoord (1–5, verplicht per gevraagd attribuut — per 2026-07-05; “geen mening” is vervallen) en de opiniegever kan optioneel één vrije reactie geven. Reageert een opiniegever niet binnen zijn antwoordtermijn, dan komt het slot weer vrij; is er geen mens beschikbaar, dan beoordeelt een AI-persona als vangnet. Na afronding waardeert de maker elke ontvangen (menselijke) beoordeling met 0–5 (op de smiley-schaal) als kwaliteitsoordeel; die telt mee in de reviewer-statistieken. Deelname loopt op credits: stellen is gratis, je uitslag inzien kost credits die je verdient door te beoordelen. (Vastgesteld per 2026-07-08b en gebouwd — met een gesimuleerde (dummy) iDeal, een echte PSP volgt:) daarnaast is er betaald advies — verdiende sterren ontsluiten een adviesniveau, vraagstellers betalen per tekstadvies uit een euro-saldo (iDeal via een PSP, strikt gescheiden van de credits) en de uitbetaling is terug. De hele architectuur staat in dienst van die cyclus en van de kernbelofte: anonimiteit — geherformuleerd als anonimiteit richting andere gebruikers (betaalgegevens leven bij de payment provider).
Uitgangspunten
Section titled “Uitgangspunten”- Lage startkosten. De applicatie moet goedkoop te draaien zijn bij weinig verkeer. Daarom een API als één Docker-image op een serverless-container-platform met scale-to-zero, managed PostgreSQL en een S3-compatibele object-storage in plaats van zware, altijd-draaiende infrastructuur.
- Klaar voor schaal. Dezelfde opzet schaalt horizontaal: de API is stateless ontworpen, zodat extra instances zonder herontwerp bijgeschakeld kunnen worden. Bij meerdere instances komt er een Redis-backplane voor realtime bij (configuratie, geen herontwerp).
- Anonimiteit als eerste regel. Een vraag met opname wordt altijd verwijderd; na afronding blijft zij nog een instelbaar resultaat-retentievenster bestaan zodat alleen de maker het resultaat + de opname kan terugzien (en die zelf kan verwijderen), waarna zij automatisch verdwijnt. Een instelbare veiligheids-timeout ruimt vragen op die nooit afgerond raken. Een geregistreerde gebruiker kan optioneel een e-mailadres opgeven, uitsluitend voor wachtwoordherstel; zonder e-mail is er geen herstel, en anonieme gebruikers hebben sowieso geen herstel. Retentie en anonimisering zijn ingebouwd, niet uitschakelbaar. Zie Beveiliging & privacy.
- Veilige vrije tekst. Alle door gebruikers ingetypte vrije tekst (de toelichting van de maker, de reactie van de reviewer) wordt vóór opslaan door de tekstmoderatie (
IModeratieDienst, OpenAI Moderation) gecontroleerd; afgekeurde tekst wordt geweigerd. - Twee portalen, één API. Een beheerportaal en een gebruikersportaal delen één deployable API. De scheiding loopt via autorisatiebeleid (
IsBeheerderversus gebruiker/anoniem), niet via een harde context-grens. - Camera-centrisch. Media ontstaat uit live capture in de browser (geen upload): een opname van maximaal 10 seconden (mag korter) of een foto, vastgelegd via de camera. Foto en video delen dezelfde pipeline; alleen de capture verschilt. Zie Media & opname.
- Single-tenant. Eén logische tenant; geen multi-tenant-isolatie of per-klant-scheiding.
- Cloud-agnostisch. Geen cloud-lock-in. Alle bouwstenen (container-platform, managed PostgreSQL, S3-compatibele opslag, Redis) zijn neutrale, uitwisselbare standaarden; wisselen van aanbieder is een DI-/config-keuze.
Bouwstenen (high-level)
Section titled “Bouwstenen (high-level)”Twee React-apps praten via één ASP.NET Core API met de buitenwereld. De API spreekt PostgreSQL voor state en een object-storage voor media-bytes; SignalR duwt realtime-gebeurtenissen terug. Een vraag met opname blijft na afronding nog een resultaat-retentievenster bestaan (alleen voor de maker) en wordt daarna verwijderd; een altijd-actieve achtergronddienst handhaaft daarnaast de instelbare veiligheids-timeout en schoont vluchtige data op.
flowchart TB
subgraph Frontend["Frontend (React 19 + Vite + TypeScript)"]
Beheer["Beheerportaal"]
Gebruiker["Gebruikersportaal (mobiel kader, camera)"]
end
subgraph Backend["ASP.NET Core API (modulaire monoliet, stateless)"]
REST["REST (Minimal API) - alle state-mutaties"]
Hubs["SignalR-hubs - realtime push"]
Retentie["RetentieHostedService (altijd actief)"]
end
subgraph Opslag["Persistentie"]
DB[("PostgreSQL - state, keys en metadata")]
Blob[("Object-storage (S3-compatibel) - media-bytes")]
end
Beheer -->|HTTPS/JSON| REST
Gebruiker -->|HTTPS/JSON| REST
Gebruiker -->|live capture video max. 10s of foto| REST
Beheer -.->|WebSocket| Hubs
Gebruiker -.->|WebSocket| Hubs
REST --> DB
REST -->|media-bytes| Blob
REST -.->|key plus metadata| DB
Retentie -->|ruimt na veiligheids-timeout op| Blob
Retentie -->|schoont vluchtige data| DB
Hubs -.->|presence en opdrachten| Gebruiker
De API is een modulaire monoliet met één module per bounded context — Beoordeling, Gebruiker & Toegang, Moderatie & Retentie en Beheer. De lagen zijn strikt naar binnen afhankelijk (Domain ← Application ← Infrastructure ← Api). De details staan in Backend & API.
Hoe de portalen zich tot de contexten verhouden
Section titled “Hoe de portalen zich tot de contexten verhouden”De twee frontends zijn geen één-op-één-projectie van de bounded contexts; ze groeperen functionaliteit langs autorisatie.
| Portaal | Doelgroep | Globaal welke contexten |
|---|---|---|
| Beheerportaal | Beheerders (IsBeheerder) | Beheer plus de admin-kant van Moderatie & Retentie |
| Gebruikersportaal | Gebruikers en anonieme bezoekers | Beoordeling (incl. credits en waarderen), melden, en de gebruikerskant van Gebruiker & Toegang |
Onderdelen van dit hoofdstuk
Section titled “Onderdelen van dit hoofdstuk”| Pagina | Omschrijving |
|---|---|
| Repo-indeling & tech stack | Mappen op één branch (docs/, app/, bron/), gekozen frameworks en versies |
| Frontend | React 19 + Vite + TypeScript-monorepo met twee apps en gedeelde libs, Tailwind, TanStack Query, gegenereerde API-client en camera-hook |
| Backend & API | .NET/C# modulaire monoliet, gelaagd + DDD, Minimal API, SignalR-hubs, EF Core + PostgreSQL en de persistentiekeuzes |
| Media & opname | Live capture (video max. 10s én foto), IMediaStore naar object-storage, promote-flow en range-streaming |
| Beveiliging & privacy | Argon2id, anonieme auth, rate limiting, CORS, HTTPS/HSTS, CSP en de altijd-actieve retentie |
| Deployment | Cloud-agnostisch hosten: container met scale-to-zero, managed PostgreSQL, object-storage en Redis bij opschalen |
| Beslissingen (ADR’s) | De belangrijkste architectuurbeslissingen met motivatie, onderbouwd op de eisen van I See You |
Belangrijkste keuzes in het kort
Section titled “Belangrijkste keuzes in het kort”- .NET / C#, gelaagd + DDD, modulaire monoliet. Eén deployable met een module per bounded context en strikt naar binnen gerichte afhankelijkheden; geen losse microservices en geen
MediatR(dunne, expliciete use-case-handlers). - EF Core + PostgreSQL (Npgsql). Value objects als owned types, scores als
jsonben de optionele reactie als nullable tekstkolom, de domeininvariantScore 1..5(per2026-07-05; zo gebouwd) enWaardering0..5met check-constraint, een niet-negatief credit-saldo, en optimistic concurrency viaxmin. - Object-storage voor media. Media-bytes gaan via de
IMediaStore-abstractie naar een S3-compatibele opslag; PostgreSQL bewaart alleen de key en metadata. Wisselen van opslag is een config-keuze. - Realtime via SignalR. Alle state-mutaties lopen via REST; SignalR doet uitsluitend push (presence en opdrachten). Geen socket-state die scale-out blokkeert.
- Anonimiteitsgerichte auth. Beheerders via ASP.NET Core Identity (in-app, gehashte wachtwoorden, MFA mogelijk); gebruikers via een eigen anonieme auth met Argon2id en een fictieve naam. Een geregistreerde gebruiker kan optioneel een e-mailadres opgeven, uitsluitend voor wachtwoordherstel; zonder e-mail is er geen herstel.
Deze keuzes — en de afweging tegenover het prototype — staan uitgewerkt in Beslissingen (ADR’s). De huidige modelversie staat bovenaan het Wijzigingslog.