's Picture

Den tekniska resan hittills

Postad av Joel Abrahamsson

I oktober förra året tog vi de första spadtagen i uppbyggnaden av Expressens nya utvecklingsavdelning. Mycket har hänt sedan dess. Vi har fått in mycket bra folk och sitter i ett nyrenoverat kontor. Vi har fått många smidiga rutiner på plats. Och inte minst så har vi släppt massvis med nya och förbättrade produkter. En tabletsajt, nya appar för Android och iOS, Omtalat.nu, Hälsoliv och en app för Android Gear för att nämna några. Men, det har naturligtvis hänt mycket bakom kulisserna, tekniskt, också.

När vi drog igång i oktober 2013 fanns det två versioner av expressen.se. En desktopversion och en mobilversion. Båda var byggda byggda på ASP.NET och EPiServer CMS 6R2. CMS:et föddes med innehåll från ett flertal system. De två viktigaste var och är NewsPilot, i vilket reportrar och redaktörer skriver texter, samt bildbanken OpenStore.

Källkoden lagrades och byggdes i Microsoft Team Foundation Server. TFS:en användes även för backlog, bugghantering och dylikt.

Sajten och CMS:et fungerade stabilt och bra. CMS:et hade utökats med ett antal egna plugins som anpassade det för det arbetssätt och innehållsstruktur som lämpar sig för en stor mediesajt. Med andra ord var läget stabilt och systemen välfungerande. Men utvecklingen var inte direkt snabbrörlig. Releaser skedde varannan vecka och gjordes nattetid för att minimera risker vid eventuella problem och för att få så liten påverkan på redaktionens arbete som möjligt.

Plattformskval

Vid den här tidpunkten hade EPiServer släppt en ny version av sitt CMS. Version 7 i vilken EPiServer gjort ett stort omtag av redaktörs-gränssnittet. Med denna version kom också ett flertal av de funktioner som vår installation hade som specialanpassningar inbyggda i systemet. Denna nya version var också avsevärt lättare att bygga egna utökningar för. Men, vi hade ju redan egna lösningar på plats och att migrera från dessa till den i systemet inbyggda funktionaliteten skulle troligtvis bli jobbigt och kostsamt. Samtidigt skulle en uppgradering inte ge våra användare någon funktionalitet som de inte redan hade.

De enda incitamenten att uppgradera var således att vi skulle ha möjlighet att senare uppgradera till framtida versioner av CMS:et samt möjliggöra utveckling med modernare tekniker. Båda sakerna fullt relevanta så klart, men var de tillräckliga för att motivera arbetsinsatsen och att redaktionen skulle utbilda sig i ett nytt gränssnitt? Tveksamt.

Att helt byta plattform var inte aktuellt.

Vad vore då alternativet till att uppgradera? Att helt byta plattform var inte aktuellt. Vi behövde röra oss framåt, inte spendera sex till 18 månader på ett plattformsbyte. Att inte göra någonting alls, vara kvar på vår nuvarande plattform och fortsätta vidareutveckla den var naturligtvis ett alternativ.

Ett sådant scenario skulle dock troligtvis innebära att vi skulle divergera från “standard EPiServer” och det var viktigt att vi i detta scenario var ärliga mot oss själva genom att inse, och prata om, att detta alternativ innebar att vi förr eller senare skulle hamna i ett läge där vi skulle komma att behöva göra ett större plattformsbyte. För att tydliggöra detta kallade vi detta alternativ för “vägen mot plattformsbyte”. Inte ett attraktivt namn, men ärligt.

Så, vi hade uteslutit ett CMS-byte och hade två alternativ att välja mellan. Vi kunde välja att inte göra någonting, med insikt om att vi skulle få gradvis svårare att hitta duktiga utvecklare som ville arbeta med vår åldrande plattform och att vi förr eller senare skulle behöva göra ett stort plattformsbytesprojekt. Vi kunde också välja att uppgradera till EPiServer 7 vilket åtminstone delvis skulle framtidssäkra vår plattform men kortsiktigt ta fokus från nyutveckling och troligtvis irritera våra användare mer än det skulle glädja dem. Åtminstone på kort sikt.

Ett tredje alternativ — en tredelad strategi

Vi behövde ett annat alternativ. Ett alternativ som inte bromsade vidareutveckling av våra befintliga sajter men som samtidigt var långsiktigt hållbart. Vi bestämde oss för en strategi med tre delar:

1. Vi investerade i att göra utveckling och deployment av den befintliga plattformen smidigare. Dels för att göra nyutveckling smidigare och dels för att förlänga plattformens livslängd.

2. Vi byggde ny funktionalitet som inte passade in naturligt i den existerande plattformen som separata system, med för ändamålet lämpliga tekniker.

3. Vi började gradvis gå över till en ny, egen, plattform med ett antal mindre system snarare än ett stort. Detta gjorde vi inte genom att köra ett parallellt utvecklingsspår där ingenting användes för att en dag kunna dra i en spak och byta till nya system. De nya systemen byggdes för att så snart som möjligt kunna användas publikt för ny eller förbättrad funktionalitet och för att fungera i harmoni med den gamla plattformen.

Den större delen av det här inlägget kommer att ägnas åt att gå igenom hur vi har exekverat strategin ovan.

Tidig designskiss på del tre i vår strategi. Högst oklart så här i efterhand vad glasögonen i övre högra hörnet symboliserar.
Tidig designskiss på del tre i vår strategi. Högst oklart så här i efterhand vad glasögonen i övre högra hörnet symboliserar.

Smidigare versionshantering och releaser

Att använda Team Foundation Server och releasea till produktion nattetid en gång varannan vecka låter kanske inte så farligt. Jag har jobbat i projekt där vi kunde deploya till produktion en gång per kvartal.

Men, vi ville jobba modernare. Vi ville inte bygga upp lager av ny funktionalitet, skriva detaljerade releaseinstruktioner och ha en klump i magen inför varje release. Vi ville kunna deploya när som helst och få ut ny funktionalitet löpande. Vi ville kunna deploya dagtid när alla var på plats.

Vi bestämde oss för att byta från TFS. För den som inte har använt TFS låter det kanske som en liten sak. Men den som har använt TFS vet att det är ett system som uppmuntrar att man gör ALLT i det och att de olika delarna tenderar att sitta ihop. Vi kunde exempelvis inte byta källkodshantering utan att samtidigt ha en ny byggserver på plats.

Skam den som ger sig. Vi såg ett stort värde i att kunna jobba med vår befintliga plattform på smidigare sätt och lade ner det ganska omfattande arbete som krävdes för att flytta källkoden, byggen och deployment till nya verktyg. Medan detta pågick gjorde vi enklare åtgärder på direkten. Vi kortade releaseintervallet från två till en vecka och vi flyttade backloggen till Trello.

Att flytta källkoden och sätta upp helt automatiserade releaser var dock inte lika enkelt. Men, i mars i år var äntligen koden överflyttad till Git, byggen uppsatta i TeamCity och fullt automatiserade deployrutiner skapade med Octopus Deploy. De sistnämnda inkluderade inte bara att skicka ut ny kod till våra servrar utan också att, automatiserat, ta ut ett mindre antal servrar ur last medan ny kod deployades till dem och värma upp dem innan de återigen fick trafik från lastbalanseraren.

Knapptryckningen som startade den första deployen med Git -> TeamCity -> Octopus Deploy -> Produktionsservrar.
Knapptryckningen som startade den första deployen med Git -> TeamCity -> Octopus Deploy -> Produktionsservrar.

Vi var dock inte klara. Nu hade vi separerat källkodshantering från byggen och deployment. Nu hade vi automatiserat releaseprocessen så att det inte fanns någon risk för mänskliga misstag och möjliggjort att releaser kunde göras av utvecklare såväl som operations. Men, vi kunde fortfarande inte deploya hela systemet utan att störa redaktionen i deras arbete.

Att deploya till våra webbfrontar utan störning var inget problem. Men att göra detsamma för de maskiner som redaktionen arbetar mot var en annan femma. Vi hade visserligen dubbla CMS-servrar men eftersom det på dessa skedde användarinmatning och olika typer av arbetsflöden som kunde vara beroende av session state och olika typer av schemalagda jobb som inte var gjorda för att köras på flera maskiner (och därigenom ligga och skriva över varandra) var den ena maskinen inte aktiv.

Så under våren njöt vi av att använda Git och andra trevliga verktyg samt avsevärt smidigare releaser. Parallellt med detta arbetade vi vidare med att lösa ett antal problem som gjorde det svårt att ha två aktiva CMS-maskiner bakom en lastbalanserare. Till slut var även detta löst och lagom till sommaren hade vi även nått vår vision: vi kunde göra releaser utan att påverka sajten varken för publika besökare eller för redaktörer. Självfallet helt automatiserat.

Där är vi nu. För ett år sedan frågade vi oss när nästa release skulle inträffa och ord som “kodfrys” och “hand-over-dokument” var frekvent använda. Nu releasar vi när vi har något att releasea. Naturligtvis med en viss struktur och enligt rutiner, men det mesta är automatiserat och alltid framtaget med så smidig utveckling som möjligt som drivkraft.

Separata funktioner, separata applikationer

Parallellt med att vi investerade i smidigare utveckling av vår befintliga plattform började vi exekvera på den andra komponenten i vår strategi. Istället för att bygga in all tänkbar funktionalitet i vår EPiServer-installation tänkte vi till innan. Sådant som hade med innehåll att göra eller på annat sätt hörde hemma i EPi-plattformen byggde vi där, men när funktionaliteten inte hörde hemma där byggde vi det som separata applikationer och hittade lättviktiga sätt att integrera de olika applikationerna.

Det första exemplet på detta var när vi förra vintern byggde funktionalitet för att logga in på expressen.se. Själva inloggningsfunktionaliteten och hantering av användarinställningar byggde vi som en separat applikation och sajt – id.expressen.se.

När vi skulle välja teknik för ID-sajten hade vi tre kriterier:

1. Det skulle vara lättjobbat – lätt att testa, lätt att deploya och inte en massa XML-konfig. Gärna hostat på *nix-maskiner.

2. Det behövde finnas ett aktivt och trevligt community kring plattformen med många bra open source-moduler.

3. Det behövde vara ett programmeringsspråk och tillhörande community som inte hade en alltför hög tröskel att komma in i och vara produktiv i oavsett vilken bakgrund man som utvecklare kom ifrån.

Baserat på ovan kriterier och tidigare erfarenhet hos oss utvecklare föll valet på Node.JS. För att få till funktionaliteten på ID-sajten använde vi även Passport.js och MongoDB. Vi använde även CoffeeScript för att utvärdera det.

Nästa exempel på en separat applikation var en wrapper runt CoveritLive, den tjänst som vi och många andra nyhetssajter använder för liverapportering i text. Då vi ville använda Websockets för denna applikation föll det sig naturligt att även här använda Node.JS. Dock beslöt vi oss för att här, och för framtida applikationer, inte använda CoffeeScript.

Innehållets befrielse

Så, vi byggde inte längre funktionalitet som inte hörde hemma i vår CMS-baserade applikation i den utan som separata, mer lättviktiga, applikationer. Men, när det handlade om artiklar och annat typ av innehåll var vi kvar i vår åldrande installation av EPiServer 6. CMS:et fungerade bra men, som jag beskrev i början, så såg vi en stor risk i att fortsätta bygga på denna plattform givet att vi inte hade tillräckligt incitament att uppgradera. Å andra sidan ville vi inte dra igång ett stort projekt där vi började bygga en ny plattform bara för sakens skull. Vi såg också en risk i att bygga en ny plattform för andra initiala use-case än att kunna använda den för www.expressen.se.

När vi i våras bestämde oss för att bygga en version av expressen.se anpassad för surfplattor såg vi vår chans. Genom att använda en fullvärdig version av sajten som drivkraft kunde vi bygga ett nytt sätt att lagra och exponera vårt innehåll för olika applikationer på ett sätt som redan från början säkerställde att vi kunde hantera det mest komplexa av användningsområden, expressen.se komplett med ettor (sektioner) och puffar av olika slag.

För att åstadkomma detta började vi med att skapa en modul i vår EPiServer-applikation som bevakade när en redaktör, eller systemet självt, publicerade innehåll. När så sker seriliseras valda delar av innehållet till JSON och publiceras på en meddelandekö. Vår första idé för denna kö var att använda Apache Kafka. Vi stötte dock på en del problem med Kafka. Exempelvis avsaknad av en bra .NET-klient och strul med ZooKeeper. Vi landade därför till slut i att använda RabbitMQ istället, vilket har visat sig fungera utmärkt.

Med hjälp av denna modul går inte nytt innehåll bara ut på desktop- och mobilsajterna. Vilket system som helst som har åtkomst till meddelandekön kan lyssna på nytt innehåll som publicerats i CMS:et. Men, om man vill bygga en ny version av expressen.se räcker ju inte det. Man behöver ju även få tillgång till äldre innehåll. Därför byggde vi även in HTTP endpoints i EPi-installationen med vilka man kan hämta ut innehåll i samma format som då innehåll publiceras på meddelandekön.

Med innehållet exponerat från CMS:et, både i realtid då något förändras och med möjlighet att hämta ut äldre innehåll, så var nästa steg att göra någonting med det. Vi skapade en applikation, återigen med Node.JS, som lagrade det i ett ElasticSearch-index och exponerade det med ett RESTish API. Naturligtvis publicerar även den meddelanden på bussen då det finns nytt innehåll.

System av den här typen kallas ibland för “content repositories” eller “innehållsnav”. I vårt fall ser vi det som vårt interna innehålls-API och refererar till det med dess namn – Ursula.

Ursula är en ganska enkel applikation. Det sker ingen större transformering av data innan den indexeras. För det mesta gäller detta även på vägen ut, även om det finns några undantag. Exempelvis ändras referenser (ID och typ) till länkar. Ett annat är att innehållstyper som är definitioner av innehållslistningar skapade av redaktörer expanderas till resultatet av den sökfråga som definierar. Men, generellt sett så är Ursula en tunn wrapper runt ElasticSearch.

Filosofin bakom detta är att det är upp till datakällorna, just nu enbart CMS:et, att se till att innehållet är vettigt. Detta eftersom det är de som känner innehållet bäst. Skulle vi någon gång stöta på en datakälla där detta inte är möjligt eller lämpligt är det en smal sak att bygga en liten applikation som transformerar datat innan det når Ursula.

Ungefär samma resonemang gäller för data på väg ut ur Ursula. Vissa konsumenter behöver bara ett fåtal fält från en enskild resurs. Andra konsumenter skulle troligtvis ha fördel av att få flera resurser hopslagna till en. Men, om Ursula skulle ta hänsyn till detta skulle applikationen snabbt växa i komplexitet. Istället ligger vårt fokus på att hålla Ursula så snabb, och enkel, som möjligt. Skulle vi behöva mer nischade API:er går det utmärkt att bygga sådana ovanpå Ursula.

Ett av de viktigaste ögonblicken under Ursulas framväxt förevigade – efter hårda överläggningar gav sig Fiesta Trio-falangen och ElasticSearch-klustret döptes till ”Chocolate Tree”. (Båda namnen hade varit fullt giltiga enligt Dora-namngivningsstandarden.)
Ett av de viktigaste ögonblicken under Ursulas framväxt förevigade – efter hårda överläggningar gav sig Fiesta Trio-falangen och ElasticSearch-klustret döptes till ”Chocolate Tree”. (Båda namnen hade varit fullt giltiga enligt Dora-namngivningsstandarden.)

Dynamisk bildomskalning

Vårt innehåll är naturligtvis inte bara text utan också bilder och video. Video serveras från vår streamingleverantör men bilder levereras från våra servrar. Tidigare var processen att bilder importerades till EPiServer från OpenStore och i samband med det skalades de om till ett antal fördefinierade storlekar. För att bättre stödja distribution av innehåll i olika kanaler och större flexibilitet i utvecklingen tog vi parallellt med utvecklingen av Ursula fram en ny applikation för dynamisk bildomskalning – Puffer. Eftersom bilderna lagrades i samma miljö som EPiServer-installationen och vi såg litet värde i att skyffla dem någon annanstans byggdes Puffer i .NET och körs på några av dessa servrar. Det är dock en fristående applikation från EPiServer-installationen.

Låt oss säga att vi hämtar ut en artikel som har en bild från Ursula. I denna återfinns då en JSON-property som pekar på bilden i form av en länk till en annan resurs i Ursula. Denna resurs innehåller inte själva bilden. Istället består den av diverse metadata samt information om hur en Puffer-URL till bilden givet en önskad storlek och format bör se ut.

Ariel

Som en första konsument av Ursula började vi bygga vår tabletsajt, en applikation vid namn Ariel. Ariel var också den applikation som, till en början, drev utvecklingen av Ursula. Dvs vi byggde inte Ursula först och sedan tabletsajten, utan de båda växte fram samtidigt. Även denna gång föll teknikvalet på Node.JS.

Jag nämnde tidigare att Ursula var en ganska enkel applikation. Den beskrivningen stämmer inte in på Ariel. Dels därför att det finns en hel del affärslogik inblandad i renderingen av innehåll på vår sajt, inte minst på ettor. Dels just därför att Ursula hölls enkel. Därför diskuterade vi flitigt i början hur Ariel skulle byggas. Skulle vi exempelvis ha ett controller-lager som byggde upp vymodeller?

Valet föll till slut på en lösning med “icke-realiserade” vymodeller. Då exempelvis en artikel efterfrågas hämtas denna från Ursula. Artikeln har i sin tur relationer till sektioner, författare med mera. Dessa hämtas initialt inte, men vymodellen innehåller promises (med Bluebird) för dessa. Det är sedan upp till vyn att välja vad den behöver och, utan att veta om det, initiera hämtning av ytterligare resurser från Ursula. För att den här modellen ska fungera behöver vyer renderas på asynkront vis. Dvs då exempelvis artikelns sektion behövs för att rendera något i sidhuvudet kan inte resten av renderingen ta en paus utan behöver fortlöpa. Därför valde vi Dust.JS som view engine vilket har stöd för detta.

Så här i efterhand har vi diskuterat huruvida Node.JS var ett bra val för att bygga en så pass komplex sajt som våran. I teamet som byggt tabletsajten har vi haft utvecklare med olika bakgrund – Java, .NET, Ruby, Python. Slutsatsen verkar vara att Node inte var ett optimalt val. Men, om man vänder på frågan och istället frågar vad vi borde ha använt istället så är det ingen som har något förslag :-)

Omtalat

Mitt under utvecklingen av Ariel och Ursula, strax innan semestrarna, så kom en fråga om att bygga en “viralsajt”. Den skulle senare komma att kallas Omtalat.nu. Målet var att bygga denna på bara någon vecka.

Omtalat skulle kunna ha varit, och var på sätt och vis, en distraktion mitt under utvecklingen av tabletsajten. Men, ur ett arkitektur-perspektiv var detta sidoprojekt perfekt. En innehållsmässigt komplett version av expressen.se var perfekt för att i början driva utvecklingen av Ursula framåt och Omtalat var ett lysande komplement. Genom att också bygga Omtalat, som är en ganska annorlunda sajt jämfört med expressen.se, så var Ursula tvungen att redan i ett tidigt skede ha stöd för att tillhandahålla innehåll till flera olika applikationer.

Även för Omtalat, som sedermera också kom att bli ett flertal andra sajter, föll teknikvalet på Node.JS. Eftersom denna applikation var av en annan karaktär med enklare innehåll valdes dock inte Dust.js som view engine. Istället föll valet på Jade och Angular kom att spela en betydande roll.

Quiz

Ariel, tabletsajten, lyfte ut en del av trafiken från EPiServer till vår nya plattform genom att skära i den enligt dimensionen användarenhet. För att vidare lyfta ut en del av trafiken till den nya arkitekturen, men skuret utefter innehållstyp istället, valde vi att bygga en ny applikation för våra webbtester.

Att bygga denna applikation, som i all enkelhet kom att döpas till Quiz, föll sig naturligt då quizzar, som är gjorda för användarinteraktion, aldrig hade passat in naturligt i EPiServers lagringsmodell. Genom att bygga en ny lösning “frigjorde” vi inte bara en del av innehållet från CMS:et. Den nya lösningen innebär också en avsevärt förbättrad upplevelse för användarna. Teknikvalen föll på Node.JS, ElasticSearch och Angular.JS.

Användarinteraktioner och fisknamn

När det gäller datakällor för Expressens sajter är innehållet naturligtvis viktigast. Men, det finns även andra. Exempelvis visar vi topplistor av olika de slag i stil med “Mest läst” med användarinteraktioner som underlag. Även dessa lagrades i EPiServer och för att bygga en plattform där det enda beroendet till EPi är som CMS behövde vi ha en ny lösning för lagring och aggregering av interaktioner.

Vi använder ett flertal analysverktyg på våra sajter och flera av dessa har möjlighet att besvara frågor som “vilka är de tio mest lästa artiklarna på www.expressen.se under de senaste 24 timmarna?”. Men, vi hade behov av mer avancerad funktionalitet med mer dynamiska tidsintervall och urval av en eller flera delar av sajten. Dessutom ser vi att vi kan bygga annan funktionalitet baserat på användarinteraktioner. För att inte tala om interna system som följer upp KPI:er och larmar om någon sådan drastiskt ändras efter en deploy.

Därför byggde vi först en mycket enkel applikation, Strömming, som tar emot sidvisningar från våra olika sajter. Denna applikation, skriven i Node.JS, publicerar varje sidvisning som ett meddelande på en meddelandekö. På detta vis kan vi enkelt bygga en, två eller 42 applikationer som kan använda användarinteraktioner i realtid som datakälla.

En första sådan är GoFish, ett system som lagrar sidvisningar i ElasticSearch och tillhandahåller ett API för dynamiska topplistor i realtid. Namnet kanske inte är det mest beskrivande och det är inte otroligt att vi döper om applikationen. Men, det finns en anledning till första delen av namnet. Teknikvalet föll nämligen här på Go.

2015

Utöver resan som jag beskrivit ovan har vi också gjort mycket annat under 2014. QuickShot-teamet har lanserat hundratals saker. Vi har släppt appar för Android, iOS och Android Gear. Vi har fräschat upp och gradvis förbättrat desktop- och mobilsajterna. Vi har släppt en ny hälsosajt, Hälsoliv. Vi har jobbat med A/B-testning och användarstudier. Vi har släppt vårt första open source-projekt. Vi har arrangerat ett publikt ElasticSearch-meetup, en rad interna seminarier och åkt till Öredev. Med mera.

...allting som vi har gjort har varit drivet utifrån Expressens affär och användare.

Givet detta är det lätt att känna sig stolt över vad vi gjort sedan vi drog igång för knappt 14 månader sedan. Men, allra mest stolt är jag över att i princip allting som vi har gjort har varit drivet utifrån Expressens affär och användare. Visserligen har vi ibland tagit till synes omständliga vägar, som exempelvis när vi började dubbel-lagra vårt innehåll i ElasticSearch/Ursula. Men, ett sådant projekt uppkom inte av tekniska anledningar. Istället har vi byggt nya system baserat på vad vår affär och våra användare behöver och i samband med det tänkt till hur vi vill lösa kraven tekniskt.

Så, efter 14 månader är jag stolt över var vi är i dag. Samtidigt är vi ödmjuka inför att vi är långt ifrån klara. Den tekniska resa som vi gett oss ut på är bara halvvägs, om ens det. Vår långsiktiga ambition är ju att vår gamla EPiServer-plattform ska få fokusera på att vara ett bra verktyg för redaktionen att arbeta i samtidigt som innehållet levereras i mindre och mer lättarbetade system. Där är vi inte än. Men vi är på god väg. Det samma kan sägas om användarinteraktioner som också är ett område där vi kan göra mycket i form av nya eller förbättrade funktioner för våra besökare.

Som tur är står vi väl rustade inför dessa, och andra, utmaningar under nästa år. Vi har en samspelt kärntrupp av medarbetare på utvecklingsavdelningen som alla har spelat olika men avgörande roller under resan hittills. För att ta nästa steg kommer vi nästa år också att ta in nya, vassa, medarbetare. En del är redan klara och en del återstår att rekrytera.

2015 blir grymt.

PS. Missa inte vår nästa bloggpost, följ oss på Twitter!

Till startsidan