As APIs RESTful tendem a incentivar modelos de domínio anêmicos?

34

Estou trabalhando em um projeto no qual estamos tentando aplicar o design controlado por domínio e o REST a uma arquitetura orientada a serviços. Não estamos preocupados com 100% de conformidade com REST; provavelmente seria melhor dizer que estamos tentando criar APIs HTTP orientadas a recursos (~ Nível 2 do modelo de maturidade REST de Richardson). No entanto, estamos tentando ficar longe do uso de estilo RPC de pedidos HTTP, ou seja, nós tentativa de implementar a nossa HTTP verbos de acordo com RFC2616 em vez de usar POSTa fazer IsPostalAddressValid(...), por exemplo.

No entanto, uma ênfase nisso parece estar à custa de nossa tentativa de aplicar o design orientado a domínio. Com apenas GET, POST, PUT, DELETEe alguns outros métodos usados raramente, tendemos a criar serviços fétido e serviços fétido tendem a ter modelos de domínio anêmicos.

POST: Receba os dados, valide-os e despeje-os nos dados. GET: Recupere os dados, devolva-os. Nenhuma lógica de negócios real lá. Também usamos mensagens (eventos) entre os serviços, e me parece que a maior parte da lógica de negócios acaba sendo construída em torno disso.

REST e DDD estão em tensão em algum nível? (Ou estou entendendo errado algo aqui? Talvez esteja fazendo algo errado?) É possível criar um modelo de domínio forte em uma arquitetura orientada a serviços, evitando chamadas HTTP no estilo RPC?

Kazark
fonte
1
O POST foi deliberadamente projetado para ser "intencionalmente vago"; o resultado de um POST é específico da implementação. O que impede você de fazer o que o Twitter e outros designers de API fazem e define cada método POST na parte não-CRUD da sua API de acordo com seus próprios requisitos específicos?
Robert Harvey
@RobertHarvey Nós interpretamos o POST como uma criação. Olhando para o padrão novamente, talvez isso seja simplista demais. Por exemplo, você acha que o POST para fazer IsPostalAddressValid(...)isso se encaixaria em "Fornecer um bloco de dados, como o resultado do envio de um formulário, para um processo de manipulação de dados"?
Kazark 14/01
Isso ocorre porque não há verbo CREATE (e nenhum verbo UPDATE, nesse caso). Eu mantenho que esses verbos estão ausentes do padrão (por qualquer motivo), e é por isso que você precisa cooptar o POST para "tudo o resto". Seu "processo de manipulação de dados", nesse caso, é o processo que examina o endereço postal e retorna um valor correspondente ao resultado dessa análise.
Robert Harvey
1
@RobertHarvey: Eu acredito que POST e PUT / PATCH é simplesmente o verbo CREATE e UPDATE que você está querendo. É nomeado apenas de forma diferente, de modo que o verbo ainda faz algum sentido, mesmo em design não RESTful.
Lie Ryan
@LieRyan: Eu garanto. Eu apenas acho que o CRUD implica modelos de dados anêmicos por definição. Você pode levar algum comportamento se, digamos, você estiver no M do MVC, mas certamente não em sistemas heterogêneos. Para tudo, menos CRUD, você precisa do POST.
Robert Harvey

Respostas:

38

Primeira lei de sistemas distribuídos de Martin Fowler: "Não distribua seus objetos!" As interfaces remotas devem ter granulação grossa e as internas, com granulação fina. Geralmente, o modelo de domínio avançado se aplica apenas a um contexto limitado .

A API REST separa dois contextos diferentes, ambos com seus próprios modelos internos. Os contextos se comunicam por meio de interface de granulação grossa (API REST) ​​usando objetos "anêmicos" (DTO).

No seu caso, parece que você está tentando espalhar um contexto por um limite que é a API REST. Isso pode levar a interface remota refinada ou modelo anêmico. Dependendo do seu projeto, pode ou não ser um problema.

simoraman
fonte
1
Fowler tem muitos pensamentos bons, mas não vamos esquecer que desastre foram as especificações e implementações originais do EJB. Foi só depois que eles descobriram que o método de baixo nível requer que todas as operações menores, como getName (), sejam um pesadelo de rede / carregamento. Interfaces de granulação grossa tornaram-se o caminho a seguir e, com ele, o conceito de que gráficos / entidades inteiros foram enviados e recebidos no contexto verbo + substantivo.
Darrell Teague
9

O POST foi deliberadamente projetado para ser "intencionalmente vago"; o resultado de um POST é específico da implementação. O que impede você de fazer o que o Twitter e outros designers de API fazem e define cada método POST na parte não-CRUD da sua API de acordo com seus próprios requisitos específicos? POST é o verbo catchall. Use-o quando nenhum dos outros verbos for adequado para a operação que você deseja executar.

Em outras palavras, sua pergunta pode ser igualmente colocada como "Os objetos 'inteligentes' incentivam o design no estilo RPC?" Até Martin Fowler (que cunhou o termo "Modelo de Domínio Anêmico") reconhece que os DTOs nus têm alguns benefícios:

A colocação de comportamento nos objetos de domínio não deve contradizer a abordagem sólida do uso de camadas para separar a lógica do domínio de coisas como responsabilidades de persistência e apresentação. A lógica que deve estar em um objeto de domínio é lógica de domínio - validações, cálculos, regras de negócios - como você quiser chamá-lo.

Com relação ao Modelo de Maturidade de Richardson , você pode chegar ao nível 3 sem se preocupar com "Modelos de Domínio Anêmicos". Lembre-se de que você nunca transferirá o comportamento para o navegador (a menos que planeje injetar algum Javascript nos seus modelos).

REST é principalmente sobre independência de máquina; implemente o modelo REST na medida em que você deseja que seus terminais representem recursos e para que os consumidores de sua API possam acessar e manter esses recursos facilmente de maneira padrão. Se isso parece anêmico, que assim seja.

Veja também
Preciso de mais verbos

Robert Harvey
fonte
Eu acho que certamente aborda o lado RFC2616 da questão. E o fato de estarmos tentando ser orientados a recursos, ou seja, pelo menos tentando atingir o Nível 2 no modelo de maturidade de Richardson para o REST?
Kazark 14/01
1
Eu li através martinfowler.com/articles/richardsonMaturityModel.html . Você pode chegar ao nível 3 sem se preocupar com "Modelos de Domínios Anêmicos". Lembre-se de que você nunca transferirá o comportamento para o navegador (a menos que planeje injetar algum Javascript nos seus modelos).
Robert Harvey
4

A API REST é apenas um tipo de camada de apresentação. Não tem nada a ver com o modelo de domínio.

A pergunta que você postou vem da sua confusão de que, de alguma forma, você precisa se adaptar um ao outro. Você não

Você mapeia seu modelo de domínio para sua API REST da mesma maneira que mapeia seu modelo de domínio para um RDBMS por meio de um ORM - deve haver essa camada de mapeamento.

Domínio ← ORM →
Domínio RDBMS ← Mapeamento REST → API REST

Elnur Abdurrakhimov
fonte
3

IMHO Eu não acho que eles tendem a incentivar modelos de domínio anêmico (ADMs), mas exigem que você reserve um tempo e pense sobre as coisas.

Antes de tudo, acho que a principal característica dos ADMs é que eles têm pouco ou nenhum comportamento neles. Isso não quer dizer que o sistema não tenha comportamento, apenas que ele geralmente está em algum tipo de classe de serviço (consulte http://vimeo.com/43598193 ).

E, claro, se o comportamento não existe na ADM, o que existe? A resposta, é claro, são os dados. E como isso é mapeado para a API REST? Presumivelmente, os dados são mapeados para o conteúdo do recurso e o comportamento é mapeado para os verbos HTTP.

Portanto, você tem tudo o que precisa para criar um modelo de domínio avançado, apenas precisa ver como os verbos HTTP são mapeados para as operações de domínio nos dados e depois colocar essas operações nas mesmas classes que encapsulam seus dados.

Acho que onde as pessoas tendem a ter problemas é que elas têm dificuldade em ver como os verbos HTTP são mapeados para o comportamento de seu domínio quando o comportamento está além do simples CRUD, ou seja, quando há efeitos colaterais em outras partes do domínio além do recurso sendo modificado pela solicitação HTTP. Uma maneira de resolver esse problema é com eventos de domínio ( http://www.udidahan.com/2009/06/14/domain-events-salvation/ ).

RibaldEddie
fonte
3

Este artigo está bastante relacionado ao assunto e acredito que responde à sua pergunta.

Um conceito central que acho que responde muito bem à sua pergunta está resumido no parágrafo a seguir do artigo mencionado:

"É muito importante distinguir entre recursos na API REST e entidades de domínio em um design controlado por domínio. O design controlado por domínio se aplica ao lado da implementação (incluindo a implementação da API), enquanto os recursos na API REST conduzem o design e o contrato da API. Recurso da API a seleção não deve depender dos detalhes de implementação do domínio subjacente ".

Majix
fonte
1

Várias implementações razoavelmente bem-sucedidas que eu já vi / construíram respondem à pergunta de como elas misturam a metáfora verbo + substantivo usando métodos grosseiros 'amigáveis ​​aos negócios' que atuam nas entidades.

Portanto, em vez do getName()método / serviço (condenado) , exponha getPerson(), passando coisas como ID / tipo identificador, retornando a Personentidade inteira .

Como os comportamentos da entidade Pessoa em tal contexto não podem ser transmitidos adequadamente (nem talvez devam estar em um contexto centrado em dados como esse), é perfeitamente razoável definir um modelo de dados (versus Objeto) para os pares de solicitação / resposta de os serviços.

Os próprios serviços e verbos definidos adicionarão alguns comportamentos, controles e até regras de transição de estado permitidos para o domínio para as entidades. Por exemplo, haveria uma lógica específica do domínio sobre o que acontece na transferPerson()chamada de serviço, mas a própria interface definiria apenas as entradas / entidades / dados de saída sem definir SEUS comportamentos internos.

Eu discordaria dos autores que diriam, por exemplo, que uma implementação de verbo de transferência pertence à classe Person ou associada a um serviço centrado em Person. De fato, o método de transferência para Personae suas opções (neste exemplo simples) seria melhor definido por a Carrier, em que o usuário Personpode não ter conhecimento nem de quais métodos de transferência estão disponíveis ou como a transferência ocorre (quem sabe como os motores a jato funcionam de qualquer forma).

Isso torna a Personentidade anêmica? Acho que não.

Pode / deve haver lógica sobre coisas específicas da Pessoa que são internas à Pessoa, como seu estado de saúde, que não deve ser definido por uma classe externa.

No entanto, dependendo dos casos de uso, é inteiramente aceitável que uma classe de entidade não tenha comportamentos importantes / relevantes em certos sistemas, como um serviço de atribuição de assento em um sistema de transporte. Esse sistema pode implementar serviços baseados em REST que lidam com instâncias de Pessoa e identificadores associados, mas nunca definem / implementam seus comportamentos internos.

Darrell Teague
fonte
Bons pontos - isso realmente traz uma nova perspectiva que outras respostas ainda não tinham.
Kazark 21/03
0

Seu problema é que você está tentando incluir seu modelo no conjunto básico de verbos, usando o POST o máximo possível?

Não é necessário - eu sei que para a maioria das pessoas REST significa POST, GET, PUT e DELETE, mas o http rfc diz:

O conjunto de métodos comuns para HTTP / 1.1 é definido abaixo. Embora esse conjunto possa ser expandido, não é possível presumir que métodos adicionais compartilhem a mesma semântica para clientes e servidores estendidos separadamente.

E sistemas como o SMTP usam o mesmo estilo de métodos baseados em verbos, mas com um conjunto totalmente diferente.

Portanto, não há razão para você usá-los; você pode usar qualquer conjunto de verbos que desejar (no entanto, na verdade, você descobrirá que pode fazer tudo o que precisa nos 4 básicos com um pouco de reflexão). O que diferencia o REST dos outros mecanismos é sua maneira sem estado e consistente de implementar esses verbos. Você não deve tentar implementar o sistema de passagem de mensagens entre as camadas, pois, basicamente, não está fazendo o REST; em vez disso, está usando um mecanismo de passagem de mensagens, RPC ou fila de mensagens que, sem dúvida, perderá os benefícios do REST (ou seja, o simplicidade que faz com que funcione muito bem em uma conexão http).

Se você deseja um protocolo de mensagens complexo e com todos os recursos, crie-o (se você puder fazer isso pela Web, há uma razão pela qual o REST é tão popular), mas tente manter o design arquitetônico do REST.

gbjbaanb
fonte