Actions directly from client an antipattern
Curious as to why it's considered an anti-pattern to call actions directly from a client. We have a separate legacy service that owns a (relatively static) chunk of our users' data so many of our reads are functionally redirects to this separate service. We do not want our convex DB to own a copy of this data as well to avoid issues trying to keep them in sync so I don't really want to put the res results into our convex DB. Is there something I'm missing that would let us handle this case with query/mutation kicking off an action instead of hitting the action from the client directly?
9 Replies
It's fine to run actions directly, the reason it's not encouraged is that
- if you can run a mutation to schedule the action, the, you can add retry logic for it on the convex deployment
- if the client is disconnected while it runs, a mutation will automatically be safely retried (it will resubmit, but the server will only run if it hadn't already run) while an action is more like a normal fetch() call, you can't tell on teh client if the server received the request or not if the client goes offline after you submit it (so it's possible it won't run, since the choice is between "run at least once" (possibly running twice) and "run at most once" (possibly not running at all))
We do not want our convex DB to own a copy of this data as well to avoid issues trying to keep them in sync so I don't really want to put the res results into our convex DBan action doesn't have to store its results in the convex DB, you can use the action to do other things
Is there something I'm missing that would let us handle this case with query/mutation kicking off an action instead of hitting the action from the client directly?You know about scheduling right, I think I'm missing something here You would need to implicitly store (at least temporarily, since the arguments of a stored action are automatically stored in the database) some data, but just for a brief moment; no need to explicitly write anything to a db table
curious how this works or what it means lol
running
ctx.runAfter(api.foo.bar, args)
stores args until the function runs
does that answer the question @wes?i think so. didn’t realize any storage was going on there
that way if the instance shuts down, crashes, you reboot where you're self-hosting it to get some OS updates etc. this state isn't lost
that's pretty cool
Thanks for the context. Yea so we discussed internally and think we landed on an acceptable pattern. For more context: these actions are all pure
GET
requests to an external service that functionally owns some columns for entities in our convex db. We'll traditionally query all the columns we own the convex way using Convex.useQuery
. For externally owned data we'll instead use Tanstack.useQuery
to call a convex action to go fetch this data and join the results in memory in the client. The Tanstack.useQuery
will have it's query key dependent on the Convex.useQuery
data so it re-runs when convex owned data changes and keeps the external data at least as fresh as the convex owned data.
Given our constraint on having some externally owned data this felt like a reasonable path forward; let me know if it sounds insane or totally off base.sounds good, this way that data never touches your convex deployment and as long as you trust your caching logic (it's possible to screw this up and have e.g. outdated data from the other service when not all data is in convex, but if you've built these systems before you're used to that) I don't see any issues here
Yup we're also ok with the data being a little stale if we do mess it up since worst case we can force tanstack to invalidate and refetch all queries on some timeout. Thanks for the consideration and rubber ducking!