[GraphQL有什么问题] ...以及如何处理

上一篇文章中 ,我们研究了GraphQL类型系统中的不便之处。
现在我们将尝试击败其中一些。 请所有感兴趣的人在猫的陪伴下。


分区编号对应于我设法解决的问题。


1.2 NON_NULL输入


在这一点上,我们检查了在GraphQL中生成可为空的实现功能的歧义。


问题是它不允许从头开始实现部分更新的概念-REST体系结构中HTTP PATCH方法的类似物。 在对过去的材料的评论中,我因“ REST”思维而受到严厉批评。 我只能说CRUD架构使我不得不这样做。 而且我还没有准备放弃REST的好处,仅仅是因为“不要这样做”。 是的,并且找到了解决此问题的方法。


因此,回到问题所在。 众所周知,更新记录时的CRUD脚本如下所示:


  1. 从背面得到记录。
  2. 编辑的记录字段。
  3. 将记录发送到后面。

在这种情况下,部分更新的概念应允许我们仅发送回已更改的字段。
因此,如果我们以此方式定义输入模型


 input ExampleInput { foo: String! bar: String } 

然后在使用此值映射类型ExampleInput的变量时


 { "foo": "bla-bla-bla" } 

在具有以下结构的DTO上:


 ExampleDTO { foo: String #   bar: ?String #   } 

我们得到一个具有以下值的DTO对象:


 { foo: "bla-bla-bla", bar: null } 

并且在使用此值映射变量时


 { "foo": "bla-bla-bla", "bar": null } 

我们得到一个DTO对象,其值与上次相同:


 { foo: "bla-bla-bla", bar: null } 

也就是说,发生熵-我们丢失有关该字段是否从客户端传输的信息。
在这种情况下,不清楚最终对象的字段需要做什么:不要触摸它,因为客户端没有传递该字段,或者不要将其设置为null因为客户端传递了null


严格来说,GraphQL是RPC协议。 我开始考虑如何在背面进行此类操作以及应该调用什么程序以完全按照自己的方式进行操作。 在后端,我对字段进行了部分更新,如下所示:


 $repository->find(42)->setFoo('bla-bla-lba'); 

也就是说,除非需要更改此属性的值,否则我实际上不接触实体属性的设置器。 如果将其移至GraphQL模式,则会得到以下结果:


 type Mutation { entityRepository: EntityManager! } type EntityManager { update(id: ID!): PersitedEntity } type PersitedEntity { setFoo(foo: String!): String! setBar(foo: String): String } 

现在,如果需要,我们可以调用setBar方法,并将其值设置为null,或者不触摸此方法,则该值将不会更改。 因此,出现了一个很好的partial update实现。 不比臭名昭著的REST的PATCH还差。


在对过去的材料发表评论时, summerwind问:为什么我们需要进行partial update ? 我回答:有很多大领域。

3.多态性


经常发生的情况是,您需要提交给输入实体,这些输入实体属于“一个且相同”,但并不完全相同。 我将使用从过去的资料创建帐户的示例。


 #   AccountInput { login: "Acme", password: "***", subject: OrganiationInput { title: "Acme Inc" } } 

 #    AccountInput { login: "Acme", password: "***", subject: PersonInput { firstName: "Vasya", lastName: "Pupkin", } } 

显然,我们不能为一个参数提交具有这种结构的数据-GraphQL根本不允许我们这样做。 因此,您需要以某种方式解决此问题。


方法0-前额


首先想到的是输入的可变部分的分离:


 input AccountInput { login: String! password: Password! subjectOrganization: OrganiationInput subjectPerson: PersonInput } 

嗯……当我看到这样的代码时,我常常想起约瑟芬·帕夫洛夫娜。 这不适合我。


方法1-不是在额头上,而是在额头上
然后事实证明,我使用UUID来标识实体(我通常将其推荐给每个人-它会多次帮助您)。 这意味着我可以直接在客户端上创建有效实体,通过标识符将它们绑定在一起,然后将它们分别发送到后端。


然后,我们可以本着以下精神做一些事情:


 input AccountInput { login: String! password: Password! subject: SubjectSelectInput! } input SubjectSelectInput { id: ID! } type Mutation { createAccount( organization: OrganizationInput, person: PersonInput, account: AccountInput! ): Account! } 

或者,事实证明它更方便(为什么更方便,我将在生成用户界面时告诉您),将其分为不同的方法:


 type Mutation { createAccount(account: AccountInput!): Account! createOrganization(organization: OrganizationInput!): Organization! createPerson(person: PersonInput!) : Person! } 

然后,我们将需要发送一个请求来创建createAccount和createOrganization / createPerson
一批。 值得注意的是,批处理必须包装在事务中。


方法2-魔术标量
诀窍在于GraphQL中的标量不仅是IntStingFloat等。 通常这就是一切(当然,虽然JSON可以处理)。
然后我们可以简单地声明一个标量:


 scalar SubjectInput 

然后,在上面写上您的处理程序,它不会蒸熟。 然后,我们可以轻松地将变量字段滑入输入。


选择哪种方式? 我同时使用了两者,并为自己制定了一条规则:
如果父实体是孩子的聚合根,则选择第二种方法,否则选择第一种。


4.泛型。


这里的一切都很琐碎,我没有想到比代码生成更好的任何东西。 如果没有Rails(railt / sdl程序包),我将无法(或者宁愿用拐杖做同样的事情)。 诀窍是Rail允许您定义文档级指令(规格表中的指令没有这样的位置)。


 directive @example on DOCUMENT 

也就是说,伪指令除了被调用的文档外没有附加到任何东西上。


我介绍了以下指令:


 directive @defineMacro(name: String!, template: String!) on DOCUMENT directive @macro(name: String!, arguments: [String]) on DOCUMENT 

我认为没有人需要解释宏的本质...


现在就这些了。 我认为这种材料不会像过去那样引起很大的噪音。 一样,标题很漂亮(黄色))


在上一篇文章的评论中,哈布罗夫斯克居民因共享访问权而淹死……因此下一篇文章将涉及授权。

Source: https://habr.com/ru/post/zh-CN435690/


All Articles