我们如何将遗留项目转换为GraphQL

哈Ha 我叫Anton Potapov,我是FINCH的iOS开发人员。 今天,我想详细讨论如何将移动项目转换为GraphQL,并描述这种方法的优缺点。 让我们开始吧。

简要介绍


“遗产。” 我想每个人都听到了这个可怕的词,而且大多数人面对面见了他。 因此,您无需讲述将新功能和出色功能集成到项目中的困难程度。

图片

我们的项目之一是一家大型彩票公司的申请(自NDA以来我就无法透露姓名)。 最近,我需要将其转换为GraphQL,而又不会迷失方向。

该项目非常大-乍一看,必须花费至少200-300个小时,但这超出了所有可能的两周冲刺。 我们还不能将整个sprint分配给一项任务,因为它还有一些辅助功能,其重要性不亚于GraphQL。
我们考虑了很长时间之后,决定逐步,逐个模型地翻译项目。 使用这种方法,过渡将很顺利,并且200-300小时将分布在几个sprint中。

技术要点


为了处理该项目,我使用了Apollo库。 Habré上已有一篇文章介绍了使用它的所有细微之处,因此我不再赘述。 我提前警告您-使用代码生成的模型不是很方便,最好将其保留为“网络”。 每个实体都包含__typename:字符串字段,奇怪的是,它返回类型名称。

任务分解


首先要做的是确定我们将哪个传统模型转换为GraphQL。 就我而言,翻译大型GameInfo模型之一并嵌入其中的DrawInfo是合乎逻辑的。

  • GameInfo-包含有关彩票游戏和抽奖的信息。
  • DrawInfo-包含循环数据

完成选择之后,我决定用GraphQL获得的新旧模型初始化旧的旧模型。 使用这种方法,不必替换使用旧模型的应用程序部分。 完全完成以前的模型是有风险的-并不是项目会像以前那样运作,并且会花费太多时间。

总的来说,可以区分GraphQL实现的三个阶段:

  • 客户创造;
  • 为传统模型创建一个初始化器;
  • 用GraphQL替换API请求。

GraphQLClient


与任何客户端一样,GraphQLClient应该具有fetch方法,此后它将加载我们需要的数据。

我认为,GraphQLClient的访存方法应采用一个枚举,基于该枚举将执行相应的请求。 这种方法将使我们能够轻松接收所需实体甚至屏幕的数据。 如果请求接受参数,则它们将作为关联值传递。 通过这种方法,可以更轻松地使用GraphQL和创建灵活的查询。

enum GraphQLRequest { case image(ImageId?) } 

我们的自定义GraphQL客户端应包含针对现有功能而自定义的ApolloClient,以及上述提取加载方法。

 func fetch<Response>(requestType: GraphQLRequest, completion: @escaping (QLRequestResult<Response>) -> Void) 

什么是QLRequestResult? 很普通

  typealias QLRequestResult<Response> = Result<Response, APIError> 

fetch方法允许您通过协议访问客户端并在requestType上执行切换。 根据requestType,可以使用相应的私有加载方法,在该方法中,可以将结果模型转换为旧模型。 例如:

  private func fetchGameInfo<Response>(gameId: String? = "", completion: @escaping (QLRequestResult<Response>) -> Void) { //   Apollo  let query = GetLotteriesQuery(input: gameId) //       apolloClient.fetch(query: query, cachePolicy: .returnCacheDataAndFetch, queue: .global()) { result in switch result { case .success(let response): guard let gamesQL = response.data?.games, let info = gamesQL.info else { completion(.failure(.decodingError)) return } //    let infos: [Games.Info] = info.compactMap({ gameInfo -> Games.Info? in guard let gameIdRaw = gameInfo?.gameId, let gameId = GameId(rawValue: gameIdRaw), let bonusMultiplier = gameInfo?.bonusMultiplier, let maxTicketCost = gameInfo?.maxTicketCost, let currentDraws = gameInfo?.currentDraws else { return nil } let currentDrawsInfo = Games.Info.CurrentDrawsInfo(currntDraw: currentDraws) let gameInfo = Games.Info(gameId: gameId, bonusMultiplier: bonusMultiplier, maxTicketCost: maxTicketCost, currentDraws: currentDrawsInfo) return gameInfo }) //    let games = Games(info: infos) guard let response = games as? Response else { completion(.failure(.decodingError)) return } completion(.success(response)) case .failure(let error): … } } } 

结果,我们得到了从GraphQL获得的现成的旧模型。

标量类型


在GraphQL文档中,标量类型描述如下:“标量类型是唯一的标识符,通常用于重新选择对象或作为缓存的键。” 为了迅速,标量类型可以很容易地与类型别名关联。

在编写客户端时,我遇到一个问题,在我的方案中有一个标量类型Long,这实际上是

 typealias Long = Int64 

一切都会好起来,但是Apollo会自动将所有用户标量强制转换为String,这会导致崩溃。 我解决了这样的问题:

  • 我在代码生成脚本中添加了–passthroughCustomScalars
  • 为typealias创建了一个单独的文件
  • 已添加到文件
     public typealias Long = Int64 

将来,对于每种标量类型,您都需要添加一个新的类型别名。 在Apollo 14.0版中,他们添加了“对Int自定义标量的支持”。 如果要避免使用此方法和代码生成中的标志,请在Apollo git中检查该问题的解决方案。

利弊


为什么这种方法好呢? 关于删除所有旧模型的方法,可以快速过渡到使用GraphQL。

将来,当我们需要获取任何屏幕/模型的数据时,可以转向GraphQLClient并获取必要的数据。

缺点:

  • 转换客户端中的模型类型后,一个人可以转移到另一个实体,因为 处理数据不是客户的责任。
  • 扩展GraphQLClient。 添加新查询后,我们的班级将越来越大。 解决方案之一是扩展,其中将描述加载方法,我在GraphQLClient一章中进行了讨论。

结论


通常,对于编写良好的客户端而言,切换到GraphQL可以快速而轻松。 我花了大约三天= 24个工作小时。 在这段时间里,我设法创建了一个客户端,转换了模型并重新创建了GameInfo模型。 所描述的方法不是灵丹妙药,而是纯粹由我决定的,是在短时间内实施的。

如果您当中有GraphQL专家,建议您分享经验,并告诉您在大型项目中使用GraphQL + Apollo有多方便。 还是游戏不值得一试?

感谢您的关注!

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


All Articles