At Adobe, we are currently developing an integration layer between AEM (Adobe Experience Manager) and Magento, which enables a seamless integration of e-commerce functionalities into our content management system. This integration layer consists of a so-called connector along with ready-to-use and extensible e-commerce components that can be used by customers to quickly build a rich shopping experience with AEM and Magento.
The main building block of this integration is GraphQL. Magento does indeed expose a rich GraphQL API that is used on the AEM side to integrate both e-commerce data (product catalog) and functionalities (cart and checkout) into AEM. The main benefit of GraphQL (compared to REST) is that it greatly optimizes the fetching of data, in the sense that each component can request the exact commerce data it needs for rendering, no more, no less.
While this integration basically sets the Magento GraphQL API as its “first class citizen,” AEM has always supported multiple e-commerce platforms so we had to find a way to expose the Magento GraphQL API on top of any e-commerce platform … and of course, in a flexible manner and at little cost.
Fortunately, our team already had plenty of experience with the serverless Adobe I/O Runtime platform, which, to quote their home page:
… allows you to quickly deploy custom code to respond to events and execute functions right in the cloud, all with no server set-up required.
This is exactly what we needed to implement and deploy a Magento GraphQL server on top of 3rd-party e-commerce platforms.
GraphQL schema and resolvers
There are a few fundamental concepts that one has to fully understand on a journey to implement a GraphQL server. We assume that the reader (yes, that’s you) is familiar with at least the basic concept of GraphQL: the schema, and how it fundamentally defines and restricts the shape of GraphQL requests and responses.
Once you have a schema, “all you need” to implement a GraphQL server are so-called resolvers. A resolver is basically a function that executes a portion of a GraphQL request, fetches the requested data (via database lookups or REST queries), and returns the JSON response for that portion of the request. This is, however, a very simplified definition, because a resolver typically resolves a query in multiple steps by basically ‘crawling’ the query in a breadth-first (top to bottom) manner.
There is actually a very good explanation of the execution and resolution process at https://graphql.org/learn/execution/, so we invite you to carefully read this reference page, or remember this essential quote:
You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.
If a field produces a scalar value like a string or number, then the execution completes. However if a field produces an object value then the query will contain another selection of fields which apply to that object. This continues until scalar values are reached. GraphQL queries always end at scalar values.
What this essentially means is that a resolver somehow behaves like an automaton (or state-machine) executing and resolving the request at the same time. In particular and because we want to implement a GraphQL server in a serverless platform, this means that one cannot easily split the implementation of resolvers into multiple actions without the help of schema delegation.
Schema delegation is a fundamental concept to avoid implementing a GraphQL server as a monolith, or to aggregate multiple distributed sub-schemas into one single schema. Quoting https://www.apollographql.com/docs/graphql-tools/schema-delegation/
Schema delegation is a way to automatically forward a query (or a part of a query) from a parent schema to another schema (called a subschema) that is able to execute the query.
In a serverless FaaS environment, schema delegation is a MUST because it allows distributing the implementation of GraphQL resolvers into multiple actions/functions which can be developed and deployed independently, and finally aggregated into one single GraphQL endpoint.
To implement a GraphQL server on Adobe I/O Runtime with
Node.js, we decided to use the graphql-js library because it supports all the basic features to setup resolvers, parse, and execute a GraphQL request. It is very lightweight and simple, and we didn’t need any advanced features from the widely popular Apollo server library.
For schema delegation, we decided to use the graphql-tools library as it provides all the needed building blocks to implement schema delegation. The only missing part was the implementation of a
Fetcher function that forwards a GraphQL request to another function in I/O Runtime, but this was very easy to implement thanks to the existing openwhisk client library.
Finally, and in order to avoid typical GraphQL issues like over-fetching, we used the dataloader library to ensure that data is being fetched in an optimal manner.
The result is an example/reference implementation available at https://github.com/adobe/commerce-cif-graphql-integration-reference. It demonstrates how a 3rd-party e-commerce GraphQL integration can be developed in Adobe I/O Runtime, and provides some examples to customize the Magento GraphQL schema. It also contains the code needed by the graphql-tools library to implement schema delegation with multiple OpenWhisk actions, hence avoiding a monolithic implementation: all that was needed to achieve this is a custom
Fetcher for graphql-tools as shown in the following code snippet.
To illustrate the distributed nature of the implementation, the following diagram shows how a simple GraphQL query is executed and resolved by multiple actions. There is no limitation on the number of actions that one can use to implement a GraphQL endpoint. With one single monolithic action, caching and data fetching can be fully optimized, while having multiple actions allows granular implementations and deployments, and makes it easy to have multiple teams work on different parts of the schema.
Even if you are not a Magento or AEM developer, our implementation is a good starting point if you want to write a GraphQL server on Adobe I/O Runtime and get used to writing resolvers and using remote schemas.
What serverless GraphQL also offers
While this article focuses on AEM and Magento, deploying a GraphQL server on a serverless platform is so easy and flexible that it makes it a very good choice to deploy other types of GraphQL services that do not necessarily require a full-fledged server implementation. For example, it’s a very good choice to:
- Extend an existing schema and server implementation with extra fields and types, by simply ‘proxying’ the queries to the existing server, and only have the extended part of the schema implemented and deployed on the serverless platform.
- Easily merge multiple remote schemas into one single GraphQL endpoint without having to maintain a server.
- Customize an existing schema, for example to modify and adapt data types to fix incompatibilities between different systems, or only expose and adapt some fields of a private schema in a reduced and more secure public version of the schema.
Keeping in mind that you also get auto-scaling, geolocation routing, and that you don’t have to maintain any infrastructure, Adobe I/O Runtime is definitely a very strong choice when implementing GraphQL.