-
Notifications
You must be signed in to change notification settings - Fork 11
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
$batch #675
base: master
Are you sure you want to change the base?
$batch #675
Conversation
b3386e8
to
051cccc
Compare
a77b917
to
4fc5b74
Compare
49e0ae4
to
2e2ff45
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would change statusCode and body as it's adding a little noise to this PR but it's fine.
The requestId for a $batch request are a different Id then the pineJs entity IDs.
method: string; | ||
url: string; | ||
data?: any; | ||
body?: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reasoning behind renaming data to body?
This is making it a breaking change, is this neccessary for implementing batch support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was following the spec, which specifies that there is a body
but says nothing about data
, yet we seem to use data
in place of body
, so it seems like we just named the property in a way that doesn't abide by the spec (I don't know what our reasoning for this was)
If we want to keep it is data
just to avoid a breaking change, I don't know if that sounds like good enough reasoning to avoid following the spec which should presumably be helpful for future changes and understanding as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This predates the odata json $batch but now that it exists I think it makes sense to match that format for our internal data structure as it should simplify a whole bunch of things
12f462a
to
f2ffc3d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potentially there needs more test coverage to test the correctnes of delivered data back and forth when using batch requests.
Is there any limit on allowed batch requests in one batch?
test/06-batch.test.ts
Outdated
); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm missing test cases for:
- request id request matches with returned response and Id. eg: mutliple gets for different resources, matching returned resources
- Expectation of two concurrent requests like 2 POST request sending the same matrix_number?
- At least some tests for $filter, $expand, ... Potentially a map of test parameters executed as test cases using the same structure [{testSet1}, {testSet2}, ...].foreach(it( run the test))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- The 'successful request should have
responses
in its body' test sort of tests for the first bullet but I'll add a separate test for multiple requests - The requests should be sequential, so this shouldn't need testing, right?
- What exactly would this be for? This feature should not change how those work. Tests for $filter, $expand, etc. should live in their own test suite, no?
}), | ||
), | ||
); | ||
requests = req.body.requests; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will execute all requests in one database transaction. Is this desired or should every request run in it's own database transaction?
The background is, if a database transaction fails the qhole transaction is rolled back, thus all batch requests will be rolled back.
@Page- What do you think, should every request in a batch run in its own db tx?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is part of the point/idea of implementing $batch @fisehara , to run all requests in a batch on one transaction. Part of the following roadmap item: https://roadmap.balena.io/posts/19/allow-changes-to-variables-to-be-batched-and-submitted-all-at-once
@@ -1273,7 +1376,13 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => { | |||
|
|||
// Parse the OData requests | |||
const results = await mappingFn(requests, async (requestPart) => { | |||
const parsedRequest = await uriParser.parseOData(requestPart); | |||
const parsedRequest = await uriParser.parseOData( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the requests are independent, should they be executed in parallel on the DB or should pinejs limit the requests to be executed sequencially to do some kind of rate limiting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requests should be executed sequentially. If a request along the way fails, the batch request should fail. However, there will be no rollback if the requests are not atomic
should pinejs limit the requests to be executed sequencially to do some kind of rate limiting?
I don't know enough about pine and the API to answer this well, your opinion based on what I stated above would be better
Change-type: major
Change-type: major
b4905b0
to
8e20418
Compare
Change-type: squash
6c8c983
to
203c4f5
Compare
const runODataRequest = (req: Express.Request, vocabulary: string) => { | ||
if (env.DEBUG) { | ||
api[vocabulary].logger.log('Parsing', req.method, req.url); | ||
} | ||
|
||
if (req.url.startsWith(`/${vocabulary}/$batch`)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be good to store this check somewhere as I've seen equivalent .startsWith
checks in a few places
const urls = new Set<string | undefined>( | ||
requests.map((request) => request.url), | ||
); | ||
if (urls.has(undefined)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also check for null
?
throw new BadRequestError('Batch requests cannot contain batch requests'); | ||
} | ||
const urlModels = new Set( | ||
Array.from(urls.values()).map((url: string) => url.split('/')[1]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can limit the splitting to stop on the first match:
Array.from(urls.values()).map((url: string) => url.split('/')[1]), | |
Array.from(urls.values()).map((url: string) => url.split('/', 1)[1]), |
throw new BadRequestError('Requests of a batch request must have a "url"'); | ||
} | ||
const containsBatch = | ||
Array.from(urls).filter((url) => !!url?.includes('/$batch')).length > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array.from(urls).filter((url) => !!url?.includes('/$batch')).length > 0; | |
Array.from(urls).some((url) => !!url?.includes('/$batch')); |
if ( | ||
request.headers != null && | ||
request.headers['content-type'] == null && | ||
(req.headers == null || req.headers['content-type'] == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(req.headers == null || req.headers['content-type'] == null) | |
req.headers?.['content-type'] == null |
const ids = new Set<string>( | ||
requests | ||
.map((request) => request.id) | ||
.filter((id) => typeof id === 'string') as string[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.filter((id) => typeof id === 'string') as string[], | |
.filter((id): id is string => typeof id === 'string'), |
'Authorization may only be passed to the main batch request', | ||
); | ||
} | ||
const ids = new Set<string>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string
should be inferred if a string[]
is passed in?
const ids = new Set<string>( | |
const ids = new Set( |
requests.find( | ||
(request) => | ||
request.headers?.authorization != null || | ||
request.url?.includes('apikey='), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically params for authentication can be customized so this isn't perfect - I'm not sure if it's something we can account for but worth being aware of
MVP: $batch
Connects-to: https://github.com/balena-io/balena-api/pull/4501
Post-MVP:
atomicityGroup
,if
,dependsOn
, relative pathurl
s, referencing with$
,nextLink
,multipart
(see: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_MultipartBatchFormat),respond-async
Change-type: major
See: https://balena.zulipchat.com/#narrow/stream/345746-aspect.2Fproduct/topic/Prompt.20When.20Restarting.20Containers