Blog

🤔 GraphQL Deveria Ser Diferente para Usuários Diferentes?

Leonardo Losoviz
Por Leonardo Losoviz ·

GraphQL é uma interface para recuperar dados de alguma origem, com a spec GraphQL definindo os requisitos para a interface. Enquanto esses requisitos forem satisfeitos, o GraphQL não se importa com como isso é realizado. O servidor GraphQL pode então ser implementado em JavaScript usando promises, usando uma arquitetura concorrente baseada em Golang, mapeado para um arquivo Excel, ou o que for, e tudo isso pode ser implementações válidas da spec GraphQL.

GraphQL fica entre o cliente e os serviços backend

Como o motor do servidor é implementado não é importante para a execução bem-sucedida de uma requisição GraphQL, pois a interação entre cliente e servidor é sempre a mesma, ocorrendo pelo envio de uma query GraphQL usando uma sintaxe definida, e obtendo uma resposta correspondente em formato JSON.

Agora, quando digo que a implementação não é importante, quero dizer isso da perspectiva do usuário da API, que simplesmente pretende obter dados do servidor. Como os dados retornados foram produzidos não é de interesse algum.

Mas a situação muda para o desenvolvedor do lado do servidor que trabalha na API, para quem os detalhes da implementação são de fato muito importantes. Se eu codifico minha API GraphQL em PHP, então farei o meu melhor para que minha API seja resolvida da forma mais eficiente possível, e para ter um design arquitetural o mais elegante possível, usando as capacidades oferecidas pelo PHP.

PHP vs Java vs JavaScript

Então, temos um possível conflito de interesses entre a necessidade de proteger a API e as capacidades esperadas pelos desenvolvedores que trabalham na API, que não querem que funcionalidades suportadas pela linguagem subjacente lhes sejam retiradas (como a capacidade de executar código recursivo).

Esse conflito ficou evidente na issue #929: Allow recursive references in fragments, que argumenta que o GraphQL não deveria proibir recursões em fragments.

Em um meetup passado do grupo de trabalho GraphQL, Roman, que é o desenvolvedor que levantou a issue, expressou por que está em desacordo com a limitação imposta pela spec:

Sou um desenvolvedor do lado do servidor, e sinto que a spec fala demais sobre a execução do lado do servidor, enquanto deveria se concentrar no que o cliente quer receber — não no como

A regra que proíbe recursões em fragments foi justificada com a premissa de manter a API pública segura. Afinal, o GraphQL foi criado pelo Facebook para entregar dados à sua aplicação voltada ao público, e os usuários não deveriam conseguir explorar uma falha no design da API que poderia derrubar o serviço.

O criador do GraphQL, Lee Byron, expressou três grandes preocupações:

recursão infinita; as limitações não seriam apenas especificação — como e quando deveria parar

validação de dados; retornar o mesmo valor várias vezes, como isso é representado nos dados. Idealmente você quer detectar que é cíclico e parar imediatamente, mas alguns servidores não conseguem detectar isso e podem fazer o loop muitas vezes antes de detectar que algo deu errado e parar

qual é o custo de não ter isso; isso justifica esses problemas? Não; é sempre possível especificar o número de níveis de profundidade na sua query — essa é efetivamente a versão "desugared" do que faríamos se fôssemos lidar com isso no GraphQL

Partindo de suas próprias perspectivas, tanto Roman quanto Lee estão certos. Lee Byron está preocupado com a segurança da API GraphQL pública. Evitar fragments recursivos é justificado para garantir que nenhum ator malicioso possa derrubar o sistema executando um loop cíclico infinito na query, e até eliminar a chance de uma equipe fazer "self-DDoSing", o que poderia acontecer se publicassem involuntariamente uma query que paralisa o sistema.

Roman, no entanto, está preocupado com as limitações às suas próprias capacidades de criar uma API GraphQL. Porque Roman pode ser o único consumidor de sua API (ou seja, uma API privada que não está exposta a usuários), ou porque seu servidor pode ter a capacidade de detectar e parar ciclos recorrentes, ele acredita que a limitação do GraphQL é prejudicial e injustificável.

No cerne da discussão, o problema não é se fragments recursivos devem ser permitidos ou não, mas algo mais fundamental: Quem é o público-alvo do GraphQL? Se não for um único grupo, uma única especificação de API poderia satisfazer os requisitos de todos os diferentes stakeholders? E se o conflito não puder ser evitado, pode pelo menos ser de alguma forma mitigado?

Vamos explorar essas questões.

Quem é o público-alvo do GraphQL?

GraphQL está sendo usado por diferentes tipos de stakeholders, entre os quais podemos identificar:

1. Usuários da API: Aqueles que consomem dados de algum endpoint GraphQL, por qualquer razão. Por exemplo, todos podemos ser usuários da API GraphQL pública do GitHub, para recuperar dados relativos aos nossos repositórios GitHub.

2. Devs do lado do cliente: Aqueles que criam aplicações do lado do cliente alimentadas por algum endpoint GraphQL. Por exemplo, desenvolvedores que constroem sites com Gatsby dependem do GraphQL para buscar o conteúdo do site.

3. Devs backend: Aqueles que criam os resolvers para a API GraphQL.

Além disso, devemos notar que a API GraphQL pode ser pública ou privada:

API pública: Como qualquer pessoa tem acesso ao endpoint GraphQL, devemos nos preocupar com medidas de segurança para evitar ataques de atores maliciosos.

API privada: Como apenas atores autorizados têm acesso à API, não há riscos de segurança inerentes, e o self-DDoSing pode ser facilmente evitado com boas práticas de codificação.

Uma única especificação de API satisfaz os requisitos de todos os stakeholders?

A issue levantada por Roman pode ser interpretada assim: "Se minha API GraphQL é privada, e sei exatamente o que estou fazendo (tendo 100% de certeza de que meu código funcionará como esperado e que nenhuma execução travada será produzida), então por que não posso usar recursões em fragments?"

Recursões @ xkcd

Um exemplo dessa situação ocorre sempre que usamos um framework baseado em GraphQL para construir sites estáticos (como Gatsby, Next.js ou RedwoodJS), porque a API GraphQL frequentemente será privada, e não podemos inadvertidamente fazer DDoS da nossa aplicação e sofrer consequências adversas (no máximo ela vai travar ao construir o site estático em um ambiente de desenvolvimento ou staging).

Desenvolvedores que usam a configuração acima podem muito bem se perguntar por que a spec GraphQL os proíbe de usar funcionalidades benéficas, que não têm nenhuma consequência adversa para a configuração deles.

Em conclusão, ao proibir fragments recursivos, a spec GraphQL está impondo uma medida de segurança que se aplica a uma seleção de todos os usos potenciais do GraphQL, não a todos, para estar do lado seguro.

A spec GraphQL poderia satisfazer melhor todos os stakeholders?

Se diferentes stakeholders têm requisitos diferentes, como a spec GraphQL pode satisfazer todos eles? (A ideia é evitar fazer um fork da spec e produzir versões personalizadas para públicos específicos.)

Vamos explorar algumas ideias, onde a primeira precisaria passar pelo processo de contribuição à spec, enquanto a segunda não.

Feature-toggle no nível da spec GraphQL

Uma possível rota a seguir é fazer com que a spec "sugira" mas não "imponha" regras. Nesse caso, a regra que proíbe recursões em fragments poderia ser fortemente sugerida, mas a funcionalidade ainda seria aceita.

Agora, essa solução mudaria a condição padrão dos fragments recursivos de "obrigatório" para "opcional", o que produziria duas consequências negativas:

  • A API seria insegura por padrão (o cenário que Lee Byron quer evitar)
  • Produziria uma breaking change, pois uma query proibida passaria a ser permitida

Então, seria melhor inverter a opção, mantendo as recursões em fragments ainda proibidas por padrão, mas dando a possibilidade de ativar um feature-flag que desabilita esse comportamento. Como a funcionalidade precisa ser explicitamente desabilitada, isso só será feito por admins que sabem o que estão fazendo.

Como a funcionalidade é mais valiosa em certas configurações, servidores e frameworks GraphQL poderiam decidir se/como/quando oferecer a configuração. Por exemplo, o Gatsby poderia exibir de forma destacada a opção por meio de alguma UI ao criar sites estáticos, e ocultá-la caso contrário.

A ideia geral é que a spec GraphQL suporte "funcionalidades habilitadas mas opcionais", que podem ser habilitadas/desabilitadas via configuração, e cujo estado padrão é o que já têm na spec.

Proibir fragments recursivos seria uma delas, e poderia haver outras funcionalidades assim também, como um tipo Map, que não foi aceito na spec por Lee Byron porque:

Há trade-offs significativos entre um tipo Map e uma lista de pares chave/valor. Um problema é a paginação sobre a coleção. Listas de valores podem ter regras de paginação claras, enquanto Maps, que frequentemente têm pares chave-valor não ordenados, são muito mais difíceis de paginar.

Outro problema é o uso. Na maioria das vezes, Map é usado dentro de APIs onde um campo do valor está sendo indexado, o que, na minha opinião, é um anti-pattern de API, pois a indexação é uma questão de armazenamento e uma questão de cache do cliente, mas não uma questão de transporte. Esse anti-pattern me preocupa. Embora haja alguns bons usos para Maps em APIs, temo que o uso comum seja para esses anti-patterns, então estou sugerindo proceder com cautela.

Lee Byron expressou seu receio de que a funcionalidade seja usada como um anti-pattern. No entanto, ele também reconheceu que existem bons usos para ela. Então, como a issue reuniu muito suporte da comunidade (com mais de 150 👍), os desenvolvedores poderiam ter a opção de habilitar explicitamente a adição de um tipo Map aos seus schemas, e lidar com as consequências.

Feature-toggle pelos servidores GraphQL

Se a proposta acima não reunir suporte por ser muito arriscada para a spec GraphQL, uma alternativa é implementá-la no nível do servidor GraphQL. Então, servidores GraphQL poderiam fornecer uma funcionalidade personalizada que desabilita as recursões em fragments.

Generalizando a ideia, servidores GraphQL poderiam oferecer a possibilidade de desabilitar certas funcionalidades da spec, e habilitar outras que estão faltando na spec. Para que esse comportamento não produza surpresas, os servidores devem garantir que o estado padrão seja o exigido pela spec, e o admin da API deve estar plenamente ciente das consequências de ativar a funcionalidade. (Essa é a estratégia seguida pelo Gato GraphQL para suas "innovative features".)

Conclusão

Como o GraphQL tem se tornado cada vez mais popular, novos frameworks que suportam novas capacidades o tornaram parte de suas stacks, e novos stakeholders (e novos tipos deles) se envolveram. Então, uma especificação inicialmente criada pelo Facebook para definir como suas aplicações obteriam dados de seus servidores precisa lidar cada vez mais com mais casos de uso.

É inevitável que conflitos surjam, onde um conjunto de stakeholders precisa de uma funcionalidade que é contraproducente, ou até prejudicial, para outros stakeholders, como é o caso com fragments recursivos. O que pode ser feito para melhorar a situação e evitar que stakeholders insatisfeitos fiquem desapontados com o GraphQL?

Argumentei que a spec poderia oferecer a chance de "desabilitar" uma funcionalidade, permitindo que admins que sabem o que estão fazendo removam algumas limitações para satisfazer seus próprios requisitos. Agora, eu mesmo não concordo com essa solução, mas a trago à tona mesmo assim porque essa discussão precisa acontecer. Como essa ideia é controversa, uma alternativa melhor é que servidores GraphQL forneçam esse comportamento por meio de funcionalidades personalizadas, que devem ser explicitamente habilitadas.


Assine nossa newsletter

Fique por dentro de todas as atualizações do Gato GraphQL.