Aggregazioni complesse di ricreazione da un'origine di persistenza

6

Sto costruendo un'applicazione web con C # e ho una radice aggregata che ha diverse entità e oggetti valore. Poi ho un oggetto repository che mantiene l'aggregazione nel database (sto usando ADO.NET come il mio livello di persistenza dell'infrastruttura).

L'aggregato ha anche diversi eventi collegati ai suoi metodi, in modo che altre azioni possano essere eseguite al di fuori dell'aggregato (ad esempio, il metodo OpenRequisition di un aggregato Cliente attiva l'evento RequisitionCreatedEvent, e un gestore quindi accoda un'azione per aggiornare le statistiche per quanto riguarda il cliente.

Questo è un esempio semplificato.

class Customer {
    private Guid _id;
    private string _shortName;
    private string _legalName;
    private TaxCode _taxCode;
    private ContactInfo _primaryContact;
    private List<Requisitions> _requisitions; //Requisition is an entity
    private IDomainEvents _events;
    // and so many other fields

    public Requisition OpenRequisition(RequisitionDefinition definition, User requestedBy) 
    {
        Requisition req = ... // create the new requisition somehow
        _requisitions.Add(req);
        _events.Invoke(new RequisitionCreatedEvent(this, req));

        return req;
    }

    // and so on
}

Il problema che sto affrontando non è su come persistere, ma su come ricostruire l'oggetto aggregato dal database. Molti dei campi del cliente hanno setter, come ShortName o LegalName, o anche TaxCode. Altri no, come il campo _requisiti. Non ho un modo diretto per ricostruire le richieste esistenti. Se espongo le richieste in qualche modo (come proprietà List), allora potrei fare qualcosa di simile nel mio repository:

var requisitions = ... // get requisitions for this customer
var customer = new Customer(id);
customer.Requisitions.AddRange(requisitions);

Ma poi altrove si potrebbe fare:

customer.Requisitions.Add(new Requisition());

aggirando la logica e le convalide impostate nel metodo OpenRequisition. D'altra parte, se invoco il metodo OpenRequisition dal mio repository, il gestore di eventi verrà chiamato ogni volta, e non dovrebbe, perché sto ricostruendo l'oggetto dal database, non aprendo effettivamente una nuova richiesta.

Quello che ho finora, è quello di creare un costruttore e quindi passare tutti questi parametri, qualcosa del genere:

public Customer(IDomainEvents events, Guid id, IEnumerable<Requisition> requisitions, ... and so on) 
{
    ... // perform validations
    _events = events;
    _id = id;
    _requisitions = new List<Requisition>(requisitions);
    ... // and so on
}

Tuttavia, questo aggregato ha molti altri nodi figli, quindi il costruttore sta crescendo e crescendo, e non sembra giusto. A un certo punto ho cercato di creare una classe factory, ma ho ancora bisogno del grande costruttore, o qualcosa come avere setter interni anche per _events e _requisitions, e non sembra ancora giusto.

Quindi, la mia domanda è: come ricostruire complesse radici aggregate, come passare tutti i parametri necessari all'aggregato per continuare a funzionare senza che l'oggetto esegua il lavoro già fatto (come l'attivazione del gestore di eventi). Altrimenti, quali opzioni considereresti su scenari come questo?

    
posta Fernando Gómez 15.05.2016 - 06:32
fonte

2 risposte

1

Questo è un problema comune quando si implementano gli aggregati. Di solito dà origine allo stato di separazione degli aggregati su un altro oggetto. Quell'oggetto può essere un puro bucket di dati con getter / setter pubblici per comodità. I chiamanti esterni potrebbero essere a conoscenza della classe di stato dell'aggregato, ma rimane privata all'aggregato (come un campo privato, ad esempio). Il repository potrebbe caricare direttamente lo stato dell'aggregato, che può essere passato come un singolo oggetto al costruttore dell'aggregato.

Avresti:

public CustomerState
{
    public Guid Id { get; set; }
    public string ShortName { get; set; }
    public string LegalName { get; set; }
    public TaxCode TaxCode { get; set; }
    public ContactInfo PrimaryContact { get; set; }
    public List<Requisitions> Requisitions { get; set; }
}

public Customer
{
    private readonly CustomerState _state;
    private readonly IDomainEvents _events;

    public Customer(CustomerState state, IDomainEvents events) 
    {
        _state = state;
        _events = events;
    }
}

...

public Requisition OpenRequisition(RequisitionDefinition definition, User requestedBy) 
{
    Requisition req = ... // create the new requisition somehow
    _state.Requisitions.Add(req);
    _events.Invoke(new RequisitionCreatedEvent(this, req));

    return req;
}

Si noti che ho inserito IDomainEvents nell'aggregazione anziché nella classe di stato, perché ha un comportamento (probabilmente dispatching). Per il tuo oggetto stato, vuoi solo dati puri.

In modo molto superficiale, questo somiglia in modo organizzativo ad un modello di dominio anemico perché lo stato è separato, e l'aggregato essenzialmente contiene solo metodi, simili a un modello di facciata. Ma la differenza è che un dominio anemico consente all'utente di manipolare direttamente lo stato ed eseguire uno Script di transazione sulle loro manipolazioni. Ma con questo, l'utente non è autorizzato a manipolare direttamente lo stato ... manteniamo il controllo delle modifiche dello stato convalidate attraverso i metodi del dominio, ma aggiungiamo il caricamento (e il salvataggio) più facilmente.

Ora, separare lo stato in questo modo presenta le sue sfide. Di solito la denominazione della confusione è inizialmente. Probabilmente non vuoi dare un nome alla tua tabella di dati CustomerState (e probabilmente è già nominata Customer). Ma nominando la tabella il cliente genererà confusione quando l'aggregato con lo stesso nome è strutturalmente dissimile. Se sei pronto per una riorganizzazione, potresti prendere in considerazione la denominazione delle vendite o degli ordini aggregati o simili (in base all'area in cui si trova) e mantenere lo stato del cliente denominato Cliente. Potresti scoprire che ciò porta a una migliore chiarezza organizzativa per i tuoi casi d'uso comunque.

    
risposta data 16.05.2016 - 05:24
fonte
0

Vorrei creare una classe per salvare oggetti valore e un'altra per caricare ogni oggetto valore. Gli oggetti valore qui significano solo le primitive. Inoltre, convalidare e provare l'immutabilità, quindi non è necessario il blocco del thread (i dati non possono essere corretti se immutabili). Il modello di build può aiutare.

Quindi per gli agretes di nome Foo fai una classe Foo persistente. Contiene tutti gli oggetti valore in Foo come finali e ha un builder. La classe Foo ha due metodi statici, Persist e Persist. Sono solo metodi di conversione ma non possono fallire.

Ora, aggiungi solo salva e carica la classe per quello. Dovrebbe essere solo una delega a quel punto e salvare e caricare.

Un ultimo suggerimento, salva e carica prende i parametri a volte ma non ha alcuno stato con esso.

    
risposta data 17.05.2016 - 13:12
fonte