# Systeemarchitectuur ## 1. Architectuurstijl VacatureRadar gebruikt een modulaire Django-monoliet. De domeinen delen één relationele database maar hebben duidelijke model-, service-, task- en UI-grenzen. Dit houdt deployment en beheer eenvoudig op Unraid, terwijl de kernlogica testbaar en later splitsbaar blijft. ## 2. Context ```mermaid flowchart LR U[Gebruiker] -->|browser| W[VacatureRadar web] M[Vacaturemailbox] -->|IMAP| C[Collectielaag] E[Publieke werkgevers- en ATS-pagina's] -->|HTTP(S)| C C --> P[Extractie en normalisatie] P --> D[(PostgreSQL)] D --> S[Scoring en dedupe] S --> W S --> N[Digest/outbox] N -->|SMTP| U O[Optionele Ollama] <--> S ``` ## 3. Containerarchitectuur ```mermaid flowchart TB RP[Reverse proxy / LAN] --> WEB[web: Gunicorn + Django] WEB --> PG[(PostgreSQL)] WEB --> REDIS[(Redis)] WORKER[Celery worker] --> PG WORKER --> REDIS WORKER --> NET[Goedgekeurde publieke bronnen] BEAT[Celery beat] --> REDIS OLLAMA[Ollama optioneel] <--> WORKER MAIL[IMAP/SMTP optioneel] <--> WORKER ``` Containerrollen: - **web** — authenticatie, dashboard, detailpagina's, profiel- en beheerflows; - **worker** — bronfetches, parsing, scoring, lifecycle, mailbox en verzending; - **scheduler** — periodieke planning, geen bedrijfslogica; - **postgres** — canonieke data, historie en outbox; - **redis** — broker/resultbackend, geen bron van waarheid; - **ollama** — optioneel lokaal model, niet nodig voor kern. ## 4. Domeinmodules ### `apps/core` - liveness/readiness; - dashboardaggregatie; - systeemstatus; - securityheaders en navigatiecontext. ### `apps/profiles` - user-scoped zoekprofielen; - harde regels, soft preferences en scoregewichten; - profielversies en gecontroleerde feedbackdelta's. ### `apps/sources` - bronregister en policy/status; - veilige fetcher; - adapters en source discovery; - raw document, source run en e-mailrecord; - Celery-orkestratie. ### `apps/jobs` - canonieke vacature- en werkgevermodellen; - normalisatie, features, dedupe en provenance; - scoreberekening en scorehistorie; - feedback, lifecycle en sollicitatiedossier. ### `apps/notifications` - dagelijkse payloadselectie; - idempotente outbox; - rendering en SMTP-verzending. ## 5. Laagregels ```text views/templates -> application services -> domain models / adapter interfaces Celery tasks -> application services -> domain models / adapter interfaces adapters -> pure extraction DTOs (geen ORM writes) fetcher -> policy + URL security (geen parserlogica) AI service -> gestructureerde optionele features (geen beslissingsbevoegdheid) ``` Verboden afhankelijkheden: - template/view die externe websites fetcht; - adapter die direct `JobPosting` schrijft; - AI die `Feedback`, `Application`, `Source.policy` of hard rules wijzigt; - task met verborgen businessregels die niet in een service testbaar zijn; - model save-hook die netwerkverkeer uitvoert. ## 6. Verwerkingsflow ```mermaid sequenceDiagram participant B as Beat participant T as Source task participant F as Safe fetcher participant A as Adapter registry participant P as Pipeline participant DB as PostgreSQL participant S as Scoring B->>T: schedule due source T->>DB: create SourceRun T->>F: fetch approved URL F->>F: policy + DNS/IP + redirect checks F-->>T: bounded document / 304 / error T->>DB: save RawDocument T->>A: extract(document) A-->>P: ExtractionResult P->>P: normalize + sanitize + features P->>DB: find exact/fuzzy existing job P->>DB: upsert job, alias, evidence, version P->>S: score for active profiles S->>DB: append ScoreRun T->>DB: finish SourceRun + schedule next ``` ## 7. Consistentie en transacties - `process_raw_document` en `persist_draft` zijn transactioneel; - unieke sleutels beschermen races op canonieke vacature, e-mail en outbox; - scorehistorie is append-only; - raw document en source run vormen debugcontext; - een mislukte individuele e-maillink wordt in het e-mailrecord vastgelegd zonder andere links te verliezen; - externe calls gebeuren buiten lange database-locks waar mogelijk. ## 8. Schaalpad De eerste schaalgrens ligt niet bij HTTP-requestvolume maar bij het aantal bronjobs. Tot ongeveer tienduizenden vacatures en honderden bronnen volstaat de monoliet. Optimalisaties vóór opsplitsing: 1. indexes en querysetprofiling; 2. chunked scoring; 3. task queues per prioriteit; 4. per-domain rate limit en distributed lock; 5. materialized/latest scoreselectie; 6. raw-bodycompressie/objectstorage. Pas na aantoonbare bottleneck kan collection worker als aparte service worden afgesplitst. De adapter- en DTO-grenzen maken dat mogelijk zonder nu operationele complexiteit toe te voegen. ## 9. Deploymenttrust boundaries - browser ↔ web: authenticated session + CSRF; - web/worker ↔ database/Redis: intern containernetwerk; - worker ↔ internet: alleen goedgekeurde URL's via safe fetcher; - worker ↔ IMAP/SMTP/Ollama: expliciete environmentconfiguratie; - reverse proxy ↔ internet: TLS, host filtering en toegangsbeleid buiten de app. Zie `SECURITY_ARCHITECTURE.md` en `docs/quality/THREAT_MODEL.md`.