Qual é a melhor maneira de validar a perspectiva de um purista de DDD?

4

Recentemente, fiz esta pergunta: Validação dentro do Construtor

Estou tentando decidir onde colocar a validação em um aplicativo DDD. Eu acredito que isso deve ser feito em todas as camadas.

Agora estou me concentrando no modelo de domínio. Eu esperava que a validação fosse incluída nos métodos do Setter assim: link

Qual é a melhor maneira de fazer a validação? Estou falando da perspectiva de um purista do DDD. Percebo que o "melhor" caminho nem sempre é o mais prático em todas as situações.

Além disso, não estou necessariamente dizendo que o DDD é sempre a melhor abordagem para resolver um problema. Estou apenas tentando melhorar meu pensamento nessa área específica.

Eu acredito que as opções são:

1) Validação dentro de setters, conforme descrito aqui: link

2) Método IsValid, conforme descrito aqui: link

3) Verifique a validade na camada de serviços de aplicativos, conforme descrito aqui: link

4) Padrão TryExecute, conforme descrito aqui: link

5) Execute / CanExecute pattern como descrito aqui: link

Estou usando o NHibernate para que os setters nos objetos de domínio tenham uma visibilidade de: protected set.

    
por w0051977 14.10.2017 / 14:01
fonte

3 respostas

7

É uma questão interessante que parece surgir em uma variedade de formas.

Eu sou da opinião de que a melhor abordagem é permitir o conceito de um objeto que é um estado inválido.

A razão é que as regras de validação para um objeto geralmente não são definidas em pedra. Eles podem mudar com o tempo ou ser diferentes para diferentes operações. Assim, um Objeto que você criou, preencheu e persistiu algum tempo atrás, pode agora ser considerado inválido.

Se você tiver verificações de validação de setter ou constructor, então você tem um grande problema, pois seu aplicativo irá cometer erros quando você tentar recuperar essas entidades de seu banco de dados, ou reprocessar entradas antigas, etc.

Além disso, não acho que as regras de negócios incorporem a simples validação sim / não na maior parte. Se o seu domínio está vendendo bolos e você não entrega ao sul do rio, mas alguém lhe oferece um milhão de libras para fazê-lo. Então você faz uma exceção especial.

Se você estiver processando milhões de aplicativos e tiver regras rígidas sobre quais caracteres podem estar em um campo, provavelmente terá um processo para corrigir campos inválidos. Você não quer ser incapaz de aceitar um campo ruim, apenas segue um caminho diferente através do Domínio.

Assim, se no código você é tão rigoroso que os dados 'inválidos' podem simplesmente não existir porque o construtor lançaria uma exceção, você está fadado a ser frágil e a falhar em questões como "quantas pessoas preencheram o formulário errado "

Permitir os dados e falhar na operação. Dessa forma, você pode ajustar os dados ou regras para a operação e executá-la novamente.

exemplo:

public class Order
{
    public string Id {get;set;}
    public string Address {get;set;}
    public void Deliver()
    {
        //check address is valid for delivery
        if(String.IsNullOrWhiteSpace(this.Address))
        {
            throw new Exception("Address not supplied");
        }

        //delivery code
    }
}

Então, aqui não conseguimos ou não desejamos enviar para endereços em branco. O objeto Ordem do domínio permite preencher um endereço em branco, mas lançará uma exceção se você tentar entregar esse pedido.

Um aplicativo, digamos, um trabalhador de fila processando pedidos de dados json armazenados em uma fila, encontrando uma ordem 'inválida':

{
    "Address"  :""
}

É capaz de criar o objeto Order, pois não há verificações de validação no construtor ou o setter para Address. No entanto, quando o método Deliver for chamado, ele lançará a exceção e o aplicativo poderá executar uma ação. eg

public class QueueWorker
{
    public void ProcessOrder(Order o)
    {
        try
        {
            o.Deliver();
        }
        catch(Exception ex)
        {
            Logger.Log("unable to deliver order:${o.Id} error:${ex.Message}");
            MoveOrderToErrorQueue(o);
        }
    }
}

O Aplicativo ainda pode trabalhar com o Pedido Inválido, movendo-o para a fila de erros, acessando seu ID, relatando erros, etc. Mas a Operação Entregar contém a lógica do Domínio de como você deseja manipular Entrega para endereços em branco. p>

Se a lógica do domínio mais tarde mudar com outros requisitos:

public class Order
{
    public string Id {get;set;}
    public string Address {get;set;}

    public void Deliver()
    {
        //check address is valid for delivery
        if(String.IsNullOrWhiteSpace(this.Address))
        {
            throw new Exception("Address not supplied");
        }
        if(Address.Contains("UK"))
        {
            throw new Exception("UK orders not allowed!");
        }
        //delivery code

    }
}

Você ainda pode processar pedidos na fila gerados quando os endereços do Reino Unido foram permitidos e obter o resultado esperado.

É interessante comparar minha resposta com a de @VoiceOfUnReason.

Eu usei uma string Address para simplificar e também porque não há uma definição absoluta do que é um endereço. Mas se tivermos uma definição mais absoluta, digamos que seu depósito sempre tenha uma moeda. É absurdo até falar sobre um depósito sem moeda.

Nesse caso, sim, você pode definir um novo tipo de valor que simplesmente não pode existir, a menos que você tenha especificado a moeda e os possíveis erros em seu código simplesmente não serão possíveis.

Mas você tem que ter certeza de que é uma coisa fundamental. Caso contrário, você está pedindo problemas depois!

    
por 14.10.2017 / 14:53
fonte
7

Um bom termo de pesquisa para revisar seria "Obsessão Primitiva"

I am trying to decide where to put the validation in a DDD app.

A resposta usual é colocar a validação nos construtores / fábricas para seus objetos de valor.

Esta é a maneira que cheguei a pensar: um dos propósitos dos objetos de valor é isolar o comportamento do domínio das representações de dados subjacentes.

Um exemplo ingênuo; vamos considerar uma conta bancária

Account {
    int currentBalance;

    void deposit (int amount) {
        int workingBalanace = currentBalance;
        workingBalance += amount;
        currentBalance = workingBalance;
    }
}

O design dessa entidade está intimamente ligado à expressão subjacente do equilíbrio como um int. Mas esse acoplamento é um acidente de implementação, não uma preocupação de domínio subjacente. Int é não tirado da linguagem onipresente do domínio bancário.

Em outras palavras, devemos ser capazes de alterar nossa decisão sobre como representamos os dados na memória sem precisar alterar como nossa lógica de negócios é implementada.

O mesmo modelo ingênuo, com essa camada extra de indireção, pode se parecer com

Account {
    Balance currentBalance;

    void deposit (Deposit amount) {
        Balance workingBalance = currentBalance;
        workingBalance = workingBalance.add(amount);
        currentBalance = workingBalance;
    }
}

Portanto, a Account não se importa com as representações subjacentes; só precisa ser passado tipos que suportam as condições corretas pós.

A validação necessária para garantir que o depósito é compatível é empurrada para mais perto do limite, que é o que você deseja.

Então eu não diria que a validação acontece em todas as camadas - acontece nos limites; um limite é onde nós convertemos mensagens do mundo externo em conceitos de domínio, outro onde convertemos mensagens do armazenamento persistente em conceitos de domínio.

Mas quando você está dentro do limite, não precisa repetir a validação, a menos que escolha não capturar o fato de que a validação foi feita.

Are you saying that you should not pass primitive types to domain classes?

Não é bem assim. O que estou dizendo é que os objetos de valor em seu modelo de domínio fornecem uma camada de indireção entre suas entidades e as representações de dados subjacentes.

No exemplo acima, passamos um Deposit para o Account . De onde veio Deposit ? É um tipo de valor em nosso domínio, presumivelmente instanciado ao pegar a mensagem (bytes) pela qual fomos passados e transformá-los em um conceito de domínio mais específico.

Deposit from(int amount) {
    // ...
}

É provável que a transformação reversa também exista em algum lugar, para que possamos usar um dispositivo de mercadorias (como um banco de dados relacional) para armazenar os dados

int from(Deposit deposit) {
    // ...
}

I am thinking anti corruption layer. Is that what you are talking about?

Idéia muito parecida; o "sistema legado" neste caso não é tanto um banco de dados, mas sim mensagens .

Mas, para mim, a idéia principal é a dissociação do conceito (a interface do objeto de valor manipulado pelo modelo de domínio) da representação subjacente (dados).

    
por 14.10.2017 / 15:14
fonte
3

Sua pergunta parece estar confundindo várias noções diferentes de validação, então vamos tentar separá-las:

  1. Podemos ter validação como um negócio & função de domínio - uma noção de validação no nível de domínio, que fala sobre as interações com clientes, clientes e fornecedores do negócio e ações que o negócio realiza, por exemplo, ao aceitar e cumprir pedidos, etc ... Neste nível, estamos falando de validação que seria feita manualmente, se não para automação.

  2. Podemos ter validação dentro dos módulos do código, de modo que o código verifique as condições que ele sabe que não está programado para manipular e / ou não espera. Neste nível, estamos falando de validações que são devidas à construção interna da arquitetura de automação / software.

Acho que o que todos estão dizendo é que devemos realizar a validação de domínio em transições de estado de domínio de negócios em vez de cruzamentos de camada de domínio de computador ou conteúdo de entidade.

Em outras palavras, vamos validar para fins comerciais; vamos validar para realizar transições de estado no nível do domínio de negócios; validar para realizar funções de negócios.

Não vamos fazer validação de negócios para os propósitos de conceitos de implementação de software de domínio de computador, como construção de objetos, acesso de setter ou cruzamento de camadas - você não encontrará nenhum deles no DDD, pois o DDD não informa como escrever código ou o código que você deve ter.

E, em particular, não permitamos que as validações do tipo (2) introduzam restrições (à operação do software para o negócio) que não estão presentes no nível do domínio do negócio.

    
por 14.10.2017 / 17:47
fonte