Ga naar inhoud

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).

  • 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 (IsBeheerder versus 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.

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 contextBeoordeling, 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.

PortaalDoelgroepGlobaal welke contexten
BeheerportaalBeheerders (IsBeheerder)Beheer plus de admin-kant van Moderatie & Retentie
GebruikersportaalGebruikers en anonieme bezoekersBeoordeling (incl. credits en waarderen), melden, en de gebruikerskant van Gebruiker & Toegang
PaginaOmschrijving
Repo-indeling & tech stackMappen op één branch (docs/, app/, bron/), gekozen frameworks en versies
FrontendReact 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 & opnameLive capture (video max. 10s én foto), IMediaStore naar object-storage, promote-flow en range-streaming
Beveiliging & privacyArgon2id, anonieme auth, rate limiting, CORS, HTTPS/HSTS, CSP en de altijd-actieve retentie
DeploymentCloud-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
  • .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 jsonb en de optionele reactie als nullable tekstkolom, de domeininvariant Score 1..5 (per 2026-07-05; zo gebouwd) en Waardering 0..5 met check-constraint, een niet-negatief credit-saldo, en optimistic concurrency via xmin.
  • 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.