Da jeg tegnede min første webapp på en serviet og pludselig lød som en “rigtig” udvikler

Det føles lidt som at forklare et brætspil for nogen. Du kan godt læse alle reglerne højt, men hvis du ikke lige viser spillepladen og hvordan brikkerne må rykke, så står folk bare og nikker høfligt uden at fatte noget.

Systemdesign er det samme. Du kan godt sige “vi har en React frontend, en Node backend og en Postgres database”, men uden et visuelt billede hænger det ikke rigtigt fast hos nogen. Heller ikke hos dig selv.

Jeg opdagede det, da jeg til et interview blev bedt om: “Kan du ikke lige forklare arkitekturen bag dit projekt?”. Jeg begyndte at remse teknologier op. De begyndte at spørge ind til dataflow, eksterne services og fejlscenarier. Jeg var lost.

Efter den samtale gik jeg hjem, fandt en notesbog frem og tegnede min webapp som fire små diagrammer. Samme projekt, helt anden følelse: nu kunne jeg faktisk forklare det.

Hvad “systemdesign” betyder, når du er junior (og hvad det ikke er)

Som junior føles “systemdesign” hurtigt som noget med enterprise-ord og slides i 16:9. Microservices, hexagonal architecture, domain driven et-eller-andet.

Jeg synes, du skal parkere alt det for nu.

Systemdesign for en junior handler om en meget mindre ting:

  • Kan du forklare, hvad din webapp består af?
  • Kan du vise, hvordan data bevæger sig igennem den i én konkret use case?
  • Kan du pege på, hvor det kan gå galt, og hvad du har tænkt om det?

Hvis du kan det, er du foran rigtig mange andre juniorer.

Det her er ikke arkitektur som i “vi designer hele bankens IT”. Det er mere på niveau med de ting, vi ofte snakker om i projektstruktur og arkitektur for små projekter: gøre dit nuværende projekt forståeligt og nogenlunde fornuftigt.

Tricket er: du behøver ikke 10 diagrammer. Jeg bruger stort set altid de samme fire.

Diagram 1 – Kontekst: hvem bruger din app, og hvad snakker den med?

Vi starter længst udefra. Forestil dig, du zoomer ud i Google Maps, så du kan se hele byen, ikke kun din gade.

Her handler det om:

  • Hvem eller hvad bruger systemet? (brugere, andre systemer)
  • Hvilke eksterne services kalder du ud til?

Sådan tegner du kontekstdiagrammet

Tag et blankt papir.

  1. Tegn en firkant i midten: skriv navnet på din app. F.eks. “OrderApp”.
  2. Tegn cirkler udenom for aktører:
    • “Kunde”
    • “Admin”
    • “Background job scheduler” (hvis du har cron-jobs)
  3. Tegn firkanter for eksterne systemer:
    • “Betalingsgateway” (Stripe, QuickPay …)
    • “Mail service” (SendGrid, Mailgun)
    • “OAuth provider” (Google login osv.)
  4. Tegn pile ind og ud af din app og skriv en kort label på pilene.
    • Kunde → OrderApp: “Opret ordre, se ordre”
    • OrderApp → Betalingsgateway: “Initier betaling”
    • Mail service → Kunde: “Ordrebekræftelse”

Det er hele kontekstdiagrammet. Ingen bokse inde i appen endnu. Bare verden udenom.

Hvorfor det her faktisk hjælper dig

To ting sker, når du tegner det her første gang:

  • Du opdager tit en ekstern afhængighed, du havde glemt (“Nå ja, vi rammer jo også det der web API”).
  • Du får et sprog, du kan bruge i samtaler: “I konteksten af den her app har vi to typer brugere og tre eksterne systemer”.

Hvis du skal forbi emner som it-sikkerhed for udviklere senere, er det også guld at vide: “hvor forlader data min app?”.

Diagram 2 – Containers: frontend, backend, database og de andre kasser

Nu zoomer du ind. Forestil dig, du skifter fra oversigtskort til bykort.

Her vil du vise de store “kasser” i din løsning:

  • Browser / mobilapp
  • API / backend
  • Database
  • Queue / worker (hvis du har det)
  • Fil-storage (S3, Cloudinary, hvad du nu bruger)

Den minimalistiske container-tegning

Eksempel for en typisk webapp:

  1. Tegn en kasse “Webbrowser (React SPA)”
  2. Tegn en kasse “Backend API (Node/Express)”
  3. Tegn en database-kasse “Postgres”
  4. (Evt.) en kasse “Job worker”
  5. (Evt.) en kasse “Fil-storage (S3)”

Forbind dem med pile:

  • Browser → Backend: “HTTP/JSON”
  • Backend → Database: “SQL queries”
  • Backend → Job worker: “Send job til queue”
  • Backend → Fil-storage: “Upload/download filer”

Stadig ingen metoder, ingen klasser. Bare kasser og forbindelser.

Hvordan du taler om det uden enterprise-sprog

Når nogen spørger: “Hvordan ser arkitekturen ud?”, kan du nu sige noget i stil med:

“Jeg har en React frontend i browseren, der snakker med et REST API i Node. API’et gemmer data i en Postgres database. Bagved har jeg en lille worker, der kører baggrunds-jobs, f.eks. til at sende e-mails, så brugerens request ikke bliver langsom.”

Det er egentlig bare en mundtlig gennemgang af container-diagrammet. Og det lyder allerede som nogen, der har tænkt sig om.

Diagram 3 – Dataflow i én konkret use case

Nu går vi helt ned på “hvordan sker tingene trin for trin?”. Det er her, mange systemdesign-svar til interviews falder fra hinanden, fordi folk bliver i fluffy niveau.

Vælg én vigtig brugerhandling. For eksempel:

  • “Bruger logger ind”
  • “Bruger opretter en ordre”
  • “Bruger uploader en fil”

Vi tager “opret ordre” som eksempel.

Sådan tegner du et simpelt dataflow

Jeg plejer at tegne det som en lodret sekvens:

  1. Bruger klikker “Opret ordre” i UI’et.
  2. Frontend sender POST /orders til API’et med ordredata.
  3. API’et validerer input.
  4. API’et gemmer ordren i databasen.
  5. API’et opretter et “send e-mail” job i en queue.
  6. API’et svarer frontend med 201 Created + ordren.
  7. Frontend viser “Ordre oprettet”.
  8. Worker plukker “send e-mail” jobbet op og kalder mail-service.

Som tegning kan det være fire lodrette søjler:

  • Bruger
  • Frontend
  • API
  • Database / Worker

Og så små pile henover med de vigtigste kald.

Lille kode-agtig pseudo, der matcher tegningen

// frontend
async function createOrder(order) {
  const res = await fetch('/api/orders', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(order)
  })

  if (!res.ok) {
    // vis fejl
  }

  const data = await res.json()
  // vis "ordre oprettet" med data
}

// backend (meget forenklet)
app.post('/api/orders', async (req, res) => {
  const order = validateOrder(req.body)

  const saved = await db.insert('orders', order)

  await queue.add('sendOrderEmail', { orderId: saved.id })

  res.status(201).json(saved)
})

Tegningen og pseudo-koden fortæller samme historie. Diagrammet er til mennesker, koden er til computeren.

Typisk fejl: du vælger en for kompliceret flow

Du behøver ikke starte med “hele appen”. Vælg én ting:

  • Login-flow med JWT eller sessioner
  • Opret ressourcer
  • Upload fil

Hvis du i forvejen bakser med login, er det faktisk en god øvelse at koble det på et diagram og måske samtidig snuse til, hvordan JWT og sessions egentlig hænger sammen.

Diagram 4 – Failure paths: hvad sker der, når noget går galt?

Det her er niveauet, der ofte imponerer interviewere: du har ikke kun tænkt på success flowet.

Vi bruger samme use case som før, men nu spørger du:

  • Hvad hvis databasen er nede?
  • Hvad hvis mail-servicen fejler?
  • Hvad hvis brugeren sender den samme request to gange?

Egentlig er det bare en udvidelse af dataflowet

Tag dit dataflow-diagram og tilføj røde bokse for fejl.

Eksempel:

  • Når API’et vil gemme i databasen:
    • Fejl-scenarie: DB timeout
    • Handling: Returner 503 Service Unavailable til frontend
    • Frontend: Vis “Noget gik galt, prøv igen”
  • Når worker sender e-mail:
    • Fejl-scenarie: Mail service svarer 500
    • Handling: Retry 3 gange, marker ordre som “email_pending” hvis stadig fejl

Det kan være små noter ved pilene, du allerede har tegnet.

Et lille pseudo-eksempel på retry-tankegangen

// worker-job
queue.process('sendOrderEmail', async (job) => {
  try {
    await mailService.sendOrderConfirmation(job.data.orderId)
  } catch (err) {
    if (job.attemptsMade < 3) {
      throw err // queue laver retry
    } else {
      await db.update('orders', job.data.orderId, {
        email_status: 'failed'
      })
    }
  }
})

Pointen er ikke koden. Pointen er, at du har overvejet: “hvad hvis mail-service er ustabil?” og markeret i dit failure-diagram, hvad der sker.

Seks tradeoffs du næsten altid kan nævne uden at lyve

Når du har de fire diagrammer, mangler du én ting for at lyde som nogen, der har styr på systemdesign for junior-niveau: du skal kunne nævne et par tradeoffs.

Tradeoff betyder bare: “jeg valgte A i stedet for B, fordi X, og prisen er Y”.

Her er seks emner, du næsten altid kan bruge med din egen app:

1. Konsistens

Eksempel-sætning:

“Jeg valgte at sende e-mails via et baggrundsjob. Det betyder, at brugeren kan få bekræftelsen vist i UI’et, selvom e-mailen ikke er sendt endnu. Fordelen er hurtigere respons, ulempen er en lille risiko for, at e-mailen fejler selvom ordren er oprettet.”

2. Latency (svartid)

Eksempel-sætning:

“Jeg har lavet en simpel REST-API uden caching. Det er nemt at forstå, men det giver lidt langsommere svartider på sider, der henter meget data. Senere kunne man optimere med caching på de mest læste endpoints.”

3. Caching

Du behøver ikke have implementeret caching for at nævne det. Du kan også sige, hvorfor du ikke har gjort det:

“Jeg har fravalgt caching pt., fordi data ændrer sig ofte, og jeg ville først have basismekanikken helt stabil. En oplagt forbedring ville være at cache læse-endpoints og have en invalidation ved skriv.”

4. Sikkerhed

Her kan du fx tale om auth, input validation, secrets, osv.

“Jeg har valgt simpel session-baseret login i stedet for JWT. Det gør det nemmere at invaliderer sessions, og det er fint til en klassisk webapp, hvor vi ikke har mange forskellige klienter.”

Hvis du har læst om, hvordan små hobbyprojekter kan blive til sikkerhedsmareridt, ved du allerede, at bare det at nævne sikkerhed bevidst er et plus.

5. Omkostning

Selv til studieprojekter kan du sige noget:

“Jeg kører alt på en lille hosted Postgres og en billig app-hosting. Det holder omkostningerne nede, men betyder, at jeg ikke har høj tilgængelighed eller automatisk skalering.”

6. Kompleksitet

Den vigtigste som junior: erkend når du har valgt den simple løsning.

“Jeg har valgt én monolitisk backend fremfor microservices. Det er lettere at udvikle som én person, men det betyder, at deployment altid sker samlet.”

Hvis du kan koble bare to-tre af de her punkter til dine fire diagrammer, er du allerede i gang med systemdesign på et niveau, der matcher rigtigt godt med softwareudvikling i praksis, ikke powerpoint-arkitektur.

Eksempel – sådan kunne du forklare dit system i et interview

Lad os samle det på en meget konkret måde, du kan øve.

Forestil dig, du bliver spurgt:

“Kan du forklare arkitekturen bag din ordre-app?”

Her er et svar, du kan bruge som skabelon og tilpasse:

  1. Kontekst
    “Overordnet har jeg en lille ordre-app, hvor to typer brugere interagerer: kunder, der opretter ordrer, og admins, der kan se og opdatere dem. Systemet snakker også med en betalingsgateway og en mail-service.”
  2. Containers
    “Frontenden er en React app, der kører i browseren og taler med et Node/Express API. API’et gemmer data i en Postgres database. Til baggrundsopgaver har jeg en simpel worker, der henter jobs fra en queue.”
  3. Dataflow for use case
    “Når en kunde opretter en ordre, sender frontenden et POST-kald til /orders med varerne. API’et validerer input, gemmer en ordre i databasen og opretter et job til at sende en bekræftelsesmail. Så returnerer API’et 201 Created til frontenden, der viser ordredetaljer. Workeren sender så e-mailen i baggrunden.”
  4. Failure paths
    “Hvis databasen svarer langsomt eller er nede, returnerer API’et en 503 fejl, og brugeren får en besked om at prøve igen. Hvis mail-servicen fejler, forsøger worker-jobbet tre gange og markerer så ordren som ‘email_failed’, så vi kan se det i admin-UI’et.”
  5. Tradeoffs
    “Jeg har bevidst holdt det i én monolitisk backend i stedet for microservices, fordi det er et lille projekt med én udvikler. Det gør deployment og debugging nemmere, men betyder også, at alt deployes samlet. Jeg bruger ikke caching pt., så noget kunne blive hurtigere senere, hvis appen skulle skaleres.”

Det svar kan vi egentlig mappe direkte til de fire diagrammer. Det er det, du øver dig på.

Skabelon – sådan laver du dine egne fire diagrammer på 30 minutter

Hvis jeg skulle timeboxe det til én kop kaffe, ville jeg gøre det her.

Step 1 – 5 minutter: skriv appens elevatorpitch

To linjer, max:

  • Hvem bruger den?
  • Hvad gør de?

Eksempel:

“Studerende kan tracke deres afleveringer og deadlines. Lærere kan se oversigter over afleverede opgaver.”

Step 2 – 5 minutter: kontekstdiagram

På papir:

  • Midterboks med app-navn
  • Cirkler for brugertyper
  • Firkanter for eksterne systemer (betaling, mail, OAuth osv.)
  • Pile ind/ud med korte labels

Step 3 – 5 minutter: containerdiagram

Tegn kun de kasser, du faktisk har:

  • Browser / mobil
  • Backend API
  • Database
  • Queue / worker (hvis ja)
  • Fil-storage (hvis ja)

Forbind med pile og skriv “HTTP”, “SQL”, “Queue” hen over.

Step 4 – 10 minutter: vælg én use case og tegn dataflow

Vælg en handling du er stolt af, ikke nødvendigvis den mest komplekse. F.eks. “opret opgave”, “tilføj kommentar”, “upload profilbillede”.

Tegn lodrette søjler for hver “kasse” fra container-diagrammet, og tegn pilene i den rækkefølge, ting sker.

Tilføj 6-8 trin max. Hvis du har 20, har du valgt for meget. Del det op.

Step 5 – 5 minutter: markér 2-3 failure paths

På det samme dataflow-diagram:

  • Marker et sted hvor en ekstern service kan fejle.
  • Marker et sted hvor databasen kan være nede eller langsom.
  • Marker et sted hvor brugeren kan gøre noget dumt (dobbeltklik, invalid input).

Skriv en lille note: “her vil jeg gerne have retry” eller “her viser jeg en brugerfejl”.

Ekstra: skriv 5 linjer README-tekst ud fra diagrammerne

Åbn dit repo og opdater README med en kort sektion “Arkitektur”:

## Arkitektur

- React frontend der kalder et Node/Express REST API
- API gemmer data i Postgres
- Baggrunds-jobs (e-mails) kører i en lille worker via en queue
- Systemet bruger Stripe til betaling og SendGrid til e-mails
- Fokus: simpel monolit, nemt at udvikle som én person

Hvis du vil arbejde mere seriøst med README’er, kan du bygge videre i den stil, jeg har skrevet om i artiklen om README’er der rent faktisk bliver læst.

Når de fire diagrammer og de fem linjer tekst er på plads, har du et systemdesign-niveau, der matcher “systemdesign for junior” ret fint.

Udover kontekstdiagrammet bruger jeg typisk: 1) Komponentdiagram - oversigt over appens indre dele (frontend, backend, database, jobs, caches). 2) Dataflow/sekvensdiagram - hvordan en konkret use case bevæger sig gennem systemet trin for trin. 3) Fejl- og afhængighedskort - hvor ting kan gå galt, hvilke afhængigheder der er kritiske, og hvilke simple mitigations du har tænkt på.
Tid det: lav en 60-90 sekunders 'elevator pitch' for hvert diagram, og øv det højt flere gange mens du tegner. Optag dig selv eller øv med en ven, bed om spørgsmål-tilbageslag, og træn at svare kort på typiske follow-ups som skalerbarhed og fejlscenarier.
Brug simple værktøjer som diagrams.net (draw.io), Excalidraw, Miro eller Figma for hurtigt at lave rene diagrammer; PlantUML hvis du foretrækker tekst-baseret diagrammer. Hold det minimalistisk - få former, tydelige pile og korte labels, så det er let at forklare på 1-2 minutter.
Vær ærlig om hvad du ikke ved, men vis hvordan du ville finde svaret - forslag til målinger, logs eller simple antagelser du kan teste. Giv et kort, velovervejet svar baseret på rimelige antagelser og tilbyd at tegne en alternativ løsning eller et trade-off for at vise tænkemåden.

Lasse Falkenberg er typen, der begyndte at rode med HTML og CSS for at lave en simpel bandside – og opdagede, at det var langt sjovere at få knapperne til at virke end at stå på scenen. Siden har han kastet sig over alt fra små JavaScript-snippets til Python-scripts, der kan spare ham for kedeligt, manuelt arbejde i hverdagen.

Han har lært det meste ved at bygge ting, der lige præcis løser hans egne problemer: en lille webapp til at holde styr på brætspilsaftener, et script til at rydde op i rodede mapper, eller en enkel side til at dele noter med venner. Undervejs har han kæmpet sig gennem alle de klassiske fejl – semikolon, forkerte indrykninger og variabler, der hedder noget helt andet end man tror – og det er præcis den rejse, han deler på Coding Class.

På Coding Class skriver Lasse praktiske, jordnære guides, der tager udgangspunkt i små, konkrete opgaver: noget du kan se, teste og bygge videre på med det samme. Han elsker at bryde en opgave ned i små bidder, vise den fulde kode og forklare linje for linje, hvad der sker – inklusive de typiske bugs, du med stor sandsynlighed også støder på.

For Lasse handler kodning ikke om flotte titler eller store ord, men om følelsen af at få noget til at virke – og om at du som læser kan gå derfra med noget, du selv har bygget. Hvis du kan kende glæden ved at få en fejl til endelig at forsvinde, er du lige på bølgelængde med hans måde at lære fra sig på.

Send kommentar

You May Have Missed