This is about adding GraphQL to an existing Java api which takes in multiple lists as input.
Background:
I have an existing Java based REST API getFooInformationByRecognizer which takes in a list of recognizers, where each recognizer object contains an id and it's type and returns information corresponding to each id.
The only 3 types possible are A, B or C. The input can be any combination of these types.
Eg:
[{"id": "1", "type": "A" }, {"id": "2", "type": "B"},{"id": "3", "type": "C"}, {"id": "4", "type": "A"}, {"id":"5", "type": "B"}]
Here's it's Java representation:
class FooRecognizer{
String id;
String FooType;
}
This api does a bit of processing.
First extracts out all the input that has ids of type A and fetches information corresponding to those ids.
Similarly, extract out the ids that has type B and fetches information corresponding to those ids and similarly for C.
So, it fetches data from 3 different sources and finally collates them to a single map and returns.
Eg:
ids of type A --> A SERVICE -> <DATA SOURCE FOR A>
ids of type B --> B SERVICE --> <DATA SOURCE FOR B>
ids of type C --> C SERVICE --> <DATA SOURCE FOR C>
Finally does this:
A information + B information + C information and puts this in a Java Hashmap.
The Java representation of the request to this service is:
class FooRequest{
private Bar bar;
List<FooRecognizer> list;
}
The Java representation of the response object from the service is:
class FooInformationResponse{
private Map<String, FooRecognizer> fooInformationCollated;
}
Sample JSON output of the response is:
"output":{
"fooInformationCollated":{
"1":{
"someProperty": "somePropertyValue"
"listOfNestedProperties": [{"x": "xValue", "y": "yValue", "z","zValue"]
"nestedProperty":{
"anotherProperty":{
"furtherNestedProperty": "value"
}
}
}
"2":{
"someProperty": "somePropertyValue"
"listOfNestedProperties": [{"a": "aValue", "b": "bValue", "c","cValue"]
"nestedProperty":{
"anotherProperty":{
"furtherNestedProperty": "value"
}
}
}
}... and so on for other ids in the input
Now, I want to convert this service to GraphQL and here is my query.
query{
getFooInformationByRecognizer(filterBy:{
fooRecognizer: [{
id: "1",
fooType: A
}],
bar: {
barId: "someId",
...<other bar info>
}
}){
fooInformationCollated{
id
fooInformation{
someProperty
listOfNestedProperties
nestedProperty{
anotherProperty{
furtherNestedProperty
}
}
}
}
}
}
Here is my GraphQL schema:
type Query{
getFooInfoByRecognizer (filterBy: getFooByRecognizerTypeFilter!):getFooByRecognizerTypeFilterResponse
}
input getFooByIdentifierTypeFilter{
bar: Bar!
fooIdentifiers: [FooIdentifier!]!
}
input Bar{
barId: String!
....
}
input FooIdentifier{
id: String!
fooIdType: fooIdtype!
}
enum fooIdType{
A
B
C
}
I have a few questions here:
Would this be the best way / best practice to represent this query? Or should I model my query to be able to take in 3 separate lists. Eg: query getFooInformationByRecognizer(barId, listOfAs, listOfBs, listOfCs). Any other choice that I have to query / model?
I found having a complex input type as the easiest. In general, is there any specific reason to choose complex input type over other choices or vice-versa?
Is there any thing related to query performance that I should be concerned with? I've tried looking into DataLoader / BatchLoading but that doesn't quite seem to fit the case. I don't think N+1 problem should be an issue as I will also create separate individual resolvers for A, B and C but the query as can be seen does not make further calls to back-end once JSON is returned in response.
The question is too broad to answer concretely, but here's my best attempt.
While there isn't a definitive answer on 1 complex input argument vs multiple simpler arguments, 1 complex argument is generally more desirable as it's easier for the clients to pass a single variable, and it keeps the GraphQL files smaller. This may be more interesting for mutations, but it is a good heuristic regardless. See the logic explained it more detail e.g. in this article.
The logic explained above echoes your own observations
For this specific scenario you listed, I don't see anything of importance for performance. You seem to fetch the whole list in one go (no N+1), so not much different from what you're doing for your REST endpoint. Now, I can't say how expensive it is to fetch the lower-level fields (e.g. whether you need JOINs or network calls or whatever), but if there's any non-trivial logic, you may want to optimize it by looking ahead into the sub-selection before resolving your top-level fields.
Related
Let's say we have a ProductType table in our database. This represents the types of products that can exist:
productTypeId
name
description
capacity
pricePerNight
1
Tent
This is a tent
4
20
Let's say we have a Product table in our database. This represents actual physical Products that exist (i.e. in a rental shop's inventory):
id
productTypeId (FK)
condition
lastUsed
6
1
good
18/11/21
7
1
bad
18/11/21
8
1
medium
18/11/21
Now, let's say I was making a public API which allows clients to query all available products and the price of those products for a specific set of dates.
My options are: (a) Return an edited version of the ProductType object with new fields (e.g. quantityAvailable and priceForDates) conveying the extra information requested:
{
"productTypeId": 1,
"name": "Tent",
"description": "This is a tent",
"capacity": 4,
"quantityAvailable": 1,
"priceForDates": 60,
}
(b) Wrap the ProductType object in a parent object then add extra fields (e.g. quantityAvailable and priceForDates) to the parent object:
{
"quantityAvailable": 3,
"priceForDates": 60,
"product":
{
"productTypeId": 1,
"name": "Tent",
"description": "This is a tent",
"capacity": 4,
}
}
I am confronted by this situation quite regularly and I'm wondering what is the best practice for a RESTful API.
IRL I am building a public API which needs to be as intuitive and easy to use as possible for our company's integrating partners.
This is borderline opinion-based but still, this is my take on this.
I would go with option (b). The reason is that you might be exposing your ProductType as the following in other endpoints:
"product": {
"productTypeId": 1,
"name": "Tent",
"description": "This is a tent",
"capacity": 4,
}
In this case, it is important to be consistent and to represent ProductType with the same structure in all your endpoints.
On top of this, this seems the response to the call of checking the availability of a product for a given period of days. Even the way you phrased it "(...) allows clients to query all available products and the price of those products for a specific set of dates." shows that the price and the number of available products are just additional information about a given ProductType. Hence, they should be included alongside ProductType but not as part of ProductType. For example priceForDates is clearly not a ProductType property.
I created an entity (Model or Data class) in a Spring-Boot (Kotlin) project which contains a field with the type Arraylist but when I send an array data in JSON format from Postman, the Array gets stored in the database as a long random string.
When I try to retrieve the data from the database I get the actual array, perfectly formated.
My question is why is an ArrayList stored in an H2 database like this???
Evaluation.kt
#Entity
data class Evaluation (
#Id val id : String,
val timeStamp : Long,
val symptoms : ArrayList<String>,
val travelHistory : Boolean,
val contactWithCovidPatient : Boolean,
val evaluatedBy : String,
var evaluationPercentage : String? = null,
#ManyToOne var user: User? = null
)
EvaluationController.kt
#RestController
class EvaluationController (val evaluationService: IEvaluationService) {
#PostMapping("evaluate/{userId}")
fun evaluateUser(#PathVariable userId : String, #RequestBody evaluation: Evaluation) : ResponseEntity<Evaluation> =
ResponseEntity.ok().body(evaluationService.addEvaluation(evaluation, userId))
}
Request Body JSON
{
"id":"e_01",
"timeStamp":"123456789",
"pinCode":"123457",
"travelHistory":true,
"contactWithCovidPatient":true,
"evaluatedBy":"u_01",
"symptoms": ["Fever","Cough"]
}
Response JSON
{
"id": "e_01",
"timeStamp": 123456789,
"symptoms": [
"Fever",
"Cough"
],
"travelHistory": true,
"contactWithCovidPatient": true,
"evaluatedBy": "u_01",
"evaluationPercentage": "95",
"user": {
"id": "u_01",
"name": "abc01",
"phoneNumber": "9876543210",
"pinCode": "123457",
"covidResult": "Positive"
}
}
H2 Database Table
This is a hex string representing the serialized ArrayList object. See Serializable Objects for details about object serialization in Java.
Running the following code yields the same result:
List<String> symptoms = new ArrayList<>(Arrays.asList("Fever", "Cough"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(symptoms);
byte[] serializedObject = byteArrayOutputStream.toByteArray();
String hex = Hex.encodeHexString(serializedObject); // Apache Commons Codec
System.out.println(hex);
aced0005737200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657870000000027704000000027400054665766572740005436f75676878
The raw string seen in the DB is a serialized object.
One way of implementing this is to first join your string ArrayList into one delimited string, but I highly recommend against this.
In general it's bad practice to put a list into a single field of a table. What you should be doing is creating a separate table for Symptoms with a one-to-many relationship with Evaluation.
You need to be aware of denormalization while designing your objects when using JPA. In your case, consider the following questions :
What happens if you want to query evaluations with specific symptoms?
What happens if you want to query a list of all the symptoms?
What happens if you want to expand symptoms with some other detail, such as date when a symptom appeared?
If you are ever in a situation of trying to add a collection of something into a database field 99.9999% of the time you're doing it wrong. Symptoms should be their own entity, and you have a one-to-many or many-to-many relationship between evaluation and symptom, depending on what you require.
Edit :
To clarify my answer further, when designing object classes think about whether a field is a value object or an entity. A value object is something that cannot be broken down further and can be represented by a primitive, such as Date, String, Int, etc. Some examples could be an object ID, name, phone number, etc.
An entity is an object that can be expanded further, like the Evaluations object you created. Within Evaluations you have a list of Symptoms, and you're treating Symptoms as a value object. Is it a value object though? I can immediately think of some additional fields you could put into a Symptom object, and by denormalizing symptoms the way you did, you are also inputting tons of duplicate data into the database.
An evaluation object containing ["Fever", "Cough"] in your implementation will be input into the database as one field. But another evaluation object containing the same symptoms will be input into the database for that evaluation because you don't have a foreign key dependency or a separate table representing symptoms. On top of not being able to query symptoms in relation with evaluations, or not being able to query symptoms on their own.
I am bulding a small app to track Bike Stations around the city, and I have an API that gives me the current status of the availability of bikes in bike stations from the company that provides the service.
My plan is to have a sort of interactive map, with all the markers for each of the bike stations, and when the user taps one of these, they get the information on that specific bike station. I have already all the locations coded in as markers on the map. What I need now is to be able to get the data for the specific bike station the user clicks.
An example of a part of the JSON I get from the API is below:
"number": INT,
"contract_name": "STRING",
"name": "STRING",
"address": "STRING",
"position": {
"lat": DOUBLE,
"lng": DOUBLE
},
"banking": BOOLEAN,
"bonus": BOOLEAN,
"bike_stands": INT,
"available_bike_stands": INT,
"available_bikes": INT,
"status": "STRING",
"last_update": 1588583133000
},
....
This structure is the same for all 100+ nodes of the JSON which I get from the API.
My question is, how would I go about filtering out one individual entry like such from the rest of the JSON. The parameter number is an ID unique to each bike station.
Is there a library that can do this for me? My idea (Very naive) was to save the whole JSON locally each time, and then go through it looking for "number":X and then parse out the data I needed, although this is obviously highly inefficient, I recognize that.
I am only interested in a part of each JSON, to be show to the user: the node's banking, bonus, available_bike_stands, available_bikes and status tags. The status tag is optional, it should simply tell me if the bike station is open (available) or closed.
Thank you very much,
Regards.
Get data from API --> Retrofit
Save local data--> SharePreference, Room
get a part of each JSON --> you create an object that contains some fields you need. when you use retrofit get data from API then it will return the result you desire
class YourClass {
#SerializedName("number")
var number: Int? = null
#SerializedName("banking")
var banking: Boolean? = null
#SerializedName("bonus")
var bonus: Boolean? = null
#SerializedName("available_bike_stands")
var availableBikeStands: Int? = null
//... fields you need
}
I'm trying to add new field (LastLoginDate of type Date) to a existing collection. Here is my sample script:
db.createCollection( "MyTestCollection",
{ "validator": { "$or":
[
{ "username": { "$type": "string" } },
{ "notes": { "$type": "string" } }
]
}
}
)
db.getCollectionInfos({name: "MyTestCollection"});
[
{
"name" : "MyTestCollection",
"options" : {
"validator" : {
"$or" : [
{
"username" : {
"$type" : "string"
}
},
{
"notes" : {
"$type" : "string"
}
}
]
}
}
}
]
What is the best way to add new field LastLoginDate : { $type: "date" }, to this existing collection "MyTestCollection".
Adding new document or updating existing collection with new field may create this field. But i'm not sure how to enforce the date type on the new field. After adding new filed, if i execute the following command again, it doesn't show type validator for newly added field.
I "should" probably prefix this with one misconception in your question. The fact is MongoDB differs from traditional RDBMS in that it is "schemaless" and you do not in fact need to "create fields" at all. So this differs from a "table schema" where you cannot do anything until the schema changes. "Validation" however is a different thing as well as a "still" relatively new feature as of writing.
If you want to "add a validation rule" then there are methods which depend on the current state of the collection. In either case, there actually is no "add to" function, but the action instead is to "replace" all the validation rules with new ones to specify. Read on for the rules of how this works.
Existing Documents
Where the collection has existing documents, as noted in the documentation
Existing Documents
You can control how MongoDB handles existing documents using the validationLevel option.
By default, validationLevel is strict and MongoDB applies validation rules to all inserts and updates. Setting validationLevel to moderate applies validation rules to inserts and to updates to existing documents that fulfill the validation criteria. With the moderate level, updates to existing documents that do not fulfill the validation criteria are not checked for validity.
This and the following example section are basically saying that in addition to the options on .createCollection() you may also modify an existing collection with documents, but should be "wary" that the present documents may not meet the required rules. Therefore use "moderate" if you are unsure the rule will be met for all documents in the collection.
In order to apply, you use the .runCommand() method at present to issue the "command" which sets the validation rules. Which is "validationLevel" from the passage above.
Since you have existing rules, we can use the `.getCollectionInfos() to retrieve them and then add the new rule and apply:
let validatior = db.getCollectionInfos({name: "MyTestCollection"})[0].options.validator;
validator.$or.push({ "LastLoginDate": { "$type": "date" } });
db.runCommand({
"collMod": "MyTestCollection",
"validator": validator,
"validationLevel": "moderate"
});
Of course as noted before, that if you are confident the documents all meet the conditions then you can apply "strict" as the default instead.
Empty Collection
If in the case is that the collection is actually "empty" with no documents at all or you may "drop" the collection since the current data is not of consequence, then you can simply vary the above and use .createCollection() in combination with .drop():
let validatior = db.getCollectionInfos({name: "MyTestCollection"})[0].options.validator;
validator.$or.push({ "LastLoginDate": { "$type": "date" } });
db.getCollection("MyTestCollection").drop();
db.createCollection( "MyTestCollection", { "validator": validator });
let previousValidator = db.getCollectionInfos({name: "collectionName"})[0].options.validator;
# push the key to required array
previousValidator.$jsonSchema.required.push("isBloodReportAvailable")
let isBloodReportAvailabl = {"bsonType" : "bool", "description" : "must be an bool object and is optional" }
# add new property to validator
previousValidator1.$jsonSchema.properties['isBloodReportAvailable'] = isBloodReportAvailabl
db.runCommand({
"collMod": "collectionName",
"validator": previousValidator,
});
Task at hand: Consider the following model for a JSON response, every API response will conform to this response, obviously the data will vary. Lets call it ResponseModel:
{
"isErroneous": false,
"Message": "",
"Result": null,
"Data": {
"objId": 38,
"objName": "StackO",
"objDescription": "StackODesc",
"objOtherId": 0,
"objLocationId": 1
}
}
How can I deserialize this response regardless of the data in Data: ? Data could contain a single object of different types, e.g a Dog, a Car. It could also contain a collection of Cars or Dogs e.g not just one like above.
It could also contain A car, the cars engine obj, the cars driver seat obj.
In short, the same response is always going to be present but the value of Data can vary wildly, I want to try best to deserialize this to some sort of Result.class for ALL possible scenarios, how do I best approach this? Setting up the class ResponseModel.class is easy for everything except the "Data" type.
Thanks
Here is another example of something which could be returned
{
"isErroneous": false,
"Message": "",
"Result": null,
"Data": [
{
"carId": 1,
"carName": "car#1",
"carDescription": "car#1",
"carOtherId": 1,
},
{
"carId": 2,
"carName": "car#2",
"carDescription": "car#2",
"carOtherId": 2,
},
{
"carId": 3,
"carName": "car#3",
"carDescription": "car#3",
"carOtherId": 3,
},
As you can see in the second example, we are returning a list of cars but in the first response we are just returning a single object. Am i trying to abstract this too much? should I setup custom response(s) for each call of the API etc?
I need to be writing integration tests asserting that the deserialized object is equal to one that I expect everytime.
you can try to check if your json input contains "Data": { or "Data": [ then parse it to seperate class accordingly
But... maybe it's not the best way to do it