Stiamo sviluppando un contesto utilizzando l'approccio CQRS. Siamo finiti con i gestori di comandi che emettevano eventi. Sembra non essere una buona idea per noi. Tuttavia non possiamo trovare alcun approccio alternativo. Abbiamo difficoltà con due particolari gruppi di scenari:
- Creazione di una nuova aggregazione.
La creazione di un aggregato può avere esito positivo. In caso di successo tutto è semplice - aggregato mantiene l'evento AggregateCreated (compilato all'interno del suo costruttore). Ma in caso di errore, l'aggregato non può emettere / pubblicare nulla perché non esiste nemmeno. In tal caso, il nostro gestore comandi emtis CreatingAggregateFailed che riusciamo a individuare come perdita del dominio.
- Risorsa non trovata
Secondo scenario per quanto riguarda la non creazione di particolari risorse. Ad esempio, rimuovere la risorsa non esistente. Nella nostra implementazione Repository :: find () solleva l'eccezione NotFound. L'excetpinon a sua volta viene catturato in un gestore che emette l'evento AggregateNotFound.
Sulla base di questi eventi creiamo gestori di processi e risposte. Ma sembra un po 'strano che gli eventi di dominio (applicazione?) Vengano emessi al di fuori degli aggregati. Anche se questi eventi ingombranti si applicano alla non esistenza di tale aggregato.
Consentitemi di considerare lo scenario più semplice. Invio il comando AddTeamMember ($ teamGuid, $ userGuid, $ role). Se team, utente e ruolo esistono ma il comando viola qualsiasi aggregato invariante aggregato può registrare l'evento MemberRejected - per quanto tutto sia buono. Ma né l'utente, il team o il ruolo dato potrebbero non esistere nemmeno. Quindi non esiste un aggregato in grado di registrare un evento. Ho bisogno di un feedback su questo fallimento per intraprendere azioni appropriate (sia in Process Manager o semplicemente informare il mio emittente di comandi). Considero MembershipRequest aggregato come destinatario del comando. Quindi ho sempre aggregato valido per pubblicare eventi e gli eventi sono significativi all'interno dell'agenda. Ma questo introduce ulteriore compelxity. Ho bisogno di un aggregato intermedio per gestire "risorsa non trovata" per ogni comando possibile.
Ho trovato una nuova idea. Lo illustrerò con esempi di codice.
Versione del gestore precedente
/**
* @param CreateChannel $command
*/
protected function handleCreate(CreateChannel $command): void
{
try {
$channel = new Channel($command->getGuid(), $command->getSymbol(), $command->getLangCodes());
$this->channelRepository->save($channel);
} catch (InvalidData $e) {
$this->eventBus->publish($channel, new ChannelCreationFailed($command->getGuid()));
}
}
Nuova idea
/**
* @param CreateChannel $command
*/
protected function handleCreate(CreateChannel $command): void
{
try {
$channel = new Channel($command->getGuid());
$channel->create($command->getSymbol(), $command->getLangCodes()))
$this->channelRepository->save($channel);
} catch (InvalidChannelData $e) {
// ?
} finally {
$this->eventBus->publish($channel, new ChannelCreationFailed($command->getGuid()));
}
}
Ma questo ha alcuni ovvi inconvenienti. Innanzitutto, il livello di servizio influisce sulla progettazione aggregata. Reinventa anche la creazione del concetto di linguaggio oggetto. Esegue il controllo con qualsiasi altro metodo se il aggregato è in stato "creato". Forse è opportuno distinguere tra creazione di oggetti e creazione di aggregati? Questo approccio non presuppone eccezioni sollevate dal costruttore. Il costruttore otterrebbe sempre solo guida valida (garantita dal comando) e nient'altro.
Avere sempre aggregato con guid risolve anche aggregati di riferimento non esistenti. Usando il doppio invio è possibile generare eccezioni "non trovate" dall'aggregato sorgente
$team->addMember($userGuid, $usersRepository);
Ma questo aggregato di makse è api brutto rispetto a
$team->addMember($user);