One model in the middle: the canonical format pattern, and how Enterspeed does it




This is part 1 of our series on design patterns in Enterspeed. Stay with us: next up is the strangler fig pattern, followed by the content enricher.
Most companies don't have one source of truth. They have several. Products live in the PIM, prices and stock in the ERP, customers in the CRM, marketing content in the CMS, and there's usually an "other system" or two that nobody wants to talk about…
On the other side, you have the clients that need that data: a website, a mobile app and, increasingly, AI agents.
Connecting the two sides is where things get messy.
The most obvious way to get data from A to B is to connect A directly to B. The website calls the PIM. The mobile app calls the PIM too. And the ERP. And the CRM. The agent needs a bit of everything, so it gets its own set of connections.

In the example above, that's 12 point-to-point integrations. And the number doesn't grow nicely. In the worst case, every source is connected to every application, so the number of integrations is sources × applications. Add one more app and you potentially add five more integrations. Add one more source system and every app might need updating.
Each of those integrations comes with its own authentication, its own data mapping, its own error handling and its own opinion about what a "product" is. When the ERP changes a field name, you get to find out how many places depended on it. Usually the hard way.
The canonical format pattern (also known as a canonical data model) is an old and well-proven idea from enterprise integration. Instead of connecting everything to everything, you introduce a shared format in the middle.
Short version: every source system publishes its data once into the canonical format, and every application consumes data from the canonical format. Sources don't know about applications, and applications don't know about sources.

The same setup now needs 8 integrations instead of 12. More importantly, the math has changed from sources × applications to sources + applications. A new app means one new integration, not five. Replacing the PIM means rewiring one connection, not every app that uses product data.
You also get a few things for free:
Decoupling. Source systems can be replaced or upgraded without the applications noticing.
One place for mapping logic. Transformation happens in one place instead of being duplicated across every consumer.
A shared language. Teams agree on what a product, an order or a customer looks like once.
If the pattern is that good, why doesn't everyone just do it?
Because in practice, a single canonical model tends to grow into a monster. The website needs product data shaped for a product page. The mobile app wants a lighter version. The agent needs something flat and queryable. If there's only one model, it has to satisfy everybody, so it keeps growing until nobody fully understands it, and every change needs sign-off from every team.
That's the classic trap: you swap integration spaghetti for a model that becomes the bottleneck.
The fix is to separate two things that are often mixed up:
The canonical data: one consistent, normalised version of what your source systems hold.
The output each consumer needs: shaped for that specific application.
Keep the first shared. Let the second vary.
That separation is exactly how Enterspeed is built.

Ingest once. Source systems push their data into Enterspeed through the Ingest API or one of the source connectors. Each source system gets its own source group, and the data is stored as source entities. Source entities aren't the output. They're the data that's available for you to work with, which means you don't have to force the PIM and the ERP into the same shape before they arrive.
Shape per consumer. Schemas define how source entities are turned into output. A schema can combine data across source groups, so a product view can pull the product description from the PIM and the stock level from the ERP. You can create one schema for the website's product page and another for the mobile app, both built on the same underlying data.
Deliver everywhere. Views are served through the Delivery API, index items through the Query API, and AI agents can query the same data through the Enterspeed Query MCP. Destinations like webhooks or Azure Service Bus let you push changes to systems that need to react to them.
So instead of one giant model that everyone has to agree on, you get one canonical layer and as many views of it as you need.
Let's say the website needs a product page with data from the PIM and stock from the ERP. In Enterspeed, that could look something like this:
1/** @type {Enterspeed.FullSchema} */
2export default {
3 triggers: function(context) {
4 context.triggers('pim', ['product']);
5 },
6 routes: function(sourceEntity, context) {
7 context.url(`/products/${sourceEntity.properties.slug}`);
8 },
9 properties: function (sourceEntity, context) {
10 return {
11 name: sourceEntity.properties.name,
12 description: sourceEntity.properties.description,
13 stock: context
14 .reference("productStock")
15 .byOriginId(sourceEntity.properties.sku)
16 .sourceGroup("erp")
17 };
18 }
19}The schema triggers on products from the PIM, and the stock comes in as a reference to a view built from ERP data. Because references are resolved when the view is requested through the Delivery API, a stock update from the ERP shows up on the product page without the product schema having to be reprocessed.
The website makes a single request and gets a single response. It has no idea the data came from two systems, and that's the point.
If the mobile app needs a slimmer version, you write another schema. The PIM and ERP integrations stay exactly as they are.
A canonical layer is not free, and it's worth being honest about that.
You're introducing a new component in the middle of your architecture, and all data flows through it. That's why it matters that the layer is fast, available and built for it, rather than something bolted onto one of your existing systems.
You also still need to think about your data model. Enterspeed makes it easier to keep the canonical data clean (for example by using references and lookups instead of duplicating the same price on multiple source entity types), but it doesn't make the modelling decisions for you.
And schema transformations take time to process, so for truly real-time data you'll want to think about which data goes through transformation and how.
In most setups with more than a couple of sources and consumers, though, those trade-offs are much smaller than the cost of maintaining a growing web of point-to-point integrations.
Point-to-point integrations work fine until they don't, and usually "don't" arrives around the third application or the second PIM migration. The canonical format pattern replaces sources × applications with sources + applications, and gives you one place to own your data mapping.
The catch is that a single rigid canonical model can become a bottleneck of its own. Enterspeed avoids that by keeping the canonical data shared and letting every consumer get its own view.
Ingest once, deliver everywhere.
Want to try it? Create a free Enterspeed tenant and check out the docs.

Loves building software that makes an impact on businesses. Untalented but happy ice hockey player (the beers just taste better in a locker room after a game...)
© 2020 - 2026 Enterspeed A/S. All rights reserved.
Made with ❤️ and ☕ in Denmark.