Skip to content

Engines, capabilities, and errors

Evolve has one GraphQL contract and three commerce engines that serve it. Around the engine sit adapters for payment, search, CMS, and cloud, one package per vendor. This page explains what you can swap, what works with what, and what your storefront sees when an engine cannot do something.

flowchart TB
  contract[["@evolve-framework/schemas<br/>the GraphQL contract"]]
  subgraph engines[Engines: the same modules]
    ct[commercetools<br/>+ Invoice, MultipleCart,<br/>ApiExtension, PaymentProvider]
    medusa[Medusa]
    sfcc[SFCC]
  end
  modules[Auth · Cart · Catalog · Checkout · Customer · Order<br/>Payment · Quote · ShoppingList · BusinessUnit · OrderApproval]
  payment[Payment adapters<br/>Adyen, Mollie, Stripe, Buckaroo, Pay.nl, on account]
  search[Search adapters<br/>Algolia, Meilisearch]
  other[CMS and cloud adapters]
  ct --> contract
  medusa --> contract
  sfcc --> contract
  modules -.- engines
  payment --> ct
  search --> ct
  search --> medusa
  other --> engines

The contract lives in @evolve-framework/schemas. Each engine package takes its schema from there and answers it against its own engine, so the storefront’s queries do not depend on the engine behind them.

Engine Package
commercetools @evolve-framework/commercetools
Medusa @evolve-framework/medusa
Salesforce Commerce Cloud @evolve-framework/sfcc

All three ship the same set of modules, with the same names: AuthModule, BusinessUnitModule, CartModule, CatalogModule, CheckoutModule, CustomerModule, OrderModule, OrderApprovalModule, PaymentModule, QuoteModule, and ShoppingListModule. Switching engines means swapping one package’s modules for another’s in each service. See Switch commerce engine.

commercetools has four more modules that the other engines do not have:

Module What it adds
MultipleCartModule Several named carts per buyer
InvoiceModule Invoices for a customer or business unit
ApiExtensionModule Your code that commercetools calls before it saves a change (API extensions)
PaymentProviderModule Runs a payment adapter as a service of its own
Category Packages Works with
Payment commercetools-adyen, commercetools-buckaroo, commercetools-mollie, commercetools-paynl, commercetools-stripe, commercetools-pay-on-account commercetools only
Search search-algolia, search-meilisearch commercetools and Medusa
CMS contentful, storyblok, payload Any engine
Cloud cloud-adapter-aws, cloud-adapter-azure, cloud-adapter-gcp Any engine

Some notes on that table:

  • Payment. The payment adapters build on commercetools’ payment model. On Medusa, payments come from Medusa’s own payment module: its offline provider, and Stripe when a key is configured. On SFCC, PaymentModule creates payments in Commerce Cloud.
  • Search. Without a search adapter, each engine answers productSearch itself. Pass a provider to the search option of CatalogModule to answer it from an index instead. SFCC always uses its own Shopper Search API. See Add search.
  • CMS. The three CMS packages answer the same content queries with the same page types, so the storefront’s content queries do not change when you switch CMS. See Add a CMS.
  • Email. @evolve-framework/user-notifications defines the messages a customer receives, such as an order confirmation. The commercetools package publishes them to a queue, and a small service in your project renders and sends them through your mail provider.

A vendor without an Evolve package becomes an adapter you write yourself. See Build your own adapter.

Not every engine can do everything the contract describes. Where it cannot, the gap shows up in one of four ways. They differ in when you find out.

What you get When you find out Example
A ValidationError with code UNSUPPORTED in the mutation’s result At run time, as a normal, typed error customerEmailUpdate on Medusa
A GraphQL error “Not implemented yet” At run time shoppingListCreate on SFCC
A GraphQL error because no resolver answers At run time cartSetDeliveryRemarks on Medusa and SFCC
A field that is not in the schema At codegen, when the storefront’s documents are validated cartCreateNamed without MultipleCartModule

A typed error is the most useful: the engine has no operation for it, and says so in the same shape as any other validation error. Your storefront handles it like a wrong password or an invalid address. Medusa answers this way for the account changes its store API has no operation for:

{
"data": {
"customerEmailUpdate": {
"customerErrors": [
{
"__typename": "ValidationError",
"message": "Medusa's store API cannot change a customer's email",
"fields": [{ "code": "UNSUPPORTED", "field": "email" }]
}
]
}
}
}

A stub is an operation the engine package has not built yet. On SFCC, several account, business-unit, and shopping-list operations answer “Not implemented yet”.

No resolver means the operation is in the schema and nothing answers it. A mutation fails, because its result cannot be null. A query with a nullable result returns null.

Not in the schema means no module that you compose declares it. This is how optional features work: a project without named carts does not compose MultipleCartModule, and a storefront document that selects cartCreateNamed fails validation at build time instead of in front of a shopper.

To see which case applies to an operation, open it in the GraphQL reference. The “Served by” list on each operation’s page names, per engine, whether it is resolved, a stub, or unanswered. Hide the features your engine cannot serve before you go live.

Some storefront feature packages select fields whichever engine is behind them. feature-cart selects Cart.purchaseOrderNumber in its cart fragment, and its order placement selects OrderPlaceResponse.approval. If an engine’s schema lacked one of those fields, the storefront’s codegen would fail on that engine.

The Medusa and SFCC packages each have a schema-parity.test.ts that composes the engine’s modules and checks that those fields are in the resulting schema. A release of either package cannot drop them.

The tests check that the fields exist. They do not check that every operation is answered: a field can be in the schema and answer with null, a stub, or an UNSUPPORTED error, as described above.