Estou tentando criar um aplicativo que tenha um domínio comercial complexo e um requisito para oferecer suporte a uma API REST (não estritamente REST, mas orientada a recursos). Estou com problemas para encontrar uma maneira de expor o modelo de domínio de maneira orientada a recursos.
No DDD, os clientes de um modelo de domínio precisam passar pela camada processual de 'Application Services' para acessar qualquer funcionalidade de negócios, implementada por Entidades e Serviços de Domínio. Por exemplo, há um serviço de aplicativo com dois métodos para atualizar uma entidade de Usuário:
userService.ChangeName(name);
userService.ChangeEmail(email);
A API deste serviço de aplicativo expõe comandos (verbos, procedimentos), não estado.
Mas se também precisamos fornecer uma API RESTful para o mesmo aplicativo, existe um modelo de recursos do usuário, que se parece com isso:
{
name:"name",
email:"[email protected]"
}
A API orientada a recursos expõe estado , não comandos . Isso levanta as seguintes preocupações:
cada operação de atualização em uma API REST pode mapear para uma ou mais chamadas de procedimento do Serviço de Aplicativo, dependendo de quais propriedades estão sendo atualizadas no gabarito de recursos
cada operação de atualização parece atômica para o cliente da API REST, mas não é implementada dessa maneira. Cada chamada do Serviço de Aplicativo é projetada como uma transação separada. A atualização de um campo em um modelo de recursos pode alterar as regras de validação para outros campos. Portanto, precisamos validar todos os campos do modelo de recursos juntos para garantir que todas as chamadas em potencial do Serviço de Aplicativo sejam válidas antes de começarmos a realizá-las. Validar um conjunto de comandos de uma só vez é muito menos trivial do que executar um de cada vez. Como fazemos isso em um cliente que nem mesmo conhece comandos individuais?
chamar métodos de Serviço de Aplicativo em ordem diferente pode ter um efeito diferente, enquanto a API REST faz parecer que não há diferença (dentro de um recurso)
Eu poderia ter problemas mais semelhantes, mas basicamente todos são causados pela mesma coisa. Após cada chamada para um Serviço de Aplicativo, o estado do sistema é alterado. Regras do que é mudança válida, o conjunto de ações que uma entidade pode executar na próxima mudança. Uma API orientada a recursos tenta fazer com que tudo pareça uma operação atômica. Mas a complexidade de atravessar essa lacuna deve ir a algum lugar, e parece enorme.
Além disso, se a interface do usuário for mais orientada a comandos, o que geralmente ocorre, teremos que mapear entre comandos e recursos no lado do cliente e depois no lado da API.
Questões:
- Toda essa complexidade deve ser tratada por uma camada de mapeamento (espessa) de REST para AppService?
- Ou estou faltando alguma coisa no meu entendimento de DDD / REST?
- O REST poderia simplesmente não ser prático para expor a funcionalidade de modelos de domínio em um certo grau (bastante baixo) de complexidade?
fonte
Respostas:
Eu tive o mesmo problema e o "resolvi" modelando os recursos REST de maneira diferente, por exemplo:
Então, eu basicamente dividi o recurso maior e complexo em vários outros menores. Cada um deles contém um grupo um tanto coeso de atributos do recurso original que se espera que sejam processados juntos.
Cada operação nesses recursos é atômica, embora possa ser implementada usando vários métodos de serviço - pelo menos no Spring / Java EE, não é um problema criar transações maiores a partir de vários métodos que originalmente pretendiam ter sua própria transação (usando a transação REQUIRED propagação). Muitas vezes, você ainda precisa fazer uma validação extra para esse recurso especial, mas ainda é bastante gerenciável, pois os atributos são (supostamente) coesivos.
Isso também é bom para a abordagem HATEOAS, porque seus recursos mais refinados transmitem mais informações sobre o que você pode fazer com eles (em vez de ter essa lógica no cliente e no servidor porque não pode ser facilmente representada nos recursos).
Naturalmente, não é perfeito - se as UIs não forem modeladas com esses recursos em mente (especialmente as orientadas a dados), isso poderá criar alguns problemas - por exemplo, a UI apresenta grande forma de todos os atributos de determinados recursos (e seus sub-recursos) e permite que você edite-os todos e salve-os de uma só vez - isso cria uma ilusão de atomicidade, mesmo que o cliente precise chamar várias operações de recursos (que são atômicas, mas a sequência inteira não é atômica).
Além disso, essa divisão de recursos às vezes não é fácil ou óbvia. Faço isso principalmente em recursos com comportamentos complexos / ciclos de vida para gerenciar sua complexidade.
fonte
O principal problema aqui é: como a lógica de negócios é chamada de forma transparente quando uma chamada REST é feita? Esse é um problema que não é tratado diretamente pelo REST.
Resolvi isso criando minha própria camada de gerenciamento de dados em um provedor de persistência como o JPA. Usando um metamodelo com anotações personalizadas, podemos chamar a lógica de negócios apropriada quando o estado da entidade é alterado. Isso garante que, independentemente de como o estado da entidade mude, a lógica de negócios seja invocada. Mantém sua arquitetura SECA e também sua lógica de negócios em um só lugar.
Usando o exemplo acima, podemos chamar um método de lógica de negócios chamado validateName quando o campo de nome é alterado usando REST:
Com essa ferramenta à sua disposição, tudo o que você precisará fazer é anotar seus métodos de lógica de negócios adequadamente.
fonte
Você não deve expor o modelo de domínio de maneira orientada a recursos. Você deve expor o aplicativo de maneira orientada a recursos.
De maneira nenhuma - envie os comandos para os recursos do aplicativo que fazem interface com o modelo de domínio.
Sim, embora exista uma maneira ligeiramente diferente de soletrar isso, que pode tornar as coisas mais simples; cada operação de atualização em uma API REST é mapeada para um processo que envia comandos para um ou mais agregados.
Você está perseguindo a cauda errada aqui.
Imagine: tire o REST da imagem completamente. Imagine, em vez disso, que você estava escrevendo uma interface da área de trabalho para este aplicativo. Vamos imaginar ainda mais que você tem realmente bons requisitos de design e está implementando uma interface do usuário baseada em tarefas. Portanto, o usuário obtém uma interface minimalista perfeitamente ajustada para a tarefa em que está trabalhando; o usuário especifica algumas entradas e depois pressiona o "VERBO!" botão.
O que acontece agora? Da perspectiva do usuário, esta é uma tarefa atômica única a ser realizada. Da perspectiva do domainModel, há vários comandos sendo executados por agregados, nos quais cada comando é executado em uma transação separada. Esses são completamente incompatíveis! Precisamos de algo no meio para preencher a lacuna!
O algo é "a aplicação".
No caminho feliz, o aplicativo recebe algum DTO e analisa esse objeto para obter uma mensagem que entende e usa os dados na mensagem para criar comandos bem formados para um ou mais agregados. O aplicativo garantirá que cada um dos comandos enviados para os agregados esteja bem formado (que é a camada anticorrupção em funcionamento) e carregará os agregados e salvará os agregados se a transação for concluída com êxito. O agregado decidirá por si próprio se o comando é válido, dado seu estado atual.
Resultados possíveis - todos os comandos são executados com êxito - a camada anticorrupção rejeita a mensagem - alguns dos comandos são executados com êxito, mas um dos agregados reclama e você tem uma contingência para mitigar.
Agora, imagine que você tenha esse aplicativo criado; como você interage com ele de uma maneira RESTful?
Aceito é a cópia comum de quando o aplicativo adia o processamento de uma mensagem até depois de responder ao cliente - normalmente usado ao aceitar um comando assíncrono. Mas também funciona bem para esse caso, onde uma operação que deveria ser atômica precisa ser mitigada.
Nesse idioma, o recurso representa a própria tarefa - você inicia uma nova instância da tarefa postando a representação apropriada no recurso da tarefa, e esse recurso faz interface com o aplicativo e o direciona para o próximo estado do aplicativo.
No ddd , praticamente sempre que você está coordenando vários comandos, você quer pensar em termos de um processo (também conhecido como processo de negócios, também conhecido como saga).
Há uma incompatibilidade conceitual semelhante no modelo de leitura. Mais uma vez, considere a interface baseada em tarefas; se a tarefa exigir a modificação de vários agregados, a interface do usuário para preparar a tarefa provavelmente incluirá dados de vários agregados. Se o seu esquema de recursos for 1: 1 com agregados, será difícil organizar; em vez disso, forneça um recurso que retorne uma representação dos dados de vários agregados, juntamente com um controle hipermídia que mapeie a relação "iniciar tarefa" com o terminal da tarefa, conforme discutido acima.
Veja também: REST in Practice por Jim Webber.
fonte