Dicembre 2016

Volume 31 Numero 13

Il presente articolo è stato tradotto automaticamente.

Cutting Edge - Riscrivere un sistema CRUD con eventi e CQRS

Da Dino Esposito

Dino EspositoIl mondo di classico creazione, lettura, aggiornamento, eliminazione (CRUD) sistemi basati su un database relazionale e riempito con blocchi di logica di business, talvolta nascosto nelle stored procedure e talvolta gabbia nei componenti di casella nera. Alla base di tali finestre nero sono le quattro operazioni CRUD: creazione di nuove entità, lettura, aggiornamento ed eliminazione. A un livello sufficientemente elevato di astrazione, ovvero tutti gli elementi, ovvero qualsiasi sistema è, in alcuni casi, un sistema CRUD. Le entità potrebbero essere a volte abbastanza complesso e più costituito da una funzione di aggregazione.

In Progettazione la (DDD), una funzione di aggregazione è un cluster commerciali di entità con un oggetto radice. Di conseguenza, la creazione, aggiornamento o eliminazione anche di un'entità può essere soggetto alle diverse regole di business complessa. Anche se lo stato di un'aggregazione è in genere problematica, principalmente per motivi di esperienza Utente. Il modello che può essere utilizzata per modificare lo stato del sistema non è necessariamente lo stesso modello che funziona per la presentazione dei dati agli utenti in tutti i casi di utilizzo.

Portare il livello di astrazione di CRUD di valori massimi lead direttamente alla separazione ciò che modifica lo stato di un sistema da ciò che semplicemente restituisce uno o più viste degli stessi. Questo è l'essenza non elaborato del comando e Query Responsibility Segregation (CQRS), vale a dire la separazione delle responsabilità di comando e query brillante.

Tuttavia, è più che gli sviluppatori e architetti software necessario prendere in considerazione. Lo stato del sistema viene modificato lo stack di comando e, in pratica, che in cui le aggregazioni vengono innanzitutto create è anche in cui le stesse funzioni di aggregazione vengono successivamente aggiornate ed eliminate. E con precisione il punto da ridefinire.

Conservazione della cronologia è fondamentale per qualsiasi sistema software. Software è scritta per il supporto di business continuativa, imparare dal passato è fondamentale per due motivi: per evitare la perdita di un singolo aspetto che si sono verificati e migliorare i servizi a clienti e dipendenti.

Nel 2016 maggio (msdn.com/magazine/mt703431) e 2016 giugno (msdn.com/magazine/mt707524) rate di questo articolo, ho presentato modi per estendere il classico CRUD per un CRUD cronologici. Nel mio 2016 agosto (msdn.com/magazine/mt767692) e ottobre 2016 (msdn.com/magazine/mt742866) le colonne, invece, ho presentato una serie di eventi-comando-Saga (ECS) e un framework Memento FX (bit.ly/2dt6PVD) come blocchi predefiniti di un nuovo modo di esprimere la logica di business che soddisfi specifiche esigenze.

In questa colonna e nella successiva, verranno descritti i vantaggi menzionati due della conservazione della cronologia in un sistema mediante la riscrittura di un'applicazione demo di prenotazione (lo stesso utilizzato nelle colonne maggio e giugno) con CQRS e origine evento.

nel quadro d'insieme

Applicazione di esempio è un sistema di prenotazione interno per sale riunioni. Il caso di utilizzo principale è un utente connesso che consente di scorrere un calendario e libri di uno o più slot in un determinato spazio. Il sistema gestisce entità quali chat, RoomConfiguration e prenotazione e, come è facile immaginare, concettualmente l'intera applicazione è sull'aggiunta e modifica di chat e configurazioni (ovvero, quando la chat è aperta per la prenotazione e la lunghezza di slot singolo) e l'aggiunta, aggiornamento e annullare le prenotazioni. Figura 1 offre una panoramica delle azioni che gli utenti del sistema sono in grado di eseguire e come hanno verrà creato in un sistema CQRS in base al modello di servizi di calcolo EXCEL.

Progettazione di alto livello del sistema e le azioni dell'utente
Figura 1 azioni dell'utente e progettazione di alto livello del sistema

Un utente può immettere una nuova prenotazione, spostare e annullare l'operazione e controllare anche la chat in modo che il sistema sappia che spazio riservato è effettivamente in uso. Il flusso di lavoro protetti da ogni azione viene gestita in una saga e la saga è una classe definita nello stack di comando. Una classe saga è costituita da metodi del gestore, ognuno dei quali elabora un comando o un evento. Inserimento di una prenotazione (o lo spostamento di una prenotazione esistente) è una questione di inserimento di un comando allo stack di comando. In generale, il push di un comando può essere semplice come richiamare direttamente il metodo saga corrispondente oppure è possibile passare ai servizi di un bus.

Per mantenere la cronologia, è necessario registrare almeno tutti gli effetti di business di comandi elaborati. In alcuni casi, è inoltre possibile tenere traccia dei comandi originali. Un comando è un oggetto di trasferimento dei dati con alcuni dati di input. Un effetto di business di eseguire un comando tramite una saga è un evento. Un evento è un oggetto di trasferimento dei dati che contiene i dati che descrive l'evento. Gli eventi vengono salvati in un archivio dati specifico. Non sono presenti vincoli strict sulla tecnologia di archiviazione da utilizzare per gli eventi. Può trattarsi di un sistema di gestione semplice database relazionali (RDBMS) o può essere un archivio dati NoSQL. (Fare riferimento alla colonna ottobre per l'installazione di MementoFX e RavenDB e bus).

Coordinamento di comandi e query

Si supponga che un utente inserisce un comando per uno slot su un determinato spazio. In uno scenario di ASP.NET MVC, il controller Ottiene i dati inseriti e inserisce un comando per il bus. Il bus è configurato per riconoscere alcuni saghe e ogni saga dichiara i comandi (e/o eventi) è interessato a gestire. Di conseguenza, il bus invia il messaggio la saga. L'input della saga è dati non elaborati che digitato dagli utenti nei moduli di interfaccia Utente. Il gestore saga è responsabile della ricezione di trasformazione dei dati in un'istanza di una funzione di aggregazione è coerente con la logica di business.

Si supponga che l'utente fa clic al libro, come illustrato nella figura 2. Il metodo controller attivato dal pulsante riceve l'ID della chat room, il giorno e l'ora e il nome utente. Il gestore saga deve trasformare il progetto in un'aggregazione personalizzate prenotazione per affrontare la logica di business previsto. Logica di business ragionevolmente risolvere problemi nell'area della concorrenza anche normale, priorità, i costi e autorizzazioni. Tuttavia, come requisito minimo il metodo saga ha creare una funzione di aggregazione di prenotazione e salvarlo.

Prenotazione di una sala riunioni nel sistema di esempio
Figura 2 prenotazione una sala riunioni nel sistema di esempio

A prima vista, il frammento di codice in figura 3 non è diverso da un normale CRUD tranne l'utilizzo di una factory e la proprietà Repository in sospeso. L'effetto combinato di factory e repository scrive negli archivi di eventi configurata tutti gli eventi generati all'interno dell'implementazione della classe di prenotazione.

Figura 3 struttura di una classe Saga

public class ReservationSaga : Saga,
  IAmStartedBy<MakeReservationCommand>,
  IHandleMessages<ChangeReservationCommand>,
  IHandleMessages<CancelReservationCommand>
{
   ...
  public void Handle(MakeReservationCommand msg)
  {
    var slots = CalculateActualNumberOfSlots(msg);
    var booking = Booking.Factory.New(
      msg.FullName, msg.When, msg.Hour, msg.Mins, slots);
    Repository.Save(booking);
  }
}

Al termine, il repository non salva un record con lo stato corrente di una classe di prenotazione in cui le proprietà sono in qualche modo mappata alle colonne. Salva solo gli eventi di business per l'archivio e alla fine in questa fase si conosce esattamente cosa è successo a prenotazione (quando è stata creata e come), ma non si dispone di tutte le informazioni classiche pronte da visualizzare all'utente. Sapete cosa è successo, ma non è nulla da mostrare. Il codice sorgente della factory viene visualizzato figura 4.

Figura 4 codice sorgente della Factory

public static class Factory
{
  public static Booking New(string name, DateTime when,
    int hour, int mins, int length)
  {
    var created = new NewBookingCreatedEvent(
      Guid.NewGuid(), name.Capitalize(), when,
      hour, mins, length);
    // Tell the aggregate to log the "received" event
    var booking = new Booking();
    booking.RaiseEvent(created);
    return booking;
  }
}

Nessuna proprietà dell'istanza appena creata della classe prenotazione interessata nell'ambiente di produzione, ma una classe di evento viene creata e popolata con i dati effettivi da archiviare nell'istanza, inclusi il nome del cliente e l'ID univoco che tengano traccia in modo permanente la prenotazione in tutto il sistema in lettere maiuscole. L'evento venga passato al metodo RaiseEvent, parte del framework MementoFX, perché è la classe base di tutte le aggregazioni. RaiseEvent aggiunge l'evento a un elenco interno che verrà esaminate nel repository quando l'istanza della funzione di aggregazione "salvataggio". Ho utilizzato il termine "salvataggio" perché questo è solo ciò che accade, ma racchiuderlo tra virgolette per sottolineare che è un tipo diverso di azione di un classico CRUD. L'archivio salva l'evento che una prenotazione è stata creata con i dati specificati. Più precisamente, l'archivio salva tutti gli eventi registrati per un'istanza della funzione di aggregazione durante l'esecuzione di un flusso di lavoro di business, vale a dire un metodo del gestore saga, come illustrato nella figura 5.

Salvataggio di eventi e il salvataggio dello stato
Figura 5 salvataggio di eventi e il salvataggio dello stato

Tuttavia, l'evento di business risultante da un comando di rilevamento non è sufficiente.

Denormalizzazione eventi allo Stack di Query

Se si osserva CRUD attraverso l'obiettivo della conservazione della cronologia dei dati, noterete che la creazione e lettura delle entità non influiscono sulla cronologia, ma lo stesso non si può dire per l'aggiornamento e l'eliminazione. Un archivio di eventi è solo di accodamento e aggiornamenti e le eliminazioni sono solo nuovi eventi correlati alle stesse funzioni di aggregazione. Un elenco di eventi per una determinata funzione di aggregazione, tuttavia, offre tutte le informazioni sulla cronologia tranne lo stato corrente. E lo stato corrente è solo ciò che è necessario per presentare agli utenti.

Ecco dove denormalizers adatta. Un denormalizer è una classe compilata come una raccolta di gestori eventi, come tali salvato nell'archivio di eventi. Si registra un denormalizer con il bus e il bus invia eventi a esso ogni volta che ottiene uno. Il risultato finale è che un denormalizer scritti in ascolto dell'evento di creata di una prenotazione abbia la possibilità di rispondere quando viene attivato uno.

Un denormalizer Ottiene i dati dell'evento e non tutte le informazioni necessarie per l'esecuzione, ad esempio, mantenere un database relazionale per query sincronizzati con gli eventi registrati. Il database relazionale (o un archivio NoSQL o una cache, se è così semplice e più vantaggioso utilizzare) appartiene allo stack di query e l'API non è concesso l'accesso all'elenco di eventi stored. Inoltre, è possibile avere più denormalizers creazione di viste ad hoc degli eventi non elaborati. (Esamineremo in questo aspetto nel mio prossimo articolo.) In figura 1, il calendario da cui un utente sceglie uno slot è popolato da un database relazionale normale che viene mantenuto la sincronizzazione con gli eventi dall'azione di un denormalizer. Vedere figura 6 per il codice della classe denormalizer.

Figura 6 struttura di una classe Denormalizer

public class BookingDenormalizer :
  IHandleMessages<NewBookingCreatedEvent>,
  IHandleMessages<BookingMovedEvent>,
  IHandleMessages<BookingCanceledEvent>
{
  public void Handle(NewBookingCreatedEvent message)
  {
    var item = new BookingSummary()
    {
      DisplayName = message.FullName,
      BookingId = message.BookingId,
      Day = message.When,
      StartHour = message.Hour,
      StartMins = message.Mins,
      NumberOfSlots = message.Length
    };
    using (var context = new MfxbiDatabase())
    {
      context.BookingSummaries.Add(item);
      context.SaveChanges();
    }  }
  ...
}

Al figura 5, denormalizers forniscono un CRUD relazionale per la lettura solo. L'output di denormalizers viene spesso definito "modello lettura". Le entità nel modello di lettura non corrispondono in genere le funzioni di aggregazione consentono di generare eventi in genere dipendono dalle esigenze dell'interfaccia Utente.

Aggiornamenti ed eliminazioni

Si supponga ora che l'utente desidera spostare uno slot prenotato in precedenza. Viene inserito un comando con tutti i dettagli relativi al nuovo slot e un metodo saga si occupa di scrittura di un evento spostati per la prenotazione specificata. La saga deve recuperare l'aggregazione e necessaria nello stato aggiornato. Se denormalizers appena creata una copia relazionale dello stato della funzione di aggregazione (pertanto il modello di lettura quasi coincide con il modello di dominio), è possibile ottenere lo stato aggiornato da lì. In caso contrario, si crea una copia aggiornata della funzione di aggregazione ed eseguire gli eventi registrati tutte su di esso. Al termine della riproduzione, l'aggregazione è stato aggiornato. Riproduzione di eventi non è un'attività che è necessario eseguire direttamente. In MementoFX ottenere un'aggregazione aggiornata con una riga di codice all'interno di un gestore saga:

var booking = Repository.GetById<Booking>(message.BookingId);

Successivamente, si applica all'istanza qualsiasi logica di business che è necessario. La logica di business genera gli eventi e gli eventi vengono mantenuti tramite il repository:

booking.Move(id, day, hour, mins);
Repository.Save(booking);

Se si utilizza il modello di dominio e seguire i principi DDD, il metodo Move contiene tutti i eventi e logica di dominio. In caso contrario, si esegue una funzione con qualsiasi logica di business e generare eventi per il bus direttamente. Associando un altro gestore eventi per il denormalizer, avere la possibilità di aggiornare il modello di lettura.

L'approccio non è diverso per l'annullamento di una prenotazione. L'evento di annullamento di una prenotazione è un evento di business e deve essere rilevato. Questo significa che è possibile disporre di una proprietà globale per eseguire l'eliminazione logico booleana. Nel modello di lettura, tuttavia, l'eliminazione potrebbe essere tranquillamente fisico a seconda se l'applicazione è destinata a una query sul modello di lettura per le prenotazioni annullate. Un interessante effetto collaterale è che è sempre possibile ricompilare il modello di lettura dalla riproduzione di eventi dall'inizio o da un punto di ripristino. Tutto ciò che serve consiste nel creare uno strumento ad hoc che utilizza l'API archivio eventi per leggere gli eventi e chiamare direttamente denormalizers.

Tramite l'API archivio eventi

Esaminare la selezione dell'elenco a discesa in figura 2. L'utente desidera estendere la prenotazione, purché possibili dall'ora di inizio. La logica di business globale deve essere in grado di individuare ed eseguire in modo che deve accedere all'interno delle prenotazioni dello stesso giorno entro l'ora di inizio. Che è semplice in un classico CRUD, ma MementoFX consente di ricercare eventi, nonché:

var createdEvents = EventStore.Find<NewBookingCreatedEvent>(e =>
  e.ToDateTime() >= date).ToList();

Il frammento di codice restituisce un elenco di eventi NewBookingCreated dopo il tempo specificato. Tuttavia, non è garantito che la prenotazione creata è ancora attiva e non è stata spostata in un altro slot. Ciò che occorre ottenere lo stato aggiornato di tali funzioni di aggregazione. L'algoritmo è responsabilità dell'utente. Ad esempio, è possibile escludere dall'elenco di eventi di creazione di prenotazioni non è più attive e quindi ottenere l'ID di prenotazioni rimanenti. Infine, controllare lo slot effettivo rispetto a quello che si desidera estendere evitando la sovrapposizione. Nel codice sorgente di questo articolo, codificato tutta questa logica in un servizio separato (dominio) nello stack di comando.

Conclusioni

Utilizzo di CQRS e approvvigionamento evento non è limitata ai sistemi particolari con requisiti di alto livello di concorrenza, scalabilità e prestazioni. Con l'infrastruttura disponibile che consente di lavorare con aggregazioni e i flussi di lavoro, uno qualsiasi dei sistemi CRUD di oggi può essere riscritto in modo che offre diversi vantaggi. Tali vantaggi includono:

  • Conservazione della cronologia dei dati
  • Un modo più efficace e flessibile di implementazione delle attività di business e la modifica di attività per riflettere le modifiche di business con impegno limitato e al rischio di regressione
  • Poiché gli eventi sono fatti non modificabile, sono semplici da copiare e verrà lettura duplicato e anche i modelli possono essere rigenerati a livello di codice in

Ciò significa che il modello di servizi di calcolo EXCEL (o CQRS/ES che talvolta si fa riferimento) è un potenziale enorme per la scalabilità. Il framework MementoFX qui ancora più efficace, è utile perché semplifica le attività comuni e offre l'astrazione di aggregazione per la programmazione più semplice.

MementoFX inserisce un approccio orientato DDD, ma è possibile utilizzare il modello di servizi di calcolo EXCEL con altri Framework e altri paradigmi di come il paradigma funzionale. È disponibile un ulteriore vantaggio e probabilmente il più rilevanti. Illustro su di esso nel mio prossimo articolo.


Dino Espositoè l'autore di "Microsoft .NET:  Architettura delle applicazioni per l'azienda"(Microsoft Press, 2014) e"Moderne applicazioni Web con ASP.NET"(Microsoft Press, 2016). Technical evangelist per .NET e a piattaforme Android al JetBrains e spesso come relatore a eventi del settore in tutto il mondo, Esposito condivide la sua visione del software in software2cents.wordpress.com e su Twitter: @despos.

Grazie al seguente esperto tecnico Microsoft per la revisione dell'articolo: Andrea Saltarello