GraphQL Federation allows splitting a monolithic GraphQL schema into separate subgraphs maintained by different teams or services. Each service owns its part of the schema; the gateway stitches them together transparently for the client. This architecture fits e-commerce well: products, inventory, pricing, and orders as independent services with a unified API layer.
Federation concepts
| Concept | Meaning |
|---|---|
| Subgraph | An independent GraphQL service managing a domain slice |
| Supergraph | The unified schema composed from all subgraphs |
| Gateway | Routes queries to the right subgraphs, merges responses |
| @key directive | Declares the entity identifier for cross-service references |
| @external | Field owned by another subgraph but referenced here |
| @requires | Fields needed from another subgraph to resolve a field here |
Example architecture – e-commerce federation
Client
|
Apollo Router (gateway)
|
+-- Catalog Subgraph (Magento 2 GraphQL)
| Products, Categories, Attributes
|
+-- Inventory Subgraph (custom service)
| Stock levels, Sources, Salable quantity
|
+-- Pricing Subgraph (custom service)
| Customer-specific pricing, Promotions, Taxes
|
+-- Orders Subgraph (custom service)
Order history, Status, Returns
Catalog subgraph – Magento 2 with Federation extensions
# etc/schema.graphqls - extend Magento's ProductInterface with @key
# The @key directive tells the gateway how to identify a Product entity
extend type Query {
_service: _Service!
_entities(representations: [_Any!]!): [_Entity]!
}
type Product @key(fields: "sku") {
sku: String!
name: String
price: Float
categoryIds: [Int]
}
scalar _Any
scalar _FieldSet
type _Service {
sdl: String!
}
union _Entity = Product
<?php
namespace Vendor\FederationModule\Model\Resolver;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
// Resolver for the _entities query - allows gateway to fetch products by key
class EntitiesResolver implements ResolverInterface
{
public function __construct(
private \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) {}
public function resolve(
Field $field,
mixed $context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): array {
$representations = $args['representations'] ?? [];
$result = [];
foreach ($representations as $representation) {
$typeName = $representation['__typename'];
if ($typeName === 'Product') {
try {
$product = $this->productRepository->get($representation['sku']);
$result[] = [
'__typename' => 'Product',
'sku' => $product->getSku(),
'name' => $product->getName(),
'price' => $product->getPrice(),
'model' => $product,
];
} catch (\Exception $e) {
$result[] = null;
}
}
}
return $result;
}
}
Inventory subgraph – custom PHP service
# Inventory service schema.graphql
# Extends the Product type from the catalog subgraph
# without owning Product itself
type Product @key(fields: "sku") @extends {
sku: String! @external
stockInfo: StockInfo
}
type StockInfo {
salableQty: Float!
isInStock: Boolean!
sources: [SourceStock!]!
}
type SourceStock {
sourceCode: String!
qty: Float!
status: Int!
}
type Query {
# Direct queries on the inventory subgraph
getSourceItems(sku: String!): [SourceStock!]!
}
<?php
// Inventory subgraph resolver - extends Product with stock data
// This service only needs the SKU (from @external) to resolve stockInfo
class ProductStockInfoResolver implements ResolverInterface
{
public function resolve(
Field $field,
mixed $context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): array {
$sku = $value['sku'] ?? '';
$salableQty = $this->getSalableQty->execute($sku, $this->defaultStockId);
$sources = $this->getSourceItems->execute($sku);
return [
'salableQty' => $salableQty,
'isInStock' => $salableQty > 0,
'sources' => array_map(fn($s) => [
'sourceCode' => $s->getSourceCode(),
'qty' => $s->getQuantity(),
'status' => $s->getStatus(),
], $sources),
];
}
}
Apollo Router configuration
# router.yaml - Apollo Router gateway configuration
supergraph:
listen: 0.0.0.0:4000
subgraphs:
catalog:
routing_url: http://magento2/graphql
inventory:
routing_url: http://inventory-service:8080/graphql
pricing:
routing_url: http://pricing-service:8081/graphql
orders:
routing_url: http://orders-service:8082/graphql
# Unified query - the gateway resolves fields from multiple subgraphs
# query {
# products(search: "shoes", pageSize: 5) {
# items {
# sku name price <- from catalog subgraph
# stockInfo { isInStock salableQty } <- from inventory subgraph
# customerPrice { finalPrice } <- from pricing subgraph
# }
# }
# }
Summary
GraphQL Federation is the right architecture when you have multiple teams or services contributing to a single API surface. It avoids the BFF (Backend for Frontend) anti-pattern where one service must know about all others. For Magento 2 the practical starting point is exposing the existing Magento GraphQL as a catalog subgraph and building inventory and pricing as separate lightweight services. Apollo Router as the gateway is production-ready and open source.
