what is the best practice to return total of obects with webflux? - spring-webflux

I am developing api with web flux.
#GetMapping(value = "/shipments/containernumbers")
#Operation(summary = "Returns a list of related container numbers", description = "Return related container numbers based on input")
public Flux<ContainerNumber> containerNumbers(final String searchTerm, final Pageable pageable) {
return shipmentService.containerNumbers(searchTerm, pageable)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "these is not container number with your input ")));
}
This will return list of objects with paging. But this will not tell front end how many object it has. The front end need know the total number of object to prepare pages.I checked some samples but did not find right way to do it.
What it the best way to realize this requirement means return list of object with paging function as well as total number of object with webflux?
thanks in advance.

The only way I can think of would be to create a second endpoint like:
#GetMapping(value = "/shipments/containernumbers/count")
#Operation(summary = "Returns a list of related container numbers", description = "Return related container numbers based on input")
public Mono<Long> containerNumbers(final String searchTerm) {
return shipmentService.containerNumbersCount(searchTerm)
.collectList()
.map(list -> list.size())
.switchIfEmpty(Mono.just(0L));
}
There is no other way if you want server side pagination. You need one endpoint to query the data. This endpoint will employ a Pageable parameter just as you did in your question above.
But to display the number of pages in the frontend, you need a full count of the objects. No big deal, just a few additional lines of code. I suggest a second endpoint for that as shown here.
The database query can be made even simpler if you use spring data. Then you create a countByXyz() query and don't need the collectList() and list.size() part of my code example.

Related

How can I get and use the properties I need from this GraphQL API using Dart?

Before you start reading: I have looked at the GraphQL documentation, but my usecase is so specific and I only need the data once, and therefore I allow myself to ask the community for help on this one to save some time and frustration (not planning to learn GraphQL in the future)
Intro
I am a CS student developing an app for Flutter on the side, where I need information about the name and location of every bus stop in a specific county in Norway. Luckily, there's an open GraphQL API for this (API URL: https://api.entur.io/stop-places/v1/graphql). The thing is, I don't know how to query a GraphQL API, and I do not want to spend time learning it as I am only going to fetch the data once and be done with it.
Here's the IDE for the API: https://api.entur.io/stop-places/v1/ide
And this is the exact query I want to perform as I want to fetch bus stops located in the county of Trondheim:
{
stopPlace(stopPlaceType: onstreetBus, countyReference: "Trondheim") {
name {
value
}
... on StopPlace {
quays {
geometry {
coordinates
}
}
}
}
}
The problem with this query though, is that I don't get any data when passing "Trondheim" to the countyReference (without countyReference I get the data, but not for Trondheim). I've tried using the official municipal number for the county as well without any luck, and the documentation of the API is rather poor... Maybe this is something I'll have to contact the people responsible for the API to figure out, which shouldn't be a problem.
But now back to the real problem - how can I make this query using the GraphQL package for Dart? Here's the package I'm planning to use: (https://pub.dev/packages/graphql)
I want to create a bus stop object for each bus stop, and I want to put them all in a list. Here is my bus stop model:
class BusStop with ChangeNotifier {
final String id;
final String name;
final LatLng location;
BusStop({
this.id,
this.name,
this.location
});
}
When it comes to authentication, here's what the documentation says:
This API is open under NLOD licence, however, it is required that all consumers identify themselves by using the header ET-Client-Name. Entur will deploy strict rate-limiting policies on API-consumers who do not identify with a header and reserves the right to block unidentified consumers. The structure of ET-Client-Name should be: "company - application"
Header examples: "brakar - journeyplanner" "fosen_utvikling - departureboard" "norway_bussekspress - nwy-app"
Link to API documentation: https://developer.entur.org/pages-nsr-nsr
Would be great to know how I should go about this as well! I'm grateful for every answers to this, I know I am being lazy here as of learning GraphQL, but for my usecase I thought it would take less time and frustration by asking here!
Getting the query right
First of all you seem to have GraphQL quite figured out. There isn't really much more to it than what you are doing. What queries an API supports depends on the API. The problem you seem to have is more related to the specific API that you are using. I might have figured the right query out for you and if not I will quickly explain what I did and maybe you can improve the query yourself:
{
stopPlace(stopPlaceType: onstreetBus, municipalityReference: "KVE:TopographicPlace:5001") {
name {
value
}
... on StopPlace {
quays {
geometry {
coordinates
}
}
}
}
}
So to get to this I started finding out more about "Trondheim" bei using the topographicPlace query.
{
topographicPlace(query: "Trondheim") {
id
name {
value
}
topographicPlaceType
parentTopographicPlace {
id
name {
value
}
}
}
}
If you do that you will see that "Trondheim" is not a county according to the API: "topographicPlaceType": "municipality". I have no idea what municipality is but the is a different filter for this type on the query that you provided. Then putting "Trondheim" there didn't yield any results so I tried the ID of Trondheim. This now gives me a bunch of results.
About the GraphQL client that you are using:
This seems to be an "Apollo Client" clone in Dart. Apollo Client is a heavy piece of software that comes with a lot of awesome features when used in a frontend application. You probably just want to make a single GraphQL request from a backend. I would recommend using a simple HTTP client to send a POST request to the GraphQL API and a JSON body (don't forget content type header) with the following properties: query containing the query string from above and variables a JSON object mapping variable names to values (only needed if you decide to add variables to your query.

Querying GemFire Region by partial key

When the key is a composite of id1, id2 in a GemFire Region and the Region is partitioned with id1, what is the best way of getting all the rows whose key matched id1.
Couple of options that we are thinking of:
Create another index on id1. If we do that, we are wondering if it goes against all Partition Regions?
Write data aware Function and Filter by (id1, null) to target specific Partition Region. Use index in local Region by using QueryService?
Can you please let me know if there is any other way to achieve the query by partial key.
Well, it could be implemented (optimally) by using a combination of #1 and #2 in your "options" above (depending on whether your application domain object also stored/referenced the key, which would be the case if you were using SD[G] Repositories.
This might be best explained with the docs and an example, particularly using the PartitionResolver interface Javadoc.
Say your "composite" Key was implemented as follows:
class CompositeKey implements PartitionResolver {
private final Object idOne;
private final Object idTwo;
CompositeKey(Object idOne, Object idTwo) {
// argument validation as necessary
this.idOne = idOne;
this.idTwo = idTwo;
}
public String getName() {
return "MyCompositeKeyPartitionResolver";
}
public Object getRoutingObject() {
return idOne;
}
}
Then, you could invoke a Function that queries the results you desire by using...
Execution execution = FunctionService.onRegion("PartitionRegionName");
Optionally, you could use the returned Execution to filter on just the (complex) Keys you wanted to query (further qualify) when invoking the Function...
ComplexKey filter = { .. };
execution.withFilter(Arrays.stream(filter).collect(Collectors.toSet()));
Of course, this is problematic if you do not know your keys in advance.
Then you might prefer to use the ComplexKey to identify your application domain object, which is necessary when using SD[G]'s Repository abstraction/extension:
#Region("MyPartitionRegion")
class ApplicationDomainObject {
#Id
CompositeKey identifier;
...
}
And then, you can code your Function to operate on the "local data set" of the Partition Region. That is, when a data node in the cluster hosts the same Partition Region (PR), then it will only operate on the data set in the "bucket" for that PR, which is accomplished by doing the following:
class QueryPartitionRegionFunction implements Function {
public void execute(FunctionContext<Object> functionContext) {
RegionFunctionContext regionFunctionContext =
(RegionFunctionContext) functionContext;
Region<ComplexKey, ApplicationDomainObject> localDataSet =
PartitionRegionHelper.getLocalDataForContext(regionFunctionContext);
SelectResults<?> resultSet =
localDataSet.query(String.format("identifier.idTwo = %s",
regionFunctionContext.getArguments);
// process result set and use ResultSender to send results
}
}
Of course, all of this is much easier to do using SDG's Function annotation support (i.e. implementing and invoking your Function anyway).
Note that, when you invoke the Function, onRegion using the GemFire's FunctionService, or more conveniently with SDG's annotation support for Function Execution, like so:
#OnRegion("MyPartitionRegion")
interface MyPartitionRegionFunctions {
#FunctionId("QueryPartitionRegion")
<return-type> queryPartitionRegion(..);
}
Then..
Object resultSet = myPartitionRegionFunctions.queryPartitionRegion(..);
Then, the FunctionContext will be a RegionFunctionContext (because you executed the Function on the PR, which executes on all nodes in the cluster hosting the PR).
Additionally, you use the PartitionRegionHelper.getLocalDataForContext(:RegionFunctionContext) to get the local data set of the PR (i.e. the bucket, or just the shard of data in the entire PR (across all nodes) hosted by that node, which would be based your "custom" PartitionResolver).
You can then query to further qualify, or filter the data of interests. You can see that I queried (or further qualified) by idTwo, which was not part of the PartitionResolver implementation. Additionally, this would only be required in the (OQL) query predicate if you did not specify Keys in your Filter with the Execution (since, I think, that would take the entire "Key" (idOne & idTwo) into account, based on our properly implemented Object.equals() method of your ComplexKey class).
But, if you did not know the keys in advance and/or (especially if) you are using SD[G]'s Repositories, then the ComplexKey would be part of your application domain abject, which you could then Index, and query on (as shown above: identifier.idTwo = ?).
Hope this helps!
NOTE: I have not test any of this, but hopefully it will point you in the right direction and/or give you further ideas.

Laravel query parameters or pretty parameters for api

i am willing to know which is the best practice to use parameters for developing an api
//url
http://localhost:8000/api/my_orders/1/10/1
//route
Route::get('/my_orders/{user_id}/{limit}/{page}', 'ApiControllers\OrderController#getOrdersByUser');
//method
public function getOrdersByUser($user_id, $limit, $page)
{
//logic using $user_id, $limit, $page
}
or
//url
http://localhost:8000/api/my_orders?user_id=1&&limit=5&&page=1
//route
Route::get('/my_orders', 'ApiControllers\OrderController#getOrdersByUser');
//method
public function getOrdersByUser(Request $request)
{
//logic using $request->user_id, $request->limit, $request->page
}
this api is for mobile applications and for front end Vue application
thank you
Laravel has pagination built in, it will check for page and perpage query string arguments using paginate() or something that uses Paginator related methods for fetching a subset of your data.
So when you have ?page=1&perpage=20 and you use paginators, they will pick these up automatically.
For REST API endpoints, try and make the urls as descriptive as possible and based on your models.
// get a list of orders
GET /api/orders
// get a list of orders belonging to user 1
GET /api/orders?user_id=1
// get a paginated list of 20 orders belonging to user 1
GET /api/orders?user_id=1&page=1&perpage=20
You are calling your endpoint my_orders, which basically says it will return orders owned by the authenticated user. So, in my opinion, it wouldn't make sense here to include a user_id argument.
// get a list of orders owned by the authenticated user
GET /api/my_orders
You can use my_orders, but more descriptive will be to use a url like:
// get a list of orders owned by user
GET /api/users/{user_id}/orders
Edit:
In your case, you probably want to create a UserOrderController
public function index($user_id)
{
// first fetch user, if fetch fails show error
$user = User::findOrFail($user_id);
// maybe add some code here to check if the authenticated user is allowed to view this user's orders
// return paginated orders
return $user->orders()->paginate();
}
And you would need to define the relations in the models:
App/User.php
public function orders()
{
// assuming orders table has a column user_id
return $this->hasMany(Order::class);
}
App/Order.php
// inverse relation
public function user()
{
return $this->belongsTo(User::class);
}
I use 1st way for only those parameters which are associated with DB, like user_id here, because it reduces 1 step for me to get the specific data from DB of that particular ID. for more dynamic url laravel will go to this route even when you supply the wrong values and you have to apply the regix to avoid that.
Secondly, URLs like 2/20/4 wouldnt make any sense and hard to understand. So the best way in my opinion is the second way.

Unable to get a total count of items in the model in the ASP.NET MVC view

I am using the following code to render a pager:
#Html.BootstrapPager(Request.QueryString("Page"), Function(index) Url.Action("Index", "Posts", New With {.page = index}), 14000, System.Web.Configuration.WebConfigurationManager.AppSettings("PageSize"), 15)
My problem is that If I use Model.Count in place of the 14000 then I only get 1 page of records since I am using skip and take in the repository to pull only the need records. How can i in the view access the total number of published records so that I don't have to hard code the value into the view right now?
The original pager code is here. I converted it to VBNET am using it. It works fine if the record count is hardcoded.
This is the repo:
Dim posts As IEnumerable(Of PostSummaryDTO)
Using db As BetterBlogContext = New BetterBlogContext
posts = db.be_Posts.OrderByDescending(Function(x) x.DateCreated).Select(Function(s) New PostSummaryDTO With {.Id = s.PostRowID, .PostDateCreated = s.DateCreated, .PostSummary = s.Description, .PostTitle = s.Title, .IsPublished = s.IsPublished}).Skip((Page - 1) * PageSize).Take(PageSize).ToList()
Return posts.ToList
End Using
You need two different methods in the lower layer - one to get the total count and one to get the desired page - and then call them both from your controller, passing both results in the model to the view. As such, the model cannot be a collection of records; it must be an object with a property for a collection of records and a property for the count. Either that or use the ViewBag to pass the count.
What we do in my office is have a service layer to contain the business logic and repository to handle the data access. There is a single method in the repository to return an IQueryable that provides access to all the records for a particular table. There are then one or more methods in the service that call that repository method and use it in different ways. In this case, there might be a GetTotalCount method and a GetPage method in the service. Both would call the same repository method to get the same IQueryable and then the first method would call Count on the result while the second method would call Skip and Take. As Skip and Take don't force execution of the query, you'd also call ToArray or the like in that second method. The service might also have a GetRecord method that you would pass an ID and call FirstOrDefault inside to get a single record with a matching ID. You can roll the service and repository into a single class if you want but I'd recommend separating the business logic from the data access.

Using Odata to get huge amount of data

I have a data source provider :
public class DSProvider
{
public IQueryable<Product> Products
{
get
{
return _repo.Products.AsQueryable();
}
}
}
The repository in the above example currently gets ALL the records (of Products) from DB and then applies the filters, this just does not sound right if you had 50000 requests/sec from a website.How can you limit the repository to just return required info from DB without converting the service to a tightly coupled request option i.e. opposite of what you try to achieve by using oData?
So to summarize I would like to know if its possible to query the DB on the oData options supplied by the user so that my request does not always have to get all products and then apply filters of oData.
I found out after doing a small POC that Entity framework takes care of building dynamic query based on the request.