-
Notifications
You must be signed in to change notification settings - Fork 25
Add pagination to resources API #805
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
base: future
Are you sure you want to change the base?
Conversation
Regenerating the API based on the open api spec lead to dropping the namespaces from the model names. This appears to be a new standard behavior and has nothing to do with the actual changes in this PR. The proper integration of the new pagination is still missing.
| /// <summary> | ||
| /// The maximum page size provided for the request | ||
| /// </summary> | ||
| public int PageSize { get; set; } |
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.
Could be Count or ItemsCount (looking at TotalCount). I'd propose something like
- Page(Number)
- Page(s)Count
- (Items)Count *of this set
- TotalCount
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 like PageSize as it is very telling in the context of pagination, while I think that
- Page(Number) -> to close to
PageNumberwhich already exists - Page(s)Count -> without the apostrophe in page's count think it is misleading
- (Items)Count *of this set -> is to long
- TotalCount -> already exists for the total number of items
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.
Oh sorry, I messed this up... This is what my idea was::
PagedResult {
long Page(Number); // Could be Page but I don't have a strong opinion on changing this
long Page(s)Count; // i.e. Pages/PageCount/PagesCount
long (Items)Count; // of this set (instead of PageSize)
long TotalCount; // Keeping it
[] Items;
}
That way, everything countable could be *Count. The 'pagination' could read sth like Page x of n Pages
| /// with corresponding metadata from the request. | ||
| /// </summary> | ||
| /// <typeparam name="ItemType">Type of the items to be returned</typeparam> | ||
| public sealed class PagedResult<ItemType> |
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.
Shouldn't it be possible to extend a paged result with other meta data? For example errors instead of items?
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.
For errors I would prefer to commit to using the RFC7807 standard conform ProblemDetails from now on, I found a nice blog post explaining it and I would definitely prefer it to any MORYX custom error API responses 🙂
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 like that! Regarding my comment: Erros might have been a bad example. I just want to make sure that there is absolutely no reason to derive from the PagedResult.
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.
Unsealing the class is a lot easier than the other way around, thats why i sealed it for now 😅
| /// <summary> | ||
| /// The number of items provided on the page; defaults to 20 with a minimum of 0 and a maximum of 100 | ||
| /// </summary> | ||
| public int PageSize { get; set => field = Math.Min(Math.Max(value, 0), 100); } = 20; |
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 not sure about numbers. I think common values are 25, 50, 100, 200 (or 250?). While the max value depends on the use case, I'd tend to go for 200 and would also use max as a default.
Min should be 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.
Agreed, should be 1 and 200 (or something reasonable that also works in the tree structures we have) 👍
src/Moryx.AbstractionLayer.Resources.Endpoints/PaginationParametersExtensions.cs
Show resolved
Hide resolved
| await this.resourceModification | ||
| .getResources({ | ||
| PageNumber: 1, | ||
| PageSize: 10000, |
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.
Why is this 10k, while the endpoints max is only 100?
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.
Good spot, wanted to circumvent implementing a propper pagination. Also that might be hard in this tree structure... Maybe we need a bigger max than 200?
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.
Having a bigger max would mitigate the feature itself. It should prevent users to request tons of data (not only to have a responsive UI, but also reduce server load).
You could ask for multiple pages (which is not necessarily better, but should be load 'balanceble').
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.
It should prevent users to request tons of data (not only to have a responsive UI, but also reduce server load).
I think this is only an argument if big requests would cause more load on the backend side, but since for all our modules we hold the working set of items in the memory anyway this is not really the case for us, is it?
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.
As discussed we agreed on 200 as the maximum value.
| } | ||
|
|
||
| [HttpPost("query")] | ||
| [RequiresQueryParameter(nameof(PaginationParameters.PageNumber))] |
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.
Is it necessary to require a page number? Isn't 1 is a legit default?
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.
True, could go for page size. We just need a parameter here for the minor conform change. I would drop the duplicate action (and the corresponding attributes) in the next major then 🙂
| } | ||
|
|
||
| [HttpPost("query")] | ||
| [RequiresQueryParameter(nameof(PaginationParameters.PageNumber))] |
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.
Some Ideas:
- Could we introduce attributes, that define things like for example maximum page size to bring it to the api spec/doc?
- Should we already think about sorting?
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.
Good ideas:
- Yes at least swagger respects the usual
DataAnnotationattributes likeRange, so we should make use of them - Somewhat, the parameter class makes it extendable to also include sorting parameters, so I would make it two separate PRs
| return values; | ||
| } | ||
|
|
||
| [HttpPost("query")] |
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.
Could we put the query parameters to the query instead of the request body and make these GET 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.
Not completely sure, but as far as I understand no, because the body is a deep object 🤔
| public ActionResult<PagedResult<ResourceModel>> GetResources([FromBody] ResourceQuery query, [FromQuery] PaginationParameters parameters) | ||
| { | ||
| var resourceModels = QueryResourceModels(query); | ||
| return new PagedResult<ResourceModel>().With(parameters).Of(resourceModels); |
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.
parameters should be updated depending on the actual data, instead of just passing them back.
Ok, I see this MR is right now rather defining the API and doesn't have any further logic yet.
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 exactly, up till now I just wanted to prove that we can make it Minor conform. Nevertheless, I agree the metadata should be updated according to the actual content 🙂
| [ProducesResponseType(StatusCodes.Status200OK)] | ||
| [ProducesResponseType(StatusCodes.Status417ExpectationFailed)] | ||
| [Authorize(Policy = ResourcePermissions.CanViewDetails)] | ||
| public ActionResult<ResourceModel[]> GetDetailsBatch([FromQuery] long[] ids) |
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.
After browsing the controller, I feel like there should be a root level GET (in a future version), that combines
- filtering
- sorting
- pagination
Summary
Adding pagination to the actions in the
ResourceModificationControllerthat return sets of data. This PR may be used as an example to change all APIs in the MORYX Framework to make use of pagination in the endpoints.The following changes are part of this PR:
ActionSelectorMethodAttributeimplementations to differentiate between API calls using the new pagination responses and legacy callsOpen TODOs:
ObsoleteAttributesLinked Issues
Partially resolves #703
Checklist for Submitter
Review
Typical tasks
Clean code