Tillbaka till Utvecklare

Systemdesign och arkitektur

Designprinciper, mönster och arkitektur — SOLID, DDD, CQRS, ren arkitektur och mikrotjänster.

Vad är en mikrotjänst och vilka fördelar och utmaningar har den jämfört med en monolitisk arkitektur?
En mikrotjänst är en liten, oberoende deploybar applikation som ansvarar för en specifik affärsfunktion. Fördelar: frikoppling möjliggör oberoende utveckling, driftsättning och skalning; felisolering förhindrar att ett enskilt fel tar ner hela systemet. Utmaningar: ökad operativ komplexitet (kommunikation mellan tjänster, distribuerad tracing, versionering), nätverksoverhead och svårare integrationstestning och felsökning.
Vad är REST och vilka best practices följer du när du designar ett REST-API?
REST (Representational State Transfer) är en arkitekturstil som använder HTTP för klient-server-kommunikation. Varje resurs identifieras av en URL och manipuleras via standard HTTP-metoder (GET, POST, PUT, DELETE). Best practices inkluderar: tillståndslös kommunikation, tydliga resursnamn, korrekt användning av HTTP-statuskoder (200, 404, 500), separation av lager och dokumentation med OpenAPI/Swagger.
Hur skulle du säkerställa att en mikrotjänst är skalbar och feltolerant?
Nyckelstrategier: 1) Felhantering med retries, backoff och circuit breakers (t.ex. Resilience4j). 2) Horisontell skalbarhet genom att designa tillståndslösa tjänster bakom en load balancer. 3) Observerbarhet via strukturerad loggning, mätvärden (Micrometer/Prometheus) och distribuerad tracing (Zipkin/OpenTelemetry). 4) Testning med enhets- och integrationstester plus dokumentation (Swagger/OpenAPI). 5) Containerbaserad driftsättning med Docker och Kubernetes för auto-skalning.
Vad är skillnaden mellan GET, POST, PUT och DELETE i HTTP? Vilka är idempotenta?
GET hämtar en resurs utan sidoeffekter — idempotent. POST skapar en ny resurs eller skickar data — inte idempotent (två identiska POST kan skapa två resurser). PUT ersätter eller uppdaterar en hel resurs — idempotent (samma begäran ger samma resultat). DELETE tar bort en resurs — idempotent (att radera något som redan är borta ändrar inte tillståndet). Idempotens är viktigt för säkra omförsök i distribuerade system.
Hur skulle du driftsätta mikrotjänster i molnet med AWS?
För driftsättning på AWS: använd ECS/EKS eller Elastic Beanstalk för containerorkestrering, Lambda för serverlösa funktioner, S3/EBS för lagring, RDS för relationsdatabaser och DynamoDB för NoSQL, ElastiCache (Redis/Memcached) för caching, CloudFront som CDN, CloudWatch/CloudTrail för övervakning, SQS/SNS för asynkron meddelandehantering och IAM för säkerhet. CI/CD-pipelines med AWS CodePipeline, GitHub Actions eller Jenkins säkerställer automatiserade, reproducerbara driftsättningar över dev-, staging- och produktionsmiljöer.
Vad är Saga-mönstret i mikrotjänster?
Saga-mönstret koordinerar en serie lokala transaktioner över flera mikrotjänster för att upprätthålla datakonsistens utan att förlita sig på en distribuerad transaktion (2PC). Varje tjänst utför sin lokala transaktion och publicerar en händelse; om ett steg misslyckas utlöses kompensationstransaktioner för att ångra tidigare steg. Detta tillvägagångssätt bevarar tjänsternas autonomi samtidigt som det hanterar affärsprocesser över tjänstgränser på ett tillförlitligt sätt.
Hur skulle du designa ett REST API-endpoint som tar emot en teckenmatris och en ordlista och returnerar matchade ord?
Definiera ett POST-endpoint (t.ex. POST /api/wordfinder) som tar emot en JSON-kropp med två fält: matrisen (array av strängar) och ordlistan (array av strängar). Svaret returnerar de matchade orden som en JSON-array. POST föredras framför GET eftersom nyttolasten kan vara stor och att koda en matris i query-strängen är opraktiskt.
Hur returnerar du de N oftast hittade orden från ett ord-sökresultat?
Lagra antalet matrisförekomster för varje matchat ord i ett dictionary/map, sortera sedan posterna i fallande ordning efter antal och ta de första N. Om N är fast (t.ex. 10) är en partiell sortering eller en min-heap av storlek N effektivare än en fullständig sortering för stora resultatmängder.
Vilka prestandaöverväganden gäller vid sökning i en 64×64-teckenmatris med en stor ordlista?
En brute-force-sökning är O(R × C × L × |ord|). Att förbygga ett Trie från ordlistan reducerar ordmatchningen till ett enda matrisgenomgång — varje tecken kontrolleras mot Trie-noden, vilket ger O(R × C × L) totalt. För den givna 64×64-begränsningen är även brute force acceptabelt, men Trie-metoden skalar bättre när ordlistan växer.
Hur skulle du designa ett notifieringssystem så att ett nytt kanal (t.ex. WhatsApp) kan läggas till utan att befintlig kod ändras?
Definiera ett gemensamt gränssnitt (t.ex. NotificationChannel) med en metod send(notification) och implementera det separat för varje kanal (Email, SMS, Push). En dispatcher väljer rätt implementering vid körtid baserat på notifieringens kanalfält. Detta följer Open-Closed-principen: systemet är öppet för utökning (ny kanalklass) men stängt för modifiering (dispatcher och befintliga kanaler rörs inte).
Hur säkerställer du att en användare bara kan läsa eller ändra sina egna notifieringar?
Extrahera den autentiserade användarens ID från JWT-claims i auth-middlewaren och filtrera sedan varje databasfråga med WHERE user_id = :currentUserId. Förlita dig aldrig på ett userId som användaren skickar i request-body eller path för ägarskapsvalidering, eftersom klienten kan manipulera det.
Varför är det viktigt att trigga notifieringssändningen direkt vid skapandet istället för som ett separat manuellt steg?
Att trigga sändningen vid skapande håller operationen atomisk ur användarens perspektiv: en notifiering som finns har alltid dispatchats. Det förenklar också API-ytan (ingen separat /send-endpoint) och minskar risken för föräldralösa notifieringar som skapats men aldrig skickats. Om dispatchingen kan misslyckas bör det hanteras via återförsök eller en bakgrundsjobb-kö snarare än att kräva klientorkestrering.
Varför är ett in-memory-cache (t.ex. Caffeine) otillräckligt för en multi-replika-driftsättning, och vad är det rekommenderade alternativet?
Varje replika håller sin egen oberoende cache, så ett värde cachat i replika A är osynligt för replika B, vilket leder till inkonsekvent beteende och redundanta externa anrop. Det rekommenderade alternativet är ett distribuerat cache som Redis, där alla repliker läser och skriver till ett delat lager. Spring Boot integreras med Redis via spring-boot-starter-data-redis och kan konfigureras transparent som CacheManager-backend.
Hur strukturerar man en docker-compose.yml för att köra en Spring Boot-applikation tillsammans med en PostgreSQL-databas?
Definiera två tjänster: en för Spring Boot-appen (byggd från en Dockerfile eller hämtad från Docker Hub) och en med den officiella postgres-imagen. Använd ett environment-block för att skicka DB-uppgifter och en depends_on-klausul så att appen väntar på databastjänsten. Exponera applikationsporten (t.ex. 8080) och montera en namngiven volym för PostgreSQL-databeständighet. En healthcheck på postgres-tjänsten säkerställer tillgänglighet innan appen startar.
Hur bör asynkron granskningsloggning utformas så att loggningsfel aldrig påverkar huvudsvaret från API:et?
Huvudbegäranhanteraren sparar sitt resultat och publicerar sedan en händelse eller anropar en @Async-metod för att skriva granskningsposten. Den asynkrona hanteraren omsluter all persistenslogik i ett try-catch och ignorerar tyst fel (eller skickar dem till en dead-letter-kö för senare inspektion). Eftersom huvudtråden aldrig inväntar det asynkrona resultatet är eventuella loggningsfel helt isolerade från API-konsumenten.
Hur fungerar Webpack Module Federation med Next.js SSR och vad är den huvudsakliga risken?
Standard Module Federation är enbart klientsidig – fjärrmoduler laddas vid körtid i webbläsaren. När Next.js försöker server-rendera en sida som importerar en fjärrkomponent misslyckas det eftersom fjärrbundeln inte finns på servern. Lösningen är att använda @module-federation/nextjs-mf (node-federation) eller att omsluta fjärrkomponenter i dynamic import med ssr: false. Den huvudsakliga risken är ett hydreringsfel: om servern renderar ett fallback men klienten laddar den verkliga fjärrkomponenten kastar React ett hydreringsfel.
Vilken strategi skulle du använda för att migrera ett legacy-monolitfrontend till microfrontends?
Använd Strangler Fig-mönstret – undvik en total omskrivning. Sätt först upp en ny shell-applikation (t.ex. Next.js). Dirigera sedan legacy-trafik via rewrites eller en omvänd proxy så att befintliga URL:er fungerar. Välj därefter ett lågriskvertikal (t.ex. Inställningar) och bygg om det som ett microfrontend exponerat via Module Federation. Migrera slutligen rutter en i taget tills legacy-applikationen kan avvecklas. Detta håller det gamla systemet live tills varje del är ersatt och verifierad.
Vad är N+1-problemet i GraphQL och hur löser man det?
N+1-problemet uppstår när en lista med N objekt orsakar N ytterligare databasfrågor – en per objekt. Till exempel orsakar hämtning av 10 användare plus varje användares adress 1 + 10 = 11 frågor istället för 1. Standardlösningen är DataLoader, som grupperar alla underfrågor i en enda fråga inom en event-loop-tick. För cachning, eftersom alla GraphQL-operationer träffar ett enskilt POST-endpoint är CDN-cachning begränsad; använd normaliserad klientcache (Apollo Client) eller Persisted Queries så att CDN:er kan cacha hashade frågor som GET-förfrågningar.
Vad är skillnaden mellan en relationsdatabas och en NoSQL-databas? När skulle du använda respektive?
Relationsdatabaser (SQL) lagrar data i tabeller med rader/kolumner, upprätthåller relationer via primär-/främmandenycklar, använder SQL för komplexa frågor och garanterar ACID-transaktioner. NoSQL-databaser lagrar data som dokument, nyckel-värdepar, kolumner eller grafer; de erbjuder flexibla scheman och är optimerade för horisontell skalbarhet och snabb läsning/skrivning. Använd relationell när du behöver stark konsistens och tydliga relationer (t.ex. banksystem). Använd NoSQL när du behöver hastighet, flexibilitet och hög skalbarhet (t.ex. loggar, produktkataloger, sessioner).
Vad är en databastransaktion och vad står ACID för?
En transaktion är en uppsättning operationer som utförs som en enda arbetsenhet — om någon operation misslyckas rullas hela transaktionen tillbaka för att bevara dataintegritet. ACID står för: Atomicitet (allt-eller-inget-exekvering), Konsistens (databasen övergår från ett giltigt tillstånd till ett annat), Isolering (samtidiga transaktioner påverkar inte varandra) och Hållbarhet (när den bekräftats kvarstår datan även om systemet kraschar).
Vad innebär Open-Closed-principen och hur tillämpas den i ett flerkanals-notifieringssystem?
Open-Closed-principen (OCP) säger att en mjukvaruenhet ska vara öppen för utökning men stängd för modifiering. I ett notifieringssystem innebär det att varje ny kanal levereras som en ny klass som implementerar ett delat gränssnitt, medan den centrala dispatch-logiken aldrig behöver ändras. Att bryta mot OCP leder till ett växande switch/case i dispatchern varje gång en kanal läggs till.
Hur skulle du modellera relationsdatabasschemat för ett notifieringssystem med användarägarskap?
Två centrala tabeller räcker: users (id, email, password_hash, created_at) och notifications (id, user_id FK → users.id, title, content, channel, created_at, updated_at). Kolumnen channel kan vara en sträng-enum. Ett index på notifications.user_id snabbar upp den vanliga frågan att hämta alla notifieringar för en given användare.
Vad vet du om enhetstestning i Java? Hur skulle du designa ett test för en service-komponent?
Enhetstester verifierar beteendet hos små kodenheter (metoder/klasser) isolerat. I Java tillhandahåller JUnit teststrukturen och Mockito skapar mockar/stubbar som simulerar externa beroenden som repositories. För att testa en service mockar man repository, definierar förväntat beteende, anropar servicemetoden och verifierar resultaten. Tester bör täcka positiva fall, negativa fall och gränsfall för att säkerställa korrekthet och möjliggöra säker refaktorering.
Vilka HTTP-statuskoder bör ett ord-söknings-API returnera för olika felscenarier?
Returnera 200 OK med resultat vid framgång, 400 Bad Request när matrisen överskrider 64×64-gränsen eller indata är felformaterad, och 500 Internal Server Error för oväntade backend-fel. Tydliga felmeddelanden i svarskroppen hjälper frontend att visa meningsfull återkoppling till användaren.
Hur fungerar tokenbaserad autentisering i ett RESTful API?
Vid inloggning validerar servern autentiseringsuppgifterna och returnerar en signerad JWT. Klienten sparar denna token och bifogar den i efterföljande förfrågningar via headern Authorization: Bearer <token>. Servern verifierar signaturen och hämtar användarens identitet från tokenns claims utan att behöva underhålla sessionstillstånd på serversidan.
Vilka HTTP-metoder och URL-mönster skulle du använda för ett RESTful CRUD-API för notifieringar?
Standardkonventioner för REST: POST /notifications (skapa), GET /notifications (lista egna), GET /notifications/:id (läs en), PUT /notifications/:id eller PATCH /notifications/:id (uppdatera), DELETE /notifications/:id (ta bort). Alla endpoints bör skyddas av en autentiserings-middleware som validerar JWT innan route-handlens körs.
Vad är rätt sätt att lagra användarlösenord i en databas?
Lagra aldrig lösenord i klartext. Använd en långsam, saltad hashalgoritm som bcrypt, Argon2 eller scrypt. Dessa algoritmer är avsiktligt beräkningskrävande, vilket gör brute-force- eller ordboksattacker opraktiska även om databasen komprometteras. Saltet lagras tillsammans med hashen så att varje lösenordshash är unikt även för identiska lösenord.
Vilken HTTP-statuskod ska ett REST API returnera när hastighetsgränsen överskrids, och vad ska svaret innehålla?
API:et ska returnera 429 Too Many Requests. Svarskroppen bör innehålla ett läsbart meddelande som förklarar gränsen (t.ex. 'Maximalt 3 requests per minut tillåts'). Valfritt kan ett Retry-After-huvud ange när klienten kan försöka igen. Detta följer RFC 6585 och hjälper klienter att implementera korrekta back-off-strategier.
Vad är syftet med en refresh token?
En refresh token låter en applikation erhålla en ny access token när den nuvarande löper ut, utan att användaren behöver logga in igen. Access tokens hålls kortlivade (minuter) för att begränsa skadan om de stjäls; den långlivade refresh token lagras säkrare (t.ex. i en HttpOnly-cookie) och byts mot en ny access token på serversidan.
Vilka är de fyra symtomen på mjukvarudesignens nedbrytning som beskrivs i guiden, och vad innebär vart och ett?
De fyra symtomen är: **Rigiditet** (mjukvaran blir svår att ändra även för enkla uppgifter, med allt större uppskattningar); **Skörhet** (ändringar orsakar fel i flera orelaterade delar av kodbasen); **Immobilitet** (det är praktiskt taget omöjligt att återanvända kod från andra projekt eller delar av samma projekt på grund av tung beroendemängd); och **Viskositet** (det är lättare att göra saker fel än på rätt sätt, och utvecklingsmiljön är långsam och ineffektiv).
Vad är Single Responsibility Principle (SRP) och hur relaterar det till kohesion och koppling?
SRP anger att en mjukvarumodul ska ha en och endast en anledning till förändring, där den anledningen är dess ansvar. Principen är nära kopplad till kohesion och koppling: vi vill öka kohesionen bland saker som förändras av samma skäl och minska kopplingen mellan saker som förändras av olika skäl. När en klass har mer än ett ansvar kan ändringar i ett område oavsiktligt påverka ett annat, vilket gör koden svårare att läsa, testa och underhålla.
Vad innebär Open/Closed Principle (OCP) och hur implementeras den vanligtvis?
OCP anger att mjukvarumoduler ska vara öppna för utvidgning men stängda för modifiering. 'Öppen för utvidgning' innebär att nytt beteende kan läggas till när krav förändras; 'stängd för modifiering' innebär att lägga till det nya beteendet inte ska kräva att den befintliga källkoden i modulen ändras. I praktiken implementeras OCP genom polymorfism, med hjälp av gränssnitt eller abstrakta klasser, så att nytt beteende införs genom att lägga till ny kod snarare än att ändra gammal.
Vad kräver Liskov Substitution Principle (LSP) och varför varnar den för att blint mappa verkligheten till en OO-modell?
LSP kräver att objekt i ett program ska kunna ersättas av instanser av deras undertyper utan att programmets korrekta funktion påverkas. I praktiken måste alla underklasser respektera beteendekontraktet för sin förälderklass. LSP varnar för att blint mappa verkligheten till en OO-modell eftersom det inte finns en en-till-en-ekvivalens mellan de två modellerna; vad som verkar vara ett giltigt 'är-ett'-förhållande i verkligheten kan bryta mot beteendekontraktet i kod.
Vad är Dependency Inversion Principle (DIP) och vilka är dess två huvudregler?
DIP anger att mjukvaruenheter ska bero på abstraktioner, inte på konkreta implementationer. Dess två huvudregler är: (1) högnivåmoduler ska inte bero på lågnivåmoduler — båda ska bero på abstraktioner; och (2) abstraktioner ska definieras utifrån konsumentens/klientens behov, inte utifrån implementationens förmågor, annars kopplas abstraktionen tätt till implementationen och förlorar flexibilitet. Detta möjliggör att byta ut komponenter utan att påverka konsumenter och underlättar testning via mock-objekt.
Vad är DRY-principen (Don't Repeat Yourself) och varför gäller den logik snarare än bara kod?
DRY anger att varje funktionalitetsbit ska ha en enda, entydig och auktoritativ representation inom ett system. Viktigt är att DRY gäller logik (den logiska funktionen), inte bara kodsyntax: tre metoder med olika kod men samma logiska syfte (t.ex. alla öppnar en databasanslutning) bryter mot DRY. När DRY tillämpas effektivt kräver en ändring i någon del av processen ändringar på bara ett ställe, vilket minskar risken för inkonsekvenser, minskar kodstorleken och sparar tid genom återanvändning.
Vad är Inversion of Control (IoC) och vilka designmönster är implementationer av denna princip?
IoC är en princip inom objektorienterad design där kontrollen över olika typer av programflöde (inklusive objektskapande och beroendehantering) delegeras till en tredje part, vilket uppnår låg koppling. Det ökar modulariteten och producerar klasser som är testbara, underhållbara och utbyggbara. Designmönster som implementerar IoC inkluderar: Service Locator, Dependency Injection, Template Method, Strategy, Abstract Factory och Observer. Principen är också känd som 'Hollywood-principen' — 'Ring inte oss, vi ringer dig'.
Vad är Demeters lag (LoD) och vilken typ av kod syftar den till att förhindra?
Demeters lag (även känd som Principen om minsta kunskap eller 'Prata inte med främlingar') anger att en metod i ett objekt bara ska interagera med: (1) objektets egna metoder, (2) dess argument, (3) objekt skapade inom metoden, och (4) direkta egenskaper/fält i objektet. Den syftar till att förhindra djupa anropskedjor som object.getX().getY().getZ().doSomething(), vilket skapar stark koppling till den interna strukturen hos de inblandade klasserna. Att tillämpa LoD minskar koppling, förbättrar återanvändbarhet och gör koden lättare att testa.
Vad är principen 'Komposition framför arv' och när föredras den?
Denna princip anger att klasser ska uppnå polymorfiskt beteende och kodåteranvändning genom komposition (genom att innehålla instanser av andra klasser som implementerar önskad funktionalitet) snarare än genom arv, när det är möjligt. Med arv strukturerar vi klasser utifrån vad de *är*; med komposition strukturerar vi dem utifrån vad de *gör*. Komposition föredras eftersom arv låser in stela, tätt kopplade hierarkier tidigt i ett projekt, vilket försvårar framtida ändringar. Komposition används när HAS-A-relationen gäller, medan arv är lämpligt bara när IS-A genuint gäller och hierarkin är enkel.
Vad är Kent Becks fyra regler för enkel design och i vilken prioritetsordning listas de?
Kent Becks fyra regler, ordnade efter relevans, är: (1) **Tester passerar** — varje funktion ska fungera som förväntat och verifieras av tester; (2) **Uttrycker avsikt** — koden är självförklarande, lätt att förstå och kommunicerar sitt syfte; (3) **Ingen duplicering (DRY)** — logisk duplicering ska minimeras för att undvika skörhet; (4) **Minsta antal element** — antalet komponenter, klasser och metoder ska reduceras till det nödvändiga och eliminera onödig komplexitet. Notera: det finns debatt om huruvida reglerna 2 och 3 bör ha lika prioritet, och regel 4 ses ofta som en konsekvens av att kontinuerligt tillämpa reglerna 2 och 3.
Vad är Boy Scout-regeln inom mjukvaruutveckling och vilken mentalitet främjar den?
Boy Scout-regeln, hämtad från scouters motto att lämna en lägerplats renare än man fann den, anger att när en utvecklare ser kod som kan förbättras, ska de förbättra den oavsett vem som skrivit den. Målet är att förhindra kodens nedbrytning över tid genom att göra små, säkra, inkrementella förbättringar som hjälper nästa utvecklare. Den främjar en lagöver-individ-mentalitet: projektets övergripande kvalitet är viktigare än att slutföra enskilda uppgifter. Att tillämpa denna regel kräver en gedigen förståelse för SOLID-principerna.
Vad är principen Last Responsible Moment och varför rekommenderar den att skjuta upp designbeslut?
Last Responsible Moment-principen rekommenderar att skjuta upp designbeslut — särskilt irreversibla sådana — tills sista möjliga ögonblick: den punkt då att INTE fatta ett beslut kostar mer än att fatta det. Motivet är att ju längre ett beslut hålls öppet, desto mer information ackumuleras för att göra rätt val. Inom mjukvaruutveckling är det vanligt att börja bygga funktioner innan kraven är fullständigt definierade, så för tidiga, svåra att ångra beslut baserade på ofullständig information utgör en betydande risk.
Vad är principen 'Encapsulate What Varies' och vilka välkända designmönster är baserade på den?
'Encapsulate What Varies' anger att när delar av en applikation identifieras som troliga att förändras, ska de isoleras och kapslas in i abstraktioner så att ändringar inte påverkar andra delar. Den stöds av SRP och OCP. Fördelarna är dubbla: variationer i krav påverkar bara den inkapslade modulen (minskar skörhet och ökar återanvändbarhet), och nya krav möts genom att lägga till nya element snarare än att modifiera befintliga (minskar rigiditet). Många designmönster bygger på denna princip, inklusive Abstract Factory, Factory Method, Adapter, Bridge, Decorator, Iterator, Observer, State, Strategy, Template Method och Visitor.
Vad är ett Value Object i kontexten av Domain-Driven Design och hur skiljer det sig från en Entity?
Ett Value Object är en oföränderlig typ som identifieras enbart av sina egenskapers värden; två Value Objects är lika om alla egenskaper är identiska. En Entity har däremot en unik identitet (en identifierare), så två Entity-instanser betraktas som olika även om alla egenskaper är likadana.
Vad är Shared Kernel-mönstret inom Domain-Driven Design och vilka är dess viktigaste begränsningar?
Shared Kernel är en delmängd av domänmodellen (tillsammans med tillhörande kod och databasdesign) som två team enas om att dela för att minska duplicering och förenkla integration. Denna delade delmängd är speciell: den kan inte ändras fritt och får inte modifieras utan att konsultera det andra teamet. När ändringar görs måste alla tester från båda teamen passera innan ändringen accepteras.
Vad är Customer/Supplier-mönstret i DDD och vilka organisatoriska utmaningar kan uppstå?
Customer/Supplier beskriver ett förhållande mellan två bounded contexts där den nedströms komponenten (kunden) konsumerar utdata från den uppströms komponenten (leverantören), med alla beroenden i en riktning. Problem uppstår när leverantörsteamet fruktar att bryta kundsystemet och begränsar sin frihet att utvecklas, eller när kunden är hjälplös inför leverantörsdrivna ändringar. Dessa problem löses bäst genom att formalisera relationen med ett dokumenterat API, en ändringskalender och gemensam planering.
Vad är Anticorruption Layer (ACL)-mönstret och när bör det användas?
Anticorruption Layer är ett isolerande lager placerat mellan ett nytt system och ett äldre eller dåligt designat externt system. Det översätter förfrågningar i båda riktningarna mellan de två domänmodellerna utan att kräva betydande ändringar i det externa systemet. Det används för att säkerställa att applikationsdesignen inte begränsas av beroenden till externa delsystem, och det beskrevs först av Eric Evans i boken 'Domain-Driven Design'.
Hur är ett Anticorruption Layer typiskt organiserat internt?
Ett ACL består typiskt av tre kompletterande element: en Fasad, som erbjuder ett förenklat och specialiserat gränssnitt mot det externa systemet utan att ändra dess modell; en Adapter, som omsluter fasaden och översätter anrop till semantiskt likvärdiga förfrågningar som det externa systemet förstår; och en Translator, ett lätt tillståndslöst objekt som ansvarar för att konvertera konceptuella objekt eller data mellan de två modellerna. Tillsammans hanterar dessa element den fullständiga översättningen mellan bounded contexts.
Vad är CQRS (Command-Query Responsibility Segregation) och vilket problem löser det?
CQRS är ett arkitekturmönster som separerar läsoperationer (Queries) från skrivoperationer (Commands) i två oberoende modeller. Skrivsidan hanterar tillståndsändringar och kan inkludera affärsvalideringslogik, medan läsidan returnerar data utan att ändra tillstånd och kan optimera sin datarepresentation för visning. Denna separation gör att varje sida kan skalas, säkras och utvecklas oberoende, men tillför ytterligare komplexitet till systemet.
I vilka scenarier är CQRS mest fördelaktigt?
CQRS är mest fördelaktigt när många användare åtkomst till samma data och var och en behöver utföra flerstegsbearbetning; när det finns en tydlig asymmetri mellan volymen av läs- och skrivoperationer, vilket möjliggör oberoende skalning; och när man vill låta UI och affärsregler utvecklas oberoende. Det rekommenderas generellt inte för enkla system eftersom det kräver dubbelt så mycket underhållsarbete för båda modellerna.
Vad är beroenderegeln (Dependency Rule) i Clean Architecture och varför är den viktig?
Beroenderegeln fastslår att ett inre lager måste vara helt isolerat från yttre lager; inre lager kan inte bero på yttre lager, men yttre lager får känna till de inres detaljer. Denna regel säkerställer att när projektet växer kan ny kod läggas till utan att bryta befintlig inre logik, och att de centrala affärsreglerna är oberoende av ramverk, databaser och andra infrastrukturdetaljer.
Vilka är de tre huvudsakliga lagren i en DDD-baserad Clean Architecture och vad ansvarar vart och ett för?
Domänlagret är kärnan i applikationen och innehåller entiteter, värdeobjekt, aggregat och domäntjänster som kodar den huvudsakliga affärslogiken. Applikationslagret koordinerar domänobjekt för att uppfylla användarförfrågningar; det innehåller ingen affärslogik i sig utan orkestrerar användningsfall med hjälp av applikationstjänster. Infrastrukturlagret tillhandahåller tekniska möjligheter (t.ex. databasbeständighet, meddelandehantering) till övre lager och måste vara helt frikopplat från domänlagret så att byte av beständighetsmotor inte påverkar resten av systemet.
Vad är Hexagonal Architecture (Ports and Adapters) och vad är skillnaden mellan en Port och en Adapter?
Hexagonal Architecture, introducerad av Alistair Cockburn, organiserar en applikation så att alla in- och utdata passerar genom väldefinierade anslutningspunkter som isolerar affärslogiken från externa verktyg. En Port är ett gränssnitt som specificerar hur ett externt verktyg kan använda affärslogiken eller hur affärslogiken använder det verktyget. En Adapter är en klass som implementerar eller omsluter en port och transformerar ett gränssnitt till ett annat så att ett externt verktyg (t.ex. en webbserver, en databas) kan kommunicera med applikationskärnan.
Vad är skillnaden mellan Driving Adapters och Driven Adapters i Hexagonal Architecture?
Driving Adapters (primära adaptrar) initierar åtgärder i applikationen – till exempel en webbkontroller som tar emot en HTTP-förfrågan och anropar ett applikationsanvändningsfall. Driven Adapters (sekundära adaptrar) reagerar på instruktioner från affärslogiken och kopplar den till backend-verktyg som databaser eller meddelandeköer – till exempel en repositoryimplementering som lagrar data i MySQL. Inversion of Control används genomgående: affärslogiken beror bara på portgränssnitt, aldrig på konkreta adapterimplementeringar.
Hur fungerar CQRS med en Command Bus och hur skiljer sig detta från CQRS utan Bus?
Utan Bus har kontrollern ett direkt beroende av ett Command- eller Query-objekt som innehåller och utför användningsfallslogiken. Med Bus beror kontrollern på Bussen och skickar ett Command eller Query (som bara agerar som databärare); Bussen dirigerar meddelandet till rätt Command Handler som innehåller den faktiska användningsfallslogiken. Användning av en Bus frikopplar förfrågan från dess utförande, förbättrar utbyggbarhet och möjliggör att tvärgående angelägenheter som loggning eller transaktioner hanteras av Bussen.
Vilken teststrategi rekommenderar Clean Architecture och varför?
Clean Architecture rekommenderar att majoriteten av tester är enhetstester, eftersom de täcker det bredaste spektrumet av affärslogikvägar utan att kräva något ramverk, databas eller infrastrukturverktyg. Integrationstester behövs också för att verifiera att infrastrukturimplementeringar fungerar korrekt tillsammans, men de behöver inte testa om alla affärslogikvägar. End-to-end-tester (acceptans, UI, API) är de mest tillförlitliga men också de dyraste och mest ömtåliga, så de bör hållas i rimlig proportion.
Vad är Event-Driven Architecture (EDA) och vilka är dess tre nyckelkomponenter?
Event-Driven Architecture är ett mönster som främjar asynkron kommunikation mellan oberoende komponenter med hjälp av händelser, och är vanligt i mikrotjänstapplikationer. Dess tre nyckelkomponenter är: Event Publishers, som sänder ut händelser när en tillståndsändring inträffar; en Message Broker (router), som filtrerar och dirigerar händelser till intresserade konsumenter; och Event Consumers, som prenumererar på specifika händelsetyper och bearbetar dem. Publishers och konsumenter är helt frikopplade, vilket gör att var och en kan skalas, uppdateras och driftsättas oberoende.
Vilka är de huvudsakliga fördelarna med Event-Driven Architecture?
Viktiga fördelar inkluderar: oberoende skalning och felhantering, eftersom tjänster bara interagerar med message brokern och inte känner till varandra; agil utveckling, eftersom brokern automatiskt hanterar filtrering och routing utan anpassad pollningskod; minskade kostnader tack vare en push-baserad modell som eliminerar kontinuerlig pollning; enklare implementering av flödeskontrollmönster som backpressure; och ingen straff för långsamma konsumenter, eftersom varje konsument bearbetar händelser oberoende utan att blockera snabbare konsumenter.
Hur bör kommunikation mellan komponenter hanteras i en Clean/Hexagonal-arkitektur för att bibehålla frikoppling?
När en komponent behöver funktionalitet från en annan komponent skulle ett direkt metodanrop skapa stark koppling. Istället bör kommunikationen förmedlas av en mekanism som en Event Dispatcher som dirigerar händelser mellan komponenter, eller åtminstone via ett väldefinierat publikt API som exponeras av målkomponenten. Detta håller komponenterna oberoende och låter varje bounded context utvecklas utan att bryta andra.
Vad är Event Sourcing och hur skiljer det sig från traditionell tillståndslagring?
I Event Sourcing lagrar applikationen en ordnad sekvens av tillståndsförändrande domänhändelser istället för entitetens aktuella tillstånd. Det aktuella tillståndet rekonstrueras genom att spela upp händelserna. Detta gör tillståndslagringen alltid atomär (en händelse = en operation) och ger en pålitlig revisionsspårning samt möjlighet att fråga entitetens tillstånd vid vilken tidpunkt som helst. En nyckelgräns är att godtyckliga sökningar kräver separata materialiserade vyer.
Vilken konsistensgaranti erbjuder transaktioner som spänner över flera tjänster i en händelsestyrd mikroservicearkitektur, och hur skiljer det sig från ACID?
Transaktioner som spänner över flera tjänster via en message broker erbjuder eventuell konsistens (BASE-garantier), inte ACID. Varje tjänst uppdaterar atomärt sin egen databas och publicerar ett event, men systemet som helhet kan vara tillfälligt inkonsistent tills alla nedströms tjänster har bearbetat dessa events.
Vilka är de främsta nackdelarna med en händelsestyrd arkitektur i mikroservices?
Programmeringsmodellen är mer komplex och kräver specifik kunskap. Applikationer måste implementera kompensationsmekanismer för att återhämta sig från fel på applikationsnivå, hantera tillfälligt inkonsistent data, och prenumeranter måste detektera och ignorera duplicerade events.
Vad är det atomära uppdateringsproblemet i händelsestyrda mikroservices och varför är det kritiskt?
När en tjänst måste både uppdatera sin databas och publicera ett event måste dessa två operationer ske atomärt. Om tjänsten kraschar efter databasuppdateringen men innan eventet publicerats blir systemet inkonsistent. Standardlösningen är en distribuerad transaktion som involverar både databasen och message brokern, men det tvingar fram en avvägning mellan tillgänglighet och konsistens.
Hur koordinerar mikroservices en flerstegs-affärstransaktion med hjälp av Saga-mönstret med events?
I Saga-mönstret hanteras varje steg i transaktionen av en separat mikroservice som uppdaterar sin lokala entitet och publicerar ett event till en message broker. Det eventet triggar nästa mikroservice i sekvensen. Message brokern garanterar minst en leverans, vilket möjliggör att den totala transaktionen spänner över flera tjänster utan en distribuerad ACID-transaktion och istället förlitar sig på eventuell konsistens.
Vilka är de tre ursprungliga kategorierna av designmönster och varför kan överanvändning av dem vara skadligt?
De tre ursprungliga kategorierna är Skapande, Strukturella och Beteendemässiga. Med tiden tillkom nya kategorier, till exempel Concurrency-mönster. Onödig eller överdriven användning av mönster kan leda till överkonstruktion och ett alltför komplext system med ineffektiv design, dålig prestanda och underhållsproblem.
Vad är Builder-designmönstret och vilka är dess huvudsakliga deltagare?
Builder-mönstret separerar konstruktionslogiken för ett objekt från dess representation. Huvuddeltagarna är: Builder (abstrakt gränssnitt för att skapa produkter), ConcreteBuilder (konkret implementering som skapar en specifik typ av produkt) och Director (ansvarar för att använda Builder för att konstruera objekt).
Vad är Singleton-mönstret och när används det typiskt?
Singleton-mönstret säkerställer att det bara finns en instans av en klass och erbjuder en enda global åtkomstpunkt till den. Det används vanligtvis när man vill kontrollera åtkomsten till en enda fysisk resurs (t.ex. en exklusivt använd fil) eller när data behöver vara tillgänglig för alla objekt i applikationen (t.ex. en logger-instans). Man bör vara uppmärksam på problem med exklusiv åtkomst i konkurrenssituationer.
Vad är Dependency Injection och hur relaterar det till Inversion of Control?
Dependency Injection (DI) är ett designmönster som tar bort ansvaret för att skapa instanser från en komponent och delegerar det till en annan (injektorn). Ett objekt tar emot sina beroenden (tjänster) utifrån istället för att skapa dem själv. Klienten behöver bara känna till tjänsternas gränssnitt, inte deras konkreta implementering. DI är ett sätt att uppnå Inversion of Control (IoC).
Vad är Service Locator-mönstret och hur skiljer det sig från Dependency Injection?
Service Locator-mönstret använder ett centralt register (ServiceLocator) som på begäran returnerar den komponent som behövs för en viss uppgift. Den viktigaste skillnaden mot Dependency Injection är att Service Locator kräver en explicit begäran för att hämta beroendet, medan DI tillhandahåller det automatiskt. Kritiker menar att det gör mjukvara svårare att testa, medan förespråkare anser att det förenklar komponentbaserade applikationer. Precis som DI är det en implementering av IoC-principen.
Vad är Abstract Factory-mönstret och vilka är dess huvudsakliga deltagare?
Abstract Factory tillhandahåller ett gränssnitt för att skapa familjer av relaterade objekt utan att ange konkreta klasser. Klienten använder det generiska fabriksgränssnittet och vet inte vilka konkreta objekt den får. Deltagarna inkluderar: Client, AbstractFactory (definierar fabriksgränssnitt), ConcreteFactory (skapar en familj av konkreta produkter), AbstractProduct (gränssnitt för generisk produktfamilj) och ConcreteProduct (specifika produktimplementeringar). Det är också en implementering av IoC-principen.
Vad är Decorator-mönstret och vilka fördelar erbjuder det jämfört med arv?
Decorator-mönstret tilldelar dynamiskt ytterligare ansvar till ett objekt och ger ett flexibelt alternativ till subklassning för att utöka funktionalitet. Deltagarna är Component (gränssnitt), ConcreteComponent, Decorator (håller en referens till Component och delegerar till det) och ConcreteDecorator. Viktiga fördelar: det är mer flexibelt än arv, möjliggör tillägg och borttagning av ansvar vid körning, undviker djupa klasshierarkier och multipelt arv.
Vad är Observer-mönstret och när bör det tillämpas?
Observer-mönstret definierar ett beroende mellan objekt så att när ett objekt (Subject) ändrar sitt tillstånd notifieras alla beroende objekt (Observers) och kan reagera. Subject håller en lista av Observers och tillhandahåller metoder för att prenumerera och avsluta prenumeration. Det bör tillämpas när en förändring i ett objekt kräver ändringar i andra utan att veta hur många, eller när ett objekt ska kunna meddela andra utan att veta vilka de är. Det respekterar Open/Closed-principen.
Vad är Command-mönstret och vilka möjligheter aktiverar det?
Command-mönstret kapslar in en begäran som ett objekt och möjliggör ett gemensamt gränssnitt för att anropa olika åtgärder. En Client skapar ett Command-objekt (vanligtvis med Receiver), och Invoker lagrar och utlöser exekvering genom att anropa Commands execute-metod. Genom att kapsla in begäran som ett objekt möjliggörs köning, loggning och ångra/gör-om-operationer, tack vare frikopplingen mellan begäran och exekvering.
Vad är Strategy-mönstret och hur relaterar det till Open/Closed-principen?
Strategy-mönstret definierar en familj av algoritmer, kapslar in var och en av dem och gör dem utbytbara, vilket gör att algoritmen kan variera oberoende mellan klienter. Beteenden bör inte ärvas utan kapslas in med gränssnitt, så att nya algoritmer kan läggas till utan att ändra befintliga kontext- eller strategigränssnitt. Detta stöder direkt Open/Closed-principen (OCP): klasser är öppna för utökning men stängda för modifiering.
Vad är State-mönstret och hur skiljer det sig från att använda villkorssatser?
State-mönstret låter ett objekt ändra sitt beteende när dess interna tillstånd förändras, vilket får det att verka byta klass. Varje tillstånd representeras av en separat klass som implementerar ett gemensamt gränssnitt, och ersätter komplexa villkorssatser. Det respekterar Open/Closed-principen och Single Responsibility-principen (SRP). Tillståndsövergångar är atomära för Context, vilket förhindrar inkonsistenta interna tillstånd.
Vad är Template Method-mönstret och hur demonstrerar det Inversion of Control?
Template Method-mönstret definierar skelettet av en algoritm i en basklass och skjuter upp vissa steg till underklasser, som kan omdefiniera dessa steg utan att ändra algoritmens övergripande struktur. Vid körning exekveras algoritmen genom att skicka mallmeddelandet till en instans av en konkret underklass, som fyller i de delegerade detaljerna via arv. Det demonstrerar Inversion of Control eftersom koden på hög nivå inte bestämmer vilken algoritm som ska köras; istället väljs en algoritm på lägre nivå vid körning.
Vad är Front Controller-mönstret i JEE och vilka är dess huvudsakliga komponenter?
Front Controller-mönstret använder en enda klass som mellanhand mellan klienten och begärda resurser, och centraliserar vanliga operationer som autentisering och felhantering för att undvika kodduplicering. Huvudkomponenterna är: FrontController (fångar upp alla webbförfrågningar och delegerar till Dispatcher), Dispatcher (koordinerar åtgärder för att lösa förfrågningar med hjälp av Helper), Helper (innehåller affärslogik) och View (visar resultatet för klienten).
Vad är ett antimönster och vad måste det innehålla för att erkännas som ett?
Ett antimönster är en vanligt tillämpad men dålig lösning på ett återkommande problem. För att erkännas som ett måste det: beskriva ett exempel på en dålig lösning, analysera orsakerna till den, lista symtom och konsekvenser som identifierar den, och slutligen presentera en refaktorerad lösning som visar hur man går från den dåliga designen till en väldesignad sådan.
Vad är The Blob-antimönstret och hur bör det refaktoreras?
The Blob-antimönstret uppstår när en allsmäktig klass monopoliserar alla procedurer och affärslogik medan övriga klasser bara innehåller data, som ett resultat av iterativ utveckling utan korrekt ansvarsfördelning. Refaktorering innebär att flytta delar av logiken till andra klasser, skapa mindre specialiserade objekt eller införa en koordinatorklass, och prevention kräver att applikationsarkitekturen planeras innan kodning börjar.
Vad är Lava Flow-antimönstret och hur löser man det?
Lava Flow beskriver kod som har vuxit organiskt utan en definierad arkitektur, typiskt med ursprung i en prototyp som nådde produktion, vilket lämnar odokumenterad och ofta oanvänd kod som ingen vågar ta bort. Lösningen kräver att ny funktionsutveckling stoppas, hela systemet analyseras för att identifiera vad som faktiskt används, arkitekturen omdefinieras utifrån aktuella affärskrav, refaktorering utförs och dokumentation skapas grundligt.
Vad är Poltergeists-antimönstret och hur åtgärdas det?
Poltergeists är klasser med mycket begränsade ansvar och korta livscykler som bara existerar för att utlösa åtgärder i andra klasser, vilket lägger till onödiga abstraktioner och redundanta navigationsvägar i designen. De åtgärdas genom att tas bort helt och att deras initierings- eller utlösningslogik flyttas in i de klasser de tidigare anropade.
Vad är Spaghetti Code-antimönstret och vilka är dess huvudsakliga orsaker?
Spaghetti Code kännetecknas av en alltför komplex och obegriplig kontrollflödesstruktur med minimala relationer mellan objekt, processorienterade metoder och ingen användning av arv eller polymorfism. Vanliga orsaker inkluderar utvecklarens oerfarenhet av objektorienterade tekniker, ineffektiva eller obefintliga kodgranskningar samt avsaknad av analys och design före implementation.
Vad är Golden Hammer-antimönstret och hur kan det förhindras?
Golden Hammer är tendensen att använda samma välkända teknik, ramverk eller språk för att lösa varje problem oavsett om det är den bästa lösningen, ofta drivet av bekvämlighet, stora tidigare investeringar eller organisatorisk tröghet. Prevention kräver att man odlar en kultur av kontinuerligt lärande, håller sig uppdaterad om ny teknik och anställer personer med olika tekniska bakgrunder.
Vad är Data Access Object (DAO)-mönstret och vilket problem löser det?
DAO-mönstret abstraherar och kapslar in all åtkomst till en datakälla och hanterar anslutningen för att hämta och lagra data. Det frikopplar affärslagret från datakällans implementering så att applikationen inte är beroende av en specifik databasmotor. Dess främsta värde idag är testbarhet: genom att mocka DAO-gränssnittet kan affärsklasser testas utan riktiga databasanslutningar.
Hur löser Outbox-mönstret det atomära uppdateringsproblemet i händelsestyrda system?
Outbox-mönstret introducerar en EVENT-tabell i tjänstens egna databas som fungerar som en meddelandekö. Inom en enda lokal transaktion uppdaterar tjänsten sina affärsentiteter och infogar en eventpost i EVENT-tabellen. En separat tråd eller process pollar sedan EVENT-tabellen, publicerar events till message brokern och markerar dem som publicerade i en ny lokal transaktion, vilket garanterar atomicitet utan en distribuerad transaktion.
Vad innebär transaction log mining-ansatsen för att publicera events, och vilka är dess avvägningar?
Transaction log mining innebär att en dedikerad tråd eller process läser databasens transaktionslogg och publicerar motsvarande events till message brokern när data ändras. En viktig fördel är att det garanterar ett event per uppdatering och separerar event-publicering från affärslogik. Den största nackdelen är att transaktionsloggens format är databasspecifikt och kan förändras mellan versioner, och det kan vara svårt att konstruera högnivå-affärshändelser från lågnivå-loggposter.

Delar av detta avsnitt är anpassade från en studieguide om mjukvarudesign (Izertis, 2024), licensierad under CC BY-SA 4.0. Detta härledda innehåll delas under samma licens.