fjorn
fjorn2mo ago

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
ballingt
ballingt2mo ago
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 DB
an 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
wes
wes2mo ago
curious how this works or what it means lol
ballingt
ballingt2mo ago
running ctx.runAfter(api.foo.bar, args) stores args until the function runs does that answer the question @wes?
wes
wes2mo ago
i think so. didn’t realize any storage was going on there
ballingt
ballingt2mo ago
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
wes
wes2mo ago
that's pretty cool
fjorn
fjornOP2mo ago
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.
ballingt
ballingt2mo ago
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
fjorn
fjornOP2mo ago
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!

Did you find this page helpful?