Como traduzimos o projeto legado para o GraphQL

Oi Habr. Meu nome é Anton Potapov, sou desenvolvedor iOS da FINCH . Hoje, quero falar em detalhes sobre como traduzir um projeto móvel no GraphQL, descrever os prós e contras dessa abordagem. Vamos começar.

Breve introdução


"Legado". Acho que todo mundo ouviu essa palavra terrível e a maioria o conheceu cara a cara. Portanto, você não precisa dizer o quão difícil é integrar novos e ótimos recursos ao seu projeto legado.

imagem

Um de nossos projetos é uma inscrição para uma grande empresa de loteria (não posso dar um nome desde o NDA). Recentemente, eu precisava traduzi-lo para o GraphQL sem perder a cabeça.

O projeto é muito grande - à primeira vista, era necessário gastar pelo menos 200 a 300 horas, mas isso vai além de todos os sprints de duas semanas possíveis. Também não foi possível alocar o sprint inteiro para apenas uma tarefa, pois havia recursos secundários não menos importantes que o GraphQL.
Pensamos durante muito tempo no que fazer e decidimos traduzir o projeto passo a passo, modelo por modelo. Com essa abordagem, a transição será suave e 200 a 300 horas serão distribuídas por vários sprints.

Pontos técnicos


Para trabalhar no projeto, usei a biblioteca Apollo. Já existe um artigo sobre Habré que descreve todas as sutilezas de trabalhar com ele, então não vou repeti-lo novamente. Eu aviso-o com antecedência - trabalhar com um modelo gerado por código não é muito conveniente e é melhor mantê-lo como uma "rede". Cada entidade contém um campo __typename: String, que, curiosamente, retorna um nome de tipo.

Decomposição de tarefas


A primeira coisa a fazer é determinar qual modelo Legacy iremos traduzir para o GraphQL. No meu caso, era lógico traduzir um dos modelos massivos de GameInfo e o DrawInfo incorporado nele.

  • GameInfo - contém informações sobre jogos e sorteios de loteria.
  • DrawInfo - contém dados sobre a circulação

Tendo finalizado a escolha, decidi inicializar os antigos modelos legados com os novos obtidos com o GraphQL. Com essa abordagem, não é necessário substituir as partes do aplicativo em que o modelo antigo é usado. Concluir completamente os modelos anteriores era arriscado - não o fato de que o projeto funcionaria como antes e levaria muito tempo.

No total, três estágios de implementação do GraphQL podem ser distinguidos:

  • Criação de clientes;
  • Criando um inicializador para o modelo herdado;
  • Substituindo solicitações de API pelo GraphQL.

GraphQLClient


Como em qualquer cliente, o GraphQLClient deve ter um método de busca, após o qual carregará os dados necessários.

Na minha opinião, o método de busca para GraphQLClient deve receber um enum, com base no qual a solicitação correspondente será executada. Essa abordagem nos permitirá receber facilmente dados da entidade desejada ou até da tela. Se a solicitação aceitar parâmetros, eles serão transmitidos como valores associados. Com essa abordagem, é mais fácil usar o GraphQL e criar consultas flexíveis.

enum GraphQLRequest { case image(ImageId?) } 

Nosso cliente GraphQL personalizado deve conter o ApolloClient personalizado para os recursos existentes, bem como o método de carregamento de busca mencionado acima.

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

O que é QLRequestResult? É comum

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

O método de busca permite acessar o cliente através do protocolo e executar uma alternância em requestType. Dependendo do requestType, você pode usar o método de carregamento particular correspondente, no qual pode converter o modelo resultante no antigo. Por exemplo:

  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): … } } } 

Como resultado, obtemos um modelo antigo pronto, obtido do GraphQL.

Tipo escalar


Na documentação do GraphQL, um tipo escalar é descrito da seguinte maneira: "Um tipo escalar é um identificador exclusivo que é frequentemente usado para selecionar novamente um objeto ou como uma chave para um cache". Para agilidade, um tipo escalar pode ser facilmente associado a tipealias.

Ao escrever um cliente, tive um problema que, em meu esquema, havia um tipo escalar Long, que era essencialmente

 typealias Long = Int64 

Tudo ficaria bem, mas o Apollo lança automaticamente todos os escalares de usuário para String, o que causa uma falha. Eu resolvi o problema assim:

  • Eu adicionei no script codegen –passthroughCustomScalars
  • Criou um arquivo separado para tipealias
  • Adicionado ao arquivo
     public typealias Long = Int64 

No futuro, para cada tipo escalar, você precisará adicionar uma nova tipealias. Com o Apollo versão 14.0, eles adicionaram "suporte para escalares personalizados Int". Se você deseja evitar o uso dessa abordagem e da bandeira no codegen, verifique a solução para esse problema no Apollo git .

Prós e contras


Por que essa abordagem é boa? Uma transição bastante rápida para o uso do GraphQL, referente à abordagem de remoção de todos os modelos antigos.

No futuro, quando precisarmos obter dados para qualquer tela / modelo, podemos recorrer ao GraphQLClient e obter os dados necessários.

Dos menos:

  • Convertendo o tipo de modelo no cliente, pode-se transferir para outra entidade desde trabalhar com dados não é de responsabilidade do cliente.
  • Spreadling GraphQLClient. Depois de adicionar novas consultas, nossa classe aumentará cada vez mais. Uma das soluções são extensões nas quais os métodos de carregamento serão descritos, sobre os quais falei no capítulo GraphQLClient.

Conclusões


Em geral, a mudança para o GraphQL pode ser rápida e fácil com um cliente bem escrito. Demorei cerca de três dias = 24 horas de trabalho. Durante esse período, consegui criar um cliente, traduzir o modelo e recriar o modelo GameInfo. A abordagem descrita não é uma panacéia, mas uma decisão puramente minha, implementada em pouco tempo.

Se você tem um guru do GraphQL, sugiro compartilhar sua experiência e dizer como é conveniente usar o GraphQL + Apollo em grandes projetos. Ou o jogo não vale a pena?

Obrigado pela atenção!

Source: https://habr.com/ru/post/pt470529/


All Articles