DDD CQRS - autorizzazione per query e per comando


15

Sommario

L'autorizzazione in CQRS / DDD dovrebbe essere implementata per comando / query o no?

Sto sviluppando per la prima volta un'applicazione online usando più o meno rigorosamente il modello DDD CQRS. Mi sono imbattuto in qualche problema, che non riesco davvero a capire.

L'applicazione che sto creando è un'applicazione di contabilità che consente alle persone di creare registri, oltre a consentire ad altre persone di visualizzarli / modificarli / eliminarli, come i dipendenti. Il creatore di un libro mastro dovrebbe essere in grado di modificare i diritti di accesso del libro mastro che ha creato. Potrebbe persino cambiare la proprietà. Il dominio ha due aggregati TLedger e TUser .

Ho letto molti post con la parola chiave DDD / CQRS riguardanti sicurezza, autorizzazione, ecc. La maggior parte di essi ha affermato che l'autorizzazione era un sottodominio generico , a meno che non si stesse creando un'applicazione di sicurezza.

In questo caso, il dominio principale è certamente un dominio contabile interessato alle transazioni, al bilanciamento e ai conti. Ma è anche richiesta la funzionalità di essere in grado di gestire un accesso fine ai registri. Mi chiedo come progettarlo in termini DDD / CQRS.

È spiegato nei tutorial DDD in tutto il luogo che i comandi fanno parte del linguaggio onnipresente. Sono significativi. Sono azioni concrete che rappresentano la "cosa reale".

Poiché tutti quei comandi e query sono azioni effettive che gli utenti eseguiranno nella "vita reale", l'implementazione dell'autorizzazione dovrebbe essere abbinata a tutti questi "comandi" e "query"? Un utente avrebbe l'autorizzazione per eseguire TLedger.addTransaction () ma non TLedger.removeTransaction () per esempio. In alternativa, un utente potrebbe eseguire la query "getSummaries ()" ma non "getTransactions ()".

Una mappatura tridimensionale esisterebbe sotto forma di comando-registro-utente o query di registro-utente per determinare i diritti di accesso.

Oppure, in modo disaccoppiato, i nomi di "autorizzazioni" verrebbero registrati per un utente. Autorizzazioni che verrebbero quindi mappate per comandi specifici. Ad esempio, l'autorizzazione "ManageTransactions" consentirebbe a un utente di eseguire "AddTransaction ()", "RemoveTransaction ()", ecc.

  1. Autorizzazioni mapping utente -> libro mastro -> comando / query

  2. Autorizzazioni mapping utente -> libro mastro -> permesso -> comando / query

Questa è la prima parte della domanda. O in breve, l'autorizzazione in CQRS / DDD dovrebbe essere implementata per comando o per query? Oppure, l'autorizzazione dovrebbe essere disaccoppiata dai comandi?

In secondo luogo, per quanto riguarda l'autorizzazione basata su autorizzazioni. Un utente dovrebbe essere in grado di gestire le autorizzazioni sui suoi registri o sui registri che gli è stato permesso di gestire.

  1. I comandi di gestione delle autorizzazioni si verificano nel libro mastro

Ho pensato di aggiungere eventi / comandi / gestori nell'aggregato Ledger , come grantPermission (), revokePermission (), ecc. In questo caso, l'applicazione di tali regole sarebbe avvenuta nei gestori di comandi. Ma ciò richiederebbe che tutti i comandi includano l'id dell'utente che ha emesso quel comando. Quindi verificherei nel TLedger se esiste l'autorizzazione per quell'utente per eseguire quel comando.

Per esempio :

class TLedger{ 
    function addTransactionCmdHandler(cmd){
        if (!this.permissions.exist(user, 'addTransaction')
            throw new Error('Not Authorized');
    }
}
  1. Comandi di gestione autorizzazioni nell'Utente

Il contrario sarebbe quello di includere le autorizzazioni nel TUser. Un TUser avrebbe una serie di autorizzazioni. Quindi, nei gestori dei comandi TLedger, vorrei recuperare l'utente e verificare se dispone dell'autorizzazione per eseguire il comando. Ma questo mi richiederebbe di recuperare l'aggregato TUser per ogni comando TLedger.

class TAddTransactionCmdHandler(cmd) {
    this.userRepository.find(cmd.userId)
    .then(function(user){
        if (!user.can(cmd)){
            throw new Error('Not authorized');
        }
        return this.ledgerRepository.find(cmd.ledgerId);
    })
    .then(function(ledger){
        ledger.addTransaction(cmd);
    })

}
  1. Un altro dominio con servizio

Un'altra possibilità sarebbe quella di modellare completamente un altro dominio di autorizzazione. Questo dominio sarebbe interessato ai diritti di accesso, all'autorizzazione, ecc. Il sottodominio contabile utilizza quindi un servizio per accedere a questo dominio di autorizzazione sotto forma di AuthorizationService.isAuthorized(user, command).

class TAddTransactionCmdHandler(cmd) {
    authService.isAuthorized(cmd)
    .then(function(authorized){
        if (!authorized) throw new Error('Not authorized');
        return this.ledgerRepository.find(cmd.ledgerId)
    })
    .then(function(){
        ledger.addTransaction(cmd);
    })

}

Quale decisione sarebbe il modo più "DDD / CQRS"?


1
Grande domanda: ho cercato di affrontare questioni simili e nessuna letteratura sembra affrontarla direttamente. Ero un po 'confuso dalla seconda metà della tua domanda. Sembrava che ti stavi chiedendo dove collocare la gestione delle autorizzazioni (aggiungendo o rimuovendo le autorizzazioni) ma gli esempi mostrati sono per l'aggiunta di una transazione, quindi sembra che la seconda parte chieda "come devo richiedere le autorizzazioni". Puoi chiarire quella parte per favore?
emragins

Ogni transazione potrebbe avere una politica di esecuzione. Ogni utente dovrebbe appartenere a uno o più gruppi, ogni gruppo avrebbe un profilo di accesso che specifica quali transazioni sono consentite. In fase di esecuzione, prima di eseguire una transazione, la politica viene verificata rispetto ai profili aggregati per l'utente che esegue. Naturalmente, questo è più facile a dirsi che a farsi.
NoChance,

Risposte:


5

Per la prima domanda ho avuto problemi con qualcosa di simile. Sempre più mi sto inclinando verso uno schema di autorizzazione in tre fasi:

1) Autorizzazione a livello di comando / query di "questo utente ha mai il permesso di eseguire questo comando?" In un'app MVC questo potrebbe probabilmente essere gestito a livello di controller, ma sto optando per un pre-gestore generico che interrogherà l'archivio autorizzazioni in base all'utente corrente e al comando di esecuzione.

2) L'autorizzazione all'interno del servizio applicativo di "questo utente" ha mai * il permesso di accedere a questa entità? "Nel mio caso questo finirà probabilmente per essere un controllo implicito semplicemente mediante filtri sul repository - nel mio dominio questo è fondamentalmente un TenantId con un po 'più granularità di OrganizationId.

3) L'autorizzazione che si basa su proprietà transitorie delle entità (come lo stato) verrebbe gestita all'interno del dominio. (Es. "Solo alcune persone possono modificare un libro mastro chiuso.") Sto optando per metterlo all'interno del dominio perché si basa fortemente sul dominio e sulla logica aziendale e non mi sento molto a mio agio a esporlo in altri luoghi.

Mi piacerebbe sentire le risposte degli altri a questa idea - strappala a brandelli se vuoi (fornisci solo alcune alternative se lo fai :))


Penso che tu abbia alcuni punti validi per quanto riguarda i diversi "livelli" di autorizzazione. Un sistema su cui stavo lavorando aveva diversi tipi di utenti: utenti registrati e membri dello staff. Le autorizzazioni del gestore comandi / query hanno eseguito un controllo di base sul tipo di utente. Se era personale, passava sempre attraverso. Se era un utente registrato, era consentito solo se venivano soddisfatte determinate condizioni (ad es. Autorizzazioni per un aggregato).
magnus,

0

Implementerei l'autorizzazione come parte dell'autorizzazione BC, ma la implementerei come filtro di azione nel tuo sistema di contabilità generale. In questo modo, possono essere logicamente disaccoppiati l'uno dall'altro - il tuo codice contabile non dovrebbe chiamare il codice di autorizzazione - ma ottieni comunque un'autorizzazione in-process ad alte prestazioni di ogni richiesta in arrivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.