Implementando DDD: usuários e permissões

16

Estou trabalhando em um pequeno aplicativo tentando entender os princípios do design controlado por domínio. Se for bem-sucedido, pode ser um piloto para um projeto maior. Estou tentando seguir o livro "Implementing Design Orientado a Domínios" (de Vaughn Vernon) e tentando implementar um fórum de discussão simples e semelhante. Também verifiquei as amostras do IDDD no github. Tenho algumas dificuldades em adotar a identidade e o acesso ao meu caso. Deixe-me dar algumas informações básicas:

  • (Espero) compreendo o raciocínio por trás da separação da lógica de usuários e permissões: é um domínio de suporte e é um contexto limitado diferente.
  • No domínio principal, não há usuários, apenas Autores, Moderadores, etc. Eles são criados acessando o contexto de Identidade e Acesso usando um serviço e, em seguida, convertendo os objetos Usuário recebidos para e Moderador.
  • As operações de domínio são chamadas com uma função relacionada como parâmetro: por exemplo:

    ModeratePost( ..., moderator);

  • O método do objeto de domínio verifica se a instância do Moderador fornecida não é nula (a instância do Moderador será nula se o usuário solicitado no contexto Identidade e Acesso não tiver a função de Moderador).

  • Em um caso, ele faz uma verificação adicional antes de alterar uma postagem:

    if (forum.IsModeratedby(moderator))

Minhas perguntas são:

  • Neste último caso, as preocupações de segurança não são misturadas novamente no domínio principal? Anteriormente, os livros afirmam "com quem pode postar um assunto ou sob quais condições são permitidas. Um fórum precisa saber que um autor está fazendo isso no momento".

  • A implementação baseada em funções no livro é bastante direta: quando um Moderador é o domínio principal, tenta converter o userId atual em uma instância do Moderador ou em um Autor quando necessário. O serviço responderá com a instância apropriada ou um valor nulo se o usuário não tiver a função necessária. No entanto, não vejo como adaptar isso a um modelo de segurança mais complexo; nosso projeto atual para o qual estou pilotando tem um modelo bastante complexo com grupos, ACLs etc.

Mesmo com regras que não são muito complexas, como: "Uma postagem deve ser editada apenas por seu proprietário ou por um editor", essa abordagem parece desmoronar, ou pelo menos não vejo a maneira correta de implementá-la.

Perguntar ao contexto Identity and Access que uma instância OwnerOrEditor não parece correta e eu terminaria com mais e mais classes relacionadas à segurança no domínio principal. Além disso, eu precisaria passar não apenas o userId, mas o identificador do recurso protegido (o ID da postagem, fórum etc.) para o contexto de segurança, que provavelmente não deveria se importar com essas coisas (está correto? )

Ao puxar as permissões para o domínio principal e verificá-las nos métodos dos objetos de domínio ou nos serviços, eu terminaria na estaca zero: misturando preocupações de segurança com o domínio.

Li em algum lugar (e tendem a concordar com isso) que essas coisas relacionadas a permissões não devem fazer parte do domínio principal, a menos que segurança e permissões sejam o próprio domínio principal. Uma regra simples como a mencionada acima justifica tornar a segurança parte do domínio principal?

LittlePilgrim
fonte
Talvez você possa encontrar o que precisa aqui: stackoverflow.com/a/23485141/329660 Além disso, apenas porque o contexto do Controle de Acesso conhece um ID de recurso não significa que ele tenha conhecimento de domínio sobre que tipo de entidade esse recurso é ou o que é faz.
guillaume31
Obrigado, eu já vi esse post anteriormente, meu problema é exatamente o que a edição diz no final: eu gostaria de mudar o controle de acesso para fora do meu domínio principal, mas sinto que bati um muro com a minha implementação. No entanto, sua sugestão sobre o ID do recurso faz sentido: como eu não uso o conceito de Usuário ou Função no domínio principal, mas funções concretas, talvez eu possa usar o conceito de Recurso na segurança BC e mapeá-lo para o concreto relacionado conceito de domínio. Vale a pena tentar, obrigado!
LittlePilgrim
Os exemplos de código no link pelo menos não respondem a "Não consigo ver como adaptar isso a um modelo de segurança mais complexo" ?
guillaume31
Meu problema não é com a implementação do modelo de segurança em si, não consigo ver como devo mapear essas regras mais complicadas para o domínio. Como o mapeamento Usuário -> Autor deve mudar se não for um modelo simples baseado em função no lado da segurança? Passar IDs de recursos para outro contexto pode funcionar, como, HasPermissionToEdit(userId, resourceId)mas não me sinto bem em contaminar a lógica do domínio com essas chamadas. Provavelmente eu deveria verificar isso nos métodos de serviço de aplicativo, antes de chamar a lógica do domínio?
21418 LittlePilgrim
Claro que deveria estar nos serviços de aplicativos ... Eu pensei que isso estava claro em partes do código, como UserService @AccessControlList[inf3rno]na resposta à qual vinculei.
precisa

Respostas:

6

Às vezes, é difícil distinguir entre regras reais de controle de acesso e invariantes de domínio que fazem fronteira com o controle de acesso.

Especialmente, as regras que dependem de dados disponíveis apenas no decorrer de uma parte específica da lógica do domínio podem não ser facilmente extraíveis do domínio. Normalmente, o Controle de Acesso é chamado antes ou depois de uma operação de domínio ser realizada, mas não durante.

O assert (forum.IsModeratedBy(moderator))exemplo de Vaughn Vernon provavelmente deveria estar fora do Domínio, mas nem sempre é viável.

Eu precisaria passar não apenas o userId, mas o identificador do recurso protegido (o ID da postagem, fórum etc.) para o contexto de segurança, que provavelmente não deveria se preocupar com essas coisas (está correto?)

Se existe um Security BC e você deseja que ele lide com essa lógica, ele não precisa saber o que é um fórum em detalhes, mas:

  • Poderia apenas ter conhecimento de conceitos como "moderado por" e conceder ou negar direitos de acesso de acordo.
  • Você pode ter uma lógica de adaptador que assine os eventos do Domínio Principal e os converta em pares de valores de chave simples (recurso, usuários autorizados) para que o Security BC armazene e use.
guillaume31
fonte
Como as duas respostas são úteis e mais ou menos estão apontando para a mesma direção, votei nas duas. Aceitei este, já que @ guillaume31 respondeu mais ou menos à minha pergunta sobre a implementação de Vernon e continuarei com minha implementação com base em sua dica sobre o uso de recursos no Security BC.
LittlePilgrim
Devo dizer que acho isso exatamente o oposto da minha resposta.
Ewan
1
Talvez eu esteja muito confuso agora, mas minha interpretação foi (para as duas respostas): 1. Mantenha as preocupações de segurança fora do domínio e use o BC de segurança como um serviço 2. Ligue para o serviço antes de chamar qualquer objeto de domínio 3. O serviço fará o mapeamento de usuários / acls para moderadores, autores etc. moderator = securityService.GetModerator(userId, forumId) 4. A lógica do domínio será implementada nesses objetos, como no moderator.EditPost () 5. Métodos como o EditPost não saberão nada sobre conceitos de segurança, não haverá verificações adicionais lá
LittlePilgrim
Ainda estou procurando respostas / orientações sobre isso, mas descobri que qualquer lógica de autorização que se baseia no estado atual do objeto (como se atualmente estiver atribuída a um moderador) é, de fato, uma lógica comercial que pertence a seu domínio e, além disso, se ele não estiver no seu domínio, você corre o risco de terminar em um estado inválido se o modelo puder ser atualizado simultaneamente. Por exemplo, se você validar a propriedade usando uma política e depois atualizar esse objeto - em muitos domínios essa propriedade está sujeita a alterações e a ação pode não ser mais válida.
Jordânia
A menos que você tenha um contexto colaborativo muito complexo, é provável que você possa implementar aqui um modelo de simultaneidade otimista usando controle de versão, mas se suas verificações não forem feitas dentro ou pelo menos em uma instância agregada específica, suas verificações podem não ser consistentes com a real estado do objeto no momento em que você persiste suas alterações.
Jordânia
5

Autenticação e autorização é um mau exemplo para o DDD.

Nenhuma dessas coisas faz parte de um domínio, a menos que sua empresa crie produtos de segurança.

O requisito comercial ou de domínio é ou deve ser "eu exijo autenticação baseada em função"

Você então verifica a função antes de chamar uma função de domínio.

Onde você tem requisitos complexos, como 'Eu posso editar minhas próprias postagens, mas não outras', verifique se o seu domínio separa a função de edição EditOwnPost()e EditOthersPost()para que você tenha uma função simples no mapeamento de funções

Você também pode separar a funcionalidade em Objetos de Domínio, como Poster.EditPost()e Moderator.EditPost()essa é uma abordagem mais OOP, embora sua escolha possa depender se o seu método está em um Serviço de Domínio ou em um Objeto de Domínio.

No entanto, você escolhe separar o código que o mapeamento de função ocorrerá fora do domínio. por exemplo, se você possui um controlador webapi:

PostController : ApiController
{
    [Authorize(Roles = "User")]
    public void EditOwnPost(string postId, string newContent)
    {
        this.postDomainService.EditOwnPost(postId, string newContent);
    }

    [Authorize(Roles = "Moderator")]
    public void EditOtherPost(string postId, string newContent)
    {
        this.postDomainService.EditOtherPost(postId, string newContent);
    }
}

Como você pode ver, embora o mapeamento de funções seja feito na camada de hospedagem, a lógica complexa do que constitui a edição da sua própria publicação ou de uma publicação de outras pessoas faz parte do domínio.

O domínio reconhece a diferença das ações, mas o requisito de segurança é simplesmente que "a funcionalidade possa ser limitada por funções" .

Talvez isso seja mais claro com a separação dos objetos de domínio, mas, essencialmente, você está verificando o método que constrói o objeto em vez do método que chama o método de serviço. Seu requisito, se você ainda deseja expressá-lo como parte do domínio, se tornará 'apenas moderadores podem construir o objeto moderador'

Ewan
fonte
4
Verificar a função "estaticamente" é um pouco simplista. E se um moderador não tiver permissão para editar a postagem de outro moderador? Essa verificação não deve fazer parte do domínio?
Réda Housni Alaoui
2
@ RédaHousniAlaoui Também estou pensando nisso. Não consigo pensar em uma maneira de lidar com isso além de incluir alguma menção de Usuários / Moderadores no domínio ou executar algum tipo de lógica nesse ApiController para obter o papel de autor da publicação. Nenhuma delas parece correta, e esse é um tipo de coisa bastante comum na minha experiência que uma orientação clara seria extremamente útil.
Jimmy
1
@ Erwan, o caso de uso de que estou falando é dinâmico. Basear a frase "Autenticação e autorização é um mau exemplo para DDD" em exemplos do Hello World é desonesto. O DDD está aqui para evitar a complexidade acidental e permitir gerenciar a complexidade do domínio. Permissões dinâmicas não são uma complexidade acidental nem algo que não acontece na vida real.
Réda Housni Alaoui
1
IMHO, o problema com sua solução é que ela não satisfaz o cliente. O cliente geralmente quer poder alterar essas relações dinamicamente. Além disso, também é o que acontece quando um fornecedor fornece o mesmo software corporativo para empresas diferentes. Se o software não puder ser ajustado, o fornecedor acabará morrendo.
Réda Housni Alaoui
1
"Mas geralmente é reconhecido como uma" coisa ruim ", suas configurações de segurança se tornam incontroláveis ​​com o tempo, o que significa que seu aplicativo se torna inseguro." Com o design e teste corretos, é totalmente gerenciável. Mas, no meu XP, para produzir o design correto, o domínio deve verificar a permissão. A alternativa é utopia.
Réda Housni Alaoui