Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct resolver batching behavior #1495

Merged
merged 25 commits into from
Apr 11, 2019
Merged

Correct resolver batching behavior #1495

merged 25 commits into from
Apr 11, 2019

Conversation

zephraph
Copy link
Contributor

@zephraph zephraph commented Feb 7, 2019

After enabling batching we found a few problems.

  1. List endpoints return an array but single endpoints return an object. I was expecting all responses to be arrays and called Array.find on the results which caused metaphysics to crash on single responses
  2. Dataloader wasn't properly deduping data. Turns out it does strict checking of the inputs to calculate a cache key (meaning objects are duplicated).
  3. For any given endpoint "id" could also be a slug.
  4. Not related to this implementation, but also a problem... list endpoints generally don't support slugs. Thankfully @mzikherman helped me out and updated the sales endpoint to do so.
  5. We support historical slugs, so the slug passed into the dataloader might not be the slug returned to the response. Given that the order of results isn't guaranteed the dataloader needs some way to match the original key to the results, which it couldn't do if the key was a historical slug.

This PR resolves 1-3. 4 was fixed in @mzikherman's PR and 5 is still an open issue.

TODO

  • Test against a few queries to ensure behavior is as expected

@zephraph zephraph self-assigned this Feb 7, 2019
@zephraph
Copy link
Contributor Author

zephraph commented Feb 7, 2019

I'm fairly certain I need more coverage on error cases. Two things come to mind.

  1. What happens when null is requested from the dataloader?
  2. What happens when the gravity response fails?

It feels like for 1 the gravity loader should just return the default value as a response and for 2 we may need to resolve with an error object... I'll have to dig into how the gravity loader actually handles that.

@zephraph zephraph changed the title [WIP] Fix batching issues [WIP] Correct resolver batching behavior Feb 7, 2019
* Otherwise we stringify the object so it can be compared via strict comparison.
*/
export const cacheKeyFn = (key: Key): string => {
if (typeof key === "object" && key !== null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this relates to. If the key is undefined, I'm just returning it like any other value. I'm not sure how to do error handling here, because if I throw an exception it's going to fail the entire query... maybe that's what we want though? Hmmm...

@alloy
Copy link
Contributor

alloy commented Feb 7, 2019

  1. What happens when null is requested from the dataloader?

If you mean that the key is null then that constitutes an argument error, in my mind. I think the types should disallow it and your code should throw an error.

  1. What happens when the gravity response fails?

Do you mean a non 2xx HTTP response? The promise will reject. (I’m going to change the way that check happens btw, no need to generate a string and compile a regex for each request.)

@zephraph
Copy link
Contributor Author

zephraph commented Feb 7, 2019

@alloy if the gravity request rejects I'll likely need to handle that separately or it'll cause the promise.all of the batch loader to fail which would cause all of the batched calls to not return.

@artsy artsy deleted a comment from peril-staging bot Apr 6, 2019
@zephraph zephraph changed the title [WIP] Correct resolver batching behavior Correct resolver batching behavior Apr 8, 2019
@zephraph
Copy link
Contributor Author

zephraph commented Apr 8, 2019

@alloy I figured it out! It was a simple mistake on my part but the error wasn't very clear.

@alloy
Copy link
Contributor

alloy commented Apr 9, 2019

@zephraph Can you confirm if this is done and needs a final review + merge? (I’m slightly confused because it’s still self-assigned.)

@zephraph zephraph assigned alloy and unassigned zephraph Apr 10, 2019
@zephraph
Copy link
Contributor Author

@alloy it is done and ready for review. Sorry about that!

: stringify(options, {
arrayFormat: "brackets",
sort: (a, b) => a.localeCompare(b),
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL. Fancy ✨

@@ -82,6 +82,19 @@ describe("batchLoader", () => {
})
})

describe("serializeParams", () => {
const { serializeParams } = require("../batchLoader")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, why are you loading these functions this way? If you would import them as normal, you could do describe(serializeParams, () => …) instead and have the name of the tests updated automatically if we’d ever rename the function (using the refactor tools).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good tip!

I'm doing the imports this way because earlier in the tests I mock the dependency to override the feature flag.

https:/artsy/metaphysics/blob/batching-bugfix/src/lib/loaders/__tests__/batchLoader.test.ts#L10

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but actually it doesn’t seem like you’d need to do any mocking for that, you can just change the value and reset it from an afterEach?

Anyways, not a blocker for this PR.

@alloy
Copy link
Contributor

alloy commented Apr 10, 2019

@zephraph FYI, re “arg names”–did you know ‘arguments’ are the values that are passed in, whereas ’parameters’ are the names of the variables in a function’s signature? Took me a while to realise this, but really helps.

@zephraph
Copy link
Contributor Author

Ohh, hmmm... I haven't thought about that. JavaScript exposes arguments as a way to access parameters. That makes it weird.

In the context of this file params actually refer to query parameters, not function parameters. I probably haven't made that very clear.

@alloy
Copy link
Contributor

alloy commented Apr 10, 2019

Ohh, hmmm... I haven't thought about that. JavaScript exposes arguments as a way to access parameters. That makes it weird.

Well, not really, it’s a list of the values, so these are the arguments.

In the context of this file params actually refer to query parameters, not function parameters. I probably haven't made that very clear.

Nah, that was clear enough to me. I feel like params is well understood to refer to HTTP parameters 👍

@alloy
Copy link
Contributor

alloy commented Apr 11, 2019

As per artsy/README#183, may I squash and merge this one?

@alloy alloy merged commit 6e55642 into master Apr 11, 2019
@alloy alloy deleted the batching-bugfix branch April 11, 2019 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants