Hoppa till huvudinnehåll

Utvalda projekt

Levererat i stor skala.

Riktiga leveranser — arkitekturbeslut, avvägningar och mätbara resultat. Alla kunduppgifter är anonymiserade, men det tekniska innehållet är oförändrat.

Utvalda projekt

Statligt ägt energibolag · Digital försäljning · Solution Designer & Senior Developer

Enterprise CMS-migrering: EPiServer 11 → Optimizely CMS 12

Ledde migreringen av en omfattande CMS-plattform som är navet för bolagets digitala försäljning: kundportalen MyPages, flödet för kontraktssignering och de publika webbsidorna. Plattformen flyttades från EPiServer CMS 11 på .NET Framework till Optimizely CMS 12 på .NET 8, vilket gav en modern, molnanpassad arkitektur som är enkel att förvalta. Migreringen skedde stegvis med parallelldrift, så att slutanvändarna aldrig påverkades under övergången.

Nyckelbeslut

  • Feature Folder StructureKoden organiserades utifrån affärsdomän (MyPages, ContractSigning, OpenPages) i stället för efter tekniskt lager. Varje mapp är helt fristående och innehåller sina egna controllers, vyer, modeller och block.
  • Stegvis migrering, prioriterad efter riskCMS 11 var i drift under hela processen. Open Pages migrerades först (lägst affärsrisk), därefter Contract Signing och sist MyPages. En domän i taget – aldrig allt på en gång.
  • Content Delivery API + Content GraphErsatte gammal, manuell egenskapshämtning (property traversal) med Optimizelys GraphQL-baserade innehållshämtning. Resultatet blev frikopplad innehållsleverans, effektivare siduppbyggnad och inga N+1-anrop.
  • Visual Builder för ett självständigt marknadsteam Marknadsteamet kunde nu bygga och ändra sidlayouter på egen hand, utan att blanda in utvecklare. Det minskade kraftigt antalet ändringsärenden som tidigare hamnade hos utvecklingsteamet.
  • .NET Framework → .NET 6StructureMap byttes ut mot .NET:s inbyggda beroendeinjektion. En renare konfiguration utan tredjepartsberoende, samordnad med själva CMS-uppgraderingen.
  • AngularJS → Angular ElementsMyPages interaktiva widgetar migrerades från AngularJS till Angular Elements (webbkomponenter). Varje element bootstrappas oberoende, laddas vid behov och är helt fristående, vilket gör att Angular och den omgivande webbsidan är fullständigt frikopplade.
Optimizely CMS 12.NET 6C#Feature Folder StructureContent Delivery APIContent GraphAzure DevOpsAngular ElementsDockerCI/CDMulti-sitePersonalization
ArkitekturbeslutLäs mer

Varför Feature Folder Structure?

Den befintliga kodbasen var organiserad efter tekniskt lager — alla kontroller i en katalog, alla modeller i en annan. Att förstå en enda funktion krävde navigering i flera kataloger och att hålla ihop sambanden i minnet. Ägandeskapet var oklart. Omorganisering efter affärsfunktion (MyPages, ContractSigning, OpenPages) gjorde varje område självständigt: en utvecklare kunde förstå MyPages genom att läsa en mapp. Det tydliggjorde också teamägarskapet — varje produktområde fick ett otvetydigt hem. Onboardningstiden för nya utvecklare minskade påtagligt.

Fasad migrering, inte big bang

En helhetsövergång av plattformen hade krävt månader av parallellt arbete utan något användarvärde, och hög samordningsrisk vid övergången. Den fasade metoden levererade fungerande mjukvara i varje steg. Open Pages gick först — tillståndslöst, publikt och med lägst affärsrisk. Contract Signing följde; dess väldefinierade in/ut-gräns gjorde det till en ren extraktion. MyPages, med den mest komplexa autentiserade tillståndshanteringen, kom sist. Anpassade migreringsscript hanterade innehållstypmappningen mellan CMS 11:s propertymodell och CMS 12:s innehållsmodell.

CMS 12-funktioner som kraftmultiplikatorer

Content Graph (GraphQL) ersatte långsam property traversal på innehållstunga sidor. Visitor Groups och Personalization möjliggjorde kontextanpassat innehåll för autentiserade MyPages-användare utan skräddarsydd routingkod. Optimizely Forms återskapade kundvända formulär med validering och backendintegration, och ersatte handrullad kod. Schemalagda jobb automatiserade innehållssynkronisering. Dessa funktioner ersatte skräddarsydd kod som teamet hade underhållit — varje funktion minskade underhållsytan.

CI/CD-pipeline som leveransförutsättning

Den befintliga driftsättningsprocessen var manuell och felbenägen. En fasad migrering över flera månader kräver tillförlitliga, repeterbara releaser — manuell driftsättning var inte förenligt med det tempot. En Azure DevOps-pipeline med automatiserad staging-befordran och bevakat produktionssläpp byggdes tidigt i projektet. Det blev grunden som gjorde inkrementell migrering hållbar.

AngularJS → Angular Elements: migrering med best practices

MyPages hade interaktiva widgets (kontraktsstatus, kontouppgifter, förbrukningsgrafer) byggda i AngularJS. Istället för en fullständig omskrivning eller en utdragen hybridperiod valdes Angular Elements (webbkomponenter via @angular/elements): varje widget kompileras till ett självständigt custom element, bootstrappas oberoende och bäddas in i Razor-vyer med en enda HTML-tagg. Best practices tillämpades: OnPush change detection för att undvika onödiga renderingscykler; explicita @Input- och EventEmitter-kontrakt så att värdsidan saknar Angular-beroende; lazy loading per element så att bara widgets som finns på en given sida laddas ned. AngularJS togs bort helt från MyPages i samma release — ingen dubbelframework-överlappning kvar i kodbasen.

Energibolag · Avtaletslivscykel · Senior Developer & Arkitekt

Mikrotjänst för elavtal: .NET 8 Web API

Designade och byggde en skräddarsydd mikrotjänst för elavtalets hela livscykel: nyteckning av avtal, hantering av befintliga kontrakt och faktureringsdata. Tjänsten bygger på Vertical Slice Architecture med CQRS och MediatR och kopplar samman frontendsystemen med SAP- och faktureringssystemen via händelser på Azure Service Bus. Den är driftsatt på Azure Kubernetes Service (AKS) med Redis-cache och kan uppdateras helt utan driftstopp tack vare rullande driftsättningar.

Nyckelbeslut

  • Vertical Slice ArchitectureVarje funktion (CreateContract, UpdateContract, GetBillingDetails) är en helt fristående slice där kommando, hanterare, validator, svarsmodell och endpoint ligger samlade på ett och samma ställe. Inga gemensamma tjänstelager som binder ihop orelaterade funktioner.
  • CQRS med MediatR pipeline behaviorsKommandon sköter skrivningar, frågor sköter läsningar. Pipeline behaviors lägger automatiskt på loggning, validering och felhantering, så att gemensamma, tvärgående delar hanteras på ett ställe i stället för att upprepas i varje hanterare.
  • EF Core för skrivningarEntity Framework Core sköter kontraktens skrivoperationer med fullt ORM-stöd och migreringar. Dapper sköter faktureringens läsfrågor med prestandaoptimerad rå SQL för komplexa joins. Varje verktyg används där det gör mest nytta.
  • Result<T>-mönster istället för undantagHanterarna returnerar Result<T> i stället för att kasta undantag vid förväntade affärsfel. Varje felfall blir explicit i metodsignaturen, syns redan vid kompilering och går att testa isolerat.
  • Azure Service Bus + Polly för motståndskraftNär ett kontrakt skapas publiceras asynkrona händelser som efterföljande system tar emot. Integrationen mot SAP sker via en HTTP-klient med retry och circuit breaker via Polly. Idempotensnycklar förhindrar att samma kontrakt skickas in flera gånger.
.NET 8C#Web APIVertical Slice ArchitectureCQRSMediatRFluentValidationEntity Framework CoreAzure Service BusRedisDockerAKSAzure DevOpsJWTPollyxUnitMinimal APIs
ArkitekturbeslutLäs mer

Varför Vertical Slice Architecture framför traditionella lager?

Traditionell horisontell lagring (Repository → Service → Controller) kopplar ihop orelaterade funktioner via delade lager — en ändring i faktureringtjänstlagret berör kod som också används av kontraktsskapande. Vertical Slice Architecture eliminerar detta: varje slice äger sin fullständiga implementation. Att lägga till en ny funktion innebär att lägga till en ny slice; inget befintligt ändras, vilket begränsar regressionsrisken. I det här projektet producerade parallell utveckling mellan teammedlemmar noll merge-konflikter på funktionsarbete, eftersom varje utvecklare ägde en distinkt, icke-överlappande slice.

EF Core och Dapper — varför båda?

EF Cores change tracking, relationshantering och migreringsverktyg lämpar sig väl för kontraktets skrivoperationer där revisionsspår och transaktionsintegritet är viktiga. För faktureringsläsfrågor med komplexa joins över flera tabeller var EF Cores genererade SQL mångordig och mätbart långsammare än nödvändigt. Dappers explicita SQL gav full kontroll över frågeform och exekveringsplan. Att använda varje verktyg där det är starkast är mer pragmatiskt än att tvinga ett ORM att täcka alla åtkomstmönster.

Result<T> — varför inte undantag?

Undantag för förväntade affärsfel (kontrakt ej hittat, dubblettinsändning) är semantiskt fel och gör felvägar osynliga vid anropsstället. Result<T>-mönstret gör varje hanterares framgångs- och felfall explicita, kompileringskontrollerade och oberoende testbara. Kombinerat med MediatR pipeline behaviors som tillämpar loggning och validering automatiskt eliminerade detta try/catch-upprepningskoden som hade duplicerats i varje hanterare i den tidigare kodbasen. Alla felvägar blev synliga — och testade.

Säkerhet designad in i slice-kontrakten

JWT bearer-autentisering med policybaserad auktorisering definieras per endpoint, inte tillämpas som ett övergripande tillägg i efterhand. Känslig kontraktsdata krypteras på fältnivå före persistering. PII exkluderas från Serilog-strukturerade loggar via property-filter — loggningskontraktet är en del av slice-specifikationen. Inget PII förekom i någon loggutdata. Detta specificerades innan någon hanterare skrevs, inte tillagt efter go-live.

Statligt ägt energibolag · Frontend-modernisering · Senior Developer & Solution Designer

AngularJS → Angular Elements: Yarn Monorepo-migrering

Ersatte en splittrad AngularJS-kodbas med ett välstrukturerat Angular Elements-monorepo byggt på Yarn Workspaces. Resultatet blev ett bibliotek med webbkomponenter som versionshanteras var för sig, driftsätts på Azure Blob Storage och distribueras globalt via Azure Front Door (CDN). Komponenterna integreras i Optimizely CMS (MVC) som ramverksoberoende custom elements, vilket för första gången gör frontend- och CMS-releaserna helt oberoende av varandra.

Nyckelbeslut

  • Yarn Workspaces-monorepo med tydliga biblioteksgränserEtt enda repo med fyra typer av bibliotek: core-lib (tjänster, auth, interceptorer), utils-lib (pipes, direktiv), ui-lib (designsystem + Storybook) och ett feature-lib per affärsdomän. TypeScript path aliases och ESLint-regler för import ser till att inga beroenden uppstår mellan lagren – brott mot reglerna fångas i CI, inte först vid körning.
  • Angular Elements som ramverksoberoende webbkomponenterVarje funktion kompileras till ett fristående Custom Element som startar sin egen Angular-zon, är isolerat med Shadow DOM och har HTML-attribut och DOM-händelser som sitt publika gränssnitt. Inget gemensamt app-skal – bara de element som faktiskt finns på sidan laddas.
  • Oföränderliga versionerade driftsättningar till Azure Blob StorageVarje bygge publiceras till /elements/v{major}.{minor}.{patch}/feature-name.js. De versionerade filerna sätts till Cache-Control: immutable med ett års TTL. En versionsmanifestfil kopplar funktionsnamn till rätt URL:er, så en återställning sker omedelbart genom att manifestet uppdateras – ingen ny driftsättning krävs.
  • Azure Front Door (CDN) med säkerhetsheaders i kantnätetFront Door ligger framför Blob Storage och sköter den globala distributionen. Content-Security-Policy, HSTS och X-Frame-Options sätts på CDN-nivå och tillämpas likadant på varje fil. DDoS-skydd är påslaget, och en sekundär Blob Storage-region fungerar som failover för origin.
  • Optimizely CMS-integration via egna blocktyperEn egen blocktyp per Angular Element, så att redaktörerna placerar komponenterna på sidorna som vilket annat CMS-block som helst. Blockens egenskaper mappas till elementets HTML-attribut. Genom att låsa versionen per block går utrullningen att styra: v1.x ligger i produktion medan v2.x testas i staging.
  • Strangler Fig-migrering – utan driftstopp, ingen big bangAngularJS och Angular Elements kördes parallellt under hela övergången. Feature flags i CMS styrde vilken version som renderades på varje sida. AngularJS avvecklades helt först när alla sidor var verifierade på Angular Elements.
Angular ElementsYarn WorkspacesWeb ComponentsTailwindCSSStorybookAzure Blob StorageAzure Front DoorCDNOptimizely CMS MVCAngularTypeScriptAzure DevOpsCI/CDVersioned DeploymentsShadow DOM
ArkitekturbeslutLäs mer

Varför Yarn Workspaces med explicita biblioteksgränser?

Ett delat repository för flera Angular-bibliotek eliminerar koordinationsoverheaden med separata repon — delad node_modules-hoisting, konsekvent lint- och testkonfiguration, enkel CI-pipeline. Yarn Workspaces hanterar paketlänkningen; TypeScript path aliases och ESLint importregler är det som faktiskt upprätthåller gränser: utan dem beror feature-bibliotek tyst på varandra och kodbasen degraderas till en kopplad monolit. Upprätthållna gränser gjorde varje biblioteks publika API explicit — vad ett bibliotek exporterar är dess kontrakt, allt annat är internt. Byggnadsstrategin per workspace möjliggjorde parallella CI-steg och isolerade driftsättningar per funktion. Ett funktionsteam kunde skicka ett nytt Angular Element utan att utlösa ett ombygge av orelaterade bibliotek.

Angular Elements: den självbootstrappande arkitekturen

Kärnkravet var att bädda in interaktiva Angular-komponenter i en Optimizely CMS MVC-applikation utan att koppla frontend-byggpipelinen till CMS. Angular Elements (Custom Elements / Web Components) löste detta på ett elegant sätt: varje element bootstrappar sin egen Angular-zon och bäddas in via en standard HTML custom element-tagg — ingen Angular-kunskap krävs av värdsidan. Shadow DOM-inkapsling innebar att värdsidans stilar inte kunde läcka in i element och att elementets stilar inte kunde läcka ut. Inmatningskonfiguration via HTML-attribut och DOM-händelser som utdata gjorde CMS-integrationen naturlig: Optimizely-blockegenskaper mappas direkt till elementattribut, konfigurerbara av CMS-redaktörer utan inblandning från utvecklare.

Oföränderliga versionerade driftsättningar — varför och hur

Den versionerade sökvägsstrategin (/elements/v{semver}/feature-name.js) är grunden för säkra oberoende driftsättningar. När ett versionerat tillgång väl är publicerat skrivs det aldrig över — Cache-Control: immutable med ett ett år max-age är då korrekt, och CDN-kanters cachar håller det på obestämd tid. Återgång är inte en driftsättning: det är en manifestuppdatering som pekar CMS-blocket mot en tidigare versionssökväg. Versionsmanifestet — som kringgår CDN-cache och alltid är färskt — mappar funktionsnamn till deras aktuella versionerade URL:er. CMS-block pinnars till en majorversion, så minor- och patch-releaser skickas utan någon CMS-ändring. Detta var mekanismen som helt frikopplade frontend- och CMS-releasecykler.

Strangler Fig: migrering utan driftstopp

En big-bang-omskrivning av en live kundriktad frontend innebär hög risk och tvingar fram ett stopp för funktionsutveckling. Strangler Fig-mönstret undvek båda. Nya Angular Elements ersatte AngularJS-komponenter inkrementellt — ett sidområde i taget. Feature flags i Optimizely CMS styrde vilken version (AngularJS eller Angular Element) som renderades per sida, vilket möjliggjorde validering per sida innan trafiken bytte permanent. AngularJS avvecklades inte förrän varje sidområde verifierats på den nya stacken. Resultatet: en fullständig plattformsmigrering utan driftstopp och utan fryst utvecklingsperiod.

TailwindCSS som ett delat designtokenlager

Med flera Angular-bibliotek som bygger oberoende är visuell konsistens mellan funktionsteam en verklig risk — varje team kan drifta mot sina egna konventioner för avstånd, färg och typografi. En delad Tailwind-förinställning exporterad från ui-lib löste detta: alla feature-bibliotek utökar samma baskonfiguration, så designtokens har en enda källa till sanning. Tailwinds per-biblioteks innehållskonfiguration eliminerar oanvänd CSS i varje byggoutput — det delade lagret lägger till ingen bundle-overhead. Monorepo-nivåns Storybook-instans dokumenterar alla ui-lib-komponenter med stories för standardtillstånd, varianter och edge cases, och blev den gemensamma designsystemreferensen för både utvecklare och designers.

Fler projekt pågår — CI/CD-pipeline-design och cloud-native-mönster kommer härnäst.