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
One contract, three engines
Section titled “One contract, three 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 |
Adapters per category
Section titled “Adapters per category”| 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,
PaymentModulecreates payments in Commerce Cloud. - Search. Without a search adapter, each engine answers
productSearchitself. Pass a provider to thesearchoption ofCatalogModuleto 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-notificationsdefines 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.
How a gap shows
Section titled “How a gap shows”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.
What the parity tests guarantee
Section titled “What the parity tests guarantee”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.

