DDD CQRS - autorização por consulta e por comando

15

Sumário

A autorização no CQRS / DDD deve ser implementada por comando / consulta ou não?

Estou desenvolvendo pela primeira vez um aplicativo online usando mais ou menos estritamente o padrão DDD CQRS. Eu me deparei com um problema que eu realmente não consigo entender.

O aplicativo que estou criando é um aplicativo de contabilidade que permite que as pessoas criem livros de contabilidade, além de permitir que outras pessoas os visualizem / editem / excluam, como funcionários. O criador de um razão deve poder editar os direitos de acesso do razão que ele criou. Pode até mudar de propriedade. O domínio possui dois agregados TLedger e TUser .

Li muitas postagens com a palavra-chave DDD / CQRS sobre segurança, autorização etc. A maioria delas afirmou que a autorização era um subdomínio genérico , a menos que alguém estivesse criando um aplicativo de segurança.

Nesse caso, o domínio principal é certamente um domínio contábil interessado em transações, balanços e contas. Mas a funcionalidade de poder gerenciar o acesso granular aos livros contábeis também é necessária. Eu estou querendo saber como projetar isso em termos de DDD / CQRS.

É declarado nos tutoriais do DDD em todo o lugar que os comandos fazem parte da linguagem onipresente. Eles são significativos. São ações concretas que representam a "coisa real".

Como todos esses comandos e consultas são ações reais que os usuários executariam na "vida real", a implementação da autorização deve ser associada a todos esses "comandos" e "consultas"? Um usuário teria autorização para executar TLedger.addTransaction (), mas não TLedger.removeTransaction (), por exemplo. Ou, um usuário poderia executar a consulta "getSummaries ()", mas não "getTransactions ()".

Um mapeamento tridimensional existiria na forma de comando de razão do usuário ou consulta de razão do usuário para determinar os direitos de acesso.

Ou, de maneira dissociada, o nome "permissões" seria registrado para um usuário. Permissões que seriam mapeadas para comandos específicos. Por exemplo, a permissão "ManageTransactions" permitiria ao usuário executar "AddTransaction ()", "RemoveTransaction ()", etc.

  1. Usuário de mapeamento de permissões -> razão -> comando / consulta

  2. Usuário de mapeamento de permissões -> razão -> permissão -> comando / consulta

Essa é a primeira parte da pergunta. Ou, resumidamente, a autorização no CQRS / DDD deve ser implementada por comando ou por consulta? Ou, a autorização deve ser dissociada dos comandos?

Em segundo lugar, referente à autorização baseada em permissões. Um usuário deve poder gerenciar as permissões em seus livros ou nos livros que ele tem permissão para gerenciar.

  1. Os comandos de gerenciamento de autorização acontecem no Ledger

Pensei em adicionar os eventos / comandos / manipuladores ao agregado Ledger , como grantPermission (), revokePermission (), etc. Nesse caso, a aplicação dessas regras aconteceria nos manipuladores de comando. Mas isso exigiria que todos os comandos incluíssem o ID do usuário que emitiu esse comando. Depois, verificaria no TLedger se existe permissão para esse usuário executar esse comando.

Por exemplo :

class TLedger{ 
    function addTransactionCmdHandler(cmd){
        if (!this.permissions.exist(user, 'addTransaction')
            throw new Error('Not Authorized');
    }
}
  1. Comandos de gerenciamento de autorização no Usuário

O contrário seria incluir as permissões no TUser. Um TUser teria um conjunto de permissões. Em seguida, nos manipuladores de comando do TLedger, eu recuperaria o usuário e verificaria se ele tem permissão para executar o comando. Mas isso exigiria que eu buscasse o agregado TUser para cada 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. Outro domínio com serviço

Outra possibilidade seria modelar outro domínio de autorização completamente. Este domínio estaria interessado em direitos de acesso, autorização etc. O subdomínio contábil usaria um serviço para acessar esse domínio de autorização na forma de 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);
    })

}

Qual decisão seria a maneira mais "DDD / CQRS"?

Ludovic C
fonte
1
Ótima pergunta - eu tenho tentado lidar com questões semelhantes e nenhuma literatura parece abordá-la diretamente. Fiquei um pouco confuso com a segunda metade da sua pergunta. Parecia que você estava se perguntando sobre onde colocar o gerenciamento de permissões (adicionando ou removendo permissões), mas os exemplos mostrados são para adicionar uma transação, então parece mais com a segunda metade perguntando "como devo consultar permissões". Você pode esclarecer essa parte, por favor?
Emragins
Cada transação pode ter política de execução. Cada usuário deve pertencer a um ou mais grupos, cada grupo terá um perfil de acesso especificando quais transações são permitidas. No tempo de execução, antes de executar uma transação, a política é verificada nos perfis agregados do usuário em execução. Claro, isso é mais fácil dizer do que fazer.
NoChance

Respostas:

5

Para a primeira pergunta, estive lutando com algo semelhante. Cada vez mais, estou inclinado a um esquema de autorização em três fases:

1) Autorização no nível de comando / consulta de "este usuário tem permissão para executar este comando?" Em um aplicativo MVC, isso provavelmente pode ser tratado no nível do controlador, mas estou optando por um pré-manipulador genérico que consultará o armazenamento de permissões com base no usuário atual e no comando de execução.

2) A autorização dentro do serviço de aplicativo de "este usuário" sempre * tem permissão para acessar esta entidade? "No meu caso, isso provavelmente acabará sendo uma verificação implícita simplesmente por meio de filtros no repositório - no meu domínio, este é basicamente um TenantId com um pouco mais de granularidade de OrganizationId.

3) A autorização que depende de propriedades transitórias de suas entidades (como Status) seria realizada dentro do domínio. (Ex. "Somente certas pessoas podem modificar um razão fechada".) Optei por colocar isso dentro do domínio, porque ele depende muito do domínio e da lógica de negócios e não me sinto confortável em expor isso em outros lugares.

Eu adoraria ouvir as respostas de outras pessoas a essa idéia - rasgue-a em pedaços, se quiser (apenas forneça algumas alternativas, se você quiser :))

emragins
fonte
Eu acho que você tem alguns pontos válidos em relação a diferentes "camadas" de autorização. Um sistema em que eu estava trabalhando tinha diferentes tipos de usuários - usuários registrados e membros da equipe. As permissões do manipulador de comando / consulta fizeram uma verificação básica no tipo de usuário. Se era pessoal, sempre passava. Se fosse um usuário registrado, somente seria permitido se determinadas condições fossem atendidas (por exemplo, permissões em um agregado).
magnus
0

Eu implementaria a autorização como parte de sua Autorização BC, mas a implantaria como um filtro de ação no seu sistema Ledger. Dessa forma, eles podem ser dissociados logicamente um do outro - seu código contábil não precisa chamar o código de autorização - mas você ainda recebe uma autorização de alto desempenho em andamento de cada solicitação recebida.

pnschofield
fonte