Convenzioni
Una solution ben strutturata è leggibile non solo dagli sviluppatori ma anche dall'IA che ci lavora. Le convenzioni che seguono massimizzano la comprensibilità contestuale senza richiedere spiegazioni aggiuntive.
Naming esplicito
I nomi descrivono l'intenzione, non il tipo:
// ✅ Il nome dice cosa fa
public class CreaOrdine : IUseCase<CreaOrdineCommand, OrdineId> { }
public class ConfermaOrdine : IUseCase<ConfermaOrdineCommand> { }
public class GestoreScorte { }
// ❌ Il nome dice cosa è, non cosa fa
public class OrdineService { }
public class OrdineManager { }
Un file, una responsabilità
Ogni file contiene una sola classe. Il nome del file coincide con il nome della classe.
CreaOrdine.cs → class CreaOrdine
OrdineStato.cs → enum OrdineStato
CreaOrdineCommand.cs → record CreaOrdineCommand
Record per comandi e DTO
I comandi e i DTO si esprimono come record, immutabili per costruzione:
public record CreaOrdineCommand(Guid ClienteId, IReadOnlyList<RigaOrdine> Righe);
public record RigaOrdine(Guid ProdottoId, int Quantita);
Interfacce solo dove necessario
Un'interfaccia si introduce quando serve sostituibilità reale (test, implementazioni multiple). Non si crea un'interfaccia per ogni classe per abitudine:
// ✅ Interfaccia che ha senso: più implementazioni possibili
public interface IEmailSender { Task SendAsync(Email email); }
// ❌ Interfaccia inutile: esiste solo GestoreScorte
public interface IGestoreScorte { }
public class GestoreScorte : IGestoreScorte { }
Program.cs minimal
Program.cs registra le dipendenze e avvia l'app. Non contiene logica:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, cfg) => cfg.ReadFrom.Configuration(ctx.Configuration));
builder.Services.AddDbContext<AppDbContext>(o =>
o.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<CreaOrdine>();
builder.Services.AddScoped<ConfermaOrdine>();
builder.Services.AddScoped<GestoreScorte>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Per solution con molte dipendenze, si usano extension method per raggruppare le registrazioni:
// Infrastructure/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddDomainServices(this IServiceCollection services)
{
services.AddScoped<CreaOrdine>();
services.AddScoped<ConfermaOrdine>();
services.AddScoped<GestoreScorte>();
return services;
}
}