Los tags sobreviven a un rename, las keys no
Los tags declarativos escalan a varios equipos. Las keys basadas en strings no.
Las keys de TanStack Query son más cortas, más ligeras y agradables de leer dentro de un solo codebase. Para un equipo de feature que posee todas las queries, la API basada en keys es la elección obvia. El sistema de tags de RTK Query añade una ceremonia que a esa escala no necesitas.
La forma que funciona para un equipo se convierte en un lastre al cruzar las fronteras entre equipos. El modo de fallo pasa de «arréglalo antes del merge» a «entérate por un ticket de soporte».
Un pedido, dos equipos
El equipo de pedidos publica una mutación placeOrder. El equipo de catálogo tiene una query getProduct que devuelve los detalles y el stock de un artículo. Cuando el pedido tiene éxito, el producto en caché queda obsoleto (su stock acaba de cambiar), y algo tiene que marcarlo para que se vuelva a pedir.
Con tags de RTK Query
El equipo de catálogo declara lo que contiene su query:
getProduct: builder.query({
query: (id) => `/products/${id}`,
providesTags: ['Product'],
}),
El equipo de pedidos declara a qué afecta su mutación:
placeOrder: builder.mutation({
query: (order) => ({ url: '/orders', method: 'POST', body: order }),
invalidatesTags: ['Product'],
}),
Cuando la mutación tiene éxito, la librería recorre su caché, encuentra las queries que proveen el tag Product y hace refetch de las que están montadas en ese momento. El equipo de pedidos nunca lee el código del catálogo. No necesita saber qué query key usa el catálogo ni qué forma tiene la entrada de caché. Declara su intención a un nivel de categoría y la librería lo conecta.
Los dos equipos están acoplados al string 'Product'. Ese acoplamiento vive en una lista tagTypes compartida y declarada en el módulo de la API, así que tiene una única fuente de verdad.
Con query keys de TanStack Query
TanStack invalida por key, no por categoría. El equipo de pedidos escribe:
queryClient.invalidateQueries({ queryKey: ['product'] });
Para que esa línea sea correcta, el equipo de pedidos tiene que conocer la forma exacta de la query key del equipo de catálogo. Mira el código del catálogo, encuentra la llamada a useQuery, lee la key y la copia en su propia invalidación.
Los equipos están acoplados al mismo string 'product', pero el acoplamiento vive en otro sitio. Es un string mágico en el handler de la mutación del equipo de pedidos, sin ningún contrato que apunte a la query del catálogo.
Qué pasa cuando los equipos se desincronizan
Los dos enfoques producen el mismo resultado cuando los equipos siguen coordinados. El producto en caché se vuelve a pedir, la UI se actualiza, el usuario ve el stock nuevo. Los enfoques divergen cuando la coordinación se afloja.
Pon por caso un rename. El equipo de catálogo está refactorizando. El término del dominio ha cambiado. Lo que antes llamaban «product» ahora se llama «listing» en toda la API, en la documentación y en el vocabulario diario del equipo. Renombran la query key:
// before
useQuery({ queryKey: ['product', productId], queryFn: fetchProduct });
// after
useQuery({ queryKey: ['listing', listingId], queryFn: fetchListing });
Lo publican.
En un mundo de RTK Query con tags, el rename no afecta a la invalidación del equipo de pedidos. Los tags son independientes de las query keys. La query del equipo de catálogo sigue proporcionando 'Product'. La mutación del equipo de pedidos sigue invalidando 'Product'. La conexión aguanta.
En un mundo de TanStack Query con keys, el rename rompe la invalidación del equipo de pedidos en silencio. invalidateQueries({ queryKey: ['product'] }) ahora coincide con cero queries en caché. La mutación tiene éxito. La librería no muestra ningún error porque encontrar cero coincidencias no es un error. La ficha de producto del usuario se queda obsoleta hasta que el usuario sale de la pantalla y vuelve, o hasta que un ticket de soporte destapa el bug.
Sin error de tipos. Sin fallo de compilación. Sin excepción en runtime. UI obsoleta en producción.
Dónde vive el acoplamiento
Los dos enfoques codifican el mismo acoplamiento entre los dos equipos. La diferencia está en dónde vive ese acoplamiento, y en cómo se nota cuando se rompe.
Los tags acoplan a los equipos a través de un nombre de categoría: un sustantivo abstracto que describe un tipo de datos. Vive en un enum compartido. Los renames necesitan coordinación a través de ese enum, y aparecen al compilar: dentro de un mismo módulo createApi, al momento; entre apps que se despliegan por separado, en la siguiente build de cada app contra el paquete compartido actualizado.
Las keys acoplan a los equipos a través de un identificador de string: la forma literal que identifica una entrada en caché. Vive allí donde alguien lo escriba. Los renames no necesitan coordinación porque nada la fuerza. Aparecen cuando el bug llega a producción.
Ninguno de los dos comportamientos viene grabado en las palabras «tag» y «key». Un equipo puede hardcodear el string de un tag fuera del enum, y un equipo de TanStack puede montar factories de keys compartidas y tipadas que delaten los renames igual de rápido. La garantía sale del contrato compartido, y la diferencia es hacia dónde empuja cada API: RTK Query pide tags de una lista tagTypes declarada, así que el contrato es el camino de menor esfuerzo; TanStack acepta cualquier array, así que el contrato es algo que tienes que construir y vigilar tú.
Esa diferencia en cuándo aparece el fallo no es un defecto del diseño de TanStack Query. La API basada en keys es más corta, más ligera y encaja bien en un solo equipo que tiene todas las queries en la cabeza. La forma deja de encajar cuando las personas que escriben la invalidación y las personas que escriben la query dejan de ser las mismas.
Cuál encaja con tu equipo
Un equipo, un codebase. Si esa es tu app, la diferencia es sobre todo estética. La API basada en keys de TanStack es más corta y no añade ceremonia. El riesgo de una desincronización silenciosa en la invalidación es pequeño porque tienes visibilidad completa de cada query key.
Varios equipos que despliegan de forma independiente. Aquí el modo de fallo de la invalidación basada en keys empieza a multiplicarse. El refactor de cada equipo es una posible rotura silenciosa para cualquier otro equipo que haya referenciado sus keys. Puedes construir convenciones para suavizarlo (factories de query keys, constantes de key compartidas, checklists de code review, tests de integración entre features), pero eso son convenciones, sin nada que las haga cumplir: estás reconstruyendo lo que el sistema de tags te da gratis.
Una elección de librería aún abierta, con federación o trabajo multiequipo por delante. En ese caso vale la pena entender el sistema de tags antes de que la forma de la API te parezca arbitraria. Elegir entre las dos librerías significa elegir con qué modos de fallo estás dispuesto a vivir.
La serie de Module Federation retoma la pregunta más amplia de la gestión de estado entre remotes desplegados de forma independiente, en Un store compartido para la mitad de servidor y en El estado de cliente cruza la frontera para la de cliente. Si aún no conoces la separación entre estado de servidor y estado de cliente que este post da por supuesta, empieza aquí.