Benvenuto nel mio universo di innovazione
Sono Cosmin Irimescu, sviluppatore software con oltre 8 anni di esperienza, guidato da una profonda passione per la tecnologia.
Iscriviti alla newsletter!
Resta al passo con .NET. Scopri, Impara, Sviluppa.Scopri gli Ultimi Articoli
Principio KISS: L’Arte della Semplicità
KISS (Keep It Simple, Stupid) nasce nella U.S. Navy con Kelly Johnson, che stabilì che ogni aereo doveva essere riparabile
Principio KISS: L’Arte della Semplicità
"La semplicità è l'ultima sofisticazione. Quando riesci a semplificare davvero qualcosa, puoi muovere montagne." - Steve Jobs
Ho sempre creduto che la vera comprensione di qualcosa si rifletta nella capacità di esprimerla in modo semplice. Nel corso della mia esperienza come sviluppatore, questa convinzione si è trasformata in un principio guida: scrivere codice che chiunque possa comprendere. Perché spesso, quando qualcosa è troppo complesso da spiegare, forse non lo abbiamo capito abbastanza bene nemmeno noi.
Questo approccio alla semplicità ha una storia affascinante, che inizia ben prima del mondo dello sviluppo software.
La Storia Dietro
KISS
Il principio KISS (Keep It Simple, Stupid
) ha origini affascinanti. Nato dalla mente di Clarence “Kelly” Johnson, un ingegnere aeronautico della Lockheed Martin, durante la Seconda Guerra Mondiale. Johnson guidava la divisione Skunk Works, un nome che oggi è sinonimo di innovazione ma che nacque come battuta interna (il team lavorava vicino a una fabbrica di plastica dall’odore terribile).
La vera genialità di Johnson fu capire che anche i jet più all’avanguardia dovevano poter essere riparati da meccanici medi con strumenti base. “Keep It Simple, Stupid
” non significava semplificare fino a banalizzare, ma eliminare la complessità non necessaria.
KISS nello
Sviluppo Software
Quando mi capita di rileggere codice che ho scritto anni fa, all’inizio della mia carriera da programmatore, non posso fare a meno di sorridere. Vedo quanto mi sforzassi di renderlo complesso, forse nel tentativo di dimostrare quanto fossi “bravo”. Con l’esperienza ho imparato che la vera sfida non è scrivere codice complesso, ma trovare il giusto equilibrio tra semplicità e necessaria complessità.
Lascia che ti mostri un esempio pratico di come lo stesso problema possa essere risolto in due modi completamente diversi. Vedrai come la seconda soluzione, più semplice, non solo è più facile da capire ma anche da mantenere nel tempo.
Approccio
Complesso
public string GetStatoSito(WebSite sito)
{
if (sito.IsOnline)
{
if (sito.HasUtentiAttivi)
{
return "Il sito è online con utenti attivi";
}
else
{
if (sito.HasAggiornamentiPendenti)
{
return "Il sito è online con aggiornamenti in sospeso";
}
else
{
return "Il sito è online ma inattivo";
}
}
}
else
{
if (sito.InManutenzione)
{
return "Il sito è offline per manutenzione";
}
else
{
return "Il sito è offline";
}
}
}
Questo primo approccio, sebbene funzionale, presenta diversi problemi:
-
Struttura Rigida
L’organizzazione ad albero delle condizioni rende complesso aggiungere nuovi stati o modificare la logica esistente senza rischiare di introdurre bug.
-
Nesting Eccessivo
La profonda struttura di if annidati rende difficile seguire il flusso logico del codice, aumentando il carico cognitivo per chi lo legge.
-
Manutenzione Rischiosa
Ogni modifica richiede particolare attenzione per non alterare la logica esistente, aumentando il rischio di introdurre errori.
Approccio
KISS
public string GetStatoSito(WebSite sito)
{
if (!sito.IsOnline)
return sito.InManutenzione ? "Il sito è offline per manutenzione" : "Il sito è offline";
if (sito.HasUtentiAttivi)
return "Il sito è online con utenti attivi";
return sito.HasAggiornamentiPendenti ? "Il sito è online con aggiornamenti in sospeso" : "Il sito è online ma inattivo";
}
Questo secondo approccio mostra i benefici del principio KISS:
-
Logica Lineare
Il flusso del codice è chiaro e sequenziale, rendendo immediata la comprensione di come vengono gestiti i diversi casi.
-
Early Return Pattern
L’uso di return anticipati elimina la necessità di nesting e rende il codice più piatto e leggibile.
-
Estensibilità Semplice
Aggiungere nuovi stati o modificare la logica esistente è semplice e sicuro, grazie alla struttura lineare del codice.
Stesso Risultato, Diversa Qualità
È importante notare che entrambe le versioni fanno esattamente la stessa cosa. La vera differenza emerge quando devi leggere, mantenere o modificare il codice. La seconda versione non è “migliore” perché fa qualcosa di più, ma perché è più facile da capire e mantenere nel tempo.
Perché KISS
Funziona
Dalla mia esperienza, quando applichi il principio KISS nel tuo codice ottieni benefici immediati:
-
Comprensibilità Immediata
Quando i tuoi colleghi leggono il tuo codice, lo capiscono subito. Non c’è bisogno di lunghe spiegazioni o documentazione estesa.
-
Manutenibilità Superiore
Quando torni sul tuo codice dopo mesi, capisci immediatamente cosa fa e come modificarlo.
-
Testing Affidabile
Più il tuo codice è semplice, più è facile testarlo in modo completo ed efficace.
Oltre il
Software
L’eleganza del principio KISS sta nella sua universalità. Gli stessi concetti si applicano in molti altri contesti:
-
Comunicazione Efficace
Che tu stia scrivendo documentazione tecnica o preparando una presentazione, un messaggio semplice e diretto ha più probabilità di essere compreso e ricordato.
-
Project Management
La semplicità nelle procedure e nei processi porta a una migliore esecuzione e a meno errori. Spesso i progetti più riusciti sono quelli con la struttura più lineare.
-
Problem Solving
Quando affronti un problema complesso, scomporlo in parti più semplici lo rende più gestibile e ti permette di trovare soluzioni più efficaci.
Conclusione
Come ho mostrato nell’esempio di codice, la vera eleganza non sta nella complessità ma nella tua capacità di trovare la soluzione più semplice a un problema complesso.
La prossima volta che ti trovi davanti a una sfida, fermati un momento e chiediti: “Sto complicando troppo le cose? C’è un modo più semplice per raggiungere il mio obiettivo?”
Nel mio percorso ho imparato che spesso la risposta è sì, esiste quasi sempre un modo più semplice.
Un Avvertimento sulla Semplificazione
Nel mio percorso ho imparato che esiste quasi sempre un modo più semplice, ma attenzione: la passione per la semplicità non deve trasformarsi in una semplificazione forzata. A volte alcuni problemi richiedono una certa complessità, e cercare di semplificarli a tutti i costi potrebbe portare a soluzioni inadeguate. La chiave sta nel trovare il giusto equilibrio.
E tu, come applichi il principio KISS nel tuo lavoro? Come bilanci semplicità e necessaria complessità? Condividi la tua esperienza nei commenti!
Principio KISS: L’Arte della Semplicità nel Codice e nella Vita
Architecture Drivers: Dal Ball of Mud al Monolite Modulare
SignalR in ASP.NET Core: Notifiche in Tempo Reale
Crescita Personale nell’Imperfezione: Il Tuo Viaggio Unico
Da HttpClient a HttpClientFactory: Una Storia di Socket Exhaustion
IEnumerable vs IList in .NET
Microservizi vs Monolite Modulare: Scegliere l’Architettura Giusta
Repository Pattern in .NET
API Gateway con YARP in .NET
Ottimizzare il Lavoro con Microsoft Viva Insights
Architecture Drivers: Dal Ball of Mud al Monolite Modulare
Come gli Architecture Drivers guidano le decisioni architetturali in contesti reali.
Architecture Drivers: Dal Ball of Mud al Monolite Modulare
Più di un anno fa mi sono trovato davanti alla sfida di riprogettare un’architettura partendo da un datato “ball of mud“. La prima decisione importante è stata evitare il trend dei microservizi, optando invece per un monolite modulare. Ma cosa mi ha portato a questa scelta? Gli Architecture Drivers.
Cosa sono gli
Architecture Drivers?
Gli Architecture Drivers sono i fattori chiave che guidano le decisioni architetturali in un progetto software. Non sono semplici preferenze o trend tecnologici, ma elementi concreti che influenzano direttamente la struttura e il design del sistema.
Immagina di dover ristrutturare una casa: non puoi basare le tue decisioni solo sulle tendenze del momento o su preferenze personali. Devi considerare:
-
Il budget disponibile
Un budget di 100.000€ per ristrutturare casa ti porta a scelte molto diverse da un budget di 20.000€. Dovrai decidere se rifare completamente il bagno o limitarti a cambiare i sanitari, proprio come nel software dovrai scegliere tra una riscrittura completa o un refactoring mirato.
-
Le necessità di chi ci vive
Una giovane famiglia ha bisogno di spazi diversi rispetto a una coppia di pensionati. Il numero di bagni, la disposizione della cucina, persino l’altezza dei mobili: ogni scelta deve rispondere a esigenze specifiche, non a preferenze astratte.
-
I vincoli strutturali dell’edificio
Un muro portante non si può abbattere solo perché rovinerebbe la simmetria della stanza. Le fondamenta dell’edificio, l’impianto elettrico esistente, persino l’orientamento delle finestre: sono vincoli che influenzano ogni decisione di ristrutturazione.
-
La qualità e durabilità dei materiali
Il pavimento più economico potrebbe sembrare un affare oggi, ma dopo due anni di uso intensivo mostrerà tutti i suoi limiti. Le scelte sulla qualità dei materiali hanno un impatto diretto sulla durabilità e sui costi di manutenzione futuri.
Allo stesso modo, nel software, gli Architecture Drivers si dividono in quattro categorie fondamentali che lavorano insieme per guidare le nostre scelte:
-
Vincoli Tecnici
Sono i limiti tecnici con cui devi fare i conti, come le competenze del team, gli strumenti disponibili e l’infrastruttura esistente. È come dover costruire con i materiali e gli strumenti che hai a disposizione.
-
Vincoli di Business
Rappresentano le realtà aziendali: budget, tempistiche, risorse umane disponibili. È come avere un budget limitato per la ristrutturazione e dover decidere cosa privilegiare.
-
Requisiti Funzionali
Sono le funzionalità che il sistema deve offrire, come gestire ordini o elaborare pagamenti. È come specificare quante stanze deve avere una casa e come devono essere collegate tra loro.
-
Attributi di Qualità
Definiscono come il sistema deve performare: velocità, sicurezza, scalabilità. È come specificare che una casa deve essere efficiente energeticamente o resistente ai terremoti.
La mia
Situazione
Mi trovavo davanti a una scelta: seguire il trend dei microservizi o optare per un approccio più conservativo? Analizziamo i driver che hanno guidato la mia decisione.
Vincoli
Tecnici
-
Team Giovane con Competenze .NET
Avevo sviluppatori talentuosi con ottima conoscenza dell’ecosistema .NET. Come affideresti un’auto sportiva a un pilota esperto di quella categoria specifica, aveva senso sfruttare questa expertise.
-
Poca Esperienza Distribuita
Il team, seppur competente, aveva limitata esperienza con architetture distribuite. Introdurre microservizi sarebbe stato come insegnare a guidare una Formula 1 a chi ha appena preso la patente.
-
Sistema Legacy Monolitico
Il nostro punto di partenza era un sistema monolitico che, nonostante i suoi problemi, funzionava. Era come una casa vecchia ma abitabile: necessitava di ristrutturazione, non demolizione.
Vincoli di
Business
-
Continuità dello Sviluppo
La priorità era non bloccare lo sviluppo per una lunga fase di formazione. Partire subito con i microservizi senza la giusta preparazione avrebbe potuto portare a scelte architetturali errate e difficili da correggere in futuro.
-
Delivery Continuo
Il business necessitava di vedere progressi tangibili e costanti. Un percorso di formazione e sperimentazione con i microservizi avrebbe rallentato significativamente il delivery di nuove funzionalità.
-
Rischio Architetturale
La scelta di un’architettura complessa senza la giusta esperienza rappresentava un rischio troppo elevato. Era preferibile partire con un approccio più consolidato che potesse evolvere nel tempo, piuttosto che rischiare di costruire un sistema distribuito difficile da gestire e mantenere.
Requisiti
Funzionali
-
Accesso ai Dati Cross-Module
Alcuni moduli hanno la necessità di accedere ai dati di altri moduli senza la complessità di chiamate remote o orchestrazioni complesse. In un monolite modulare, questo tipo di comunicazione rimane semplice mantenendo comunque un buon livello di incapsulamento.
-
Design Modulare Evolutivo
L’architettura deve essere strutturata in moduli ben definiti fin dall’inizio. Questo non solo mantiene il codice organizzato, ma apre anche la possibilità di estrarre specifici moduli in microservizi quando necessario, senza dover riarchitettare l’intero sistema.
-
Separazione delle Responsabilità
Ogni modulo deve avere un dominio di responsabilità chiaro e definito, pur mantenendo la possibilità di comunicare efficientemente con gli altri moduli. Questo equilibrio tra isolamento e comunicazione è fondamentale per una buona architettura modulare.
Attributi di
Qualità
-
Sviluppo e Manutenzione Efficiente
Il team deve poter sviluppare e mantenere il codice in modo efficiente, concentrandosi sulla logica di business invece che sulla gestione di problematiche distribuite. Un monolite modulare ben strutturato permette agli sviluppatori di lavorare in modo produttivo utilizzando pattern e pratiche familiari.
-
Testing Semplificato
L’architettura deve essere strutturata in moduli ben definiti fin dall’inizio. Questo non solo mantiene il codice organizzato, ma apre anche la possibilità di estrarre specifici moduli in microservizi quando necessario, senza dover riarchitettare l’intero sistema.
-
Deployment Affidabile
Il deployment di una singola applicazione, seppur modulare, riduce significativamente la complessità operativa rispetto a un sistema distribuito. Questo si traduce in rilasci più sicuri e una gestione più semplice degli aggiornamenti, mantenendo comunque la possibilità di evolvere verso un’architettura distribuita in futuro.
Come Bilanciare gli
Architecture Drivers
Gli Architecture Drivers non operano in isolamento. Nel mio caso, ho dovuto bilanciare diversi fattori apparentemente contrastanti.
Il Peso del
Contesto
Non tutti i driver hanno lo stesso peso. Nel mio caso, i technical constraints(competenze del team)
e i business constraints(necessità di delivery continuo)
hanno avuto un peso maggiore rispetto ad altri fattori. Questo non significa che gli altri driver fossero meno importanti, ma che il contesto ha determinato le priorità.
Gestire i
Trade-off
A volte i driver possono entrare in conflitto tra loro. Per esempio, la necessità di accesso ai dati tra moduli(requisiti funzionali)
potrebbe suggerire un monolite, mentre future esigenze di scalabilità potrebbero suggerire microservizi. La chiave sta nel trovare una soluzione che soddisfi le necessità attuali mantenendo la flessibilità per il futuro.
L'Evoluzione nel
Tempo
La bellezza di un’architettura modulare ben progettata è che permette di evolvere nel tempo. I driver di oggi potrebbero non essere quelli di domani, e la nostra architettura deve essere pronta a questo cambiamento.
Conclusione
Gli Architecture Drivers sono stati fondamentali nel guidare la mia scelta verso un monolite modulare. Non si è trattato di seguire un trend o di scegliere l’approccio più semplice, ma di ascoltare attentamente ciò che il contesto ci stava dicendo.
L’esperienza mi ha insegnato che:
-
Driver Concreti vs Mode del Momento
La scelta di un’architettura non può basarsi sui trend o su cosa è “cool” nel momento. Nel mio caso, i microservizi erano la moda del momento, ma i driver concreti (competenze del team, necessità di delivery continuo, accesso ai dati cross-module) hanno guidato verso una scelta diversa ma più appropriata.
-
La Saggezza della Semplicità
La semplicità non significa “basic” o “limitato”. Un monolite modulare ben progettato può essere tanto sofisticato quanto necessario, mantenendo la semplicità operativa. In questo caso, ha permesso al team di concentrarsi sulla qualità del codice invece che sulla gestione di un’architettura distribuita prematura.
-
Architettura Evolutiva
Un’architettura modulare ben definita permette di evolvere naturalmente con il business. La possibilità di estrarre singoli moduli in microservizi quando necessario dimostra che non dobbiamo scegliere tra “tutto o niente”: possiamo evolvere gradualmente in base alle reali necessità.
Importante !
La prossima volta che ti trovi davanti a una decisione architetturale importante, fermati un momento e ascolta i tuoi driver. Ti guideranno verso la soluzione più appropriata per il tuo contesto specifico.
E tu, quali driver guidano le tue decisioni architetturali? Come bilanci requisiti contrastanti nelle tue scelte? Condividi la tua esperienza nei commenti!
Architecture Drivers: Dal Ball of Mud al Monolite Modulare
SignalR in ASP.NET Core: Notifiche in Tempo Reale
Crescita Personale nell’Imperfezione: Il Tuo Viaggio Unico
Da HttpClient a HttpClientFactory: Una Storia di Socket Exhaustion
IEnumerable vs IList in .NET
Microservizi vs Monolite Modulare: Scegliere l’Architettura Giusta
Repository Pattern in .NET
API Gateway con YARP in .NET
Ottimizzare il Lavoro con Microsoft Viva Insights
Sviluppo Backend in un Lampo con Appwrite
SignalR in ASP.NET Core: Notifiche in Tempo Reale
Come aggiungere notifiche real-time alla tua applicazione ASP.NET Core con SignalR, incluso il supporto multi-tab.
SignalR in ASP.NET Core: Notifiche in Tempo Reale
Qualche anno fa mi sono trovato davanti ad un progetto che richiedeva un sistema di notifiche in tempo reale. Analizzando i vari casi d’uso, uno in particolare sembrava complesso: gli utenti dovevano ricevere notifiche su tutte le schede aperte del browser. Con SignalR, quella che sembrava una sfida si è rivelata un gioco da ragazzi.
Se preferisci vedere subito il codice completo, puoi trovare il progetto su GitHub: SignalR.NET
Cos'è
SignalR
SignalR è una libreria per .NET che facilita la comunicazione in tempo reale tra server e client. Permette lo scambio di dati bidirezionale, rendendo semplice l’implementazione di funzionalità come chat, notifiche e aggiornamenti in tempo reale nelle applicazioni web. Utilizza tecnologie come WebSockets, fallback automatici e connessioni persistenti per garantire una comunicazione fluida e reattiva.
Implementazione
Per prima cosa, crea una nuova applicazione ASP.NET Core Web App con pagine Razor è successivamente installa la libreria necessaria per registrare il servizio di SignalR.
1. Installazione SignalR
dotnet add package Microsoft.AspNetCore.SignalR.Client.Core 8.0.10
2. Creazione HUB
Il hub è il punto centrale di comunicazione in SignalR. Immagina un hub come un dispatcher che gestisce tutte le comunicazioni tra server e client. Quando un client vuole inviare un messaggio, lo invia all'hub, che poi può inoltrarlo ad altri client. L'hub gestisce automaticamente connessioni, disconnessioni e il mantenimento dello stato delle connessioni.
Hubs/NotifyHub.cs
using Microsoft.AspNetCore.SignalR;
namespace SignalR.NET.Hubs;
public class NotifyHub : Hub
{
public async override Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "users");
await base.OnConnectedAsync();
}
public async Task CreateNotify(string message)
{
await Clients.All.SendAsync("Notify", message);
}
}
Importante !
I gruppi in SignalR sono come “stanze virtuali” dove puoi organizzare le connessioni dei client. Immagina di avere un’applicazione dove gli utenti possono seguire diversi argomenti o categorie.
3. Registrazione SignalR
Una volta creato l'Hub, il prossimo passo è duplice: registrare SignalR come servizio nell'applicazione e configurare l'endpoint che i client utilizzeranno per stabilire la connessione.
Program.cs
using SignalR.NET.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub("/notify");
await app.RunAsync();
Configurazione client
Dopo aver configurato SignalR lato server, è necessario procedere con la configurazione del client per verificarne il funzionamento. In questa sezione importerai la libreria SignalR via CDN e implementerai la logica delle notifiche con CSS e JavaScript.
1. SignalR via CDN
Per velocizzare l'implementazione, puoi importare la libreria SignalR client direttamente via CDN. In alternativa, è possibile utilizzare NPM.
Oltre all'import della libreria, è necessario aggiungere un container per le notifiche.
Pages/Shared/_Layout.cs
@await RenderSectionAsync("Scripts", required: false)