CQRS, dove esattamente è la logica di business che coordina tra gli aggregati?

0

Dove esattamente mettere la logica aziendale. Mentre tutti hanno detto di metterlo nel complesso.

Sì, ma cosa succede se la logica aziendale ha bisogno di dati di multipli aggregati?

O se la logica aziendale deve aggiornare più aggregati.

È accettabile essere in command handlers o aver bisogno di essere in saga? E perché?

Il mio primo approccio

  • Camera

    • ID
    • Capacità
    • IsDestroyed
  • UserRoomSession

    • ID
    • RoomID
    • UserID
    • Stato // Inattivo, Away, Leaved

Ho il comando CreateUserRoomSession che questo comando usa per unire la stanza

Nel gestore comandi

  • Crea una UserRoomSession ()
  • Convalida se la stanza è piena o no. ottenendo tutto l'utente
  • Convalidare la stanza non viene distrutto.
  • Convalida l'utente è già in una stanza.
  • Se non valido, chiama il metodo UserRoomSessionJoinRoomFail (roomID)
  • Se valido, chiama il metodo JoinRoom (roomID)
  • Salva in ES

Ad esempio, dobbiamo fare affidamento su Room per creare UserRoomSession.

Ho provato a pensare in approcci diversi a tutti loro non è possibile terminare la convalida della logica di business all'interno dello stesso aggregato.

    
posta b.ben 11.08.2018 - 13:02
fonte

2 risposte

4

Se è necessario aggiornare più aggregati, i limiti di aggregazione potrebbero essere errati.

Naturalmente, ci possono essere degli scenari in cui è necessario aggiornare più aggregati. Una saga è il modo tipico per affrontare questo. Nota, tuttavia, che stai lavorando oltre i limiti di consistenza qui.

Se hai la necessità di aggiornare più aggregati atomicamente, è molto probabile che devi ripensare ai tuoi limiti.

La tua applicazione di chat è un esempio perfetto per mostrare come questo può essere molto facile (se possibile anche contro-intuitivo a volte), oltre che abbastanza coinvolto.

Confini

Supponiamo che tu abbia un semplice requisito: un utente non può essere nella stessa stanza due volte.

Un'opzione è di rendere Room il tuo aggragate, al quale puoi applicare comandi come JoinUser , GrantPrivileges , PostMessage , ecc. Il Room sarà responsabile per far rispettare l'invariant.

Un'altra opzione è avere un Chatter aggregato (essenzialmente l'utente). Di nuovo, hai comandi come JoinRoom e Chatter applica l'invariante.

Il layout che usi dipende da quali altri invarianti vuoi applicare.

  • L'approccio Room ti consente di specificare le restrizioni tra le azioni nella stessa stanza (ad esempio non più di 20 utenti possono trovarsi in una stanza).

  • L'approccio Chatter ti consente di specificare le restrizioni del tuo utente, possibilmente per quanto riguarda più stanze (ad esempio, un utente può essere solo in una stanza alla volta).

Sagas

Dove diventa complicato è se vuoi una combinazione di queste restrizioni. Se non riesci a trovare un modo per suddividere i tuoi aggregati di conseguenza (senza creare una sorta di singleton-super-aggregato che comprenda l'intera applicazione), potresti riuscire a risolvere il problema con una saga.

In questo scenario, l'aggiunta del concetto di Session potrebbe essere la mossa giusta. Dopo aver creato la sessione, la tua saga prova a registrarla con User e Room aggregati (e uccide la sessione se uno dei due fallisce).

Vorrei ancora passare un po 'di tempo a cercare di capire se c'è un altro (forse non ovvio) modo di suddividere prima gli aggregati. Ricorda che non devono rispecchiare i tuoi modelli di lettura: se non hai vincoli di coerenza che si applicano a un'intera stanza, potresti non aver bisogno di un Room di aggregato sul lato comando.

    
risposta data 11.08.2018 - 13:11
fonte
1

Quando una relazione molti-a-molti si presenta ( [Room] <- [UserRoomSession] -> [User] ), l'approccio più semplice è quello di scegliere un singolo lato della relazione per "possedere" i dati. In questo caso, stiamo scegliendo Room (che è probabilmente la scelta corretta). L'idea di un aggregato è che tutti i dati entro del suo confine di consistenza sono trattati come uno. Considera quanto segue (sì sì PHP ... è stato quello che il mio editor aveva aperto oggi):

class JoinRoomCommand
{
    public $roomId;

    public $userId;
}

class JoinRoomCommandHandler
{
    private $roomRepository;

    private $sessionRepository;

    public function handle( JoinRoomCommand $cmd ) : void
    {
        $room = $this->roomRepository->find( $cmd->roomId );

        // how you "find or create" is up to you
        try {
            $session = $this->sessionRepository->findByUserId( $cmd->userId );
        } catch( \Exception $e ) { // SessionNotFound
            $session = $room->createSessionFor( $cmd->userId );
        }

        $room->startSession( $session );

        $this->roomRepository->save( $room );
    }
}

// Aggregate
class Room
{
    private $id;

    private $capacity;

    private $isDestroyed;

    private $sessions = [];

    public function createSessionFor( int $userId ) : RoomSession
    {
        return new RoomSession( $this->roomId, $userId );
    }

    public function startSession( RoomSession $session ) : void
    {   
        // room must not be destroyed
        if( $this->isDestroyed )
            throw new \Exception("Room does not exist");

        // room cannot be full
        if( false === ($this->capacity < count($this->sessions)) ) 
            throw new \Exception("Room is full");

        // session cannot already be in a romm
        if( NULL !== $session->roomId )
            throw new \Exception("Session is in another room");

        // two sessions cannot exist for same user
        foreach( $this->sessions as $currentSession ) {
            if( $currentSession->userId === $session->userId ) 
                throw new \Exception("Session already exists");
        }

        // add session to private collection
        $this->sessions[] = $session;
    }
}

// Value Object (pretend public "get", private "set")
class RoomSession
{
    public $roomId;

    public $userId;

    public $status;

    public function __construct( ?int $roomId, ?int $userId, string $status = 'ACTIVE' )
    {
        $this->roomId = $roomId;
        $this->userId = $userId;
        $this->status = $status;
    }
}

Nell'esempio precedente, tutta la convalida si trova nell'aggregazione Room . In questo caso, direi che separare l'idea di "creare" RoomSession e "unire" un Room probabilmente non è fedele al tuo dominio / UL. Mi sembra che RoomSessions sia solo un dettaglio tecnico necessario per facilitare il caso d'uso di "User joins room".

    
risposta data 13.08.2018 - 23:18
fonte

Leggi altre domande sui tag