Realtime communicatie voor digitale schermen

Hoe ik een bestaand schermplatform uitbreidde met een socketserver die wijzigingen aankondigt aan verbonden mediaplayers.

Backend & architectuur 12 min leestijd

Dit artikel is geanonimiseerd. Bedrijfs-, product- en persoonsnamen en interne verwijzingen zijn weggelaten of vervangen, ook in diagrammen en voorbeelden.

Wie content publiceerde voor de digitale schermen, moest wachten tot de mediaplayers opnieuw bij het CMS (contentmanagementsysteem) controleerden. Dat deden ze ongeveer iedere vijf minuten. Een publicatie vlak na zo'n controle kon dus bijna vijf minuten blijven liggen. Mijn opdracht was een socketserver te ontwikkelen waarmee het CMS zelf een wijziging kon aankondigen aan verbonden mediaplayers. Het bestaande PHP/CakePHP-CMS en de werking van de mediaplayers moesten behouden blijven.

Deze praktijkopdracht vormde ook de basis voor mijn ontwerpgerichte afstudeeronderzoek Informatica bij Avans Hogeschool. Een socketserver was al gevraagd; ik onderzocht welke techniek paste, hoe de onderdelen moesten samenwerken en hoe ik de werking kon toetsen.

Ik leverde een werkende socketserver in TypeScript op, met tests, mijn scriptie en technische documentatie. Deze afzonderlijke backendapplicatie onderhoudt de verbindingen met mediaplayers en stuurt meldingen uit het CMS door naar de juiste ontvangers. Daarnaast paste ik de contentcontrole in het CakePHP-CMS aan. Ik werkte zelfstandig, met AI (Artificial Intelligence) als ondersteuning bij analyse, code, tests en documentatie. De keuzes en de controle van de uitkomsten bleven mijn verantwoordelijkheid. De socketserver was lokaal getoetst; beta-uitrol en acceptatie moesten bij de overdracht nog plaatsvinden.

  1. Voorbereiding
    • Het probleem
    • De onderzoeksvraag
    • Aanpak
  2. Uitvoering
    • Analyse
    • Ontwerp
    • Realisatie en toetsing

    Werkende socketserver

  3. Afronding
    • Resultaat
    • Meetgrenzen
De drie fasen geven de hoofdlijn weer. Onderzoek, ontwerp, bouw en toetsing overlapten tijdens de uitvoering.

Voorbereiding

Vaker controleren zou ook meer werk veroorzaken

In de PHP-code volgde ik hoe het CMS een aanvraag van een mediaplayer verwerkte. Bij vrijwel iedere controle bouwde het een volledige afspeellijst op, ook als de inhoud gelijk was gebleven. Die lijst gebruikte SMIL (Synchronized Multimedia Integration Language) om vast te leggen wat de mediaplayer moest afspelen. Het CMS kon de lijst pas teruggeven nadat de mediaplayer erom had gevraagd. Dit periodiek controleren heet polling.

Persoon CMS-beheerder
Extern systeem · 1..N Mediaplayer

Speelt content af op het scherm

CMS · softwaresysteem · CakePHP 2.10
Container · PHP 7.4 · CakePHP 2.10 CakePHP-applicatie

Verwerkt polls, genereert SMIL

  1. CMS-beheerder → CakePHP-applicatieBeheert content · HTTPS
  2. Mediaplayer → CakePHP-applicatiePollt · elke circa 5 min
  3. CakePHP-applicatie → MediaplayerVolledige SMIL-afspeellijst terug
Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De mediaplayer vraagt periodiek een afspeellijst op; het CMS beantwoordt die aanvraag.

Een korter interval zou nieuwe publicaties eerder zichtbaar maken voor de mediaplayer, maar ook meer aanvragen aan het CMS opleveren. Bovendien moest het CMS zelf berichten kunnen sturen. De uitbreiding moest daarom naast de bestaande route werken. Polling bleef nodig voor oudere mediaplayers en om na een gemiste melding alsnog actuele content op te halen.

  1. Vertraging tot 5 min

    Wachten op de volgende ronde

  2. Meer periodieke verzoeken

    Elk scherm controleert afzonderlijk

  3. Wachten op een aanvraag

    Het CMS kan geen contact starten

  4. Beperkt verbindingsinzicht

    Geen doorlopend statusinzicht

Meer verzoeken verkleinen het wachtinterval, maar voegen geen door het CMS gestarte berichtenroute toe.
Polling behoudenMet socketserver · doel
Controle elke circa 5 minBerichtontvangst p95 < 1 s
Meer schermen, meer controlesSchaalbaarheid Aparte berichtdistributie
Geen server-pushKansen Berichten vanuit het CMS
Beperkt verbindingsinzichtBetrouwbaarheid Verbinding bewaken en fouten vastleggen
Het doel van p95 < 1 s geldt voor berichtontvangst; downloaden en afspelen vallen erbuiten. Databasewinst en lagere kosten zijn niet aangetoond.

Van de opdracht naar toetsbare vragen

In het eerste overleg bepaalden we de opdracht. Ik werkte die uit in een voorstel voor mijn opleiding, met de aanleiding, eerste eisen en het beoogde eindproduct. Na goedkeuring organiseerde ik het onderzoek en de uitvoering zelf.

  1. Stap 01 Overleg
    • Lead developer
    • Product owner
  2. Stap 02 Voorstel
  3. Stap 03 Goedkeuring
De opdracht stond vast; de technische invulling vroeg nog om onderzoek.

Mijn plan van aanpak verbond vijf deelvragen: hoe werkte het platform, wat moest de uitbreiding kunnen, welke techniek paste, hoe kon ik de oplossing bouwen en hoe zou ik haar toetsen? Ik onderzocht broncode, documentatie en literatuur, sprak met betrokkenen en beproefde oplossingen met een prototype en tests.

Plan van
aanpak
  1. Probleemstelling
  2. Doelstelling
  3. Afbakening
  4. Centrale vraag
  5. Vijf deelvragen
  6. Theoretisch kader
  7. Methodologie
  8. Planning
Deze onderdelen verbinden de onderzoeksvraag met de uitvoering en de beoordeling van het resultaat.

Documentatie en code kwamen niet altijd overeen. Voor de feitelijke werking ging ik uit van de actuele code. Tijdens het bouwen kwamen vervolgens nieuwe vragen naar voren, waardoor onderzoek, ontwerp en implementatie elkaar bleven beïnvloeden.

  1. Deelvraag 1 Huidige werking

    Code en documentatie analyseren.

    De bestaande werking maakt duidelijk wat de uitbreiding moet oplossen.
  2. Deelvraag 2 Toetsbare eisen

    Eisen bespreken en prioriteren.

    De eisen geven richting aan de technologievergelijking.
  3. Deelvraag 3 Technologie afwegen

    Kandidaten vergelijken op projectcriteria.

    De gekozen techniek vormt de basis voor het ontwerp en de bouw.
  4. Deelvraag 4 Ontwerpen en bouwen

    Ontwerp en prototype uitwerken.

    De implementatie wordt met functionele scenario’s en prestatiemetingen getoetst.
  5. Deelvraag 5 Resultaat toetsen

    Scenario’s toetsen aan de eisen.

Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De deelvragen bouwen op elkaar voort. In de uitvoering overlapten analyse, ontwerp en bouw.

Onderzoek en ontwikkeling samen plannen

Voor het traject had ik twintig weken met zestien uur projecttijd per week. Mijn plan van aanpak bevatte al een globale planning. Ik werkte die uit in negen sprints, met taken voor onderzoek, bouwen, tests en documentatie.

Daarbij koppelde ik een kennisbank en mijn eigen Jira-project via MCP (Model Context Protocol) aan een AI-agent. De agent hielp mijn plan te verdelen over epics en bijbehorende taken. Ik controleerde de indeling en paste die waar nodig aan. Het Gantt-overzicht liet de planning in de tijd zien; op het Jira-bord hield ik de voortgang bij.

  • KennisbankOpdracht en beoordelingscriteria
    MCP
  • JiraProjectomgeving en planning
    MCP
AI-agentOnder mijn regie
  • Planning uitwerkenEpics, issues en sprints
De kennisbank en Jira waren via MCP beschikbaar voor de agent. De inhoudelijke keuzes bleven bij mij.

Scroll horizontaal om de volledige planning te bekijken.

Werk Planning van februari tot en met juni
Sprints
123456789
Onderzoeksproject
Voorbereiding
Plan van aanpak
Uitvoering
Conceptverslag
Socketserver
Definitief verslag
Afronding
Presentatie en verdediging
De balken tonen de globale planning; exacte begin- en einddagen zijn weggelaten.
BacklogBordTijdlijn

To Do

  • Beoordeling van het eindverslag ontvangenAfronding
  • Onderzoek en implementatie presenterenAfronding
  • Herstel bij uitval van de berichtendienst onderzoeken

In Progress

  • Presentatie uitwerkenAfronding
  • Restart-opdracht vanuit CMS werkt niet via de mediaplayerSocketserver
  • Toegang van langdurig verbonden schermen opnieuw controlerenBeveiliging

In Review

  • Validatie en performancetestsSocketserver
  • CMS-integratie via Strangler FigSocketserver
  • Pipeline-smoketest vervangen door compose-stackSocketserver
  • Prototype socketserver bouwenSocketserver
  • Softwareontwerp uitwerkenSocketserver

Done

  • Voortgangsupdates versturenOnderzoeksproject
Dit was de stand van het projectbord op één moment. Terugkerende voortgangsupdates zijn samengenomen.

Uitvoering

Snel bezorgen, bij de juiste mediaplayer

Met technische en productverantwoordelijken besprak ik snelheid, groei en uitval. Ik vertaalde hun behoeften naar eisen met een herkomst, prioriteit en voorwaarden voor goedkeuring. MoSCoW (Must, Should, Could en Won’t) hielp onderscheid te maken tussen noodzakelijk werk en aanvullingen.

Voor berichtontvangst gold bijvoorbeeld een tijdsgrens. Bij klantscheiding moest ik kunnen aantonen dat een bericht niet bij een andere klant terechtkwam. Ook de grens van de opdracht stond vast: mediaplayers bleven zelf hun mediabestanden downloaden. Deze afspraken bepaalden zowel het ontwerp als de latere tests.

  1. 1
    Stakeholdergesprekken

    Verwachtingen en bestaande werking

  2. 2
    Drie prioriteiten

    Snelheid, groei en continuïteit

  3. 3
    MoSCoW-prioritering

    Must / Should / Could / Won’t

De meetbare eisen
17functionele eisen
12niet-functionele eisen
  • Berichtbezorgingp95 onder 1 s in de lokale prestatietoets.
  • Herverbindenp95 onder 5 s in het herstelscenario.
  • KlantscheidingBerichten voor een andere klant afwijzen.
  • ContinuïteitPolling blijft naast push functioneren.
De lijst bevat 17 functionele en 12 niet-functionele eisen, inclusief werk dat expliciet buiten scope valt.

De bestaande omgeving gaf de doorslag

Met een multicriteria-analyse vergeleek ik oplossingen op twaalf gewogen criteria, waaronder berichtbezorging, herstel na uitval, integratie en kennis binnen de organisatie. Ik onderscheidde de verbinding met de mediaplayer van de berichtenuitwisseling tussen systemen. Socket.IO en Centrifugo waren kandidaten voor de eerste taak; Redis Pub/Sub (publish-subscribe), Redis Streams en RabbitMQ onderzocht ik voor de tweede.

  1. Verkennen29

    Kandidaten uit technische bronnen.

  2. Selecteren26

    Formeel beoordeeld; drie vooraf uitgesloten wegens onderhoud of veroudering.

  3. Afwegen9

    Kandidaten voor de gewogen vergelijking.

2 platforms gelijk op 93

De keuzeSocket.IO + Redis Pub/Sub

Frameworks en platforms · selectie uit de matrix

  • Socket.IOGekozen 93 / 99
  • Centrifugo 93 / 99
  • deepstream.io 67 / 99

Brokers · eigen architectuurlaag

  • RabbitMQ 70 / 99
  • Redis Pub/SubGekozen 69 / 99
  • Redis Streams 69 / 99
Historische projectscores, gewogen op twaalf criteria. Verschillende architectuurlagen zijn geen gezamenlijke ranglijst.

Socket.IO en Centrifugo eindigden gelijk. De bestaande Redis- en containerinfrastructuur en de aanwezige kennis gaven de doorslag voor een eigen service met Socket.IO en Redis Pub/Sub. Ik bouwde die in TypeScript op Node.js. Daarmee kreeg de realtime communicatie een eigen plek naast het CMS. Daar stond extra beheer tegenover: een afzonderlijke service en berichtafspraken die tussen CMS, service en mediaplayers moesten blijven aansluiten.

Een wijziging aankondigen, de contentroute behouden

Het CMS bleef bepalen welke content voor welke doelgroep beschikbaar was. Mijn socketserver kondigde een wijziging aan; de mediaplayer haalde de inhoud op en speelde die af. Zo kon ik de communicatie uitbreiden terwijl contentbeheer en afspeellogica bij de bestaande onderdelen bleven.

Polling5 min
Push · doel< 1 s

Een seintje, geen content

Vijf minuten is het bestaande controle-interval; één seconde is het doel voor berichtontvangst. Dit zijn geen metingen van zichtbare schermverversing.
CMS · publicatievoorbeeld
Bericht bewerkenTitelWelkom bij de receptieInhoud

Meld je bij de balie voor je afspraak.

AfspeellijstAfspeellijst 1Publiceren
Mediaplayer · na ophalen van de content
Afspeellijst 1Welkom bij de receptie

Meld je bij de balie voor je afspraak.

Receptie
Schematisch voorbeeld van publiceren en afspelen, zonder live CMS, echte mediaplayer of tijdmeting.

Het CMS publiceert daarvoor een bericht op een Redis-kanaal. De socketserver is op die kanalen geabonneerd, controleert het bericht en stuurt de melding naar de juiste verbonden mediaplayers. Dit publish-subscribe-model voorkomt dat het CMS elk draaiend exemplaar van de service afzonderlijk moet aanspreken.

PersoonCMS-beheerder

Beheert en publiceert content.

Bestaande containerCakePHP-CMS

Content, identiteit en SMIL-afspeellijsten.

Extern systeemMediaplayer

Socket.IO-client; haalt op en speelt af.

Redis / ElastiCachePub/Sub

Verspreidt publicatiesignalen.

Nieuwe communicatielaag · ontworpen doelomgeving

AWS ALBLoad balancer

TLS en WebSocket-upgrade.

Node.js · Elastic BeanstalkSocketserver

Controleert berichten en kiest de ontvangers; gebouwd in TypeScript.

  1. CMS-beheerder → CakePHP-CMSDe beheerder wijzigt en publiceert content.
  2. CakePHP-CMS → Pub/SubHet CMS publiceert een event op Redis.
  3. Pub/Sub → SocketserverDe socketserver ontvangt berichten via zijn abonnement op Redis.
  4. Socketserver ↔ Load balancerSocket.IO-verkeer tussen de service en de load balancer.
  5. Mediaplayer ↔ Load balancerDe mediaplayer verbindt en identificeert zich; de service verstuurt signalen en ontvangt bevestigingen.
  6. Socketserver → CakePHP-CMSDe socketserver laat de identiteit van de mediaplayer via het CMS controleren.
  7. Mediaplayer → CakePHP-CMSNa een signaal haalt de mediaplayer zelf de SMIL-afspeellijst op.
  8. Mediaplayer → CakePHP-CMSDe periodieke contentcontrole blijft naast push beschikbaar.
Het CMS blijft bron van waarheid. De nieuwe laag bezorgt signalen; content ophalen en polling blijven rechtstreekse mediaplayer–CMS-routes. De cloudopstelling is het ontworpen uitvoeringsdoel.

Voor de ontvangerselectie koppelde ik Socket.IO-rooms, groepen verbindingen, aan klanten, mediaplayers en afspeellijsten. In het voorbeeld hieronder gebruiken A, B en C dezelfde afspeellijst. D hoort bij dezelfde klant, maar gebruikt een andere lijst en ontvangt deze melding daarom niet.

Met ioredis ontvangt elk exemplaar van de service de CMS-publicatie via Redis en bezorgt die bij zijn eigen verbonden mediaplayers. De Socket.IO Redis-adapter heeft daarnaast een afzonderlijke taak: Socket.IO-berichten tussen service-exemplaren uitwisselen. Hij verspreidt deze CMS-publicatie niet nogmaals.

CMSRedis Pub/SubKlant A · Afspeellijst 1
Socketinstantie 1

Kiest uit de eigen verbonden mediaplayers

  • Mediaplayer A Afspeellijst 1 Ontvangt melding
  • Mediaplayer B Afspeellijst 1 Ontvangt melding
Socketinstantie 2

Kiest uit de eigen verbonden mediaplayers

  • Mediaplayer C Afspeellijst 1 Ontvangt melding
  • Mediaplayer D Afspeellijst 2 Niet geselecteerd
A, B en C gebruiken afspeellijst 1 en ontvangen de melding. D gebruikt een andere lijst. Dit is een schematisch voorbeeld.

De verbinding was de basis voor mijn eigen controles

Socket.IO bood benoemde berichten, groepen ontvangers en ontvangstbevestigingen. Engine.IO verzorgde daaronder het opzetten en bewaken van de verbinding. Het transport kan beginnen met long-polling via HTTP (Hypertext Transfer Protocol) en overgaan naar een blijvende WebSocketverbinding. Deze transportkeuze staat los van de periodieke contentcontrole bij het CMS.

Op die basis bouwde ik de regels voor identiteit, berichtinhoud en ontvangers. Een ontvangstbevestiging zegt daarbij alleen dat het bericht is aangekomen. Het downloaden en tonen van nieuwe content gebeurt daarna op de mediaplayer.

Socket.IO

Berichten, ontvangersgroepen, ontvangstbevestigingen en herverbinden.

Engine.IO

De verbinding opzetten, bewaken en van transport laten wisselen.

Transport · Engine.IO kiest
HTTP-long-pollingVerbinding opbouwen of terugvallenupgrade WebSocketBlijvende verbinding
TCP en TLS
Het HTTP-long-pollingtransport staat los van de periodieke contentcontrole bij het CMS. Identiteit, berichtvalidatie en toegestane ontvangers blijven regels van mijn eigen service.

Ontwerpen terwijl de berichtafspraken veranderden

Het prototype maakte duidelijker welke gegevens de berichten moesten bevatten en hoe de service daarop moest reageren. Ik werkte de berichtafspraken en het ontwerp daarom tijdens de bouw bij.

Met UML (Unified Modeling Language) beschreef ik de gegevens en interacties. Een sessie, verbinding en bericht hebben bijvoorbeeld elk een eigen levensduur. Bij de identiteitscontrole gaf ik de CMS-koppeling, sessieopslag en telemetrie van buitenaf mee. Deze dependency injection maakte het mogelijk om in een test een CMS-antwoord of tijdelijke fout na te bootsen.

De keuzes legde ik vast in ADR’s (Architecture Decision Records), met aanleiding, alternatieven en gevolgen. Het Strangler Fig-principe diende als ontwerpkader om de communicatielaag naast het bestaande systeem te plaatsen. Polling bleef bestaan, waardoor beide routes onderhouden moesten worden.

  1. ADR 1Socket.IOi.p.v. Kale WebSocket

    Verbindingsgedrag benutten binnen een eigen service.

    • RedenRooms, herverbinden en bevestigingen beschikbaar.
    • AfwegingEigen authenticatie en berichtregels blijven nodig.
  2. ADR 2Redis Pub/Subi.p.v. Directe koppeling

    CMS en socketserver via events verbinden.

    • RedenAansluiten op de bestaande Redis-infrastructuur.
    • AfwegingGeen duurzame eventgeschiedenis of replay: gemiste berichten worden niet bewaard.
  3. ADR 3Uitbreiden naast pollingi.p.v. Alles tegelijk vervangen

    Een aparte communicatielaag, met Strangler Fig als ontwerpkader.

    • RedenBestaande mediaplayers en contentcontrole behouden.
    • AfwegingTwee communicatiepaden blijven bestaan.
  4. ADR 4AWS als uitvoeringsdoeli.p.v. Zelfhosting

    Elastic Beanstalk met ElastiCache en een load balancer.

    • RedenAansluiten op de infrastructuurrandvoorwaarden.
    • AfwegingDe werking in deze omgeving moest nog worden getoetst.
  5. ADR 5Sessies in geheugeni.p.v. Externe sessiestore

    Een sessie tijdens korte verbindingsuitval in geheugen bewaren.

    • BeperkingSessies verdwijnen bij een herstart en worden niet tussen service-exemplaren gedeeld.
De beslisdocumentatie legt de uiteindelijke afwegingen vast. Ontwerp en implementatie werden tijdens het bouwen bijgesteld.

Bij een korte onderbreking bewaarde de service sessiegegevens tijdelijk in het geheugen. Voor hervatting controleerde hij opnieuw de identiteit en klant. Een procesherstart wist die sessies, en Redis Pub/Sub bewaart geen berichten voor latere bezorging. Polling kon de actuele content alsnog ophalen; een gemiste eenmalige opdracht werd daarmee niet opnieuw uitgevoerd.

Eerst vaststellen of de afspeellijst is veranderd

Omdat polling beschikbaar bleef, keek ik ook naar de contentcontrole in CakePHP. Een mediaplayer had bij ongewijzigde inhoud geen nieuwe lijst nodig, terwijl het CMS die vrijwel iedere keer volledig opbouwde. Ik scheidde daarom het vaststellen van een wijziging van het samenstellen van de lijst.

De lichte controle berekent per mediaplayer een revisiekenmerk uit bestaande gegevens. De mediaplayer vergelijkt dat met de vorige waarde en haalt alleen bij een wijziging de volledige afspeellijst op. Voor het kenmerk hoeft het CMS de volledige lijst niet op te bouwen.

Een teller bij iedere opslaghandeling was onvoldoende. Sommige schrijfroutes in de legacycode omzeilden de gebruikelijke opslaghooks. Bovendien kan een afspeelrooster veranderen doordat een tijdvenster ingaat, zonder dat iemand gegevens opslaat. Daarom liet ik de controle aansluiten op de bestaande gegevens en de tijd- en regelselectie van de afspeellijstgenerator.

Voorheen

  1. Periodieke aanvraag

    De mediaplayer vraagt de afspeellijst op bij het CakePHP-CMS.

  2. Volledige lijst opbouwen

    Het CMS doorloopt vrijwel iedere keer de volledige generatorpijplijn.

  3. Afspeellijst teruggeven

    Ook wanneer de inhoud gelijk is gebleven.

Met revisiecontrole

  1. Controle met mediaplayerstatus

    De mediaplayer vraagt het actuele revisiekenmerk op en geeft zijn status door.

  2. Berekenen en vergelijken

    Het CMS berekent het kenmerk; de mediaplayer vergelijkt het met de vorige waarde.

  3. Vervolg bepalen

    Gelijk: huidige lijst behouden. Gewijzigd: volledige lijst ophalen. Controle mislukt: bestaande ophaalroute gebruiken.

De periodieke controle blijft bestaan. Alleen bij een gewijzigd revisiekenmerk of een mislukte controle volgt de volledige ophaalroute.

De mediaplayer bleef bij de lichte controle zijn status doorgeven. Mislukte de controle, dan gebruikte hij de bestaande volledige ophaalroute. Ik voegde regressietests toe voor onder meer verwijderde content, gewijzigde instellingen en roostergrenzen. De database-uitvoering blokkeerde lokaal door ontbrekende configuratie. Hoeveel belasting deze aanpassing in de praktijk bespaart, is niet met een gecontroleerde meting vastgesteld.

Een geldige verbinding geeft niet overal toegang

Omdat klanten infrastructuur delen, controleert de service eerst wie een mediaplayer is en daarna welke ontvangersgroepen hij mag gebruiken. Het bestaande CMS blijft de bron voor de identiteitscontrole. Al vóór die controle begrenst de service het aantal verbindingspogingen, zodat niet iedere poging onbeperkt verdere verwerking kan veroorzaken.

Ook berichten worden gecontroleerd. Ajv toetst hun structuur, verplichte velden en gegevenstypen. Daarna vergelijkt de service de klantidentiteit in het bericht met die in het Redis-kanaal en controleert hij de bestemming. Een bericht met geldige velden maar een verkeerde klant of ontvangersgroep wordt afgewezen.

Het diagram zet deze maatregelen naast de WebSocket-richtlijn van OWASP (Open Worldwide Application Security Project). Twee grenzen blijven relevant: ingetrokken toegangsgegevens beëindigen een bestaande verbinding niet automatisch, en de omringende infrastructuur moet de verbinding versleutelen.

OWASP · maatregelen

In de service en bouwroute

  • KlantscheidingKlantcontext, rooms en toegestane ontvangers controleren.
  • SchemavalidatieAjv controleert de berichtstructuur vóór bezorging.
  • AfhankelijkhedenDependency-audit en containerscan met Snyk in de ontwikkelstraat.
OWASP · grenzen

Buiten de service of niet afgedekt

  • TLSIn de doelomgeving handelt de proxy of load balancer de versleuteling af.
  • Actieve verbindingenIngetrokken toegangsgegevens sluiten een bestaande verbinding niet automatisch.
  • HTTP-security-headersGeen eigen HTTP-security-headers in de service.
Controlepunten uit de OWASP WebSocket Security Cheat Sheet, toegepast op de socketserver en zijn infrastructuur.

Bij verbindings- en bezorgproblemen legt de service logs en meetgegevens vast. Daarmee kan worden onderzocht waar het misging. De registratie van mislukte bezorging start zelf geen contentcontrole; die blijft de verantwoordelijkheid van de mediaplayer.

Foutscenario’s testen vóór het bouwen

Met Jest toetste ik onder meer verkeerde ontvangers, mislukte authenticatie en ontbrekende ontvangstbevestigingen. In unit-tests verving ik afhankelijkheden door testimplementaties om fouten gericht op te roepen. De testsuite bevat daarnaast integratietests die met Testcontainers een echte Redis-container starten en een Socket.IO-testclient verbinden.

Regeldekking hielp mij zien welke code de tests bereikten. Of de uitkomst klopte, moest blijken uit de controles op het verwachte gedrag.

Jest · regeldekking96,87%Uitgevoerde regels tijdens de testrun
Berekening en meetbereik

Regeldekking = uitgevoerde meetbare regels / totaal meetbare regels × 100%.

Meetbereik: src/main/**/*.ts, exclusief src/main/index.ts. Een regel telt als gedekt zodra deze minstens eenmaal is uitgevoerd.

Statements, branches en functies hebben eigen dekkingspercentages. De 96,87% is uitsluitend regeldekking: de regelaantallen worden over alle bestanden opgeteld, niet de percentages per bestand gemiddeld.

Het oorspronkelijke coveragebestand met aantallen gedekte en meetbare regels ontbreekt in de onderzochte bronnen. Het percentage komt uit de presentatie; de exacte verhouding kan daardoor niet opnieuw worden nagerekend.

Historisch gerapporteerde regeldekking uit een lokale Jest-run. De pipeline toetste op slagen of falen.

Ik nam de controles op in een geautomatiseerde bouwroute: code- en opmaakcontrole, TypeScript-controle, tests en controle van afhankelijkheden. Pas daarna volgden het bouwen van een Docker-image en een kwetsbaarheidsscan met Snyk. De lockfile legde pakketversies vast voor volgende installaties.

CodewijzigingStart van de pipeline
5 kwaliteitscontroles
  • LintESLint en opmaak
  • TypenTypeScript-controle
  • TestsJest
  • AuditAfhankelijkheden
  • LockfileVaste pakketversies

Een mislukte controle stopt de bouw.

Bouwen + scannenDocker-image bouwen en scannen met Snyk
UitrolrouteGeconfigureerd
  • Beta-uitrol
  • Acceptatie
  • Productie-uitrol
Een mislukte controle stopt de bouwroute. De uitrolstappen zijn geconfigureerd; beta-uitrol en acceptatie stonden nog open.

Voor de doelomgeving was uitrol via AWS (Amazon Web Services) voorzien, met Elastic Beanstalk voor de service, ElastiCache voor Redis en een ALB (Application Load Balancer) vóór de service. Daar zou ook TLS (Transport Layer Security) worden afgehandeld. Deze uitrolroute was geconfigureerd; de volledige omgeving was bij de overdracht nog niet getoetst.

AI-uitkomsten terugleggen naast de code

Tijdens het onderzoek gebruikte ik een semantische zoekindex om relevante code op betekenis te vinden. Daarin kwamen eerst de broncode van het CMS en de mediaplayer, en later de code en documentatie van mijn socketserver. De zoekresultaten verwezen terug naar hun bronlocatie, zodat ik de omliggende code kon controleren.

De agent kreeg daarnaast context uit codedocumentatie, een kennisbank en taakspecifieke instructies. Ik toetste voorgestelde wijzigingen aan de code, eisen en tests en verantwoordde deze werkwijze in mijn scriptie. Het diagram laat zien hoe de bronnen samenkwamen; via Codeonderzoek kun je de zoekroute verder openen.

Overzicht

AI-agentOnder mijn regie
  • SocketserverCode, tests en documentatie
  • Bestaande integratieAansluiten op CMS en mediaplayer
Contextbron AI-agent Voorgestelde uitwerking Open een diepere diagramlaag Gerichte relatie

Codeonderzoek

Broncode · PHP / CakePHP CMS

Contentbeheer en publicaties

Broncode Mediaplayer

Content ophalen en afspelen

Broncode · groeit tijdens de bouw Socketserver

Mijn broncode en bijbehorende documentatie

Context ophalen Semantische zoekindex

Relevante code en uitleg op betekenis vinden

Bekijk de zoekketen
Broncontext voor de agent Codecontext

Gevonden code en documentatie

Onder mijn regie AI-ondersteunde analyse

Verantwoordelijkheden en berichtafspraken vergelijken

Eigen verificatie Mijn beoordeling

Controleren aan broncode, eisen en tests

  • CMS → Semantische zoekindex
  • Mediaplayer → Semantische zoekindex
  • Socketserver → Semantische zoekindex
  • Semantische zoekindex → Codecontext
  • Codecontext → AI-ondersteunde analyse
  • AI-ondersteunde analyse → Mijn beoordeling
Onderdeel Open een diepere diagramlaag Gerichte relatie

Semantische zoekindex

Bronnen Code en documentatie

De geselecteerde bronbestanden

Indexeren Chunks

Code opdelen in samenhangende passages

Indexeren Embeddings

Passages als getallen weergeven om op betekenis te zoeken

Opslag Vectorindex

Vectoren, tekst en bronlocaties

Zoeken Zoekvraag

Wat wil ik over de code weten?

Zoeken Vraag-embedding

De vraag omzetten voor de zoekindex

Selecteren Semantisch zoeken

Passages vinden die bij de vraag passen

Rangschikken Reranking

De gevonden passages op relevantie rangschikken

Context Broncontext

Passages met bronlocaties

Context toepassen AI-agent

Onder mijn regie

  • Code en documentatie → Chunks
  • Chunks → Embeddings
  • Embeddings → Vectorindex
  • Vectorindex → Semantisch zoeken
  • Zoekvraag → Vraag-embedding
  • Vraag-embedding → Semantisch zoeken
  • Semantisch zoeken → Reranking
  • Reranking → Broncontext
  • Broncontext → AI-agent
Onderdeel AI-agent Gerichte relatie
Ik controleerde de voorstellen aan de broncode, eisen en tests.

Afronding

De berichtbezorging naast de eisen leggen

Om snelheid en herverbinden te toetsen, bouwde ik een eigen meetopstelling met gesimuleerde clients, de socketserver en een echte lokale Redis-server. Ik mat van publicatie in Redis tot ontvangst bij de testclient. Voor de herstelproef liet de testcode verbindingen verbreken en opnieuw openen.

  1. Start van de meting Testpublicatie

    Tijdstip vastleggen en event publiceren.

    De testpublicatie gaat via Redis naar de draaiende socketserver.
  2. Lokale runtime Redis en socketserver

    Event valideren en routeren.

    De aankomst van het event bij de virtuele client beëindigt de tijdmeting.
  3. Einde van de meting Virtuele client

    Aankomsttijd vastleggen.

Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De meting loopt van testpublicatie tot clientontvangst. CMS-verwerking, downloads, schermweergave en productiegebruik vallen erbuiten.

Bij 1.000 virtuele clients kwam 95% van de berichten binnen 79,28 ms (milliseconden) aan. Dat is de p95, het 95e percentiel. Voor herverbinden was de p95 ongeveer 1,35 seconde. Beide waarden lagen binnen de gestelde grenzen van één seconde voor berichtontvangst en vijf seconden voor herverbinden.

De proeven draaiden lokaal op Node 20, met testauthenticatie en verhoogde limieten. De tijden omvatten geen downloads of schermweergave. De beoogde Node 24-omgeving met het echte CMS, echte mediaplayers en de cloudinfrastructuur was hiermee nog niet getoetst. De scenario’s waren eenmalig gemeten.

Redis → testclient79,28 msp95 berichtontvangst
1,35 s
p95 herverbinden

Eis: onder 5 s. Verbindingen werden door de test verbroken en heropend.

P95 betekent dat 95% van de gemeten tijden op of onder deze waarde lag. De lokale berichtproef met 1.000 virtuele clients omvatte vijftien publicaties en 15.000 ontvangsten; geen productiegarantie. Bij de formele toetsing op 10 mei 2026 waren 14 van de 18 Must-eisen volledig en vier gedeeltelijk aangetoond.

De oplossing overdraagbaar maken

Ik leverde de socketserver op met tests, mijn scriptie en onderzoeks- en ontwerpdocumentatie. De berichtafspraken en foutafhandeling had ik tijdens het bouwen bijgehouden. Voor de overdracht bracht ik installatie-instructies, beheerhandleidingen en architectuuruitleg samen in een documentatiesite met VitePress.

Daarin bouwde ik een interactieve C4-kaart (Context, Containers, Components en Code). Een ontwikkelaar kon vanuit het systeemoverzicht naar de socketserver, de authenticatiecomponent en de bijbehorende methode navigeren. Daar stond welke gegevens de methode verwachtte en of de controle kon leiden tot accepteren, afwijzen of opnieuw proberen.

De bovenste architectuurlagen beschreef ik zelf; geselecteerde codedetails werden uit TypeScript gegenereerd. Hiervoor gebruikte ik TSDoc-commentaren en TypeDoc om API-documentatie (Application Programming Interface) uit mijn code te maken. Cytoscape.js tekende de kaart, ELK (Eclipse Layout Kernel) bepaalde de plaatsing en een uitbreiding verzorgde het in- en uitklappen.

Met de Skillit-plugin voor TypeDoc genereerde ik ook documentatie voor AI-agents, waaronder llms.txt en llms-full.txt. De kaart en deze navigatiebestanden gebruikten dezelfde gegevens over onderdelen en relaties. Daardoor waren die verbanden zowel voor ontwikkelaars als voor agents beschikbaar.

De demo hieronder toont deze manier van navigeren met twee generieke voorbeeldcomponenten. De namen en relaties zijn illustratief en geven niet de oorspronkelijke projectarchitectuur weer.

C4-demokaart

Volg een voorbeeldaanvraag van het systeemoverzicht naar de code.

C1 · Extern systeem

Wisselt gegevens uit met het voorbeeldsysteem.

C1 · Voorbeeldsysteem

Omvat de voorbeeldapplicatie en haar onderdelen.

C2 · Voorbeeldapplicatie

Bevat twee voorbeeldcomponenten.

C3 · Component A

Ontvangt een aanvraag in deze demo.

C4 · Voorbeeldhandler

Geeft de aanvraag door aan de voorbeeldfunctie.

Deze API-documentatie gebruikt generieke TypeScript-voorbeelden.

class ExampleHandler {
  handle(request: ExampleRequest): ExampleResult;
}
Methode · handle(request: ExampleRequest)
Accepteert een aanvraag volgens de voorbeeldinterface.
Retourwaarde · ExampleResult
Geeft het resultaat van processRequest terug.
Fout · TypeError
Geeft bij een lege waarde de fout door aan de aanroeper.
const handler = new ExampleHandler();
const result = handler.handle({ value: 'voorbeeld' });
C4 · Voorbeeldinterface

Beschrijft de invoer en uitvoer van de demo.

Deze API-documentatie gebruikt generieke TypeScript-voorbeelden.

interface ExampleRequest {
  value: string;
}

interface ExampleResult {
  value: string;
}
Eigenschap · ExampleRequest.value: string
Een verplichte tekstwaarde; de functie controleert of die leeg is.
Eigenschap · ExampleResult.value: string
De verwerkte waarde die wordt teruggegeven.
const request: ExampleRequest = { value: 'voorbeeld' };
const result: ExampleResult = processRequest(request);
C3 · Component B

Verwerkt de doorgegeven aanvraag.

C4 · Voorbeeldfunctie

Verwerkt de aanvraag en geeft het resultaat terug.

Deze API-documentatie gebruikt generieke TypeScript-voorbeelden.

function processRequest(request: ExampleRequest): ExampleResult
Parameter · request: ExampleRequest
Een aanvraag waarvan het veld value niet leeg mag zijn.
Retourwaarde · ExampleResult
Een object met de verwerkte waarde in het veld value.
Fout · TypeError
Treedt op als request.value leeg is.
const result = processRequest({ value: 'voorbeeld' });
// result: { value: 'voorbeeld' }
Deze C4-demo gebruikt twee voorbeeldcomponenten. De namen en relaties tonen niet de oorspronkelijke projectarchitectuur.
TERUG NAAR SITE