Le entità interne in un aggregato rispondono direttamente agli eventi del dominio?

5

Sono abbastanza nuovo nell'implementazione di CQRS e Event Sourcing, mentre applichiamo le regole del design guidato dal dominio. Diciamo che ho un Order aggregato e al suo interno c'è un elenco di OrderLine entità. Sto usando questo modello nel contesto di un sistema basato su eventi (con CQRS) e quindi i cambiamenti di stato sono principalmente guidati dagli eventi. Le modifiche all'aggregato devono essere eseguite attraverso Order poiché funge da radice aggregata. Quando, ad esempio, viene generato un evento come ItemAdded , il rispettivo aggregato Order verrà chiamato per applicarlo a se stesso, qualcosa come order.apply(itemAdded) . Ciò attiverà quindi la creazione di un nuovo oggetto OrderLine interno per rappresentare l'elemento appena aggiunto nell'ordine. In termini di implementazione, ha senso che il nuovo oggetto OrderLine applichi direttamente l'evento a se stesso? Oppure, dovrei lasciarlo al Order aggregato per creare l'oggetto OrderLine attraverso un costruttore?

Quale è più appropriato?

void apply(ItemAdded itemAdded) {
    OrderLine orderLine = new OrderLine();
    oderLine.apply(itemAdded);
    orderLines.add(orderLine);
}

o

void apply(ItemAdded itemAdded) {
    OrderLine orderLine = new OrderLine(
        itemAdded.getId(), 
        itemAdded.getProductId(), 
        itemAdded.getQuantity(), 
        itemAdded.getPrice()
    );
    orderLines.add(orderLine);
}

Per ora, penso che il primo approccio sia più conciso e più facile da capire. Ci sono eventi i cui campi possono essere molto lunghi e avere l'entità interna stessa che si occupa della costruzione rende il codice più pulito. Il problema, tuttavia, penso sia che, fornendo un metodo apply nel OrderLine , lo sto aprendo per eventuali modifiche al di fuori del contesto di Order . Se fornisco un accessor, diciamo, getOrderLines() , in Order , anche se faccio in modo che la collezione / lista stessa non sia modificabile, gli oggetti contenuti saranno comunque mutabili. Un modo per prevenire ciò è restituire i cloni, ma ciò può risultare un po 'complicato da implementare per entità nidificate abbastanza complesse.

Per risolvere questo problema, quello che sto pensando è mantenere il metodo apply in OrderLine class, ma limitarne la visibilità. L'approccio che mi dà maggiore flessibilità in questo senso è di rendere OrderLine una classe nidificata sotto Order class. Ciò significa che posso mantenere il metodo apply privato ma ancora accessibile nel contesto della classe Order . Tuttavia, questo potrebbe significare una classe molto lunga di Order , specialmente se ho bisogno di aggiungere altre entità nidificate in futuro.

In alternativa, posso limitare l'accesso al metodo apply a livello di pacchetto, il che significa che devo fare qualche ristrutturazione per limitare tutte le classi di dominio in un unico pacchetto.

Qualche consiglio in merito?

    
posta Psycho Punch 29.06.2017 - 14:40
fonte

4 risposte

2

Se prendiamo la tua domanda esclusivamente al valore nominale, la risposta è semplicemente passare l'oggetto itemAdded nel costruttore di OrderLine invece di fornire un metodo apply() . In questo modo, l'unica differenza tra i tuoi due esempi è se stai passando l'intero oggetto in un singolo parametro o le sue proprietà come parametri multipli.

Tuttavia, se itemAdded è in realtà un evento e non un articolo, devi rinominarlo come qualcosa di simile a itemAddedEvent . Potresti anche considerare di avere una classe Item che rappresenta l'oggetto e costruirla dall'evento. Quindi potresti passare un oggetto in OrderLine. Potrebbe rendere il tuo dominio più in linea con il modo in cui un esperto o cliente commerciale parlerebbe del processo.

    
risposta data 29.06.2017 - 19:15
fonte
1

Which one is more appropriate?

Sono quasi sicuro che il primo approccio sia quello che sarà più sano a lungo termine.

Riddle: se la comprensione delle imprese di OrderLine dovesse cambiare durante il ciclo di vita del modello di dominio, a quale progetto sarà più facile lavorare?

Nel primo caso, estendi il messaggio ItemAdded per includere i nuovi campi di cui hai bisogno e aggiorni l'elemento OrderLine per leggere quei nuovi campi e fare qualcosa di interessante, e il gioco è fatto.

Nel secondo caso, devi ancora estendere il messaggio ItemAdded, e devi ancora modificare OrderLine , e tu anche devi modificare Order . Non sembra altrettanto buono.

Parnas ha scritto in ... Decomposizione dei sistemi nei moduli

We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.

In questo esempio, OrderLine è il modulo che nasconde la decisione "come consumare un messaggio ItemAdded?"

I'm opening it up for possible mutation outside the context of the Order. If I provide an accessor, say, getOrderLines(), in Order, even if I make the collection/list itself unmodifiable, the objects contained will still be mutable. One way to prevent this is to return clones, but that can get a little cumbersome to implement for fairly complex nested entities.

Separazione delle query di comando e Interfacce di ruolo possono aiutarti qui. L'idea approssimativa è di pensare a OrderItem come a diversi ruoli che può giocare. Quindi l'interfaccia che implementa OrderItem sarebbe divisa in sezioni diverse, una delle quali sarebbe IApplyEvents o anche IHandle<ItemAdded> e si progetta la logica in modo che i ruoli che consentono all'elemento di essere modificati siano esposti solo nell'uso casi in cui li vuoi.

Quindi, se disponi di un accessore come getOrderLines , restituisce una raccolta di RoleInterfaces composti interamente da query.

interface OrderLineDetails {
    ProductDetails getProductDetails();
}

interface IHandle<Event> {
    void apply(Event event);
}

class OrderLine implements OrderLineDetails, IHandle<ItemAdded> {
    // ...
}

I nomi possono diventare disordinati; tutti vogliono che "OrderLine" significhi cosa significhi in quel contesto specifico. DependencyInversion può aiutarti qui.

One way to prevent this is to return clones, but that can get a little cumbersome to implement for fairly complex nested entities.

A volte le entità nidificate piuttosto complesse sono valori nidificati molto complessi, che possono semplificare le cose.

State next = current.apply(change)

è abbastanza ragionevole. In astratto, comunque. Fai attenzione a riconoscere quale complessità sta venendo dall'idea, e cosa dagli strumenti che stai usando; potrebbe essere un suggerimento che tu abbia bisogno di qualcosa che soddisfi meglio il problema .

    
risposta data 30.06.2017 - 14:11
fonte
0

Hai una cosa sbagliata nel tuo progetto. Sembra che tu stia modellando secondo un database relazionale piuttosto che il tuo dominio. Perché lo penso? Perché hai una radice aggregata Order , che contiene internamente un OrderLine contenente Item s. Hai modellato una relazione M: N utilizzando la classe OrderLine .

Il fatto è che quando si usa DDD non si modella per abbinare i dati, quindi la struttura non dovrebbe riflettere il database relazionale.

Chiedi a te stesso quanto segue:

  1. A cosa interessa il business?
  2. Ti interessa il business per avere un'entità aggiuntiva (molto probabilmente debole) chiamata OrderLine per mappare gli articoli a un ordine?
  3. A livello aziendale è necessario aggiungere / rimuovere record dalla tabella OrderLine mappatura?

Le risposte alle domande 2 e 3 sono no. I tuoi manager aziendali non potrebbero importare di meno di come modelli la relazione, ma ciò di cui si preoccuperanno quando si tratta delle statistiche è il seguente:

  • Quanti oggetti ha in media un ordine che viene completamente elaborato?
  • Quante volte le persone aggiungono un elemento a un ordine e rimuovono l'elemento dall'ordine ?
  • Qual è il costo medio di un ordine che viene completamente elaborato?
  • Quanti ordine s hanno in media un utente ?
  • Quante volte un elemento è stato rimosso da un ordine solo per essere riaggiunto?

Queste sono tutte domande valide che potrebbero venire dal reparto commerciale in futuro. Ho evidenziato gli aggregati interessati in ciascuna di queste domande. Come puoi vedere, non esiste affatto OrderLine , perché quell'entità modella solo la relazione. La tua azienda si preoccupa dei tuoi articoli se ordini e, quindi, dovresti.

Con questo in mente, il OrderLine dovrebbe essere semplicemente un semplice List / Vector / Set contenente oggetti / entità valore molto semplici come questo:

class OrderLineItem extends Entity {
    private ItemId itemId;
    private int quantity = 1;

    public bool Equals(Entity other) {
        return
          other instanceof OrderLineItem &&
          itemId.Equals(other.itemId);
    }
}

Un elemento è referenziato solo da un id, perché NON DEVI tenere altre aggregazioni direttamente.

Come puoi vedere con questo approccio, non devi più preoccuparti se OrderLine deve accettare direttamente un evento, perché l'entità è semplicemente sparita.

In un sistema basato su eventi la tabella delle relazioni M: N non esiste affatto, perché il tuo archivio dati sono eventi, non relazioni.

Quando si creano modelli di lettura dai propri eventi per il lato query, i modelli di lettura dovrebbero essere il più piatti possibile. Idealmente dovresti essere in grado di fare SELECT * FROM read_model WHERE ... e nient'altro. Nessun join. Quindi è abbastanza probabile che tu non abbia la relazione M: N neanche sul lato di lettura. Per essere onesti, perché i modelli di lettura sono comunque documenti, ha molto più senso usare un database diverso e più appropriato come MongoDB.

    
risposta data 30.06.2017 - 08:14
fonte
0

nessuna regola lì. Dipende da così tanto: - Il tuo modello di persistenza - Se stai applicando CQRS - la complessità & gli invarianti delle tue entità.

Di solito mi affido a un'astrazione per il mio AR & ES che esegue in cascata eventi naturali a qualsiasi interfaccia ES nello stesso AR.

Ma la maggior parte delle mie radici aggregate ES sono piccole e abbastanza "semplici", cerco di progettarle solo con VO. Di solito, il tuo AR non deve mutare un altro stato di entità.

Nel tuo esempio, non vorrei ES una OrderLine. A meno che tu non abbia delle regole piuttosto complesse che ti preoccupano applicare quando OrderLine cambia stato. Considerando il tuo attuale comportamento aziendale: aggiungi un elemento. Disegnerei OrderLine come VO.

Dato che stai eseguendo il tuo ordine, non riesco a trovare alcun utilizzo per ES le linee. Sarai sempre in grado di monitorare il classico ciclo di vita dell'e-commerce.

L'ES a cascata indica che il tuo aggregato è molto complesso, interroga il tuo progetto prima di andare lì.

    
risposta data 14.10.2018 - 19:33
fonte

Leggi altre domande sui tag