superlifter icon indicating copy to clipboard operation
superlifter copied to clipboard

Context-switch?

Open hden opened this issue 3 years ago • 5 comments

When used with Lacinia, it is useful to allow a field resolver to modify the application context, with the change exposed just to the fields nested below, but to any depth.

(resolve/with-context result new-context)

In our specific use-case, we'd like to update a specific db parameter for just to the field below. However superlifter requires the parameter to be placed in its context. We are able to specify the initial context via the :urania-opts key, but what's the recommended way to update the urania env just to the fields nested below, but to any depth?

Refs:

  • https://lacinia.readthedocs.io/en/latest/resolve/context.html

hden avatar Dec 08 '21 04:12 hden

Hello,

Superlifter's operation is pretty disconnected from Lacinia's - this is the only way it can achieve the Dataloader functionality. Essentially you create a superlifter instance and throw fetches into it from Lacinia and it returns a promise that Lacinia understands, but it has no knowledge of resolvers or ancestry. The fetches are also performed on a thread pool so thread local bindings won't work either.

One way to achieve this is to alter the Lacinia context appropriately for child resolvers and make this an argument to your fetches, which are just records so can contain extra information for the fetch itself.

Can you think of any other ways?

Cheers

oliyh avatar Dec 08 '21 20:12 oliyh

to alter the Lacinia context appropriately for child resolvers and make this an argument to your fetches

Yup. That's exactly what I'm trying. In my case the context in question is a Datomic DB (kind like a DB snapshot).

What do you think if I lazily create a bucket if there isn't one? I'll have to peek into superlifter's context. Is it acceptable usage?

(defn- has-bucket? [context bucket-id]
  (contains? @(get context :buckets)
             bucket-id))

(defn enqueue! [{:keys [superlifter]} {:keys [muse bucket-id env]}]
  (when-not (has-bucket? superlifter bucket-id)
    (s/add-bucket! superlifter bucket-id (assoc-in default-bucket-options
                                                   [:urania-opts :env]
                                                   env)))
  (s/enqueue! superlifter bucket-id muse))

hden avatar Dec 15 '21 14:12 hden

Looks generally ok, you need to be careful of two things:

  1. You have appropriate triggers on the bucket - if you know in advance when you create it what the size will be, that would be most efficient
  2. There is a race condition between you checking for the bucket and adding it - if another thread has created it and added a muse to it in the meantime, you may lose that muse. Ideally you would do this at the parent level before branching off into multiple child resolvers

Superlifter could make this a bit easier for you, either with a way to check the buckets or an atomic way of adding a bucket. Let me know what you settle on and we can see if there are some useful improvements to the superlifter API.

oliyh avatar Dec 15 '21 18:12 oliyh

an atomic way of adding a bucket

Sounds cool.

hden avatar Dec 15 '21 23:12 hden

Reading the implementation of add-bucket! it does atomically check for a bucket with the same id so you won't lose any muse you've already enqueued there, but your new bucket won't be added (and it will emit a warning). So, you could just keep your code without the has-bucket? guard and the behaviour will be correct; you will however also get this warning that "bucket already exists" every time, which might be annoying.

oliyh avatar Feb 07 '22 12:02 oliyh

Hi @hden reading this now, superlifter has an atomic way to add a bucket, which would mean you don't need to check for the existence of one first.

If I've not misunderstood, are you happy for this to be closed?

Thanks

oliyh avatar Apr 21 '23 15:04 oliyh

Sure. Thanks for reaching out.

hden avatar Apr 22 '23 00:04 hden