AirHelp, teknologi for den ledende talsmannen for passasjerrettigheter
Personen denne nettsiden er bygget for, sitter på en flyplass etter en kansellering. Hun bruker telefonen, ofte i roaming, på et svakt nett, hun er irritert, og alt hun har med seg er et bilde av et ombordstigningskort. Hun kom ikke for å lese om selskapet. Hun kom for å finne ut om hun har krav på noe, og hun vil ha svaret på to steg.
Den brukerprofilen opphever en god del av de vanlige valgene man tar for en bedriftsnettside. En innholdsrik forside gir lite mening når de fleste besøk lander rett på skjemaet eller på en juridisk artikkel fra søk. Å laste analyseskript før innholdet gir ingen mening når de to første sekundene avgjør om noen i det hele tatt får se skjemaet. Til gjengjeld gir én ting mening som sjelden blir prioritert: skjemaet må overleve at forbindelsen ryker midt i utfyllingen, for på en flyplass er det ikke et kanttilfelle, det er normalen.
AirHelp ble grunnlagt i januar 2013 av Henrik Zillmer og har hovedkontor i Berlin. Det juridiske grunnlaget i Europa er forordning 261/2004, og utenfor unionen arbeider selskapet gjennom UK261, Montreal-konvensjonen, brasilianske ANAC 400 og flere nasjonale regelverk. Selskapet har hjulpet over 13 millioner mennesker med å forstå rettighetene sine og kreve kompensasjon for forsinkede, kansellerte eller overbookede flyvninger, representerer dem i tvister med flyselskaper og driver påvirkningsarbeid for rettferdige regler på regjeringsnivå.
Jeg designet og implementerte nettstedet og ledet et fempersoners team som Team Pilot. Implementeringen tok omtrent seks uker fra behovsanalyse til publisering, og oppsettet og plasseringen av elementene kom fra kunden.
Den andre målgruppen, og hvorfor de to kolliderer
Den andre gruppen kommer fra en søkemotor måneder etter flyvningen og leter etter et konkret juridisk punkt. Den trafikken er jevn, godt egnet for caching, leser lange tekster og lander på sider på 24 språk.
Én plattform betjener begge mønstrene under samme domene, og det meste av den arkitektoniske spenningen i prosjektet kommer nettopp derfra. Det som er riktig for den første gruppen, minimal markup, gjengivelse på serveren, ingenting som blokkerer skjemaet, er bortkastet på den andre. Det som er riktig for den andre, aggressiv caching helt ute på kanten av nettet, er ubrukelig for den første, fordi en søknadsstatus per definisjon er personlig.
Bak innholdet står samarbeid med advokatfirmaer i 30 land og et team på 700 ansatte, deriblant verdens største gruppe advokater spesialisert på luftfartsrett. For nettstedet betyr det at juridisk tekst har en reell eier hos kunden og går gjennom godkjenning før den når produksjon. Innholdsmodellen måtte bære den arbeidsflyten, ikke omgå den.
Skjemaet som må tåle å miste nettet
Skjemaets tilstand lagres lokalt etter hvert steg. Et brudd i avgangshallen sletter dermed ikke femten minutters arbeid. Kostnaden er reell og bør sies rett ut: søknadsdata inneholder flynummer og personopplysninger, og å oppbevare dem i nettleseren krever en egen opprydding etter innsending og en tydelig regel for når et forlatt utkast utløper. Uten den regelen blir en bekvemmelighet til en personvernforpliktelse.
Validering følger samme logikk. Det er fristende å avvise et flynummer som ikke passer mønsteret, umiddelbart og i nettleseren. I praksis varierer mønstrene mellom flyselskaper, og passasjeren skriver av nummeret fra et skjevt bilde av ombordstigningskortet. En advarsel som ikke blokkerer innsending, tar imot søknaden fra den som forvekslet en null med en bokstav. Hard validering forkaster den, og ingen får vite om det.
Tilgjengelighet handler her mindre om kontrast og alt-tekst enn om dette skjemaet. Det som avgjør om det fungerer, er nettopp det en automatisk revisjon ikke fanger: om en feilmelding er programmatisk knyttet til feltet den gjelder, om et stegbytte flytter fokus til overskriften for det nye steget, om fremdrift blir annonsert i det hele tatt. Et skjema som ruller til toppen etter en valideringsfeil uten å flytte fokus, er en løkke uten utgang for den som bruker tastatur, og det består hver eneste automatiske test.
24 språk er et juridisk problem, ikke et oversettelsesproblem
En setning om hvor mye kompensasjon en passasjer har krav på, er ikke én setning oversatt tjuefire ganger. Det er et par dusin selvstendige juridiske påstander, og mange av dem er sanne bare innenfor én landegrense. En flyvning fra Warszawa til London faller inn under et annet regime etter brexit enn den samme flyvningen internt i unionen, og en flyvning fra São Paulo følger regler som ikke har noe med noen av delene å gjøre.
Følgen for innholdsmodellen er at hver språkvariant må være et fullverdig dokument med egen godkjenningssyklus, ikke et oversettelsesfelt hengt på originalen. En variant som ikke er gjennomgått av en advokat fra den aktuelle jurisdiksjonen, kan ikke stille falle tilbake til engelsk, for da publiserer nettstedet en fremmed rettstilstand som sin egen. Den må i stedet forsvinne fra navigasjonen og fra hreflang. Det er den eneste trygge standardoppførselen, og det er også den generiske flerspråksutvidelser tar feil, fordi standarden deres alltid er å vise noe fremfor ingenting.
Frontend-arkitekturen bygger på Next.js med gjengivelse på serveren og dekker 24 språk gjennom i18n, med WCAG 2.1 i tankene og optimalisering for mobile enheter. Servergjengivelse er ikke et estetisk valg her. Med juridisk innhold som skal rangere i flere titalls markeder samtidig, og med mobiler på dårlige nett, ville det å flytte sidesammensetningen inn i besøkendes nettleser bety at den svakeste telefonen på det dårligste nettet gjør mest arbeid.
Tekniske funksjoner for AirHelp
Kompensasjonsprosessen henter flydata dynamisk via GraphQL, snakker med flyselskapenes API-er og lagrer søknaden i PostgreSQL med AES-256-kryptering. Valget av GraphQL fremfor flere REST-kall har en konkret begrunnelse: hvert steg i skjemaet trenger en annen del av den samme flyoppføringen. Med REST ender det enten i å hente alt på forhånd eller i et antall forespørsler som vokser med antallet steg. Å spørre etter nøyaktig de feltene det aktuelle steget viser, reduserer det til én rundtur over nettet per steg, og på en telefon i roaming er det en merkbar forskjell.
Informasjonsseksjonen med juridiske artikler lastes via REST API med caching i Redis og gjengis i React. Delingen mellom de to grensesnittene er bevisst. Redaksjonelt innhold er identisk for alle besøkende og egner seg for aggressiv caching. Søknadsdata egner seg ikke for caching i det hele tatt. Å holde begge bak ett grensesnitt ville spart litt kode og kostet muligheten til å sette hver sin levetid.
Brukerdashbordet viser status i sanntid via WebSocket, cachet i Memcached. Avveiningen fortjener å bli nevnt. En vedvarende forbindelse bruker serverressurser så lenge en fane står åpen, og en søknadsstatus endrer seg kanskje én gang hver par uker. Å spørre én gang i minuttet ville vært billigere i drift og tilstrekkelig for selve informasjonen. Argumentet for den åpne kanalen er oppførselen den dagen statusen faktisk endrer seg, for da sitter brukeren på denne siden og laster den på nytt for hånd, og hver manuell oppdatering koster mer enn å holde kanalen.
Sikkerhetskopier går automatisk til Amazon S3 med replikering mellom regioner, versjonering og Zstandard-komprimering. Versjonering veier tyngre enn replikering her. Replikering beskytter mot å miste et datasenter, som er sjeldent. Versjonering beskytter mot å overskrive data gjennom egen feil i en utrulling, som ikke er sjeldent i det hele tatt, og en kopi replikert til tre regioner er da rett og slett samme feil tre ganger.
Teknisk SEO dekker uttrykk som «kompensasjon for forsinket fly», dynamiske XML-sitemaps og akselerert indeksering. Med juridisk innhold som endrer seg etter hver vesentlige avgjørelse, slutter et sitemap generert én gang ved bygg å beskrive nettstedet i løpet av en uke, så det bygges fra gjeldende databasetilstand i stedet.
Ytelsesutfordringer og løsninger
Databasebelastningen traff PostgreSQL. Svaret var Redis med vedvarende lagring for de hyppigste spørringene, og sharding med lese-replikater på Amazon RDS. Lese-replikater har en pris som er lett å glemme: replikeringen er asynkron, så en lesing rett etter en innsending ser kanskje ikke søknaden ennå. Brukeren sender inn og lander på en tom liste, noe som ser nøyaktig ut som datatap. Derfor går lesinger umiddelbart etter en skriving til primærinstansen, og bare stier som tåler noen hundre millisekunders etterslep blir fordelt.
Treg lasting av skjemaet kom fra integrasjonen mot flyselskapenes API-er, særlig etter masse kanselleringer. API-kall gikk over til asynkron behandling gjennom RabbitMQ, med fallback til statiske data cachet i Elasticsearch ved tidsavbrudd. Fallbacken er den viktige halvdelen. Flyselskapenes systemer er gjerne utilgjengelige nøyaktig når de trengs mest, fordi den samme forstyrrelsen som kansellerte flyvningene, belaster infrastrukturen deres. Et skjema som viser en feil i det øyeblikket, mister en søknad fra noen som har full rett til kompensasjon.
Høy latens på bilder rammet telefoner i regioner med svak tilkobling. Fastly med Brotli-komprimering, WebP og lazy loading via Intersection Observer API kortet ned leveransen, og geo-optimalisering kortet ned avstanden. Gevinsten fra bildeformatet er likevel underordnet gevinsten fra at bilder under skjermkanten ikke lenger konkurrerer om båndbredde med teksten som faktisk leses.
Forsinkelser i sanntidsdashbordet dukket opp ved 13 millioner brukere. Kafka overtok datastrømmen, med throttling på serversiden og en AWS ALB som fordeler trafikken. Throttling er det minst imponerende elementet i det settet og det mest nødvendige, for uten det legger én klient i en feilløkke beslag på en kanal som tilhører alle andre.
Utdatert cache løses med Varnish, tilpasset VCL, purge over webhooks og Edge Side Includes for dynamiske seksjoner, supplert med versjonering av adresser. ESI svarer på en konkret spenning: en juridisk artikkelside er identisk for alle over det meste av flaten, og avhenger bare i et lite fragment av om leseren har en åpen sak. Uten den delingen ugyldiggjør det lille fragmentet caching av hele siden.
Ressursbehovet i toppene dekkes av auto-scaling på AWS EC2 med CloudWatch, og Cloudflare Rate Limiting mot overdreven bottrafikk. Auto-scaling har en svakhet som følger direkte av trafikkformen i denne bransjen: den reagerer på last som allerede har inntruffet, og en bølge etter en masse kansellering bygger seg opp på minutter. Terskelverdiene ligger derfor lavere enn en kostnadsberegning på en rolig dag ville tilsi, og den marginen er kjøpt med vilje.
Brukte teknologier
Yoast SEO tar seg av metadata, dynamiske XML-sitemaps og varsler til søkemotorene. UpdraftPlus kjører sikkerhetskopier til Amazon S3 med replikering og AES-256-kryptering. Cloudflare leverer kantlaget med Argo Smart Routing, Brotli-komprimering og DDoS-beskyttelse gjennom rate limiting. Redis cacher i minnet, delt mellom sesjoner, skjemaer og dashbord. Varnish cacher på serversiden med tilpasset VCL, grace mode og ESI. Grace mode fortjener sin egen setning, for den serverer en litt utdatert side i det øyeblikket backend slutter å svare, i stedet for å servere en feil, og mot trafikk som kommer i bølger er det forskjellen mellom et tregere nettsted og et utilgjengelig.
Lighthouse kjører Core Web Vitals-revisjoner inne i CI/CD-løpet i Jenkins. RabbitMQ køer API-behandling og e-postutsending med retry og dead letter queue. Elasticsearch driver søk i flyvninger og innhold med fuzzy matching og aggregering, og uskarpheten er et krav snarere enn en finesse: flynumre skrives av fra bilder av ombordstigningskort, og flyplassnavn staves på tjue måter på tjuefire språk. Fastly fordeler medier geografisk, og Kafka bærer sanntidsstrømmen med partisjonering.
Styring og teknisk support
AirHelp krever kontinuerlig optimalisering og oppfølging. Jeg oppdaterer kjerne og utvidelser regelmessig og tester i et testmiljø med sikkerhetskopier på Amazon S3. Testmiljøet er en kopi av produksjon, ikke en tom installasjon, og nettopp den forskjellen er det som gjør testen verdt å kjøre. En spørring som svarer umiddelbart mot to hundre søknader, kan vokse med to størrelsesordener mot millioner av rader, og på en tom database ser ingen det. Det samme gjelder cache-treffraten, som bare lar seg måle mot en reell fordeling av inngangssider.
Cloudflare, Redis og Fastly bærer ytelsen under global trafikk, mens Varnish, RabbitMQ og Kafka stabiliserer de dynamiske prosessene. Overvåkingen går på Elasticsearch og CloudWatch, SQL- og NoSQL-spørringer gjennomgås mot indeksene sine, og cachen ugyldiggjøres ved innholdsendringer. Det viktigste signalet der er ikke responstid, men cache-treffrate, for på et nettsted formet som dette sender en bom forespørselen til opphavet uansett hvor godt opphavet er justert.
Plattformen kan utvides med ERP-integrasjoner, en modul for flyanalyse eller en seksjon for juridiske rapporter. Hver av dem løper inn i den samme konflikten som går gjennom hele prosjektet: jo mer innhold som avhenger av hvem som ser på det, desto mindre lar seg levere fra kanten av nettet, og fra det punktet må beslutningene herfra regnes om i stedet for å flyttes over.
Ofte stilte spørsmål
Praktiske svar for å bruke temaet i faktisk arbeid.
Hvilket omfang hadde prosjektet Airhelp?
#Hvordan gikk leveransen for Airhelp?
#Hva var hardest teknisk i Airhelp?
#Hvilken del av Airhelp kan gjenbrukes på et nytt bygg?
#Trenger du FAQ tilpasset bransje og marked? Vi lager en versjon som støtter dine forretningsmål.
Ta kontakt