paint-brush
Come gestire la complessità durante la progettazione di sistemi softwaredi@fairday
64,370 letture
64,370 letture

Come gestire la complessità durante la progettazione di sistemi software

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

Troppo lungo; Leggere

La complessità è il nemico! Impariamo come affrontarla!
featured image - Come gestire la complessità durante la progettazione di sistemi software
Aleksei HackerNoon profile picture

Di cosa si tratta?

Ogni giorno, ogni momento della nostra carriera ingegneristica, incontriamo molti problemi diversi di varia complessità e situazioni in cui dobbiamo prendere una decisione o rimandarla a causa della mancanza di dati. Ogni volta che creiamo nuovi servizi, costruiamo infrastrutture o persino formiamo processi di sviluppo, tocchiamo un mondo enorme di varie sfide.


È difficile, e forse persino impossibile, elencare tutti i problemi. Incontrerai alcuni di questi problemi solo se lavori in una nicchia specifica. D'altro canto, ce ne sono molti che tutti dobbiamo capire come risolvere, poiché sono cruciali per la creazione di sistemi IT. Con un'alta probabilità, li incontrerai in tutti i progetti.


In questo articolo condividerò le mie esperienze con alcuni dei problemi che ho riscontrato durante la creazione di programmi software.

Che cosa si intende per preoccupazione trasversale?

Se guardiamo su Wikipedia, troveremo la seguente definizione


Nello sviluppo software orientato agli aspetti, le preoccupazioni trasversali sono aspetti di un programma che interessano diversi moduli, senza la possibilità di essere incapsulati in nessuno di essi. Queste preoccupazioni spesso non possono essere nettamente scomposte dal resto del sistema sia nella progettazione che nell'implementazione e possono causare dispersione (duplicazione del codice), groviglio (dipendenze significative tra sistemi) o entrambi.


Descrive ampiamente di cosa si tratta, ma voglio estenderlo e semplificarlo un po':

Una preoccupazione trasversale è un concetto o un componente del sistema/organizzazione che influenza (o "attraversa") molte altre parti.


Gli esempi migliori di tali preoccupazioni sono l'architettura di sistema, la registrazione, la sicurezza, la gestione delle transazioni, la telemetria, la progettazione del database e ce ne sono molte altre. Ne approfondiremo molte più avanti in questo articolo.


A livello di codice, le preoccupazioni trasversali sono spesso implementate utilizzando tecniche come Aspect-Oriented Programming (AOP) , dove queste preoccupazioni sono modularizzate in componenti separate che possono essere applicate in tutta l'applicazione. Ciò mantiene la logica aziendale isolata da queste preoccupazioni, rendendo il codice più leggibile e manutenibile.

Classificazione degli aspetti

Esistono molti modi possibili per classificare gli aspetti segmentandoli con diverse proprietà come ambito, dimensione, funzionalità, importanza, target e altro, ma in questo articolo userò una semplice classificazione dell'ambito. Con questo intendo dove è diretto questo aspetto specifico, che si tratti dell'intera organizzazione, di un sistema particolare o di un elemento specifico di quel sistema.


Quindi, dividerò gli aspetti in Macro e Micro .


Per aspetto macro intendo principalmente le considerazioni che seguiamo per l'intero sistema, come l'architettura di sistema scelta e la sua progettazione (monolitica, microservizi, architettura orientata ai servizi), lo stack tecnologico, la struttura organizzativa, ecc. Gli aspetti macro sono correlati principalmente a decisioni strategiche e di alto livello.


Nel frattempo, l'aspetto Micro è molto più vicino al livello di codice e allo sviluppo. Ad esempio, quale framework viene utilizzato per interagire con il database, la struttura del progetto di cartelle e classi o persino specifici pattern di progettazione di oggetti.


Sebbene questa classificazione non sia ideale, aiuta a strutturare la comprensione dei possibili problemi e dell'importanza e dell'impatto delle soluzioni che applichiamo.


In questo articolo mi concentrerò principalmente sugli aspetti macro.

Aspetti Macro

Struttura organizzativa

Quando ho appena iniziato a studiare l'architettura software, ho letto molti articoli interessanti sulla legge di Conway e il suo impatto sulla struttura organizzativa. In particolare questo . Quindi, questa legge afferma che


Qualsiasi organizzazione che progetta un sistema (definito in senso lato) produrrà un progetto la cui struttura è una copia della struttura di comunicazione dell'organizzazione.


Ho sempre creduto che questo concetto sia davvero universale e rappresenti la Regola d'Oro.


Poi ho iniziato a imparare l'approccio Domain-Driven Design (DDD) di Eric Evans per la modellazione dei sistemi. Eric Evans sottolinea l'importanza dell'identificazione del Bounded Context. Questo concetto implica la suddivisione di un modello di dominio complesso in sezioni più piccole e gestibili, ciascuna con il proprio set limitato di conoscenze. Questo approccio aiuta nella comunicazione di squadra efficace, poiché riduce la necessità di una conoscenza approfondita dell'intero dominio e riduce al minimo il cambio di contesto, rendendo così le conversazioni più efficienti. Il cambio di contesto è la cosa peggiore e più dispendiosa in termini di risorse di sempre. Anche i computer hanno difficoltà con questo. Sebbene sia improbabile che si raggiunga una completa assenza di cambio di contesto, ritengo che sia ciò a cui dovremmo aspirare.


Fantasy about keeping in mind a lot of bounded contexts

Tornando alla legge di Conway, ho trovato diversi problemi.


Il primo problema che ho incontrato con la legge di Conway, che suggerisce che la progettazione del sistema rispecchia la struttura organizzativa, è il potenziale per la formazione di contesti delimitati complessi e completi. Questa complessità sorge quando la struttura organizzativa non è allineata con i confini del dominio, portando a contesti delimitati che sono fortemente interdipendenti e carichi di informazioni. Ciò porta a frequenti cambi di contesto per il team di sviluppo.


Un altro problema è che la terminologia organizzativa trasuda a livello di codice. Quando le strutture organizzative cambiano, ciò richiede modifiche alla base di codice, consumando risorse preziose.


Pertanto, seguire la manovra Inverse Conway aiuta a costruire il sistema e l'organizzazione che incoraggiano l'architettura software desiderata. Tuttavia, è degno di nota dire che questo approccio non funzionerà molto bene in architetture e strutture già formate poiché i cambiamenti in questa fase sono prolungati, ma è eccezionalmente performante nelle startup poiché sono veloci a introdurre qualsiasi cambiamento.

Grande palla di fango

Questo schema o "anti-schema" spinge a costruire un sistema senza alcuna architettura. Non ci sono regole, né confini, né strategie su come controllare l'inevitabile crescente complessità. La complessità è il nemico più formidabile nel percorso di costruzione di sistemi software.


Entertaining illustration made by ChatGPT

Per evitare di costruire un sistema di questo tipo, dobbiamo seguire regole e vincoli specifici.

Architettura del sistema

Esistono innumerevoli definizioni di Architettura Software. Molte di esse mi piacciono perché ne coprono diversi aspetti. Tuttavia, per poter ragionare sull'architettura, abbiamo naturalmente bisogno di formarne alcune nella nostra mente. Ed è degno di nota dire che questa definizione potrebbe evolversi. Quindi, almeno per ora, ho la seguente descrizione per me stesso.


L'architettura software riguarda le decisioni e le scelte che prendi ogni giorno e che hanno un impatto sul sistema sviluppato.


Per prendere decisioni è necessario avere nella propria "borsa" principi e modelli per risolvere i problemi emergenti, è anche essenziale affermare che comprendere i requisiti è la chiave per costruire ciò di cui un'azienda ha bisogno. Tuttavia, a volte i requisiti non sono trasparenti o addirittura non sono definiti, in questo caso, è meglio aspettare di ottenere maggiori chiarimenti o affidarsi alla propria esperienza e fidarsi del proprio intuito. Ma in ogni caso, non è possibile prendere decisioni correttamente se non si hanno principi e modelli su cui fare affidamento. Ecco dove arrivo alla definizione di stile di architettura software.


Lo stile dell'architettura software è un insieme di principi e modelli che stabiliscono come sviluppare un software.


Esistono molti stili architettonici diversi, incentrati su vari aspetti dell'architettura pianificata, e applicarne più di uno contemporaneamente è una situazione normale.


Ad esempio, come:

  1. Architettura monolitica

  2. Progettazione guidata dal dominio

  3. Basato su componenti

  4. Microservizi

  5. Tubo e filtri

  6. Guidato dagli eventi

  7. Microkernel

  8. Orientato al servizio


e così via…


Naturalmente, hanno i loro vantaggi e svantaggi, ma la cosa più importante che ho imparato è che l'architettura si evolve gradualmente, pur dipendendo da problemi reali. Iniziare con l'architettura monolitica è un'ottima scelta per ridurre le complessità operative, molto probabilmente questa architettura soddisferà le tue esigenze anche dopo aver raggiunto la fase Product-market Fit (PMI) della creazione del prodotto. Su larga scala, potresti prendere in considerazione di passare a un approccio event-driven e microservizi per ottenere una distribuzione indipendente, un ambiente di stack tecnologico eterogeneo e un'architettura meno accoppiata (e meno trasparente nel frattempo a causa della natura degli approcci event-driven e pub-sub se questi vengono adottati). Semplicità ed efficienza sono vicine e hanno un grande impatto l'una sull'altra. Di solito, le architetture complicate influenzano la velocità di sviluppo di nuove funzionalità, supportando e mantenendo quelle esistenti e sfidando l'evoluzione naturale del sistema.


Tuttavia, i sistemi complessi richiedono spesso un'architettura complessa e completa, il che è inevitabile.


Abbastanza, questo è un argomento molto molto ampio e ci sono molte idee fantastiche su come strutturare e costruire sistemi per l'evoluzione naturale. Sulla base della mia esperienza, ho elaborato il seguente approccio:

  1. Quasi sempre inizia con lo stile di architettura monolitica poiché elimina la maggior parte dei problemi che sorgono a causa della natura dei sistemi distribuiti. Ha anche senso seguire il monolito modulare per concentrarsi sulla creazione di componenti con confini chiari. L'applicazione di un approccio basato sui componenti potrebbe aiutarli a comunicare tra loro tramite eventi, ma avere chiamate dirette (ovvero RPC) semplifica le cose all'inizio. Tuttavia, è importante tenere traccia delle dipendenze tra i componenti poiché se il componente A sa molto sul componente B, forse ha senso unirli in uno.
  2. Quando ci si avvicina alla situazione in cui è necessario ridimensionare lo sviluppo e il sistema, si può prendere in considerazione l'idea di seguire il modello Stangler per estrarre gradualmente i componenti che devono essere distribuiti in modo indipendente o addirittura ridimensionati in base a requisiti specifici.
  3. Ora, se hai una chiara visione del futuro, il che è un po' una fortuna incredibile, potresti decidere l'architettura desiderata. In questo momento, potresti decidere di muoverti verso un'architettura di microservizi applicando anche approcci di Orchestration e Choreography, incorporando il pattern CQRS per operazioni di scrittura e lettura su scala indipendente, o persino decidere di attenerti all'architettura monolitica se soddisfa le tue esigenze.


È inoltre fondamentale comprendere numeri e metriche quali DAU (utenti attivi giornalieri), MAU (utenti attivi mensili), RPC (richieste al secondo) e TPC (transazioni al secondo), poiché potrebbero aiutarti a fare delle scelte, in quanto l'architettura per 100 utenti attivi e quella per 100 milioni di utenti attivi sono diverse.


Come nota finale, direi che l'architettura ha un impatto significativo sul successo del prodotto. Un'architettura mal progettata per i prodotti è richiesta nella scalabilità, il che molto probabilmente porta al fallimento poiché i clienti non aspetteranno mentre si scala il sistema, sceglieranno un concorrente, quindi dobbiamo anticipare la potenziale scalabilità. Sebbene ammetta che a volte potrebbe non essere un approccio snello, l'idea è di avere un sistema scalabile ma non già scalabile. D'altra parte, avere un sistema molto complicato e già scalabile senza clienti o piani per ottenerne molti ti costerà soldi per la tua attività per niente.

Selezione dello stack tecnologico

La scelta di uno stack tecnologico è anche una decisione a livello macro, poiché influenza le assunzioni, le prospettive di evoluzione naturale del sistema, la scalabilità e le prestazioni del sistema.


Ecco l'elenco delle considerazioni di base per la scelta di uno stack tecnologico:

  • Requisiti e complessità del progetto. Ad esempio, una semplice applicazione web può essere creata con il framework Blazor se i tuoi sviluppatori hanno esperienza con esso, ma a causa della mancanza di maturità di WebAssembly, scegliere React e Typescript per il successo a lungo termine potrebbe essere una decisione migliore
  • Esigenze di scalabilità e prestazioni. Se prevedi di ricevere una grande quantità di traffico, optare per ASP.NET Core anziché Django potrebbe essere una scelta saggia, grazie alle sue prestazioni superiori nella gestione delle richieste simultanee. Tuttavia, questa decisione dipende dalla portata del traffico che ti aspetti. Se hai bisogno di gestire potenzialmente miliardi di richieste con bassa latenza, la presenza di Garbage Collection potrebbe essere una sfida.
  • Assunzione, tempo di sviluppo e costo. Nella maggior parte dei casi, questi sono i fattori di cui dobbiamo preoccuparci. Time to Market, costi di manutenzione e stabilità delle assunzioni guidano le esigenze della tua azienda senza ostacoli.
  • Competenze e risorse del team. Il set di competenze del tuo team di sviluppo è un fattore critico. In genere è più efficace utilizzare tecnologie con cui il tuo team ha già familiarità, a meno che non ci sia una forte ragione per investire nell'apprendimento di un nuovo stack.
  • Maturità. Una comunità forte e un ricco ecosistema di librerie e strumenti possono semplificare notevolmente il processo di sviluppo. Le tecnologie più diffuse hanno spesso un migliore supporto della comunità, che può essere prezioso per risolvere problemi e trovare risorse. In questo modo, potresti risparmiare risorse e concentrarti principalmente sul prodotto.
  • Manutenzione e supporto a lungo termine. Considerare la fattibilità a lungo termine della tecnologia. Le tecnologie ampiamente adottate e supportate hanno meno probabilità di diventare obsolete e generalmente ricevono aggiornamenti e miglioramenti regolari.


In che modo la presenza di più stack tecnologici può influire sulla crescita aziendale?

Da una prospettiva, introdurre un altro stack potrebbe aumentare le assunzioni, ma dall'altra parte, comporta costi di manutenzione aggiuntivi poiché è necessario supportare entrambi gli stack. Quindi, come ho detto in precedenza, dal mio punto di vista, solo la necessità aggiuntiva dovrebbe essere un argomento per incorporare più stack tecnologici.


Ma qual è il principio che sta alla base della scelta dello strumento migliore per un problema specifico?

A volte non si ha altra scelta che adottare nuovi strumenti per risolvere un problema specifico, basandosi sulle stesse considerazioni sopra menzionate; in questi casi ha senso selezionare la soluzione migliore.


La creazione di sistemi senza un elevato accoppiamento a una tecnologia specifica potrebbe essere una sfida. Tuttavia, è utile impegnarsi per una condizione in cui il sistema non sia strettamente accoppiato alla tecnologia e non morirà se domani un framework o uno strumento specifico diventasse vulnerabile o addirittura deprecato.


Un'altra considerazione importante è legata alle dipendenze del software open source e proprietario. Il software proprietario offre meno flessibilità e la possibilità di essere personalizzato. Tuttavia, il fattore più pericoloso è il vendor lock-in, in cui si diventa dipendenti dai prodotti, dai prezzi, dai termini e dalla roadmap di un fornitore. Ciò può essere rischioso se il fornitore cambia direzione, aumenta i prezzi o interrompe il prodotto. Il software open source riduce questo rischio, poiché non è una singola entità a controllarlo. Eliminare un singolo punto di errore a tutti i livelli è la chiave per costruire sistemi affidabili per la crescita.

Singolo punto di errore (SPOF)

Un singolo punto di errore (SPOF) si riferisce a qualsiasi parte di un sistema che, se si guasta, causerà l'interruzione del funzionamento dell'intero sistema. Eliminare gli SPOF a tutti i livelli è fondamentale per qualsiasi sistema che richieda elevata disponibilità. Tutto, tra cui conoscenza, personale, componenti di sistema, provider cloud e cavi Internet, può guastarsi.


Esistono diverse tecniche di base che potremmo applicare per eliminare i singoli punti di errore:

  1. Ridondanza. Implementa la ridondanza per i componenti critici. Ciò significa avere componenti di backup che possono subentrare se il componente primario fallisce. La ridondanza può essere applicata a diversi livelli del sistema, tra cui hardware (server, dischi), networking (link, switch) e software (database, server applicativi). Se stai ospitando tutto in un Cloud Provider e hai anche dei backup lì, prendi in considerazione la creazione di un backup aggiuntivo regolare in un altro per ridurre i costi persi in caso di disastro.
  2. Data Center. Distribuisci il tuo sistema su più sedi fisiche, come data center o regioni cloud. Questo approccio protegge il tuo sistema da guasti specifici della sede, come interruzioni di corrente o calamità naturali.
  3. Failover. Applica un approccio di failover per tutti i tuoi componenti (DNS, CDN, Load balancer, Kubernetes, API Gateway e Database). Poiché i problemi possono sorgere inaspettatamente, è fondamentale avere un piano di backup per sostituire rapidamente qualsiasi componente con il suo clone, se necessario.
  4. Servizi ad alta disponibilità. Assicurati che i tuoi servizi siano progettati per essere scalabili orizzontalmente e altamente disponibili fin dall'inizio, rispettando i seguenti principi:
    • Praticate la statelessness del servizio ed evitate di archiviare le sessioni utente nelle cache in memoria. Utilizzate invece un sistema di cache distribuito, come Redis.
    • Quando si sviluppa la logica, evitare di affidarsi all'ordine cronologico di fruizione dei messaggi.
    • Ridurre al minimo le modifiche di rottura per evitare di interrompere i consumatori di API. Ove possibile, optare per modifiche retrocompatibili. Inoltre, considerare i costi poiché a volte implementare una modifica di rottura potrebbe essere più conveniente.
    • Incorporare l'esecuzione della migrazione nella pipeline di distribuzione.
    • Stabilire una strategia per gestire le richieste simultanee.
    • Implementare l'individuazione, il monitoraggio e la registrazione dei servizi per migliorare l'affidabilità e l'osservabilità.
    • Sviluppare una logica aziendale idempotente, riconoscendo che i guasti di rete sono inevitabili.
  5. Revisione delle dipendenze. Rivedere regolarmente e ridurre al minimo le dipendenze esterne. Ogni dipendenza esterna può introdurre potenziali SPOF, quindi è essenziale comprendere e mitigare questi rischi.
  6. Condivisione regolare delle conoscenze. Non dimenticare mai l'importanza di diffondere le conoscenze all'interno della tua organizzazione. Le persone possono essere imprevedibili e affidarsi a un singolo individuo è rischioso. Incoraggia i membri del team a digitalizzare le proprie conoscenze tramite la documentazione. Tuttavia, fai attenzione a non documentare eccessivamente. Utilizza vari strumenti di intelligenza artificiale per semplificare questo processo.

Conclusione

In questo articolo abbiamo trattato diversi aspetti chiave della macroeconomia e come possiamo gestirne la complessità.


Grazie per aver letto! Alla prossima!