我使用 Apollo-iOS 后觉得好的地方。不好的地方
这篇文章是2020年iOS#2降临日历的第18天。
简要概括
我目前在工作中使用Apollo-iOS。
在参考了网络上的各种网站后,我成功实施了它,但回想起来,虽然有很多关于如何使用Apollo-iOS的文章,但实际上很少看到关于使用它的感受,所以这次我主观地写下了我认为Apollo-iOS很棒的地方和我认为不太好的地方。
希望我的文章能让对是否要引入GraphQL感到迷茫的人一瞥,如果能在流览中看到就很高兴。
因为根据需求和环境不同,可能有些情况不适用。
另外,如果有任何关于我的知识范围之外的错误或意见,请在Twitter上留言,我会很开心的。
GraphQL是什么
简而言之,它类似于用于服务器和客户端通信的协议,但并不特指于某个库或工具。
由于已经在各种网站上详细介绍过,因此我会附上一些相关链接作为参考。
GraphQL.org
让前端工程师了解Apollo Client
GraphQL初学者与Web API一起学习
阿波罗iOS是什么。
这是一个用于iOS应用程序与服务器进行GraphQL交互的库。以下是一个可供参考的链接。
Apollo-iOS
Apollo-iOS文档
这个地方很棒
与服务器工程师的认识不一致问题得到解决
这段文字是关于Apollo-iOS和GraphQL的,请通过引入诸如GraphiQL或Playground等工具,可以在浏览器上轻松尝试请求,并通过附带的文档功能方便地了解API的规范。您可以通过《初识GraphQL》一书中提到的示例来实际操作Playground。
通过使用GraphQL提供的工具,可以更高效地进行API规范的审核,先紧密规定接口规范,然后服务器和客户端分别进行实现,这种以模式为驱动的方式非常棒。
此外,关于常见操作例如分页和身份验证,GraphQL社区已经制定了实现模式,通过参考这些模式可以保持实现方法和规范的一致性,我认为这一点也非常好。
通过拦截器,通信处理可以更容易地进行定制。
以下是关于Apollo-iOS的内容。
在Apollo-iOS中,将通信、解析、缓存等处理操作定义为拦截器(Interceptor),并将它们作为一个数组进行注册,以便按照顺序执行处理操作。
默认情况下,LegacyInterceptorProvider已定义,并且在其中定义了拦截器的数组。
通过将符合Interceptor协议的结构体嵌入自定义的InterceptorProvider中,可以在通信之前和之后执行任意处理。
比如,您可以很容易地在请求头中添加访问令牌。
不太好的地方
错误未在架构中定义。
在GraphQL中,错误以以下的JSON格式作为HTTP状态码200进行返回。消息和位置的值会被自动存储在库中。扩展中存储了服务器工程师设置的任意信息。
{
"errors": [
{
"message": "Cannot query field \"n\" on type \"Lift\".",
"locations": [
{
"line": 3,
"column": 5
}
],
"extensions": {
"code": “HogeError”
“message”: “HogeMessage”
}
}
]
}
错误信息与正常情况不同,不会在架构等方面明确提及。
此外,GraphQL 规范没有严格规定。
自定义错误,如认证错误和其他业务逻辑错误应该存储在扩展中返回,但需要与服务器工程师协商并统一意识,以确定返回的时间和结构。
要在Apollo-iOS中访问扩展内的代码,请按照以下方式操作。
response?.parsedResponse?.errors?[0].extensions?[“code”] == “HogeError”
如果在这里,extensions内的结构不符合预期,或者错拼了HogeError,那么错误处理就会失败。所以,就个人而言,我希望错误也能尽可能地通过模式进行管理,并且能够进行类型安全处理。
此外,需要注意的是,这种类型的错误返回,在200系列中会被包装在Result.success中,并返回给处理程序,而不是Result.failure。
需要查看Apollo iOS的内部实现,以了解每个拦截器的错误类型。
每个拦截器都定义了自己独特的错误类型(例如:MaxRetryInterceptor),需要检查每个拦截器的内部实现,将错误强制转换为各自特定的类型并进行适当的处理。
只要了解结构,就没有太大的问题,但因为需要阅读库内的代码,所以觉得有些困难。
DDD的思想和GraphQL的特点在某些方面不相匹配
作为GraphQL的理念之一,它可以“仅获取所需信息(按照每个页面)”。但是,如果以DDD思想为基础构建架构,则我认为GraphQL的理念与以下方面存在冲突。
-
- 画面ごと構造体を作る == 画面駆動になってしまう。
-
- 自動生成されたコードをそのままViewで使いたくない。本来データ層だけをApollo-iOSに依存させたい。
-
- 自動生成されたコードのモックのデータが用意が難しくてドメイン層のテストがしづらい。
- クエリのプロパティをフラグメントで共通化するとコードの構造も変わる。。API都合の変更でView側に修正を加えたくない。
最終的に,我們將在應用程序內將其映射為易於使用的域對象。我認為這將需要具有可選值,以在多個屏幕上使用。同時,為了進行映射,我們需要處理難以處理的自動生成代碼。
此外,如果要进行映射测试,就需要创建自动生成的结构体的模拟。
为了创建模拟,需要准备传递给以下初始化器的参数。
public init(unsafeResultMap: [String: Any?]) {
self.resultMap = unsafeResultMap
}
创建参数的步骤如下。
-
- 自动生成的代码中含有以下属性:
-
- public private(set) var resultMap: [String: Any?]
-
- 在运行时,在调试控制台上使用 “po print(resultMap)” 打印值。
- 1.的输出应该可以作为 Swift 代码直接运行,如果将其定义为 [String: Any]? 类型的变量,应该可以通过编译。(由于代码量巨大,如果不明确指定类型可能无法通过编译。)
拦截器的单元测试很困难
由于 Interceptor 处理的代码在以下方法内部,所以为了进行测试,需要准备参数的模拟。这四个参数都是由自定义类型构成的,为了创建正确的模拟,需要了解内部代码,我认为这是相当困难的。
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void)
顺便提一下,关于断言,我认为可以创建一个继承自RequestChain的模拟对象,并将其作为参数传递,然后捕获执行chain.proceedAsync()或chain.handleErrorAsync()等操作。
在查询(Query)和变更(Mutation)中执行的方法是不同的。
在GraphQL中,根据特性主要存在三种操作。其中,查询(Query)用于从服务器获取值,变更(Mutation)用于改变服务器的值。
在Apollo-iOS中,需要使用fetch来发起查询请求,使用perform来发起变更请求。但是,如果简单地同时使用fetch和perform,就需要实现两个相似的处理过程,为了实现共通化,需要编写一些复杂的代码。
在自动生成的代码中,进行强制解包的盛大庆典。
我没有因为这个原因崩溃过。但是,如果可能的话,我希望不要强制解包。