Não exponha objetos do domínio em uma “API pública”

Recentemente, Oren Eini (Ayende) publicou um post analisando o código de parte de uma API que continha uma falha grave de design. Nesse post, reproduzimos algumas das ideias que ele compartilhou e adicionamos algumas considerações que consideramos relevantes.

[Route("/public/api/v1/tickets/{org}")]
public async Task<IActionResult> Get(string org, int skip = 0) 
{
    var tickets = await session.Query<Domain.SupportTicket>()
        .Where(x=>x.Organization == org)
        .OrderByDescending(x => x.LastUpdate)
        .Skip(skip)
        .ToListAsync();
    
    return Ok(tickets);
}

O problema de design apontado por Oren é a exposição de um objeto de domínio em uma API pública (feita para ser consumida em aplicações desenvolvidas por outros times, fora da empresa). Essa, aliás, é uma dívida técnica extremamente comum contraída, geralmente, por inocência ou falta de conhecimento.

Um dos maiores problemas de expor um objeto de domínio em uma API pública é o acoplamento. Como Oren destaca, tal decisão vincula, para começar, o versionamento do domínio ao versionamento da API e, eventualmente, mudanças no domínio  com potencial para tornar código consumidor incompatível podem “passar” de forma inadvertida.

Outro problema a considerar é o potencial vazamento de dados sensíveis. Afinal, é normal que objetos de domínio sejam “enriquecidos” na medida que o projeto avança.

Um problema adicional, em nossa análise, é que o acoplamento resultante da exposição de objetos de domínio direciona o desenvolvimento para monolíticos distribuídos.

A recomendação de Oren foi criar um modelo exclusivamente para compartilhamento da API. Além disso, ele recomendou a não utilização de bibliotecas de mapeamento como o Automapper. Segundo ele, é importante evitar qualquer chance de exposição acidental.

[Route("/public/api/v1/tickets/{org}")]
public async Task<IActionResult> Get(string org, int skip = 0)
{
    var tickets = await session.Query<Domain.SupportTicket>()
        .Where(x=>x.Organization == org)
        .OrderByDescending(x => x.LastUpdate)
        .Skip(skip)
        .Select(ticket => new PublicTicketDto
        {
          Subject = ticket.Subject,
          LastUpdate = ticket.LastUpdate,
          Status = ticket.Status,
          // etc
        })
        .ToListAsync();
    return Ok(tickets);
}

Na EximiaCo, defendemos a categorização de APIs como internas ou externas. Nossa experiência é que APIs de propósito geral costumam gerar baixa adesão, além de outros prejuízos (como aumento de consumo da rede e requisições desnecessárias).

A categorização de APIs como propomos é um exemplo de como decisões arquiteturais mitigam os riscos de decisões ruins de design como a que estamos tratando nesse post.

Dissemos, no passado, que nem tudo que parece divida técnica, efetivamente, é. Entretanto, nesse caso, temos um problema oposto – uma dívida técnica que é difícil de ser percebida como tal.

Os “juros” dessa dívida técnica podem levar muito tempo para serem percebidos. Mas, quando finalmente a “cobrança” acontece, podem ter impactos difíceis de absorver.

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
9 Comentários
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Eduardo Spaki
Eduardo Spaki
4 anos atrás

Independente de ser publica a API, considero uma boa prática a exposição por meio de DTOs em casos simples (com CQRS pode ser query result).

Tiago Santos
Tiago Santos
4 anos atrás

É uma ótima abordagem, mas e quando estamos falando de um Enum ou de um objeto de valor voltado deste domínio?

Imagine que temos um enum TicketStatus, esse DTO poderia expor esse enum? E quanto a um objeto de valor?

Márcio Althmann
Márcio Althmann
4 anos atrás

Falando sobre os auto mappers. Um erro comum que eu vejo é na mistura auto mapper + EF + Lazy Collections. Várias e várias vezes fui analisar problemas de performance e encontrei coleções desnecessárias sendo entregues, e o culpado 90% das vezes é mapeamento inocente das coisas.

[]s

Gustavo Bigardi
Gustavo Bigardi
4 anos atrás

Esta abordagem é bem interessante e assunto de extrema importância, dado que versionamento de API acaba se tornando doloroso e gera problemas com integrações, outros times, quando fazemos a exposição de entidades de domínio diretamente.

No caso para exposição e complementando a pergunta do Tiago Santos, Enums e outros objetos complexos, devemos expor eles de forma mais primitiva possível (string, int, etc) do que com Enums ou devemos refletir estes tipos mais “complexos”?

Jorge
Jorge
4 anos atrás

Excelente parabéns e obrigado!

Luiz Henrique
Luiz Henrique
4 anos atrás

Gustavo, particularmente gosto mais da expressividade, logo, retornaria uma string com que o enum significa.
O XML tem uma vantagem que não sei se existe nas documentações das APIs REST que é uma espécie de dicionário dos valores possiveis sobre uma determinada propriedade, o que ajuda bastante na expressividade.

Sean Lennon
Sean Lennon
4 anos atrás

Também penso dessa forma, procuro sempre criar Methods Extensions das entidades, assim retorno apenas a propriedades que forem nescessária.

Takashi
Takashi
4 anos atrás

Queria saber sua opinião sobre essa solução que acabei pensando sobre o problema:

Seguindo essa mesma ideia de visualizar os impactos de uma mudança de um objeto de domínio em uma API pública, no meu caso de uma API GraphQL, eu acabei pensando em um fluxo diferente. NOTA: são casos de APIs desenvolvidas em JS/Ruby.

É comum que o objeto de domínio na maioria dos casos simples (ao menos nas APIs que modelei), acabe refletindo exatamente o modelo do GraphQL. Nesses casos, acabo gerando o type do GraphQL baseado nesse modelo com metaprogramming. Em JS/Ruby, o custo disso é baixo.

O GraphQL permite gerar o schema da API, nesse caso eu salvo isso em um arquivo json. Eu uso esse arquivo para na etapa de CI/CD, buscar os metadados da API atual do GraphQL e efetuar um diff entre os schemas. Com isso, eu consigo gerar uma descrição amigável das diferenças de API que tiveram do código comparando os schemas. Tem várias maneiras de mostrar essa informação, aqui uma ideia disto: https://github.com/kamilkisiela/graphql-inspector

Dessa maneira, a pessoa visualiza claramente os impactos do código que vai ter por toda a API se mexer no código. E se ver necessário criar o type do GraphQL explicitamente, o faz.

E para passar no CI/CD e aceitar as novas mudanças na API, tem que mandar gerar novamente os metadados no JSON.

Vale lembrar que no caso de usar um github, tem como colocar um Github Bot para em todo o PR, efetuar um comentário com todas as diffs que o PR vai efetuar na API (e atualizar o comentário conforme novos commits/mudanças no PR).

Portella
Portella
4 anos atrás

Meu comentário à cerca do assunto é um tanto quanto ácido.
Quando passível dos riscos mencionados essa atitude em sua grande maioria é tomada conscientemente, justificado pela redução na complexidade e sempre apoiado em fatores externos.
Para os casos onde não há consciência do autor. Defendo que houve a distribuição de um serviço sem o devido domínio do modelo envolvido. O que mais uma vez sugere a criação de um novo modelo com liberação das propriedades sob tutela até garantir segurança na extensibilidade.

AUTOR

Elemar Júnior
Fundador e CEO da EximiaCo atua como tech trusted advisor ajudando empresas e profissionais a gerar mais resultados através da tecnologia.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Arquivo

Pós-pandemia, trabalho remoto e a retenção dos profissionais de TI

CTO Consulting e Especialista em Execução em TI
9
0
Queremos saber a sua opinião, deixe seu comentáriox
Oferta de pré-venda!

Mentoria em
Arquitetura de Software

Práticas, padrões & técnicas para Arquitetura de Software, de maneira efetiva, com base em cenários reais para profissionais envolvidos no projeto e implantação de software.

Muito obrigado!

Deu tudo certo com seu envio!
Logo entraremos em contato

Não exponha objetos do domínio em uma “API pública”

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Não exponha objetos do domínio em uma “API pública”

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?