GraphQL can be a very efficient way of transferring data via API calls.
I’ve been RESTing happily since the early 2000s after Roy Fielding’s doctoral dissertation, “Architectural styles and the design of network-based software architectures,” caused many in the software world to move to representational state transfer (REST) to solve their API needs.
Prior to that, I was building web-enabled software services, called service-oriented architecture (SOA) or web services. REST helped to formalize API definitions, but SOA and web services were essentially equivalent to traditional approaches in two key ways: The API developer predetermines both the endpoints and the data returned for each API.
Over the past few years, many have come to consider REST the de facto standard for API usage, even for noninternet applications. It’s easy to embed a web server to serve up a REST API, and there are plenty of frameworks available to enable it. Additionally, REST APIs are language- and platform-neutral, and those APIs are often used as a facade to enable legacy applications in a modern web or mobile application architecture. In this article, I’ll talk about both REST and another architecture, GraphQL.
REST has drawbacks
Although REST solves many API-related problems, it’s not perfect. The architecture’s deficiencies include the following.
Overfetching. REST APIs are defined to return data as a predefined structure, usually in XML or JSON. If a caller wants only some fields of data returned, too bad: They get all the data anyway. This doesn’t seem like a big deal, but this inefficiency adds up when an API returns multiple records.
Underfetching. You may need to make multiple REST calls to aggregate all the data you need for one user or back-end operation. The associated round trips are inefficient and can lead to multiple database transactions.
Overfetching and underfetching. Ironically, underfetching often leads to overfetching, because one or more of the REST calls required to satisfy a single user operation likely contain data that’s not needed or that’s duplicated..
Implicit intent. REST is built upon HTTP, and it leverages GET and PUT/POST calls to indicate read or write operations. With REST, it’s frowned upon to name API calls explicitly, for example, GetUser or CreateUser. Instead, you are encouraged to name the API and the user, and then rely on the HTTP operation that’s used to imply the intent. For example, an HTTP GET is equivalent to GetUser, PUT is equivalent to either UpdateUser or CreateUser, a POST is usually equivalent to CreateUser but sometimes to UpdateUser, and DELETE is equivalent to DeleteUser. Because of this, the API’s intent can be hidden behind the communication protocol, so it isn’t always obvious. It’s also not a precise match; hence, the confusion between POST, PUT, and PATCH.
Lack of agility. Each REST API call exists and returns the prescribed data only because its creator decided it should. Even if the API is well designed, it’s unlikely to serve every client’s needs precisely, and changing needs will render it less of a fit over time. Additionally, once APIs are used, it’s difficult or impossible to change them without impacting external applications. Building dependencies between applications is less than agile.
Introducing GraphQL
In 2012, developers at Facebook developed an improvement on REST, which was then released as an open source data query language called GraphQL.
GraphQL is similar to REST except that it’s data oriented: The caller precisely defines the data to be returned, and the server complies by returning that data and nothing else. For instance, if a user wants to know the balance for a bank account, the front-end code will make a call to a GraphQL web interface using a JSON-like request such as the one shown in Listing 1. (The ssn field is for a nine-digit identifier issued by the US government called a Social Security Number.)
Listing 1. A sample GraphQL query
{
account {
id(id: "987654321")
name
type
customer {
firstName
lastName
ssn
}
availableBalance
totalBalance
}
}
The server will fulfill the query with a JSON-compliant response, as shown in Listing 2.
Listing 2. A sample GraphQL query response
{
"data": {
"account": {
"id": "987654321",
"name": "Personal Checking",
"type": "Basic Checking",
"customer": {
"firstName": "Eric",
"lastName": "Bruno",
"ssn": "123-45-6789"
},
"availableBalance": "1234.56",
"totalBalance": "1234.56"
}
}
}
In this case, the identification of the bank account is provided as an input key for lookup. So far, this is a straightforward query. However, consider that this single GraphQL call combines data from multiple resources: the user as well as basic account and balance information from the bank.
By contrast, common REST APIs often break this into multiple endpoints and calls: one for the balance of the given account number, another for account data, and yet another for user data. Additionally, there’s likely a lot more data about the account and the user than what was returned here.
Individual REST calls to get user and account information would likely have resulted in overfetching, which is inefficient and may even be a security risk in a financial application.
Looking inside GraphQL
Although GraphQL’s name contains the word graph, the architecture doesn’t supply true graph operations. However, GraphQL does provide a type system with introspection, a defined query language, and execution semantics with explicit indication of reads and writes. A single request, called a query, can return data for more than one resource, as shown in the previous example, by following references between them.
In other words, GraphQL queries allow you to express relationships in the call itself, dynamically, offering efficiency and flexibility.
Unlike REST APIs, which use endpoints to describe and group operations, GraphQL organizes them by schemas, data types, and associated fields. Types are used to constrain requests to only what is feasible, and they indicate how data is to be used. Using the query in Listing 1, related GraphQL types might look like Listing 3.
Listing 3. GraphQL types for the query in Listing 1
type Query {
account: Account
}
type Account {
id: Int
name: String
type: [
"Basic Checking"
"Advanced Checking"
"Business Checking"
]
owner: Customer
availableBalance: Balance
totalBalance: Balance
}
type Customer {
firstName: String
lastName: String
ssn: String
address: Address
phone: Phone
email: String
active: Boolean
}
type Address {
street: String
city: String
state: [
"Alabama"
"Alaska"
...
]
zip: String
}
type Phone {
...
}
type Balance {
amount: Float
asOf: Date
...
}
As shown in this example, the GraphQL type system is expressive and comprehensive.
GraphQL mutations
Notice that the GraphQL description for type in Listing 3 begins with the keyword Query. This indicates that this is a read schema. GraphQL provides the mutation schema to mark an API as writable. It’s a requirement that every GraphQL API have a query type, but a mutation type is optional, and it is similar to queries in that you specify nested fields and a return type. The following is an example of the mutation type definition:
mutation CreateAccount($account: Account,) {
createAccount(account: $account) {
id
name
}
}
The createAccount mutation creates a new account and returns the id and name of that account. The matching request, which is an input object type, would look like the following:
{
"account": {
"name": "Personal Checking",
"type": "Basic Checking",
"customer": {
"firstName": "Eric",
"lastName": "Bruno",
"ssn": "...",
"address": "...",
"phone": "...",
"email": "eric@ericbruno.com",
"active": "true"
}
}
:
}
The result would be the new account id and name, as shown below.
{
"data": {
"createAccount": {
"id": "987654321",
"name": "Personal Checking",
"Customer:" {
"ssn": "..."
}
}
}
}
The mutation in this example can create a new customer along with the account or return an existing customer if the record is located with the ssn provided; GraphQL is flexible this way.
The GraphQL schema includes more advanced features, such as interfaces, lists, the ability to specify bounds on fields, enumerations, unions, inputs, operations, and more. There’s also a sophisticated validation schema based on the GraphQL type system.
Java and GraphQL
GraphQL includes open source helper code in many languages, including Java, to make it easy to create and consume GraphQL APIs.
On GitHub, you’ll find Java classes to help generate queries, define schemas, execute queries, and parse the results. Other GraphQL Java libraries are available and also integrate with other tools and server frameworks such as Spring.
Source: oracle.com
0 comments:
Post a Comment