I have been looking at some GraphQL implementations and what I understand is that GraphQL lets you traverse through the data in the form of a Graph.
Which means you get a list of books which is a REST call. The books might have an Author Name.
When the user wants more info on an author he says so and another REST call is made to fetch more details about that author.
Similarly the books list may have a field called Publisher and so on.
So here you fetch data as if you are connecting a node of Book to a node of Author or Publisher.
But I have seen some implementations where data from two rest calls are combined using for loops and presented in the UI. e.g. make a call to REST API of Books, then make a call to REST API of Authors who have written those books. Run a for nested for-loop(n^2 complexity) to combine the result and show information of both books and authors in one single summary view.
Is this an acceptable practice or is it breaking some core concepts of what GraphQL is not supposed to do?
I'm not sure I would saying doing so is "breaking some core concepts", but there is a potential disadvantage to "front loading" all your calls to the REST endpoint this way. GraphQL allows you to define a resolver for each field, which determines what value the field returns. A "parent" field is resolved before its "children" fields, and the value of the "parent" is passed down to the resolvers for each "child".
Let's say you have a schema like this:
type Query {
book(id: ID): Book
}
type Book {
title: String
author: Author
publisher: Publisher
}
Here, the book field would resolve to an object representing a book, and that object would be passed down to the resolvers for title, author, and publisher.
If no resolver is provided for a particular field, the default behavior is to look for a property on the parent object with the same name as the field and return that. So, your resolver for book could fetch both the book, the author and the publisher from some REST endpoint (3 calls total) and returned the combined result. Alternatively, you could just fetch the book, and let the author field resolver fetch the author and the publisher field resolver fetch the publisher.
They key is that a resolver is only called if that field is requested. The difference between these two approaches is that you could potentially query a book and only request the title.
query SomeQuery {
book(id: 1) {
title
}
}
In this scenario, if you front load all your API calls, then you're making two additional calls to the REST endpoint (for the author and the publisher) unnecessarily.
Of course, if you have a field that returns an array of books, you may not want to hammer your REST endpoint by fetching the author for each book requested, so in those situations it may make sense to fetch both books and authors all in the root query.
There's no one right answer here, since there may be trade-offs either way. It's a question of how your schema is designed, how it will be used and what costs are acceptable to you.
Related
I am learning GraphQL and one basic point has me puzzled. I know there is an easy explanation, but I can't find it. Specifically, from the Apollo documentation (https://www.apollographql.com/docs/apollo-server/essentials/data.html#operation):
...it makes sense to name the operation in order to quickly identify
operations during debugging or to aggregate similar operations
together...Operations can be named by placing an identifier after the
query or mutation keyword, as we’ve done with HomeBookListing here:
query HomeBookListing {
getBooks {
title
}
}
If HomeBookListing is the name of the query, what, then, is getBooks? The name of the resolver?
Similarly, when you pass variables to a query, why are there "two levels" of parameters, like this
mutation HomeQuickAddBook($title: String, $author: String = "Anonymous") {
addBook(title: $title, author: $author) {
title
}
}
So, would $title: String, $author: String = "Anonymous" be the variables passed to the query, and title: $title, author: $author variables passed to the resolver?
Of course I can memorise the pattern, but I'm keen to understand, conceptually, what the different pieces are doing here. Any insights much appreciated!
You may find it helpful to review the spec, but what follows is a somewhat shorter explanation:
What is an operation?
There are three operations in GraphQL (query, mutation and subscription). Typically, a GraphQL request consists of only one of these three operations, and it forms the root of the request, or the entry point into the rest of the schema.
Each operation has a single object type associated with it. By convention, these types are named Query, Mutation and Subscription, but their naming is functionally irrelevant to your schema. Other than their association with a particular operation, there's nothing special about these object types -- each has a name, description and fields just like any other object type in your schema. Collectively, we call these three types root operation types.
In your example, the query root type has a field called getBooks. That field is resolved according to the same rules as any other field in your schema. The only special thing about this field is that it's at the root -- there is no "parent" field that was resolved before it.
Operation names are optional because they do not impact the data returned by the server -- they are there generally for debugging purposes (although some clients and tools use them to provide other features, so it's always good to have them). Specifying at least one field name for your root operation type, however, is necessary, otherwise your operation would not actually do anything (i.e. query the server for the data). Again, these fields are your entry point into the rest of the schema and the starting point for your data graph.
Ok, but what about the variables?
According to the spec:
Variables must be defined at the top of an operation and are in scope throughout the execution of that operation.
While we do not initialize a variable inside the document with a value, we do need to define it by telling GraphQL what the type of the variable it is. This allows GraphQL to then validate the usages of your variables throughout the document. For example, if you define a variable as a String and then attempt to use it at an input field that is an Int, validation will fail and your request will blow up before it is even executed.
Variables are always defined as part of the operation definition -- they can be used anywhere in the document, though, even multiple times. So there are no "two levels of parameters" here -- one line is simply the definition, the other line is usage.
A word on semantics
Even though we have a spec, the language around GraphQL has evolved past the terms outlined inside it. The term "query" has taken on multiple meanings that you may encounter while reviewing various docs and articles. It helps to keep these definitions in mind to avoid getting confused:
By convention, we name the root operation type associated with the query operation the Query type
Informally, the fields on that Query (i.e. getBooks) that are often referred to as the "queries" of your schema (just like the fields on the Mutation type are often called the "mutations" of your schema.
The complete request string we send to the server, which includes the whole operation and any relevant fragments is officially called the document. However, we often refer to making a request as querying your server. This has led to the document itself often being called a query, whether the operation is contains is actually a query or a different operation like a mutation.
Assume we have a model Book which contains another model Author.
Now lets send a query like:
query Book {
newestBooks(count: 200) {
id
title
author {
name
}
}
}
In my BookResolver I provide a method getAuthor() which is called once for each Book.
If the getAuthor() method loads the author from another service over the network or from the database a lot of overhead will occur for a large number of Books.
Is there a way in GraphQL to do some kind of a bulk request for populating the author field of the n Book instances?
I am evaluationg GraphQL with Java and Spring Boot, but I guess this topic is only concept and not environment related...
You are probably looking for the DataLoader concept. DataLoader ported to Java is available here.
I am building a Spring Boot based application to expose a JSON REST API.
In this application I have a 1-to-many relationship: one Order has multiple Items (and one Item belongs to exactly one Order).
I would like to have the following 4 API endpoints:
GET all Orders: In this case I just want the Order itself - so excluding the associated Items
GET a single Order: get the Order itself including the associated Items
GET single Item: get a single Item including the Order it belongs to (here it does not matter whether just the ID (=primary key) of the order is included or the whole order itself
GET all Items: the all the items; the associated Order is not necessary - but it also would not hurt.
Unfortunately I am a bit lost on how to model my associations and/or controller methods that expose the API endpoints.
Do you have some hints for me?
Thanks a lot!
Your first choice should always be to resort to Software Design Patterns. When developing applications which may require remote connections (or not), there is one that should be implemented in your rest api: Data Transfer Object.
Having into account you are developing under Java/Spring Framework, you should take a look at modelmapper library and to this guide.
I have successfully done the same task in my rest api.
Not sure if there is a better method of doing that, but my approach would be to model and fetch the relations using Hibernate, but in a lazy manner (https://howtoprogramwithjava.com/hibernate-eager-vs-lazy-fetch-type/).
In your controller, you do not return the entity but a DTO class that might be pretty similar to your entity. That DTO is created by some mapper component that provides the logic of including or not including associated items, etc.
I run a Java GraphQL server with Apollo client. My model is based on personas - each one can be an 'Actor', 'SoccerPlayer', 'Politician', etc.., all implement the 'Person' interface.
I have a 'Search' field, returning list of 'Person', however one entity might be of two types..
For example, when querying for
Search (text: "Ronald Reagan"){
id
name
... on Actor{
films{
name
}
}
... on Politician{
party{
name
}
}
}
I would expect to get both 'films' and 'party' for the former US president and actor, although typeResolver (on server side) forces me to return only one type. Is this doable at all with GraphQL? Maybe my model is wrong?
Thanks
GraphQL spec does not allow for multi-level inheritance, so with your specific structure, a Person can either be one or the other, but not both Actor and Politician.
You could get slightly closer to your goal if you turned Actor and Politician into interfaces themselves, but you'd have to repeat the fields from Person in each one as, again, no multi-level inheritance is possible.
In this great book about Domain-Driven Design, a chapter is dedicated to the user interface and its relationship to domain objects.
One point that confuses me is the comparison between Use case optimal queries and presenters.
The excerpt dealing with optimal queries (page 517) is:
Rather than reading multiple whole Aggregate instances of various
types and then programmatically composing them into a single container
(DTO or DPO), you might instead use what is called a use case optimal
query.
This is where you design your Repository with finder query
methods that compose a custom object as a superset of one or more
Aggregate instances.
The query dynamically places the results into a
Value Object (6) specifically designed to address the needs of the use
case.
You design a Value Object, not a DTO, because the query is
domain specific, not application specific (as are DTOs). The custom
use case optimal Value Object is then consumed directly by the view
renderer.
Thus, the benefit of optimal queries is to directly provide a specific-to-view value object, acting as the real view model.
A page later, presenter pattern is described:
The presentation model acts as an Adapter. It masks the details of the
domain model by providing properties and behaviours that are designed
in terms of the needs of the view.
Rather than requiring the
domain model to specifically support the necessary view properties, it
is the responsibility of the Presentation Model to derive the
view-specific indicators and properties from the state of the domain
model.
It sounds that both ways achieve the construction of a view model, specific to the use case.
Currently my call chain (using Play Framework) looks like:
For queries: Controllers (acting as Rest interface sending Json) -> Queries (returning specific value object through optimal queries)
For commands: Controllers (acting as Rest interface sending Json) -> Application services (Commands) -> domain services/repositories/Aggregates (application services returns void)
My question is: if I already practice the use case optimal query, what would be the benefit of implementing the presenter pattern? Why bother with a presenter if one could always use optimal queries to satisfy the client needs directly?
I just think of one benefit of the presenter pattern: dealing with commands, not queries, thus providing to command some domain objects corresponding to the view models determined by the presenter. Controller would then be decoupled from domain object.
Indeed, another excerpt of Presenter description is:
Additionally, edits performed by the user are tracked by the
Presentation Model.
This is not the case of placing overloaded
responsibilities on the Presentation Model, since it's meant to adapt
in both directions, model to view and view to model.
However, I prefer sending pure primitives to application services (commands), rather than dealing directly with domain object, so this benefit would not apply for me.
Any explanation?
Just a guess :)
The preseneter pattern could reuse your repository's aggregate finder methods as much as possible. For example, we have two views, in this case we need two adapters(an adapter per view), but we only need one repository find method:
class CommentBriefViewAdapter {
private Comment comment;
public String getTitle() {
return partOf(comment.getTitle());
//return first 10 characters of the title, hide the rest
}
.....//other fields to display
}
class CommentDetailViewAdapter {
private Comment comment;
public String getTitle() {
return comment.getTitle();//return full title
}
.....//other fields to display
}
//In controller:
model.addAttribute(new CommentBriefViewAdapter(commentRepo.findBy(commentId)));
// same repo method
model.addAttribute(new CommentDetailViewAdapter(commentRepo.findBy(commentId)));
But optimal queries is view oriented(a query per view). I think these two solutions are designed for none-cqrs style ddd architecture. They're no longer needed in a cqrs-style arichitecture since queries are not based on repository but specific thin data layer.