Facebook GraphQL vs GetOption

Tade Samson
8 min readJan 17, 2021

--

query getChurch($churchType: ChurchTypes, $withMembers: Boolean!, $memberCursor) {church(churchType: $churchType) {statecountrymembers @include(if: $withMembers first:10 after:$memberCursor) {emailusername}}}{"churchType": "RCCG","withMembers": false}option={"searchExpression":" (country%nigeria&&state%lagos)","loadedPropertyOption":{"loadedProperties":["state","country",members.email,members.username]},"paginationOption":[{“targetNode”:”members”"pageSize":10,"pageNo":1}],"sortOption":{"ascending":true,"property":”dateCreated”}}

As one of our major goals with every project we execute, we always strive to achieve results in different and better ways. When working on complex projects, and as a major architectural pattern of software development, we maintain a restful service-based endpoint which every tier of our product development relies on data entry and consumption. From mobile apps to web applications.

While we have built lots of restful services for different products. But on each occasion, We’ve always asked ourselves one question “how can we build a better restful service”? This very important question came as an answer to various challenges we feel needs to be overcome and here are a few:

  1. The necessity for web apps to request for complex response data of an endpoint and for mobile apps to request for miniaturized response data of the same endpoint
  2. The necessity to perform a surface or/and deep down search on the response object
  3. The necessity to delegate pagination to our restful service
  4. The necessity to delegate sorting to our restful service.

To make our implementation more interesting and a bit faster, we aim to port the client-side request option directly to our repository level. Not just executing the whole filtering at the service layer. We want it done at the database layer. This will require us to build another database-agnostic pattern which would help map every client request option to an executable query for the desired repository.

While we were here in Nigeria thinking of the best way to solve these restrictions, somewhere in Silicon Valley, Facebook was also thinking in our direction.

We ended up building what we called GetOption to solve our API request restrictions and give control to how we consume our restful services from different environments.

Facebook on the other hand released an RPC-focused query-based service to outwit the restrictions of restful services. Facebook released GraphQL a service now widely adopted and gaining tractions within the tech ecosystem.

While our GetOption implementation doesn’t in any way serve as a replacement or even near comparison to Facebook GraphQL, we’re just happy we thought in that direction and we implemented something over restful service. By sharing our experience, we hope to get various responses on how it could have been better done. Perhaps, you have built something similar, you can share your experience and insight on your approach for learning purposes.

Just like GraphQL, we have a server-side engine and a client-side construction.

I hope we have fun together while I talk about how we get close to Facebook GraphQL on each of the 4 initial restful service restrictions pointed out earlier. I’ll also discuss our server-side implementation and client-side consumption

  1. The necessity for web apps to request for more complex response data of an endpoint and for mobile apps to request for miniaturized response data of the same endpoint.

As one of the major restrictions we wanted to break away from, we implemented a RequestLoad utility on the service end. We achieve this by leveraging on the generic expressionType which provides us with the possibility of writing a single reflective nugget to handle every type exposed via our service.

On the service end, our RequestLoad utility looks like this:

public static bool RequestLoad(this ClientGetOption option, Expression<Func<TModelOption,object>> property){if (option == null || option.loadedPropertyOption==null)return true;string prop = (property.Body as MemberExpression).Member.Name;return  option.loadedPropertyOption.LoadedProperties.Any(p => p.ToLower().Trim() == prop.Trim().ToLower());}

on the client-side, requesting needed properties is as simple as configuring the option object to look like this.

option={

"loadedPropertyOption":{

"loadedProperties":["state","country",members.email,members.username]

},

the mighty GraphQL specification solves the same problem on the client-side by constructing a query that looks similar to this

query getChurch() {

church {

state

country

members{

email

username

}

}

}

GraphQL also has Directives, which gives the possibility of dynamically changing the response structure of your request with a query like this

query getChurch($withMembers: Boolean!) {

church() {

state

country

members @include(if: $withMembers) {

email

username

}

}

}

{

"withMembers": false

}

with the $withMembers directives, you instruct graphQL to only fetch church members only when value true is passed to the called service method.

While GraphQL follows a type of language definition in defining how arguments are used and how resources are accessed which is pretty beautiful. With our GetOption, we can also achieve this by constructing our loadedPropertyOption without the member's property.

Also worth noting is that GraphQL specification does not restrict to what level desired object properties can be accessed, we have limited our loadedPropertyOption to just two layers.

which implies; for a church object like this

public class Church{

public string name{get;set;}

public string country{get;set;}

public Member[] members{get;set}

}

public class Member{

public string email{get;set;}

public string username{get;set;}

public PrayerRequest[] prayerRequests{get;set;}

}

public class PrayerRequest{

public string requestNo{get;set;}

pubic dateTime postedOn{get;set;}

}

our loadedPropertyOption can only be configured to specify needed properties on the Church object as well as on the members object but not three layers deeper to specifying needed Properties on each member’s prayerRequests object. But with graphQL, it’s an open world, anything is possible as far as the server-side graphQL service supports it.

  1. The necessity to perform a surface or/and deep down search on the response object

Most times, during product design and developments, we’re presented with a functional feature that required us to serve results that match different properties of an entity to the user. Just like the diagram above. How do you achieve that?

To achieve this with a conversation restful service call, we have two options

(a) Fetch all the entity from the service endpoint and do client-side filtering

While the first option isn’t a problem when the environment is the web. the downside is that it affects server response time and causes unnecessary server delays in requests service time. worst if the server is synchronous.

The other drawback of this approach would be in the mobile environment where small data response is preferred because of memory restrictions.

(b) perhaps, the server takes a simple search parameter when fetching the list of entities. With this, different request to the server is required for each property type.

We understand this restful service restriction so we had it in mind to build an abstraction that could allow us to perform such query with a single server request without returning redundant entities. We also want to project the search query beyond our service layer to the repository layer. This ensures that filtering and searching are delegated to the database which would ensure more time is available to our service to serve other asynchronous requests.

While facebook positioned GraphQL as a typed query language which operates more like a remote procedure call, support for complex binary search expression like this is not currently supported.

the best one can do currently with GraphQL is to make a single request that returns multiple instances of churches based on each search criterion like this.

this uses GraphQL directives and alias

query getResults() {

nameMatchedResults: churches("name" "xyz") {

...singlePropertySearchResult

}

aliasMatchedResults: churches("alias" "xyz") {

...singlePropertySearchResult

}

denominationMatchedResults: churches("denomination" "xyz") {

...singlePropertySearchResult

}

}

fragment singlePropertySearchResult on Church {

state

country

members{

email

username

}

}

while all these returns matched churches based on each search property, there’s a high tendency of duplicate churches in different results. This leaves the client developer with the option of refactoring results to avoid displaying duplicate contents.

Though the GraphQL community insists a mental shift is required to understand why complex expression is not supported but as updates will be made to the schema, we can expect anything in the near future.

With our getOption, we wanted to achieve this since our default server implementation maps client queries to the database, we felt we could relieve our service of some work.

To achieve this, we built as part of our getOption a search utility called searchExpresssion. We built seachExpression as a complex data structure on the service layer and as a simple string expression when consuming on the client-side. Taking the whole complexity away from client-side developers.

To achieve a query similar to the input above with our getOption, the configuration below is required

option={

"searchExpression":"(name%"+searchTerm+"||alias%"+searchTerm+"|| denomination%"+searchTerm)"

}

var searchTerm="searchValue";

the search expression is somewhat flexible that any combination of complex binary expression including bracket group and the use of || and && is supported. this flexibility offered to client-side developers leaves us with the necessity to implement an expression parser on the service layer which conforms to our binaryExpression syntax and semantics.

Our parser performs 2 main functions.

  1. generate tokens from the string expression passed from the client-side. We achieved this by validating against our binaryExpression syntax tree. From a visual point of view, our expression validator looks like this.

If the expression fails during token generation, we return a parse error to back to the client. If succeeded, we proceed to generate the server-side expression which can be complex expression type or simple expression type based on the search query from the client.

with the use of postfix notation, we could parse our token to our SearchExpression like this

SearchExpression GetSearchExpression(Stack tokenList)

{

Stack output = new Stack.Token>();

bool error = false;

while((tokenList.Count>1 || tokenList.Count==1 && tokenList.Peek().TokenType!=TokenType.Expresssion))

{

Token token= tokenList.Pop();

if (token.TokenType == TokenType.BinaryOperator || token.TokenType == TokenType.AssignmentOperator)

{

Token rightHand = output.Pop();

Token leftHand = output.Pop();

ExpressionType expressionType = GetExpressionType(leftHand, rightHand, token);

if (expressionType == ExpressionType.member)

{

Token propertyExpressionToken = new Token();

PropertySearchExpression propertySearchExpression = new PropertySearchExpression();

propertySearchExpression.Property = leftHand.value as string;

propertySearchExpression.Operator = (string)token.value;

propertySearchExpression.Value = (string)rightHand.value;

propertyExpressionToken.value = propertySearchExpression;

propertyExpressionToken.TokenType = TokenType.Expresssion;

tokenList.Push(propertyExpressionToken);

while (output.Count > 0)

tokenList.Push(output.Pop());

}

else if (expressionType == ExpressionType.binary && leftHand.TokenType == TokenType.Expresssion && rightHand.TokenType == TokenType.Expresssion)

{

Token binaryExpressionToken = new Token();

BinarySearchExpression<T> binarySearchExpression = new BinarySearchExpression<T>();

binarySearchExpression.BinaryOperator = token.value as string;

binarySearchExpression.LeftSearch = leftHand.value as SearchExpression;

binarySearchExpression.RightSearch = rightHand.value as SearchExpression;

binaryExpressionToken.TokenType = TokenType.Expresssion;

binaryExpressionToken.value = binarySearchExpression;

tokenList.Push(binaryExpressionToken);

while (output.Count > 0)

tokenList.Push(output.Pop());

}

else

{

error = true;

break;

}

}

else

output.Push(token);

}

if (error || tokenList.Count > 1)

return null;

return tokenList.Pop().value as SearchExpression;

}

The entire project source code can be found here on Github.

--

--

Tade Samson

CEO/Co-Founder @QuizacApp. Innovation Catalyst @thribyte. Talk Business in the day, speak codes at night. An optimist, finding the path to becoming a visionary.