Introducción
Este documento detalla las decisiones y justificaciones sobre el manejo de parámetros y el flujo de respuesta en la operación Find dentro de nuestra aplicación, la cual utiliza gqlgen para la generación de resolvers en GraphQL y sigue las convenciones de Go para el manejo de parámetros y datos.
El objetivo es que cualquier desarrollador que se integre al proyecto comprenda el razonamiento detrás de las decisiones y cómo se gestiona el flujo de parámetros y respuestas en la aplicación, asegurando consistencia, eficiencia y escalabilidad.
1. Generación de Resolvers con gqlgen
Cuando utilizamos gqlgen para generar resolvers de GraphQL, los parámetros opcionales en las consultas GraphQL se representan como punteros en Go. Esto se debe a que, en GraphQL, la ausencia de un valor es distinta de un valor vacío, y los punteros (*string, *int, etc.) nos permiten distinguir entre ambos escenarios en Go.
Ejemplo:
query {
findUsers(orderBy: "name", dir: "asc")
}
Se traduce en Go como:
func (r *resolver) FindUsers(ctx context.Context, orderBy *string, dir *string, search *SearchQuery) ([]*models.User, error)
Aquí, los parámetros orderBy y dir son opcionales en la consulta GraphQL, lo que significa que pueden ser nulos (nil) si no se pasan en la solicitud.
2. Convenciones de Go: Copias vs. Punteros
Aunque gqlgen genera punteros para los parámetros opcionales, seguimos las convenciones de Go para decidir cuándo un parámetro debe pasarse por puntero y cuándo por copia. Las convenciones de Go indican que:
- Datos pequeños (como cadenas, enteros, etc.) que no cambian deben pasarse por copia.
- Datos complejos (como estructuras o datos que pueden cambiar durante el flujo) deben pasarse por puntero.
Parámetros pequeños: orderBy y dir
Tanto orderBy como dir son strings y, una vez validados, no cambiarán dentro del flujo de la aplicación. Por lo tanto, después de validar su existencia en el resolver, los convertimos en valores por copia y no continuamos usando punteros.
Para este propósito, implementamos una función auxiliar llamada NormalizeFindParamsForUC, que:
- Resuelve los punteros opcionales
orderByydir. - Inicializa el campo
searchcomo un puntero a una estructuraSearchQuery, dado que es un dato complejo.
func NormalizeFindParamsForUC(orderBy, dir *string, search *models.SearchQuery) (string, string, *models.SearchQuery) {
resolvedOrderBy := ""
if orderBy != nil {
resolvedOrderBy = *orderBy
}
resolvedDir := ""
if dir != nil {
resolvedDir = *dir
}
if search == nil || len(search.Filters) == 0 {
search = &models.SearchQuery{
LogicalOperator: "AND",
Filters: []models.FilterCondition{},
}
}
return resolvedOrderBy, resolvedDir, search
}
3. Manejo de SearchQuery como un Puntero
El parámetro search es una estructura más compleja que puede cambiar durante la ejecución del caso de uso (UC). Esta estructura contiene filtros, operadores y potencialmente filtros anidados que se utilizan para construir consultas dinámicas en bases de datos (como MongoDB).
Al seguir las convenciones de Go, pasamos search como un puntero para garantizar que cualquier modificación dentro del UC se refleje directamente en la estructura, evitando la creación de copias innecesarias y permitiendo mayor eficiencia en memoria.
Justificación:
- Eficiencia: Evitamos copiar estructuras grandes.
- Consistencia: Todas las modificaciones al
SearchQueryse reflejan en el mismo objeto a lo largo de todo el flujo, permitiendo agregar filtros o modificar condiciones de búsqueda sin duplicar datos.
4. Uso de []*User en las Respuestas
En cuanto al retorno de datos, devolvemos un slice de punteros a User ([]*models.User). La razón para utilizar punteros en este caso es doble:
- Eficiencia en la Memoria:
Useres una estructura con múltiples campos, lo que la convierte en un dato relativamente grande. Si devolviéramos copias ([]models.User), estaríamos duplicando los datos en cada capa del flujo, lo que no es eficiente.- Usar punteros (
[]*models.User) nos permite evitar la duplicación, ya que pasamos referencias a los objetosUseroriginales.
- Consistencia y Flexibilidad:
- En cada capa (Repositorio → UC → Resolver), cualquier modificación a un campo de
Userafectará al objeto original. Esto es importante cuando el caso de uso (UC) necesita ajustar los datos antes de devolverlos al resolver.
- En cada capa (Repositorio → UC → Resolver), cualquier modificación a un campo de
func (repo *userRepo) FindUsers(ctx context.Context, search *models.SearchQuery) ([]*models.User, error) {
var users []*models.User
// Consulta a MongoDB y llenado de la lista `users`
return users, nil
}
5. Proceso de Serialización con gqlgen
Aunque usamos punteros en Go ([]*models.User), el cliente que recibe la respuesta GraphQL no verá los punteros, ya que gqlgen se encarga de la serialización de los datos a formato JSON. Esto significa que el cliente final solo recibe los valores de User, sin ninguna referencia a los punteros o estructuras internas de Go.
Ejemplo de respuesta:
{
findUsers {
id
name
email
}
}
El cliente recibe los datos en formato JSON, y gqlgen garantiza que los punteros se conviertan a los valores correspondientes en el formato de respuesta.
6. Conclusión
El flujo que hemos implementado para la operación Find respeta las convenciones de Go, manteniendo un equilibrio entre la eficiencia y la claridad en el código. Al manejar punteros solo cuando es necesario (para datos complejos y grandes), y al mismo tiempo optimizando la serialización de los datos con gqlgen, hemos logrado un diseño consistente y escalable.
El uso de la función NormalizeFindParamsForUC asegura que los parámetros se traten correctamente en función de su naturaleza, y el uso de slices de punteros para las respuestas ([]*models.User) nos permite trabajar de manera eficiente con los datos a lo largo de las capas de la aplicación.
Cualquier nuevo desarrollador en el proyecto debe seguir estas pautas para mantener la coherencia en el manejo de datos y parámetros en la aplicación.
Este documento justifica cada paso y decisión tomada en la implementación del flujo de la operación Find, y debe servir como referencia para cualquier futura optimización o expansión de funcionalidades relacionadas.