No artigo anterior , examinamos pontos inconvenientes no sistema de tipos GraphQL.
E agora vamos tentar derrotar alguns deles. Todos os interessados, por favor, sob o gato.
A numeração da partição corresponde aos problemas com os quais eu consegui lidar.
Neste ponto, examinamos a ambiguidade que gera um recurso de implementação anulável no GraphQL.
E o problema é que ele não permite implementar o conceito de atualização parcial do zero - um análogo do método HTTP PATCH na arquitetura REST. Nos comentários sobre material passado, fui fortemente criticado pelo pensamento "REST". Só posso dizer que a arquitetura CRUD me obriga a isso. E eu não estava pronto para desistir dos benefícios do REST, simplesmente porque "não faça isso". Sim, e uma solução para este problema foi encontrada.
E assim, de volta ao problema. Como todos sabemos, o script CRUD ao atualizar o registro se parece com o seguinte:
- Tenho um registro nas costas.
- Campos de registro editados.
- Enviou um registro para trás.
O conceito de atualização parcial, neste caso, deve permitir o envio apenas dos campos que foram alterados.
Então, se definirmos um modelo de entrada dessa maneira
 input ExampleInput { foo: String! bar: String } 
ao mapear uma variável do tipo ExampleInput com este valor
 { "foo": "bla-bla-bla" } 
em um DTO com esta estrutura:
 ExampleDTO { foo: String  
nós obtemos um objeto DTO com este valor:
 { foo: "bla-bla-bla", bar: null } 
e ao mapear uma variável com esse valor
 { "foo": "bla-bla-bla", "bar": null } 
obtemos um objeto DTO com o mesmo valor da última vez:
 { foo: "bla-bla-bla", bar: null } 
Ou seja, ocorre entropia - perdemos informações sobre se o campo foi transmitido do cliente ou não.
Nesse caso, não está claro o que precisa ser feito com o campo do objeto final: não toque nele porque o cliente não passou o campo ou defina-o como null porque o cliente passou null .
A rigor, o GraphQL é um protocolo RPC. E comecei a pensar em como faço essas coisas nas costas e em quais procedimentos devo chamar para fazer exatamente da maneira que quero. E no back-end, faço uma atualização parcial dos campos como este:
 $repository->find(42)->setFoo('bla-bla-lba'); 
Ou seja, eu literalmente não toco no setter de uma propriedade da entidade, a menos que precise alterar o valor dessa propriedade. Se você mudar isso para o esquema GraphQL, obterá o seguinte resultado:
 type Mutation { entityRepository: EntityManager! } type EntityManager { update(id: ID!): PersitedEntity } type PersitedEntity { setFoo(foo: String!): String! setBar(foo: String): String } 
Agora, se quisermos, podemos chamar o método setBar e definir seu valor como nulo, ou não tocar nesse método, e o valor não será alterado. Assim, uma boa implementação de partial update é lançada. Não é pior que PATCH do famoso REST.
Nos comentários de material passado, summerwind perguntou: por que precisamos de uma partial update ? Eu respondo: existem MUITO grandes campos.
3. Polimorfismo
Muitas vezes acontece que você precisa enviar para as entidades de entrada que são "iguais e iguais", mas não completamente. Vou usar o exemplo da criação de uma conta a partir de material passado.
 #   AccountInput { login: "Acme", password: "***", subject: OrganiationInput { title: "Acme Inc" } } 
 #    AccountInput { login: "Acme", password: "***", subject: PersonInput { firstName: "Vasya", lastName: "Pupkin", } } 
Obviamente, não podemos enviar dados com essa estrutura para um argumento - o GraphQL simplesmente não nos permite fazer isso. Então, você precisa resolver de alguma forma esse problema.
Método 0 - testa
A primeira coisa que vem à mente é a separação da parte variável da entrada:
 input AccountInput { login: String! password: Password! subjectOrganization: OrganiationInput subjectPerson: PersonInput } 
Hmm ... quando vejo esse código, muitas vezes me lembro de Josephine Pavlovna. Isso não combina comigo.
Método 1 - não na testa, mas na testa
Em seguida, soube que, para identificar entidades, uso o UUID (geralmente recomendo a todos - isso ajudará mais de uma vez). E isso significa que eu posso criar entidades válidas diretamente no cliente, vinculá-las pelo identificador e enviá-las para o back-end, separadamente.
Então podemos fazer algo no espírito de:
 input AccountInput { login: String! password: Password! subject: SubjectSelectInput! } input SubjectSelectInput { id: ID! } type Mutation { createAccount( organization: OrganizationInput, person: PersonInput, account: AccountInput! ): Account! } 
ou, que acabou sendo ainda mais conveniente (por que é mais conveniente, vou lhe dizer quando chegarmos à geração de interfaces de usuário), divida-o em diferentes métodos:
 type Mutation { createAccount(account: AccountInput!): Account! createOrganization(organization: OrganizationInput!): Organization! createPerson(person: PersonInput!) : Person! } 
Em seguida, precisaremos enviar uma solicitação para createAccount e createOrganization / createPerson
um lote. Vale ressaltar que o processamento do lote deve ser agrupado em uma transação.
Método 2 - o escalar mágico
O truque é que o escalar no GraphQL não é apenas Int , Sting , Float , etc. Isso geralmente é tudo (bem, enquanto o JSON pode lidar com isso, é claro).
Então podemos simplesmente declarar um escalar:
 scalar SubjectInput 
Em seguida, escreva seu manipulador nele e ele não ficará no vapor. Em seguida, podemos deslizar facilmente os campos variáveis para a entrada.
Qual o caminho a escolher? Eu uso os dois e desenvolvi uma regra para mim:
Se a entidade pai for uma Raiz Agregada para o filho, escolho o segundo método, caso contrário, o primeiro.
4. Genéricos.
Tudo é trivial aqui e eu não criei nada melhor do que a geração de código. E sem o Rails (pacote railt / sdl) eu não teria conseguido (ou melhor, teria feito o mesmo com muletas). O truque é que o Rail permite definir diretivas em nível de documento (não existe essa posição para diretivas na folha de especificações).
 directive @example on DOCUMENT 
Ou seja, as diretivas não estão anexadas a nada além do documento em que são chamadas.
Eu introduzi as seguintes diretrizes:
 directive @defineMacro(name: String!, template: String!) on DOCUMENT directive @macro(name: String!, arguments: [String]) on DOCUMENT 
Eu acho que ninguém precisa explicar a essência das macros ...
Por enquanto é tudo. Não acho que esse material cause tanto ruído quanto o passado. Mesmo assim, o título era bastante "amarelo")
Nos comentários do artigo anterior, os residentes de Khabrovsk se afogaram por compartilhar o acesso ... então o próximo artigo será sobre autorização.