paint-brush
Hvordan man håndterer kompleksitet, når man designer softwaresystemerved@fairday
64,467 aflæsninger
64,467 aflæsninger

Hvordan man håndterer kompleksitet, når man designer softwaresystemer

ved Aleksei23m2024/02/05
Read on Terminal Reader
Read this story w/o Javascript

For langt; At læse

Kompleksitet er fjenden! Lad os lære at håndtere det!
featured image - Hvordan man håndterer kompleksitet, når man designer softwaresystemer
Aleksei HackerNoon profile picture

Hvad handler det om?

Hver dag, hvert øjeblik i vores ingeniørkarriere, støder vi på mange forskellige problemer af forskellig kompleksitet og situationer, hvor vi er nødt til at træffe en beslutning eller udskyde den på grund af mangel på data. Når vi bygger nye tjenester, konstruerer infrastruktur eller endda danner udviklingsprocesser, berører vi en enorm verden af forskellige udfordringer.


Det er udfordrende, og måske endda umuligt, at opremse alle problemerne. Du vil kun støde på nogle af disse problemer, hvis du arbejder i en bestemt niche. Til gengæld er der mange, som vi alle skal forstå at løse, da de er afgørende for opbygningen af it-systemer. Med stor sandsynlighed vil du støde på dem i alle projekter.


I denne artikel vil jeg dele mine erfaringer med nogle af de problemer, jeg er stødt på under oprettelse af softwareprogrammer.

Hvad er tværgående bekymring?

Hvis vi kigger på Wikipedia, finder vi følgende definition


I aspektorienteret softwareudvikling er tværgående bekymringer aspekter af et program, der påvirker flere moduler, uden mulighed for at blive indkapslet i nogen af dem. Disse bekymringer kan ofte ikke dekomponeres rent fra resten af systemet i både design og implementering, og kan resultere i enten spredning (kodeduplikering), sammenfiltring (betydelige afhængigheder mellem systemer) eller begge dele.


Det beskriver i høj grad, hvad det er, men jeg vil gerne udvide og forenkle det lidt:

En tværgående bekymring er et koncept eller en komponent i systemet/organisationen, der påvirker (eller 'skærer på tværs') mange andre dele.


De bedste eksempler på sådanne bekymringer er systemarkitektur, logning, sikkerhed, transaktionsstyring, telemetri, databasedesign og der er mange andre. Vi kommer til at uddybe mange af dem senere i denne artikel.


På kodeniveau implementeres tværgående bekymringer ofte ved hjælp af teknikker som Aspect-Oriented Programming (AOP) , hvor disse bekymringer er modulariseret i separate komponenter, der kan anvendes i hele applikationen. Dette holder forretningslogikken isoleret fra disse bekymringer, hvilket gør koden mere læsbar og vedligeholdelig.

Klassificering af aspekter

Der er mange mulige måder at klassificere aspekter ved at segmentere dem med forskellige egenskaber som omfang, størrelse, funktionalitet, vigtighed, mål og andre, men i denne artikel vil jeg bruge en simpel omfangsklassificering. Med dette mener jeg, hvor dette specifikke aspekt er rettet, uanset om det er hele organisationen, et bestemt system eller et specifikt element i det system.


Så jeg vil opdele aspekter i makro og mikro .


Med makroaspekt mener jeg hovedsageligt overvejelser, vi følger for hele systemet, såsom valgt systemarkitektur og dets design (monolitisk, mikrotjenester, serviceorienteret arkitektur), teknologistak, organisationsstruktur osv. Makroaspekter er hovedsageligt relateret til strategisk og højt niveau beslutninger.


I mellemtiden er Micro- aspektet meget tættere på kodeniveauet og udviklingen. For eksempel, hvilken ramme der bruges til at interagere med databasen, projektstrukturen af mapper og klasser eller endda specifikke objektdesignmønstre.


Selvom denne klassifikation ikke er ideel, hjælper den med at strukturere en forståelse af mulige problemer og vigtigheden og virkningen af løsninger, vi anvender på dem.


I denne artikel vil mit primære fokus være på makroaspekterne.

Makro aspekter

Organisationsstruktur

Da jeg lige begyndte at lære om softwarearkitektur, læste jeg mange interessante artikler om Conways lov og dens indvirkning på organisationsstrukturen. Især denne . Så det siger denne lov


Enhver organisation, der designer et system (defineret bredt) vil producere et design, hvis struktur er en kopi af organisationens kommunikationsstruktur.


Jeg har altid troet, at dette koncept faktisk er meget universelt og repræsenterer den gyldne regel.


Så begyndte jeg at lære Eric Evans's Domain-Driven Design (DDD) tilgang til modellering af systemer. Eric Evans understreger vigtigheden af Bounded Context identifikation. Dette koncept involverer opdeling af en kompleks domænemodel i mindre, mere håndterbare sektioner, hver med sit eget begrænsede sæt af viden. Denne tilgang hjælper med effektiv teamkommunikation, da den reducerer behovet for omfattende viden om hele domænet og minimerer kontekstskifte, hvilket gør samtaler mere effektive. Kontekstskift er det værste og mest ressourcekrævende nogensinde. Selv computere kæmper med det. Selvom det er usandsynligt at opnå et fuldstændigt fravær af kontekstskifte, regner jeg med, at det er det, vi bør stræbe efter.


Fantasy about keeping in mind a lot of bounded contexts

For at vende tilbage til Conways lov, har jeg fundet flere problemer med den.


Det første problem, jeg er stødt på med Conways lov, som antyder, at systemdesign afspejler organisatorisk struktur, er potentialet for at danne komplekse og omfattende Bounded Contexts. Denne kompleksitet opstår, når den organisatoriske struktur ikke er tilpasset domænegrænser, hvilket fører til Bounded Contexts, der er stærkt indbyrdes afhængige og fyldt med information. Det fører til hyppige kontekstskifte for udviklingsteamet.


Et andet problem er, at organisatorisk terminologi lækker til kodeniveauet. Når organisatoriske strukturer ændrer sig, nødvendiggør det kodebasemodifikationer, hvilket tærer på værdifulde ressourcer.


Følgende Inverse Conway Maneuver hjælper således med at opbygge systemet og organisationen, der fremmer den ønskede softwarearkitektur. Det er dog bemærkelsesværdigt at sige, at denne tilgang ikke vil fungere særlig godt i allerede dannede arkitekturer og strukturer, da ændringer på dette stadium er langvarige, men den er exceptionelt præsterende i startups, da de er hurtige til at indføre ændringer.

Stor mudderkugle

Dette mønster eller "anti-mønster" driver opbygningen af et system uden arkitektur. Der er ingen regler, ingen grænser og ingen strategi for, hvordan man kontrollerer den uundgåelige voksende kompleksitet. Kompleksitet er den mest formidable fjende på rejsen med at bygge softwaresystemer.


Entertaining illustration made by ChatGPT

For at undgå at konstruere en sådan type system er vi nødt til at følge specifikke regler og begrænsninger.

Systemarkitektur

Der er utallige definitioner for softwarearkitektur. Jeg kan godt lide mange af dem, da de dækker forskellige aspekter af det. Men for at kunne ræsonnere om arkitektur, er vi naturligvis nødt til at danne nogle af dem i vores sind. Og det er bemærkelsesværdigt at sige, at denne definition kan udvikle sig. Så, i det mindste for nu, har jeg følgende beskrivelse for mig selv.


Softwarearkitektur handler om beslutninger og valg, du træffer hver dag, og som påvirker det byggede system.


For at træffe beslutninger, du skal have i din "bag" principper og mønstre for at løse opståede problemer, er det også vigtigt at sige, at forståelse af kravene er nøglen til at opbygge, hvad en virksomhed har brug for. Men nogle gange er kravene ikke gennemsigtige eller endda ikke definerede, i dette tilfælde er det bedre at vente med at få mere afklaring eller stole på din erfaring og stole på din intuition. Men alligevel kan du ikke træffe beslutninger ordentligt, hvis du ikke har principper og mønstre at stole på. Det er her, jeg kommer til definitionen af Software Architecture Style.


Software Architecture Style er et sæt principper og mønstre, der angiver, hvordan man bygger software.


Der er mange forskellige arkitektoniske stilarter med fokus på forskellige sider af den planlagte arkitektur, og det er en normal situation at anvende flere af dem på én gang.


For eksempel, såsom:

  1. Monolitisk arkitektur

  2. Domænedrevet design

  3. Komponentbaseret

  4. Mikrotjenester

  5. Rør og filtre

  6. Begivenhedsdrevet

  7. Mikrokerne

  8. Serviceorienteret


og så videre…


Selvfølgelig har de deres fordele og ulemper, men det vigtigste, jeg har lært, er, at arkitekturen udvikler sig gradvist, mens den afhænger af faktiske problemer. Begyndende med den monolitiske arkitektur er et godt valg til at reducere operationelle kompleksiteter, meget sandsynligt vil denne arkitektur passe til dine behov, selv efter at have nået ud til Product-market Fit (PMI) fasen af opbygningen af produktet. I stor skala kan du overveje at gå i retning af en begivenhedsdrevet tilgang og mikrotjenester for at opnå uafhængig implementering, heterogent teknologisk stackmiljø og mindre koblet arkitektur (og mindre gennemsigtig i mellemtiden på grund af arten af begivenhedsdrevne og pub-under-tilgange, hvis disse er vedtaget). Enkelhed og effektivitet er tæt på og har stor indflydelse på hinanden. Normalt påvirker komplicerede arkitekturer udviklingshastigheden af nye funktioner, understøtter og vedligeholder eksisterende og udfordrer systemets naturlige udvikling.


Imidlertid kræver komplekse systemer ofte kompleks og omfattende arkitektur, hvilket er uundgåeligt.


Dette er rimeligvis et meget bredt emne, og der er mange gode ideer om, hvordan man strukturerer og bygger systemer til naturlig evolution. På baggrund af mine erfaringer har jeg udarbejdet følgende fremgangsmåde:

  1. Begynder næsten altid med den monolitiske arkitekturstil, da den eliminerer de fleste problemer, der opstår på grund af de distribuerede systemers natur. Det giver også mening at følge modulær monolit for at fokusere på bygningskomponenter med klare grænser. Anvendelse af en komponentbaseret tilgang kunne hjælpe dem med at kommunikere med hinanden ved at bruge begivenheder, men at have direkte opkald (aka RPC) forenkler tingene i begyndelsen. Det er dog vigtigt at spore afhængigheder mellem komponenter, da hvis komponent A ved meget om komponent B, er det måske fornuftigt at fusionere dem til én.
  2. Når du kommer tættere på situationen, når du skal skalere din udvikling og dit system, kan du overveje at følge Stangler- mønsteret for gradvist at udtrække komponenter, der skal implementeres uafhængigt eller endda skaleres med specifikke krav.
  3. Nu, hvis du har en klar vision om fremtiden, hvilket er lidt utroligt held, kan du beslutte dig for den ønskede arkitektur. I dette øjeblik kan du beslutte dig for at gå i retning af mikroservicearkitektur ved også at anvende orkestrerings- og koreografi-tilgange, inkorporere CQRS-mønster til uafhængig skala skrive- og læseoperationer, eller endda beslutte at holde fast i monolitisk arkitektur, hvis det passer til dine behov.


Det er også vigtigt at forstå tallene og målene som DAU (Daily Active Users), MAU (Monthly Active Users), RPC (Request Per Second) og TPC (Transaction Per Second), da det kan hjælpe dig med at træffe valg, fordi arkitektur for 100 aktive brugere og 100 millioner aktive brugere er forskellige.


Som en sidste bemærkning vil jeg sige, at arkitektur har en væsentlig indflydelse på produktets succes. Dårligt designet arkitektur til produkterne er påkrævet i skalering, hvilket med stor sandsynlighed fører til fejl, da kunderne ikke vil vente, mens du skalerer systemet, de vil vælge en konkurrent, så vi skal være på forkant med potentiel skalering. Selvom jeg indrømmer, at det nogle gange ikke kunne være en slank tilgang, er ideen at have et skalerbart, men ikke allerede skaleret system. På den anden side vil det at have et meget kompliceret og allerede skaleret system uden kunder eller planer om at få mange af dem koste dig penge på din virksomhed for ingenting.

Valg af teknologistak

At vælge en teknologistack er også en beslutning på makroniveau, da det påvirker ansættelser, systemets naturlige udviklingsperspektiver, skalerbarhed og systemets ydeevne.


Dette er listen over grundlæggende overvejelser for at vælge en teknologistak:

  • Projektkrav og kompleksitet. For eksempel kan en simpel webapplikation bygges med Blazor frameworket, hvis dine udviklere har erfaring med det, men på grund af WebAssemblys manglende modenhed kan det være en bedre beslutning at vælge React og Typescript for langsigtet succes
  • Skalerbarhed og præstationsbehov. Hvis du forventer at modtage en stor mængde trafik, kan det være et klogt valg at vælge ASP.NET Core frem for Django på grund af dets overlegne ydeevne til at håndtere samtidige anmodninger. Denne beslutning afhænger dog af omfanget af trafik, du forventer. Hvis du har brug for at administrere potentielt milliarder af anmodninger med lav latenstid, kan tilstedeværelsen af Garbage Collection være en udfordring.
  • Ansættelse, udviklingstid og omkostninger. I de fleste tilfælde er det disse faktorer, vi skal bekymre os om. Time to Market, vedligeholdelsesomkostninger og ansættelsesstabilitet driver dine forretningsbehov uden forhindringer.
  • Team ekspertise og ressourcer. Dit udviklingsteams færdigheder er en kritisk faktor. Det er generelt mere effektivt at bruge teknologier, som dit team allerede er bekendt med, medmindre der er en stærk grund til at investere i at lære en ny stak.
  • Modenhed. Et stærkt fællesskab og et rigt økosystem af biblioteker og værktøjer kan i høj grad lette udviklingsprocessen. Populære teknologier har ofte bedre fællesskabsstøtte, hvilket kan være uvurderligt til at løse problemer og finde ressourcer. Således kunne du spare ressourcer og fokusere hovedsageligt på produktet.
  • Langsigtet vedligeholdelse og support. Overvej teknologiens langsigtede levedygtighed. Teknologier, der er bredt anvendt og understøttet, er mindre tilbøjelige til at blive forældede og modtager generelt regelmæssige opdateringer og forbedringer.


Hvordan kan det påvirke virksomhedens vækst at have flere teknologistakke?

Fra et perspektiv kunne introduktion af en stak mere skalere din ansættelse, men på den anden side medfører det ekstra vedligeholdelsesomkostninger, da du skal understøtte begge stakke. Så, som jeg sagde tidligere, efter mit synspunkt, burde kun ekstra behov være et argument for at inkorporere flere teknologistakke.


Men hvad handler om princippet om at vælge det bedste værktøj til et specifikt problem?

Nogle gange har du ikke andet valg end at bringe nye værktøjer til at løse et specifikt problem baseret på de samme overvejelser førnævnte, i sådanne tilfælde giver det mening at vælge den bedste løsning.


At skabe systemer uden høj kobling til en specifik teknologi kan være en udfordring. Alligevel er det nyttigt at stræbe efter en tilstand, hvor systemet ikke er tæt koblet til teknologi, og det vil ikke dø, hvis i morgen, en specifik ramme eller et værktøj bliver sårbart eller endda forældet.


En anden vigtig overvejelse er relateret til open source og proprietære softwareafhængigheder. Proprietær software giver dig mindre fleksibilitet og mulighed for at blive tilpasset. Alligevel er den farligste faktor leverandørlåsning, hvor du bliver afhængig af en leverandørs produkter, priser, vilkår og køreplan. Dette kan være risikabelt, hvis leverandøren ændrer retning, hæver priserne eller stopper produktet. Open source-software reducerer denne risiko, da en enkelt enhed ikke kontrollerer den. At eliminere et enkelt fejlpunkt på alle niveauer er nøglen til at opbygge pålidelige systemer til vækst.

Single Point of Failure (SPOF)

Et enkelt fejlpunkt (SPOF) refererer til enhver del af et system, der, hvis det fejler, vil få hele systemet til at holde op med at fungere. Eliminering af SPOF'er på alle niveauer er afgørende for ethvert system, der kræver høj tilgængelighed. Alt, inklusive viden, personale, systemkomponenter, cloud-udbydere og internetkabler, kan fejle.


Der er flere grundlæggende teknikker, vi kan anvende for at eliminere enkelte fejlpunkter:

  1. Redundans. Implementer redundans for kritiske komponenter. Det betyder at have backup-komponenter, der kan tage over, hvis den primære komponent svigter. Redundans kan anvendes på tværs af forskellige lag af systemet, herunder hardware (servere, diske), netværk (links, switches) og software (databaser, applikationsservere). Hvis du hoster alt i én Cloud-udbyder og endda har sikkerhedskopier der, kan du overveje at bygge en regelmæssig ekstra backup i en anden for at reducere dine tabte omkostninger i tilfælde af en katastrofe.
  2. Datacentre. Distribuer dit system på tværs af flere fysiske lokationer, såsom datacentre eller skyområder. Denne tilgang beskytter dit system mod stedspecifikke fejl som strømafbrydelser eller naturkatastrofer.
  3. Failover. Anvend en failover-tilgang til alle dine komponenter (DNS, CDN, belastningsbalancere, Kubernetes, API-gateways og databaser). Da problemer kan opstå uventet, er det afgørende at have en backup-plan for at erstatte enhver komponent med dens klon efter behov.
  4. Tjenester med høj tilgængelighed. Sørg for, at dine tjenester er bygget til at være horisontalt skalerbare og meget tilgængelige fra starten ved at overholde følgende principper:
    • Øv service-statsløshed og undgå at gemme brugersessioner i in-memory caches. Brug i stedet et distribueret cachesystem, såsom Redis.
    • Undgå at stole på den kronologiske rækkefølge af beskedforbrug, når du udvikler logik.
    • Minimer brydende ændringer for at forhindre, at API-forbrugere forstyrres. Hvor det er muligt, vælg bagudkompatible ændringer. Overvej også omkostningerne, da det nogle gange kan være mere omkostningseffektivt at implementere en brydende ændring.
    • Inkorporer migreringsudførelse i implementeringspipelinen.
    • Etabler en strategi for håndtering af samtidige anmodninger.
    • Implementer serviceopdagelse, overvågning og logning for at øge pålideligheden og observerbarheden.
    • Udvikl forretningslogik til at være idempotent, idet du anerkender, at netværksfejl er uundgåelige.
  5. Afhængighedsgennemgang. Gennemgå regelmæssigt og minimer eksterne afhængigheder. Hver ekstern afhængighed kan introducere potentielle SPOF'er, så det er vigtigt at forstå og afbøde disse risici.
  6. Regelmæssig videndeling. Glem aldrig vigtigheden af at sprede viden i din organisation. Folk kan være uforudsigelige, og det er risikabelt at stole på et enkelt individ. Tilskynd teammedlemmer til at digitalisere deres viden gennem dokumentation. Vær dog opmærksom på at overdokumentere. Brug forskellige AI-værktøjer til at forenkle denne proces.

Konklusion

I denne artikel dækkede vi flere vigtige makroaspekter , og hvordan vi kan håndtere deres kompleksitet.


Tak fordi du læste med! Vi ses næste gang!