paint-brush
Cum să faceți față complexității atunci când proiectați sisteme softwarede@fairday
64,676 lecturi
64,676 lecturi

Cum să faceți față complexității atunci când proiectați sisteme software

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

Prea lung; A citi

Complexitatea este inamicul! Să învățăm cum să ne descurcăm cu asta!
featured image - Cum să faceți față complexității atunci când proiectați sisteme software
Aleksei HackerNoon profile picture

Despre ce este vorba?

În fiecare zi, în fiecare moment de-a lungul carierei noastre de inginer, întâlnim multe probleme diferite de complexitate variată și situații în care trebuie să luăm o decizie sau să o amânăm din cauza lipsei de date. Ori de câte ori construim noi servicii, construim infrastructură sau chiar formăm procese de dezvoltare, atingem o lume imensă cu diverse provocări.


Este o provocare, și poate chiar imposibil, să enumerați toate problemele. Veți întâlni unele dintre aceste probleme doar dacă lucrați într-o anumită nișă. Pe de altă parte, există multe pe care trebuie să le înțelegem cu toții cum să le rezolvăm, deoarece sunt cruciale pentru construirea sistemelor IT. Cu o mare probabilitate, le vei întâlni în toate proiectele.


În acest articol, voi împărtăși experiențele mele cu unele dintre problemele pe care le-am întâlnit în timpul creării de programe software.

Ce este preocuparea transversală?

Dacă ne uităm la Wikipedia, vom găsi următoarea definiție


În dezvoltarea de software orientată pe aspecte, preocupările transversale sunt aspecte ale unui program care afectează mai multe module, fără posibilitatea de a fi încapsulate în niciunul dintre ele. Aceste preocupări adesea nu pot fi descompuse în mod curat de restul sistemului atât în proiectare, cât și în implementare și pot duce fie la împrăștiere (duplicarea codului), fie la încurcare (dependențe semnificative între sisteme) sau ambele.


Descrie foarte mult ce este, dar vreau să o extind și să o simplific puțin:

O preocupare transversală este un concept sau o componentă a sistemului/organizației care afectează (sau „transmite”) multe alte părți.


Cele mai bune exemple de astfel de preocupări sunt arhitectura sistemului, înregistrarea în jurnal, securitatea, gestionarea tranzacțiilor, telemetria, proiectarea bazelor de date și există multe altele. Vom detalia multe dintre ele mai târziu în acest articol.


La nivel de cod, preocupările transversale sunt adesea implementate folosind tehnici precum programarea orientată pe aspecte (AOP) , unde aceste preocupări sunt modularizate în componente separate care pot fi aplicate în întreaga aplicație. Acest lucru păstrează logica de afaceri izolată de aceste preocupări, făcând codul mai lizibil și mai ușor de întreținut.

Clasificarea aspectelor

Există multe modalități posibile de clasificare a aspectelor prin segmentarea acestora cu diferite proprietăți, cum ar fi domeniul de aplicare, dimensiunea, funcționalitatea, importanța, ținta și altele, dar în acest articol, voi folosi o clasificare simplă a domeniului de aplicare. Prin aceasta, vreau să spun unde este direcționat acest aspect specific, fie că este vorba despre întreaga organizație, un anumit sistem sau un element specific al acelui sistem.


Deci, voi împărți aspectele în Macro și Micro .


Prin aspect macro mă refer în principal la considerente pe care le urmărim pentru întregul sistem, cum ar fi arhitectura sistemului aleasă și designul acestuia (monolitic, microservicii, arhitectură orientată spre servicii), stiva de tehnologie, structura organizațională etc. Aspectele macro sunt legate în principal de aspectele strategice și de nivel înalt. deciziilor.


Între timp, aspectul Micro este mult mai aproape de nivelul de cod și dezvoltare. De exemplu, ce cadru este folosit pentru interacțiunea cu baza de date, structura proiectului de foldere și clase sau chiar modele specifice de proiectare a obiectelor.


Deși această clasificare nu este ideală, ajută la structurarea unei înțelegeri a posibilelor probleme și a importanței și impactului soluțiilor pe care le aplicăm acestora.


În acest articol, accentul meu principal va fi pe aspectele macro.

Aspecte macro

Structura organizatorică

Când tocmai am început să învăț despre arhitectura software, am citit multe articole interesante despre legea lui Conway și impactul acesteia asupra structurii organizaționale. Mai ales acesta . Deci, această lege prevede că


Orice organizație care proiectează un sistem (definit în linii mari) va produce un design a cărui structură este o copie a structurii de comunicare a organizației.


Întotdeauna am crezut că acest concept este într-adevăr foarte universal și reprezintă Regula de Aur.


Apoi am început să învăț abordarea lui Eric Evans Domain-Driven Design (DDD) pentru modelarea sistemelor. Eric Evans subliniază importanța identificării contextului delimitat. Acest concept implică împărțirea unui model de domeniu complex în secțiuni mai mici, mai ușor de gestionat, fiecare cu propriul set limitat de cunoștințe. Această abordare ajută la comunicarea eficientă în echipă, deoarece reduce nevoia de cunoaștere extinsă a întregului domeniu și minimizează schimbarea contextului, făcând astfel conversațiile mai eficiente. Schimbarea contextului este cel mai rău și mai consumator de resurse. Chiar și computerele se luptă cu asta. Deși este puțin probabil să obținem o absență completă a comutării contextului, consider că pentru asta ar trebui să ne străduim.


Fantasy about keeping in mind a lot of bounded contexts

Revenind la Legea lui Conway, am găsit mai multe probleme cu ea.


Prima problemă pe care am întâlnit-o cu legea lui Conway, care sugerează că proiectarea sistemului oglindește structura organizațională, este potențialul de a forma Contexte delimitate complexe și cuprinzătoare. Această complexitate apare atunci când structura organizațională nu este aliniată cu granițele de domeniu, ceea ce duce la Contexte delimitate care sunt puternic interdependente și încărcate cu informații. Aceasta duce la schimbarea frecventă a contextului pentru echipa de dezvoltare.


O altă problemă este că terminologia organizațională se scurge la nivelul codului. Atunci când structurile organizaționale se schimbă, necesită modificări ale bazei de cod, consumând resurse valoroase.


Astfel, urmărirea Inverse Conway Maneuver ajută la construirea sistemului și a organizației care încurajează arhitectura software dorită. Cu toate acestea, este de remarcat să spunem că această abordare nu va funcționa foarte bine în arhitectura și structurile deja formate, deoarece modificările în această etapă sunt prelungite, dar are performanțe excepționale în startup-uri, deoarece acestea introduc rapid orice modificări.

Minge mare de noroi

Acest model sau „anti-model” conduce la construirea unui sistem fără nicio arhitectură. Nu există reguli, limite și nicio strategie cu privire la modul de a controla inevitabila complexitate tot mai mare. Complexitatea este cel mai formidabil inamic în călătoria construirii sistemelor software.


Entertaining illustration made by ChatGPT

Pentru a evita construirea unui astfel de sistem, trebuie să respectăm reguli și constrângeri specifice.

Arhitectura sistemului

Există nenumărate definiții pentru arhitectura software. Îmi plac multe dintre ele, deoarece acoperă diferite aspecte ale acesteia. Cu toate acestea, pentru a putea raționa despre arhitectură, trebuie în mod natural să ne formăm unele dintre ele în mintea noastră. Și este de remarcat să spunem că această definiție poate evolua. Deci, cel puțin deocamdată, am următoarea descriere pentru mine.


Arhitectura software se referă la deciziile și alegerile pe care le faceți în fiecare zi care au impact asupra sistemului construit.


Pentru a lua decizii pe care trebuie să le ai în „pungă” principii și tipare pentru rezolvarea problemelor apărute, este, de asemenea, esențial să afirmi că înțelegerea cerințelor este esențială pentru a construi ceea ce are nevoie o afacere. Cu toate acestea, uneori cerințele nu sunt transparente sau chiar nu sunt definite, în acest caz, este mai bine să așteptați să obțineți mai multe clarificări sau să vă bazați pe experiența dvs. și să aveți încredere în intuiția dvs. Dar oricum, nu poți lua decizii corect dacă nu ai principii și modele pe care să te bazezi. Aici ajung la definiția stilului de arhitectură software.


Stilul de arhitectură software este un set de principii și modele care desemnează modul de construire a software-ului.


Există o mulțime de stiluri arhitecturale diferite axate pe diferite laturi ale arhitecturii planificate, iar aplicarea mai multor dintre ele simultan este o situație normală.


De exemplu, cum ar fi:

  1. Arhitectură monolitică

  2. Design bazat pe domeniu

  3. Bazat pe componente

  4. Microservicii

  5. Conductă și filtre

  6. Condus de evenimente

  7. Microkernel

  8. Orientat spre servicii


și așa mai departe…


Desigur, au avantajele și dezavantajele lor, dar cel mai important lucru pe care l-am învățat este că arhitectura evoluează treptat, în funcție de problemele reale. Începând cu arhitectura monolitică este o alegere excelentă pentru reducerea complexităților operaționale, foarte probabil că această arhitectură se va potrivi nevoilor dvs. chiar și după ce ajungeți la etapa Product-market Fit (PMI) de construire a produsului. La scară, puteți lua în considerare trecerea către o abordare bazată pe evenimente și către microservicii pentru a obține o implementare independentă, un mediu tehnologic eterogen și o arhitectură mai puțin cuplată (și mai puțin transparentă între timp datorită naturii abordărilor bazate pe evenimente și pub-sub dacă acestea sunt adoptate). Simplitatea și eficiența sunt apropiate și au un mare impact una asupra celeilalte. De obicei, arhitecturile complicate influențează viteza de dezvoltare a noilor caracteristici, susținând și menținând pe cele existente și provocând evoluția naturală a sistemului.


Cu toate acestea, sistemele complexe necesită adesea o arhitectură complexă și cuprinzătoare, ceea ce este inevitabil.


În mod corect, acesta este un subiect foarte larg și există multe idei grozave despre cum să structurați și să construiți sisteme pentru evoluția naturală. Pe baza experienței mele, am elaborat următoarea abordare:

  1. Aproape întotdeauna începe cu stilul arhitecturii monolitice, deoarece elimină majoritatea problemelor care apar din cauza naturii sistemelor distribuite. De asemenea, este logic să urmați monolitul modular pentru a vă concentra pe componente de construcție cu limite clare. Aplicarea unei abordări bazate pe componente i-ar putea ajuta să comunice între ei prin utilizarea evenimentelor, dar apelurile directe (aka RPC) simplifică lucrurile la început. Cu toate acestea, este important să urmăriți dependențele dintre componente, deoarece dacă componenta A știe multe despre componenta B, poate că are sens să le îmbinați într-una singură.
  2. Când vă apropiați de situația în care trebuie să vă scalați dezvoltarea și sistemul, ați putea lua în considerare să urmați modelul Stangler pentru a extrage treptat componentele care trebuie implementate independent sau chiar scalate cu cerințe specifice.
  3. Acum, dacă ai o viziune clară asupra viitorului, ceea ce este un pic de noroc incredibil, te-ai putea decide asupra arhitecturii dorite. În acest moment, puteți decide să treceți către arhitectura de microservicii aplicând și abordări de orchestrare și coregrafie, încorporând modelul CQRS pentru operațiuni de scriere și citire la scară independentă sau chiar decizi să rămâneți cu arhitectura monolitică dacă se potrivește nevoilor dvs.


De asemenea, este vital să înțelegeți cifrele și valorile precum DAU (Utilizatori activi zilnici), MAU (Utilizatori activi lunari), RPC (Solicitare pe secundă) și TPC (Tranzacție pe secundă), deoarece vă poate ajuta să faceți alegeri, deoarece arhitectura pentru 100 de utilizatori activi și 100 de milioane de utilizatori activi sunt diferiți.


Ca o notă finală, aș spune că arhitectura are un impact semnificativ asupra succesului produsului. La scalare este necesară o arhitectură prost proiectată pentru produse, ceea ce duce foarte probabil la eșec, deoarece clienții nu vor aștepta în timp ce scalați sistemul, ei vor alege un concurent, așa că trebuie să fim în fața potențialului scalare. Deși recunosc că uneori nu ar putea fi o abordare lean, ideea este să avem un sistem scalabil, dar nu deja scalat. Pe de altă parte, a avea un sistem foarte complicat și deja scalat, fără clienți sau planuri de a obține mulți dintre ei, vă va costa bani pentru afacerea dvs. degeaba.

Selectarea stivei de tehnologie

Selectarea unei stive de tehnologie este, de asemenea, o decizie la nivel macro, deoarece influențează angajarea, perspectivele de evoluție naturală a sistemului, scalabilitatea și performanța sistemului.


Aceasta este lista de considerente de bază pentru alegerea unei stive de tehnologie:

  • Cerințe și complexitate ale proiectului. De exemplu, o aplicație web simplă poate fi construită cu cadrul Blazor dacă dezvoltatorii dvs. au experiență cu acesta, dar din cauza lipsei de maturitate a WebAssembly, alegerea React și Typescript pentru succes pe termen lung ar putea fi o decizie mai bună.
  • Nevoi de scalabilitate și performanță. Dacă anticipați că veți primi o cantitate mare de trafic, optarea pentru ASP.NET Core peste Django ar putea fi o alegere înțeleaptă datorită performanței sale superioare în gestionarea cererilor concurente. Cu toate acestea, această decizie depinde de amploarea traficului la care vă așteptați. Dacă trebuie să gestionați potențial miliarde de solicitări cu o latență scăzută, prezența Garbage Collection ar putea fi o provocare.
  • Angajare, timp de dezvoltare și cost. În cele mai multe cazuri, aceștia sunt factorii de care trebuie să ne pasă. Time to Market, costurile de întreținere și stabilitatea angajării vă conduc nevoile afacerii fără obstacole.
  • Experiența și resursele echipei. Setul de aptitudini ale echipei de dezvoltare este un factor critic. În general, este mai eficient să folosești tehnologii cu care echipa ta este deja familiarizată, cu excepția cazului în care există un motiv serios pentru a investi în învățarea unei noi stive.
  • Maturitate. O comunitate puternică și un ecosistem bogat de biblioteci și instrumente pot ușura foarte mult procesul de dezvoltare. Tehnologiile populare au adesea un sprijin mai bun al comunității, care poate fi de neprețuit pentru rezolvarea problemelor și găsirea de resurse. Astfel, puteți economisi resurse și vă puteți concentra în principal pe produs.
  • Întreținere și asistență pe termen lung. Luați în considerare viabilitatea pe termen lung a tehnologiei. Tehnologiile care sunt adoptate și acceptate pe scară largă sunt mai puțin probabil să devină învechite și, în general, primesc actualizări și îmbunătățiri regulate.


Cum ar putea avea mai multe stive de tehnologie să afecteze creșterea afacerii?

Dintr-o perspectivă, introducerea încă o stivă vă poate mări angajarea, dar, pe de altă parte, aduce costuri suplimentare de întreținere, deoarece trebuie să susțineți ambele stive. Deci, așa cum am spus anterior, din punctul meu de vedere, doar o nevoie suplimentară ar trebui să fie un argument pentru încorporarea mai multor stive de tehnologie.


Dar care este principiul selectării celui mai bun instrument pentru o problemă specifică?

Uneori nu ai altă opțiune decât să aduci noi instrumente pentru a rezolva o problemă specifică pe baza acelorași considerente menționate mai sus, în astfel de cazuri, are sens să selectezi cea mai bună soluție.


Crearea de sisteme fără cuplare ridicată la o tehnologie specifică ar putea fi o provocare. Cu toate acestea, este util să lupți pentru o condiție în care sistemul nu este strâns cuplat cu tehnologia și nu va muri dacă mâine, un anumit cadru sau un instrument devine vulnerabil sau chiar depreciat.


Un alt aspect important este legat de dependențele software-ului open-source și proprietar. Software-ul proprietar vă oferă mai puțină flexibilitate și posibilitatea de a fi personalizat. Totuși, cel mai periculos factor este blocarea furnizorului, în care devii dependent de produsele, prețurile, termenii și foaia de parcurs ale unui furnizor. Acest lucru poate fi riscant dacă vânzătorul își schimbă direcția, crește prețurile sau întrerupe produsul. Software-ul open-source reduce acest risc, deoarece o singură entitate nu îl controlează. Eliminarea unui singur punct de eșec la toate nivelurile este cheia pentru construirea de sisteme fiabile pentru creștere.

Punct unic de eșec (SPOF)

Un singur punct de defecțiune (SPOF) se referă la orice parte a unui sistem care, dacă eșuează, va face ca întregul sistem să nu mai funcționeze. Eliminarea SPOF la toate nivelurile este crucială pentru orice sistem care necesită disponibilitate ridicată. Totul, inclusiv cunoștințele, personalul, componentele sistemului, furnizorii de cloud și cablurile de internet, pot eșua.


Există mai multe tehnici de bază pe care le-am putea aplica pentru a elimina punctele singulare de defecțiune:

  1. Redundanţă. Implementați redundanța pentru componentele critice. Aceasta înseamnă că aveți componente de rezervă care pot prelua în cazul în care componenta principală eșuează. Redundanța poate fi aplicată pe diferite straturi ale sistemului, inclusiv hardware (servere, discuri), rețele (linkuri, comutatoare) și software (baze de date, servere de aplicații). Dacă găzduiți totul într-un furnizor de cloud și chiar aveți copii de rezervă acolo, luați în considerare construirea unui backup suplimentar regulat într-un altul pentru a reduce costul pierdut în caz de dezastru.
  2. Centre de date. Distribuiți-vă sistemul în mai multe locații fizice, cum ar fi centre de date sau regiuni cloud. Această abordare vă protejează sistemul împotriva defecțiunilor specifice locației, cum ar fi întreruperile de curent sau dezastrele naturale.
  3. Failover. Aplicați o abordare de failover pentru toate componentele dvs. (DNS, CDN, balansoare de încărcare, Kubernetes, gateway-uri API și baze de date). Deoarece problemele pot apărea pe neașteptate, este esențial să aveți un plan de rezervă pentru a înlocui rapid orice componentă cu clona sa, după cum este necesar.
  4. Servicii de mare disponibilitate. Asigurați-vă că serviciile dvs. sunt construite pentru a fi scalabile orizontal și foarte disponibile încă de la început, respectând următoarele principii:
    • Practicați apatridia serviciului și evitați stocarea sesiunilor utilizatorilor în cache-urile din memorie. În schimb, utilizați un sistem cache distribuit, cum ar fi Redis.
    • Evitați să vă bazați pe ordinea cronologică a consumului de mesaje atunci când dezvoltați logica.
    • Minimizați modificările nerespective pentru a preveni perturbarea consumatorilor API. Acolo unde este posibil, optați pentru modificări compatibile cu versiunea inversă. De asemenea, luați în considerare costul, deoarece uneori, implementarea unei schimbări de ruptură poate fi mai rentabilă.
    • Încorporați execuția migrației în conducta de implementare.
    • Stabiliți o strategie pentru gestionarea cererilor concurente.
    • Implementați descoperirea, monitorizarea și înregistrarea serviciilor pentru a îmbunătăți fiabilitatea și observabilitatea.
    • Dezvoltați logica de afaceri pentru a fi idempotent, recunoscând că eșecurile rețelei sunt inevitabile.
  5. Revizuirea dependenței. Examinați și minimizați în mod regulat dependențele externe. Fiecare dependență externă poate introduce potențiale SPOF, așa că este esențial să înțelegem și să atenuăm aceste riscuri.
  6. Partajarea regulată a cunoștințelor. Nu uitați niciodată importanța răspândirii cunoștințelor în cadrul organizației dvs. Oamenii pot fi imprevizibili, iar a te baza pe un singur individ este riscant. Încurajați membrii echipei să-și digitalizeze cunoștințele prin documentare. Cu toate acestea, aveți grijă de supradocumentare. Utilizați diverse instrumente AI pentru a simplifica acest proces.

Concluzie

În acest articol, am acoperit mai multe aspecte cheie ale macro-urilor și cum putem face față complexității acestora.


Vă mulțumim pentru citit! Ne vedem data viitoare!