Hola Habr Mi nombre es Anton Potapov, soy desarrollador de iOS en
FINCH . Hoy quiero hablar en detalle sobre cómo traducir un proyecto móvil a GraphQL, describir los pros y los contras de este enfoque. Empecemos
Breve introducción
"Legado". Creo que todos escucharon esta terrible palabra, y la mayoría lo conoció cara a cara. Por lo tanto, no necesita decir cuán difícil es integrar nuevas y excelentes funciones en su proyecto heredado.

Uno de nuestros proyectos es una solicitud para una gran empresa de lotería (simplemente no puedo dar un nombre desde la NDA). Recientemente, necesitaba traducirlo a GraphQL sin perder la cabeza.
El proyecto es muy grande: a primera vista, era necesario pasar al menos 200-300 horas, pero esto va más allá de todos los posibles sprints de dos semanas. Tampoco pudimos asignar todo el sprint a una sola tarea, ya que también había características secundarias, no menos importantes que GraphQL.
Pensamos durante mucho tiempo qué hacer y decidimos traducir el proyecto paso a paso, modelo por modelo. Con este enfoque, la transición será suave y 200-300 horas se distribuirán en varios sprints.
Puntos técnicos
Para trabajar en el proyecto, utilicé la biblioteca Apollo.
Ya hay un artículo sobre Habré que describe todas las sutilezas de trabajar con él, por lo que no lo repetiré nuevamente. Te advierto de antemano: trabajar con un modelo generado por código no es muy conveniente y es mejor mantenerlo como una "red". Cada entidad contiene un campo de cadena __typename: que, por extraño que parezca, devuelve un nombre de tipo.
Descomposición de tareas
Lo primero que debe hacer es determinar qué modelo heredado traduciremos a GraphQL. En mi caso, era lógico traducir uno de los modelos masivos de GameInfo, y DrawInfo incrustado en él.
- GameInfo: contiene información sobre juegos y sorteos de lotería.
- DrawInfo - contiene datos sobre la circulación
Habiendo terminado la elección, decidí inicializar los viejos modelos heredados con los nuevos obtenidos con GraphQL. Con este enfoque, no es necesario reemplazar aquellas partes de la aplicación donde se usa el modelo anterior. Completar completamente los modelos anteriores era arriesgado, no el hecho de que el proyecto funcionaría como antes, y tomaría demasiado tiempo.
En total, hay tres etapas de implementación de GraphQL:
- Creación de clientes;
- Crear un inicializador para el modelo Legacy;
- Reemplazar solicitudes de API con GraphQL.
GraphQLClient
Al igual que con cualquier cliente, GraphQLClient debería tener un método de recuperación, después de lo cual cargará los datos que necesitamos.
En mi opinión, el método de búsqueda para GraphQLClient debería tomar una enumeración, en función de la cual se ejecutará la solicitud correspondiente. Este enfoque nos permitirá recibir fácilmente datos para la entidad deseada o incluso la pantalla. Si la solicitud acepta parámetros, se pasarán como valores asociados. Con este enfoque, es más fácil usar GraphQL y crear consultas flexibles.
enum GraphQLRequest { case image(ImageId?) }
Nuestro cliente GraphQL personalizado debe contener ApolloClient personalizado para las funciones existentes, así como el método de carga de extracción mencionado anteriormente.
func fetch<Response>(requestType: GraphQLRequest, completion: @escaping (QLRequestResult<Response>) -> Void)
¿Qué es QLRequestResult? Es ordinario
typealias QLRequestResult<Response> = Result<Response, APIError>
El método fetch le permite acceder al cliente a través del protocolo y realizar un cambio en requestType. Dependiendo de requestType, puede usar el método de carga privado correspondiente, donde puede convertir el modelo resultante al anterior. Por ejemplo:
private func fetchGameInfo<Response>(gameId: String? = "", completion: @escaping (QLRequestResult<Response>) -> Void) {
Como resultado, obtenemos un modelo antiguo listo para usar obtenido de GraphQL.
Tipo escalar
En la documentación de GraphQL, un tipo escalar se describe de la siguiente manera: "Un tipo escalar es un identificador único que a menudo se utiliza para volver a seleccionar un objeto o como clave para un caché". Para swift, un tipo escalar puede asociarse fácilmente con typealias.
Al escribir un cliente, me encontré con un problema de que en mi esquema había un tipo escalar Long, que era esencialmente
typealias Long = Int64
Todo estaría bien, pero Apollo lanza automáticamente todos los escalares de usuario a String, lo que provoca un bloqueo. Resolví el problema así:
En el futuro, para cada tipo escalar, deberá agregar un nuevo typealias. Con la versión 14.0 de Apollo, agregaron "soporte para escalares personalizados Int". Si desea evitar utilizar este enfoque y la bandera en el codegen, consulte la solución a este problema en el
Apollo git .
Pros y contras
¿Por qué es bueno este enfoque? Una transición bastante rápida para usar GraphQL, con respecto al enfoque con la eliminación de todos los modelos antiguos.
En el futuro, cuando necesitemos obtener datos para cualquier pantalla / modelo, podemos recurrir a GraphQLClient y obtener los datos necesarios.
De las desventajas:
- Al convertir el tipo de modelo en el cliente, se podría transferir a otra entidad desde El trabajo con datos no es responsabilidad del cliente.
- Extensivo GraphQLClient. Después de agregar nuevas consultas, nuestra clase crecerá más y más. Una de las soluciones son las extensiones en las que se describirán los métodos de carga, de los que hablé en el capítulo GraphQLClient.
Conclusiones
En general, cambiar a GraphQL puede ser rápido e indoloro con un cliente bien escrito. Me llevó unos tres días = 24 horas de trabajo. Durante este tiempo, logré crear un cliente, traducir el modelo y recrear el modelo GameInfo. El enfoque descrito no es una panacea, sino una decisión puramente mía, implementada en poco tiempo.
Si tiene un gurú de GraphQL entre ustedes, sugiero compartir su experiencia y decirle lo conveniente que es usar GraphQL + Apollo en proyectos grandes. ¿O el juego no vale la pena?
Gracias por su atencion!