Nuværende state af mit custom blogsystem

Jeg har i mange år arbejdet på mit blog setup. Setuppet har taget mange former over årenes løb (fra VMs, over AngularJS til WCF services, Kibana og CouchDB), men jeg synes jeg er endt ud med noget som jeg er tilfreds med.

Dog har jeg i det sidste år perfektioneret hvad jeg det er jeg gerne vil. Jeg har på en måde fundet lige præcis den måde, som virker bedst for mig at blogge på (så må vi se om jeg også får det gjort). Jeg har i mange år ledt efter "the sweet spot", og det er også derfor jeg er blevet ved med at lave systemet om. Min initielle tanke har altid været, at jeg gerne vil kunne blogge fra en (eller flere) editors, som virkelig var godt til en ting (faktisk to):

  1. tekst (skrevet i markdown)
  2. vise kode

Jeg vil IKKE have mine drafts gemt i et eller andet obskurt data format, most ned i en database. Den allerstørste grund hertil er rimelig simpel: adgang. Jeg vil på ingen måde være afhængig af en browser for at skulle editere tekst. Jeg vil på ingen måde være afhængig af en custom made editor, som kan dykke ned i databasen, hente tekst frem osv. Alt dette skal ske med allerede kendte teknologier, så jeg kan vælge den editor på markedet som er bedst, editere teksten på en hvilken som helst platform, gemme teksten, og se teksten dukke op på bloggen. Dette valg har jeg været glad for. Jeg har ikke mange indlæg på min nuværende blog, men på de foregående systemer, har jeg udgivet indlæg skrevet fra Visual Studo, Notepad, Notepad++ etc. Jeg har derved forfinet den måde jeg har fundet mest optimal, og jeg synes selv jeg har endt et rimelig godt sted. Jeg er endt et så godt sted, at jeg for første gang gerne vil blogge omkring systemet, noget jeg aldrig har haft i tankerne før.

Pt bruger jeg Visual Studio Code, og jeg er rimelig tilfreds med valget da:

  1. den kender Markdown. Markdown er en first class citizen i editoren, hvilket er ret lækker, da
  2. den har live preview af markdown, som kan vises side om side, og sidst med ikke mindst,
  3. den har INLINE color coding af kodestykker.

Disse tre ting kombineret gør, at VS Code er den ultimative editor i mine øjne, og da mit nuværende setup tillader at jeg kan bruge editoren, så gør jeg selvfølgelig det. Jeg er glad for at jeg ikke skal skule langt i en eller anden obskur browser baseret editor, som aldrig får nogle updates (da jeg selv skal lave dem), efter VS Code, men rent faktisk kan bruge den.

Nuværende setup

Nuværende blog setup

Hele min blog lever i Azure. Jeg har valgt Azure fordi jeg

  1. altid har været glad for Microsoft teknologi stakken
  2. er imponeret over den åbenhed som Microsoft har vist de seneste år
  3. har en C# certificering som gør at jeg har månedlig credit i Azure til at lege for

Jeg er ydermere blevet meget glad for VSTS. Jeg har arbejdet meget med TeamCity, en del med Octopus deploy og lidt med Jenkins, og jeg må sige at jeg VSTS har alt. Og det er selvfølgelig et kæmpe plus at den integrerer nydeligt med Azure (andet ville være mærkeligt).

Dette setup er IKKE en oneclick wonderland

Jeg har i sinde at udgive hele baduljen som open source. Jeg har undgået at sovse kildekoden ind med adgangskoder, brugernavne og andet privat (som jeg iøvrigt aldrig kunne finde på medmindre jeg for explicit besked på det). Dog vil der gå noget tid før alt kan ske fuldautomatisk. Jeg har i sinde at udgive koden, sammen med en readme, som udførligt fortæller opsætning.

Den native editor

En af de ting jeg har haft fokus på igennem hele forløbet er understøttelse for lige præcis den editor jeg finder præcis. Formatet som indlæggene skrives i er markdown, så finder man en editor, som understøtter dette format, så er man good to go. Editoren skal også kunne læse fra og skrive til en fil som er opsat som et netværksdrev (dog skulle dette helst virke på alle operativ systemer da det er en egenskab som udstilles gennem styresystemet).

Mit nuværende valg er Visual Studio Code, som har nativ undersøttelse for markdown, og kan vise markdown side-by-side

VSC med side-by-side load af markdown

En virkelig fed feature. Visual Studio Code undersøtter også inline highlight af kode, hvilket er rimlig sejt, når man som mig stort set kun skriver indlæg som indeholder kodestumper

highlight af kode i VSC

Vel at mærke med automatisk indryk mens man skriver. Det gør faktisk VSC mere eller mindre de facto hvis man vil have en god editor til blogindlæg indtil der kommer en bedre forbi. Kommer der en bedre editor forbi, så kan man med mit blog system bare skifte. Man er ikke bundet, og det er lige præcis det som er kernen i det hele. Det at man lader de andre folk, som er ekstrem gode til at lave editors, om at lave gode editors, så laver man selv resten. En god editor tager lang tid at lave, og den tid er det de færreste som har til rådighed.

Det specielle format

Det ene af de to ting som er lidt kluntet ved systemet er det format som man bruger når man skal poste et indlæg. Formatet (om man vil) består af to filer:

  1. selve indlægget - som er skrevet i markdown
  2. en xml fil som indeholder metadata omkring indlægget

De to filer skal:

  1. ligge i samme mappe
  2. have præcis samme navn (på nær endelsen selvfølgelig)

Som vist her

highlight af kode i VSC

Selve indlægget er plain markdown, så det gider jeg ikke vise, dog er meta dataformatet lidt sjovt:

<?xml version="1.0" encoding="utf-8"?>
<!--state can be Draft/Publish/RePublish-->
<!--date times is on following format 2008-09-22T14:01:54.9571247Z year-month-day-->
<information    state="Draft"
                isSticky="false"
                publishtime="2018-04-28T14:10:01.9571247Z" 
                headline="Nuværende state af mit custom blogsystem" 
                author="Martin Slot" 
                tags="blog,custom,csharp,azure" 
                creationtime="2018-04-28T14:10:01.9571247Z" 
                id=""
                rev=""/>

Metadata filen beskriver følgende:

  1. state - hvad state indlægget er i (valide states er Publish, Published, RePublish og Draft)
  2. isSticky - fortæller om indlægget skal have et menupunkt (og derved optræde som en side)
  3. publishtime - er tidspunktet hvor indlægget skal publiseres
  4. headline - er overskriften for indlægget
  5. author - er indlæggets forfatter
  6. creationtime - er oprettelsestidspunktet for indlægget
  7. id - er artiklens id, som bliver givet af databasen
  8. rev - er databasens revisionsnummer (hvis en sådan findes)

Disse metadata skal sættes manuelt, dog er det let at gøre. Når en artikel oprettes første gang sker dette via en import (mere om importen i sektionen efter denne). Når importen er sket, sættes state til Published samt så sættes id og revisionsnummeret. Jeg har endnu ikke lavet logik som kan give en forfatter besked om at der findes en nyere revision. Jeg har udelukkede haft fokus på publish, og republish (samt draft). Rent teoretisk ved jeg godt at jeg nok aldrig får brug for denne logik selv, da jeg er ene forfatter på bloggen, dog lægger jeg op til at dette kan laves.

Kluntet importering (men også lidt fedt)

Den del som står for selve importen (Min oversigtstegning indeholder en "Importer"), er en TimeTrigger Azure funktion. Koden som bliver kørt her har levet i alle mulige afskygninger i alle de år som jeg har udviklet bloggen (og koden bærer præg af mange delvise omskrivninger, og ikke særlig meget refaktorering). Fx findes denne kodeklump:

       private static Automation.Model.UpsertState WriteToDb(CloudFile file)
        {
            Automation.Model.UpsertState upsertState = null;
            string fileName = file.Name.Replace(".md", String.Empty);
            string metadataFileName = String.Format("{0}.{1}", fileName, "xml");
            var metaDataFile = _articlesDirectory.GetFileReference(metadataFileName);

            var fileExistsTask = metaDataFile.ExistsAsync();
            fileExistsTask.Wait();
            if (fileExistsTask.Result)
            {
                var downloadTask = metaDataFile.DownloadTextAsync();
                downloadTask.Wait();
                string metadataContents = downloadTask.Result;
                var metadataContentsBytes = System.Text.Encoding.UTF8.GetBytes(metadataContents);
                System.Xml.Linq.XDocument document = System.Xml.Linq.XDocument.Load(new MemoryStream(metadataContentsBytes));
                System.Xml.Linq.XElement stateXml = document.Descendants("information").FirstOrDefault();
                string author = stateXml.Attribute("author").Value;
                string tags = stateXml.Attribute("tags").Value;
                var idAttribute = stateXml.Attribute("id");
                var revAttribute = stateXml.Attribute("rev");
                var isStickyAttribute = stateXml.Attribute("isSticky");

                bool isSticky = false;
                string id = String.Empty;
                string rev = String.Empty;

                if (isStickyAttribute != null)
                {
                    isSticky = bool.Parse(isStickyAttribute.Value);
                }

                if (idAttribute != null)
                    id = idAttribute.Value;

                if (revAttribute != null)
                    rev = revAttribute.Value;

                DateTime creationDateTime = DateTime.Parse(stateXml.Attribute("creationtime").Value);
                string headline = stateXml.Attribute("headline").Value;
                DateTime publishTime = DateTime.Parse(stateXml.Attribute("publishtime").Value);
                var downloadTextTask = file.DownloadTextAsync();
                downloadTextTask.Wait();
                string text = downloadTextTask.Result;
                bool isDraft = false;

                FileStates state = (FileStates)Enum.Parse(typeof(FileStates), stateXml.Attribute("state").Value);

                if (state == FileStates.Draft)
                    isDraft = true;

                string connectionstring = GetEnvironmentVariable("blogConnectionstring");
                string primaryKey = GetEnvironmentVariable("blogPrimarykey");

                Repository.Blog.ArticleRepository.Interface.IArticleRepository articleRepository = new Repository.Blog.ArticleRepository.CosmosDb.ArticleRepository(connectionstring, "Blog", "Article", primaryKey);
                Service.Blog.BlogService.Interface.IBlogService blogService = new BlogService(articleRepository);

                Automation.Model.Article article = new Automation.Model.CosmosDb.Article();
                article.IsSticky = isSticky;
                article.Author = author;
                article.CreationDateTime = creationDateTime;
                article.Document.Headline["da"] = headline;
                article.Document.Text["da"] = text;
                article.IsDraft = isDraft;
                article.PublishTime = publishTime;
                article.AddTagsString(tags);
                article.Id = id;
                article.Revision = rev;

                var saveTask = blogService.UpsertArticleAsAsync(article);
                Task.WaitAll(saveTask);

                if (saveTask.IsCompleted)
                {
                    upsertState = saveTask.Result;
                    Console.WriteLine($"Upserted: {upsertState.Upserted}");
                    Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(article, Newtonsoft.Json.Formatting.Indented));

                }
                else
                {
                    Console.WriteLine("fault when upserting");
                }
            }

            return upsertState;
        }

Ja. Du har læst rigtigt. En funktion på næsten 100 linjer. Dette er slet ikke normalen for hvordan jeg udvikler på mit job. På jobbet holder jeg mine metoder små, og de vigtige metoder er dækket af enten en, eller flere unit test, og/eller en eller flere integrationstests. Dog forbeholder jeg mig rettet til at programmere i min fritid (og ikke udvikle). Mit fokus har aldrig været lave kode, som er dokumenteret og testet i form af noget automatiseret test. Jeg har altid haft fokus på at prøve nye ting i min fritid, og det bærer importeren også præg af. Kan man se gennem fingre med den dårlige kodekvalitet, så læg mærke til at funktionen faktisk:

  1. Gemmer artiklen in en database (her CosmosDB)
  2. Henter filen fra Azure Storage (jeg gifer en CloudFile med som parameter til funktionen)

Hele to nye teknologier, som jeg aldrig har leget med før I EN FUNKTION. Fedt! Jeg omskrev metoden, fra at læse fra en folder i filsystemet på min PC, til at læse fra et mounted drev fra en docker container, som kørte på en RaspberryPI, til at lægge i en Azure funktion. Pyyyh. Men min kode overlevede næsten (jeg skulle kun lave små omskrivninger). Og jeg er rimlig tilfreds som det virker i dag.

Min Importer kører hver time, og læser alle filerne som ligger i roden af en folder, som jeg peger på. Herefter opretter den indlægget, eller ændre dem, alt afhængig af hvad state som står i metadata filen. Rimlig simpel, og lige til at forstå. Og ja, til den skarpe læser, så kan man ikke slette endnu. Denne feature ligger i støbeskeen:

Og Importer'en skal derefter kunne reagere på disse. Jeg har aldrig haft brug for at slette endnu, og når jeg har, jamen så skriver jeg koden. Det er nu en gang det smukke ved selv at eje koden. Ship it when you need it.

Den dyre database

Helt i starten gemte jeg alle mine artikler i Couchdb. En fin dokument database. Det oprindelige system bestod af tre VMs, som kørte i Azure:

  1. en database VM med Couchdb og Elastic
  2. en VM som kørte en WCF service som man kunne bruge til at hente indlæg, gemme indlæg, søge efter indlæg osv
  3. en VM som stod for at hoste min hjemmeside

Opsætning virkede faktisk fint (faktisk kørte det hele i et virtuelt netværk med en frontend og backend subnet - meget fancy hvis man er til den slags).

Jeg byggede alt fra en fjerde VM, som kørte TeamCity og Octopus. Opsætningen var før der fandtes VSTS. Da VSTS kom besluttede jeg mig for at nedlægge setuppet, og lige så stille steppe væk fra de virtuelle maskiner. Jeg ville holde alt i Azure, og ikke kalde op til tredjeparts database (beliggende et helt andet sted i en helt anden sky i en helt anden region et fjerde sted i verden), derfor faldt valget på CosmosDB. Den native svejtserkniv, som bare kan altid ... næsten. Og så er den semi dyr, selv hvis man ikke bruger den.

Vælger man som jeg, at køre med den SQL motor, så skulle man tro at der var en 1-til-1 mellem MSSQL servers SQL og CosmosDB SQL. Det er det som Microsoft gerne vil have til at tro, men kradser man lidt i overfladen, så er verdenen en anden. Fx er groupings ikke understøttet (ikke pr. dagsdato, men det ligger vist nok i deres roadmap). Mange af deres aggregate funktioner var heller ikke implementeret fra starten af, selvom de sagde at SQL var understøttet. De er nogle snydere dem fra Microsoft. Heldigvis har jeg aldrig skulle gøre brug heraf, så CosmosDB passede fint til min usecase.

Hvad prisen angår, så koster det ca 300 kr om måneden for den "mindste" CosmosDB, og vi snakker altid bare for at have den tændt, og med nogle få 100 opslag om måneden. CosmosDB er dyr, men hvis man skal bruge en dokument dabase, som kan skalere mellem regioner og klare et meget stort pres, uden selv at stå for maintance, så tror jeg roligt man kan tage CosmosDB med i sine overvejelser. Teknologien fejler intet.

X-Cache: HIT vs cf-cache-status: HIT

Den nyeste teknologi jeg har rodet med er CDN. Jeg har på mit job beskæftiget mig med Akamai, men aldrig gennem Azure. Da min CosmosDB alligevel koster lidt, og da jeg ikke skriver særlig mange indlæg, så besluttede jeg mig for at sætte en CDN ind foran. Først prøvede jeg Azures to CDN tjenester:

  1. Akamai
  2. Verizon

Dog følte jeg ikke rigtig jeg fik et værktøj som var brugbart. De hostede ikke selv DNS, og min egen DNS leverandør understøttede ikke redirects fra http(s)://sigsys.dk og til http(s):/www.sigsys.dk. Administrationsværktøjet for dem begge var som hevet ud af halvfemserne. Nøj noget lort. Jeg gad godt jeg havde et screenshot (heldigvis har jeg ikke), så I kunne se det. Da jeg manglede et eller andet besluttede jeg mig for at se på Cloudflare. OG BOOM. Jeg var solgt. Cloudflare er en klar anbefaling. De overtager min DNS (undskyld unoeuro, I har været gode, men ikke så gode som Cloudflare). De hacker egentlig lidt CNAME direktivet i DNS protokollen, så man kan lave redirects fra non-www til www, og derefter spreder de magi udover dit website. Jeg har valgt den gratis løsning ved dem, hvor man får tre page rules, som lige er nok til det jeg gerne vil have. Jeg har slået caching til både på edge noden, som leverer siden og i browseren som modtager siden. Edge noden cacher i en dag, og browseren cacher i et par timer ad gangen. Det tager meget load fra mit website, og det load af min CosmosDB. Nu har jeg ikke meget load endnu, men det er altid godt at tage sorgerne på forskud. Specielt når det handler om Azure credits. De har det med at slippe op.

Jeg fik desuden bundlet alt min css og javascript til en fil, site.css og site.js bliver bygget og kopieret til en blog storage, som derefter serverer filerne. Dette sker også med billeder. Derved fjerner jeg presset fra mit website. Igen. Der er ikke meget pres på det som det er nu, men det er altid godt at tage sorgerne på forskud når det handler om Azure credits. De har det med at slippe op.

Automagisk deploy med VSTS (Næsten)

Min Importer og mit website bliver deployet via VSTS. I VSTS hoster jeg mit kode. Hver gang jeg laver et commit, bygges koden, og der laves et release. Jeg behøver ikke gøre noget, og jeg får en mail når der er deployet. Jeg er meget overrasket over VSTS. VSTS tager det bedste (synes jeg) fra TeamCity, Jira (tildels) og Octopus deploy, og giver et fedt administrations interface, hvor man kan planlægge, hoste og release software. Da VSTS er firstclass citizen i Azure, så integrerer den perfekt med Azure (alt andet ville være mærkelig).

Jeg sætter ikke mine resorucer op via ARM templates endnu. Det er noget jeg har lidt erfaring med, men det kommer også i nærmeste fremtid, da det skal laves, hvis jeg på et tidspunkt frigiver koden til fri afbenyttelse.

Hvad mangler der (der er altid småting)

Der mangler altid noget når man laver software, og specielt når man som mig ikke har særlig meget fritid, og selv er kunden:

  1. webpack til js og css - bundling af js og css
  2. ARM template - til automatisk deploy (så man hurtigt kan spinde test eller feature miljøer op uden at røre en finger)
  3. Deploy kræver stadig at MIME types bliver sat korrekt på js og css - et hurtigt fiks, men det ligner at dette ikke sker første gang man lægger js og css filer op på blob storage
  4. Ny template til bloggen - min nuværende template er ... ja. Skod
  5. Udskiftning af bower - bower anbefaler selv man kigger på Yarn eller NPM
  6. Som I kan se så skalere billederne ikke særlig godt (det ser faktisk grimt ud) - så det skal fikses

Det med småt

Jeg kan kun anbefale ghost platformen (selvom jeg aldrig selv har prøvet kræfter med den direkte), hvis man ikke selv vil til at lave et system som jeg har gjort. Lav kun et system som dette hvis du vil eksperimentere med forskellige ting. Dog må jeg indrømme at jeg bruger mit blogsystem som grundlag for at rode med nye teknologier:

  1. Docker
  2. VSTS
  3. Storage
  4. Azure functions
  5. Akamai, Verizon og Cloudflare CDN

Bare for at nævne nogle stykker. Jeg kan altid anbefale at udfordre ens egen teknologiske indsigt.

Det var et længere indlæg fra min side. Stavefejl og skøre sætninger bliver rettet, som jeg finder dem. Skriv endelig på keybase.io, hvis i har nogle spørgsmål (mit username er martinslot).

Skrevet af Martin Slot den 5/28/2018