Skip to content

feat: implement migrations strategy for production deployment #97

@artcava

Description

@artcava

🎯 Descrizione

Attualmente l'applicazione usa EnsureCreated() + EnsureDeleted() per gestire il database in sviluppo (Issue #13, PR #96). Questo approccio ricrea il DB se lo schema è obsoleto, eliminando tutti i dati.

Problema: In produzione questo comportamento distruggerebbe i dati utente.

Soluzione: Implementare EF Core Migrations per aggiornare lo schema preservando i dati esistenti.


📝 Contesto da Issue #13 e PR #96

Approccio Attuale (Sviluppo)

// App.xaml.cs - InitializeDatabase()
try
{
    _ = context.ScheduledVisits.Any(); // Test schema
    DbInitializer.Initialize(context); // Popola se vuoto
}
catch (SqliteException ex) when (ex.Message.Contains("no such table"))
{
    context.Database.EnsureDeleted(); // 🗑️ DISTRUTTIVO!
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}

Pro (Dev):

  • ✅ Ricrea DB automaticamente se schema obsoleto
  • ✅ Dati fake sempre disponibili
  • ✅ No migrations complesse in sviluppo

Contro (Prod):

  • EnsureDeleted() elimina dati utente
  • EnsureCreated() non supporta schema changes
  • DbInitializer non dovrebbe girare in prod

✅ Tasks per Produzione

1. Creare Initial Migration

# Da root del progetto
cd src/PTRP.Data
dotnet ef migrations add InitialCreate --startup-project ../PTRP.App

Genera:

  • Migrations/YYYYMMDDHHMMSS_InitialCreate.cs
  • Snapshot dello schema corrente

2. Aggiornare InitializeDatabase() per Produzione

private void InitializeDatabase()
{
    using var scope = _serviceProvider.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<PTRPDbContext>();
    
#if DEBUG
    // SVILUPPO: Ricrea se schema obsoleto
    try
    {
        _ = context.ScheduledVisits.Any();
        DbInitializer.Initialize(context); // Dati fake
    }
    catch (SqliteException ex) when (ex.Message.Contains("no such table"))
    {
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();
        DbInitializer.Initialize(context);
    }
#else
    // PRODUZIONE: Applica migrations senza perdere dati
    context.Database.Migrate(); // ✅ Preserva dati esistenti
    
    // NO DbInitializer in produzione!
    // Dati reali importati da ConfigurationService (Issue #49)
#endif
}

3. Workflow Migrations per Schema Changes

Ogni volta che modifichi un Model:

# 1. Modifica PatientModel.cs (esempio: aggiungi proprietà)
public string? Email { get; set; }

# 2. Crea migration
dotnet ef migrations add AddPatientEmail --startup-project ../PTRP.App

# 3. Applica migration (automatico in prod via Migrate())
dotnet ef database update --startup-project ../PTRP.App

Genera SQL:

ALTER TABLE Patients ADD Email TEXT NULL;

4. Gestione Dati Produzione

Primo Deploy (DB vuoto):

  1. Migrate() crea tutte le tabelle (da InitialCreate)
  2. NO seed automatico
  3. Utente importa dati via ConfigurationService (Issue FASE 1 - Foundation: Schermata Primo Avvio (First Run Setup) #49)

Deploy successivi (DB popolato):

  1. Migrate() applica solo nuove migrations
  2. Dati esistenti preservati
  3. Nuove colonne: NULL o default values

5. Testing Strategy

# Test migrations localmente
rm ~/AppData/Local/PTRP/ptrp.db
dotnet run --project src/PTRP.App --configuration Release

# Verifica:
# - DB creato con Migrate() invece di EnsureCreated()
# - Nessun DbInitializer.Initialize() eseguito
# - Schema corretto senza dati fake

📊 Schema Transizione Dev → Prod

DEVELOPMENT (#if DEBUG)
  └─ EnsureDeleted() + EnsureCreated()
  └─ DbInitializer.Initialize() (dati fake)
  └─ Ricrea DB ad ogni schema change

PRODUCTION (#else)
  └─ context.Database.Migrate()
  └─ NO DbInitializer (dati reali da import)
  └─ Schema changes incrementali (preserva dati)

📝 Documentazione da Creare

  • docs/migrations-guide.md

    • Come creare migrations
    • Come gestire breaking changes
    • Come rollback migrations
  • Aggiornare README.md

    • Setup database per sviluppo vs produzione
    • Comandi dotnet ef utili
  • Aggiungere workflow CI/CD

    • Verifica migrations pending prima del deploy
    • Backup automatico DB prima di Migrate()

⚠️ Breaking Changes da Gestire

Esempi comuni:

  1. Rinominare colonna:

    // Migration custom necessaria!
    migrationBuilder.RenameColumn(
        name: "OldName",
        table: "Patients",
        newName: "NewName");
  2. Cambiare tipo colonna:

    // Richiede conversione dati!
    // 1. Crea colonna temporanea
    // 2. Copia/converti dati
    // 3. Elimina vecchia colonna
    // 4. Rinomina temporanea
  3. Aggiungere constraint NOT NULL:

    // Fornisci default value o popola prima
    migrationBuilder.AddColumn<string>(
        name: "RequiredField",
        table: "Patients",
        nullable: false,
        defaultValue: "Unknown"); // 👈 IMPORTANTE!

🔗 References


✅ Acceptance Criteria

  • Initial migration creata con schema completo
  • #if DEBUG / #else implementato in InitializeDatabase()
  • Build Release usa Migrate() invece di EnsureCreated()
  • DbInitializer non eseguito in produzione
  • Documentazione migrations in docs/
  • Test su DB vuoto con build Release
  • Test schema change con migration incrementale

Priority: Low (implementare prima del primo deploy produzione)
Milestone: Pre-Production
Labels: enhancement, documentation, database

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions