Applicare DDD, avere più aggregati che rappresentano lo stesso concetto da una vista diversa è una buona idea?

1

Stiamo completamente rimodellando un sistema presso la società per la quale sto attualmente lavorando. Stiamo applicando il DDD e per la prima volta ho effettivamente qualcuno nel mio team che ha anche qualche precedente esperienza con DDD (yay!)

Questo nuovo sistema è estremamente incentrato sull'utente, cioè quasi tutte le operazioni all'interno del sistema provengono dall'utente finale. Alcune di queste operazioni potrebbero essere:

  1. Crea un account . Un utente non pagante è limitato a un account attivo, i membri pro possono avere tutti gli account che desiderano.
  2. Aggiungi una Transazione a un account , ma solo se un utente ha accesso a detto Account .
  3. Riordina Divisione s in un account , ma solo se un utente ha accesso a detto Account .

Ci sono molte di queste regole, tutte puntano a un singolo utente e questo è il punto in cui io e il mio collega siamo entrati in discussione.

La mia opinione è che gli aggregati dovrebbero essere tanto piccoli quanto necessari, quanto coesivi, questo rende più facile la loro verifica e comprensione. Per esempio. se modellino la prima regola, creerei un semplice aggregato, che potrebbe apparire come questo:

class UserWithActiveAccounts {

    private UserId id;
    private NonNegativeNumber countOfActiveAccounts;
    private MembershipType membershipType;

    public Account createNewAccount(AccountId accountId, NonEmptyString accountName) {
        if (
            MembershipType.FREE.equals(membershipType) &&
            countOfActiveAccounts.GT(NonNegativeNumber.fromValue(1))
        ) {
            throw AccountLimitExceeded.forFreeUserTooManyAccounts(id);
        }

        return Account.withOwner(accountId, accountName, id);
    }
}

Questo aggregato contiene solo il conteggio degli account attualmente attivi e il tipo di iscrizione dell'utente, perché è questo che interessa la regola aziendale quando si crea un nuovo account.

Quindi, se dovessi modellare gli altri 2 casi utente, avrei bisogno di un altro aggregato, come UserWithAccessibleAccounts , che contenga internamente un List di AccountId valori, che potrei ripetere sull'aggiunta di un < strong> Transazione con uno specifico AccountId da controllare, indipendentemente dal fatto che l'operazione sia consentita o meno.

class UserWithAccessibleAccounts {

    private UserId id;
    private List<AccountId> accessibleActiveAccounts;

    public Transaction addTransaction(
        TransactionId transactionId,
        AccountId toAccountId,
        Number value
    ) {
        if (hasAccessToAccount(toAccountId)) {
            throw CannotAddTransaction.noAccessToAccount(id, toAccountId);
        }

        return Transaction.byUserInAccount(transactionId, value, id, toAccountId);
    }

    private bool hasAccessToAccount(AccountId accountId) {
        return accessibleActiveAccounts.contains(accountId);
    }
}

Ovviamente, con il mio approccio avrai un sacco di piccoli aggregati ciascuno responsabile di una piccola regola aziendale.

Ciò che il mio collega vorrebbe è avere una classe grande, come User , che conterrebbe tutte le operazioni. Che sono contro Credo che porterebbe ad una classe disordinata, dove sarebbe molto più difficile rintracciare i bug. Inoltre, con il crescere di questa classe, ogni volta che si desidera eseguire una qualsiasi delle operazioni sull'utente, è necessario caricare molte proprietà che non sono correlate all'operazione che l'utente sta tentando di eseguire (come il caricamento i valori di List di AccountId quando è necessario un semplice conteggio di essi, in questo esempio molto semplificato).

Stiamo essenzialmente cercando di risolvere il problema di:

  • o doversi inventare un sacco di nomi di classe per tutti quei piccoli aggregati,
  • affronta il problema della scalabilità e una classe gigantesca grazie al prelievo di tutti i dati per ciascuna operazione, indipendentemente dalla loro entità.

Sono propenso all'approccio dei piccoli aggregati, ma forse non lo guardo dall'angolo corretto e c'è qualcosa nell'approccio che il mio collega sta suggerendo che semplicemente non sto vedendo.

    
posta Andy 13.02.2018 - 09:54
fonte

3 risposte

2

Prima di tutto. Se si desidera seguire DDD, una delle regole per la creazione di aggregati è che le transazioni non devono superare i limiti di aggregazione. Ma nel tuo approccio se un UserWithActiveAccount ha un nuovo account accessibile, dovrai cancellare UserWithActiveAccount e creare un nuovo UserWithAccessibleAccount nella stessa transazione. Che sta infrangendo una delle regole di progettazione degli aggregati in DDD.

Posso capire perché potresti pensare che sia meglio creare entità utente diverse. Ma ricorda che ora stai creando entità che non esistono nemmeno nel tuo dominio. E questo può facilmente andare storto a un certo punto. Si può facilmente finire con un gran numero di classi utente che non esistono nel linguaggio ubiquo e quindi la quantità di carico cognitivo (le informazioni necessarie per comprendere il codice) sarà enorme.

Pensa di avere più stati possibili per l'utente (utente con account scaduto, utente con account cancellato ...). Dovrai creare una nuova classe per ognuno e poi che dire di un utente che può essere in due stati contemporaneamente? Creeresti una nuova classe per ogni combinazione?

DDD significa progettare un modello che riflette il dominio (business) e nel tuo dominio c'è un utente che può avere stati diversi ma è sempre lo stesso utente. Quindi suggerirei di avere lo stesso nel codice. Ciò significa che un'entità utente può avere stati diversi.

Ora discutiamo su cosa può andare storto con questo approccio:

aggregates should be as small as necessary, as cohesive, this makes their testing and understanding easier.

Questo non è necessariamente vero. Mantenere le funzioni di piccole dimensioni e seguire alcune delle buone pratiche di codifica manterrà la classe semplice e facile da capire. La dimensione della classe non significa necessariamente che sia complicata.

Also, as this class would grow, each time you would want to run any of the operations on the user, you would need to load a lot of properties which are unrelated to the operation that the user is currently trying to do (such as loading the List of AccountId values when a simple count of them is necessary, in this very simplified example).

Qual è il problema con questo? Disponi di memoria limitata sulla macchina in cui verrà eseguito questo codice? questo si tradurrà in un notevole calo delle prestazioni? In caso contrario, non c'è motivo di fare qualche ottimizzazione prematura per un problema che probabilmente non dovrai mai affrontare. Se tutti i dati sono ben incapsulati nella tua classe, non ci sono problemi se contiene molte proprietà.

    
risposta data 13.02.2018 - 14:22
fonte
1

Non sono un fan del tuo approccio aggregato più piccolo. Stai esagerando nella valutazione a scapito di un ampio overhead di classi leggermente diverse che si ripetono molto. Tuttavia, penso che entrambi siete troppo concentrati sull'utente e non stanno arricchendo correttamente altri oggetti di dominio.

In base alle tue tre regole, l'utente dovrebbe essere una classe astratta con implementazioni gratuite e premium, un unico metodo per creare un account. Quindi hai un oggetto account che contiene il suo utente valido e ha due metodi, uno per aggiungere transazioni e uno per le divisioni di riordino (o un metodo di aggiornamento più generale).

La tua preoccupazione di dover caricare troppi dati con ogni richiesta è un segno che la tua astrazione non è corretta e che deve essere ottimizzata o che non stai sfruttando l'esecuzione posticipata / il caricamento lento. Inoltre, la necessità di trovare molti nomi simili è un segno che non stai utilizzando un'astrazione utile.

esempio di account di base:

class Account{
  private int AccountId;
  private user ValidUser;
  private TransactionCollection myTransactions;

  public AddTransaction(transaction, User){
      if(validUser==User)
          myTransactions.Add(transaction);
      else
          throw noAccessException;
  }
}
    
risposta data 13.02.2018 - 15:45
fonte
0

Dal titolo della tua domanda, deduco che sì, va bene avere aggregati separati per il concetto da diversi punti di vista, dal momento che sembra che tu abbia diversi contesti limitati. L'aggregato Account quindi apparentemente significa qualcosa dal punto di vista X e di nuovo qualcosa di diverso dal punto di vista Y.

Tuttavia, per me non sembra che tu abbia a che fare con diversi contesti limitati basati sui frammenti di codice che hai fornito. È più come se avessi creato degli aggregati che descrivono due diversi stati di un Account di aggregato.

Prendendo questa posizione, direi quello che @Mohamed Bouallegue ha commentato sopra sembra un bel messaggio da portare a casa cosa potresti fare invece.

    
risposta data 13.02.2018 - 19:13
fonte

Leggi altre domande sui tag