Why store the webhook before doing anything with it?
The first thing that happens to a webhook is that it's saved in a DynamoDB
table, before it's turned into anything else. The main reason is auditing and
debugging: because every webhook we received is kept, we can look back at
exactly what Sanity told us. We can also feed them through the pipeline again,
but a replay isn't guaranteed to give the same result as the first time, because
a later step asks Sanity for the document again.
The receiving service is an external service gateway (ESG).
That's a pattern where one service sits at the edge, talks to the outside system
(here Sanity) and translates for everything behind it. Storing the webhooks was
my design decision.
Why does a second step fetch the document again?
The stored webhook is only a note that something changed: the document's ID, its
type, whether it was created, updated or deleted, and when. Our content is
modelled as lots of linked documents. For example, the Dover to Calais route
document refers to the Dover and Calais port documents. So a separate Lambda
picks the note up from the DynamoDB stream (a feed of changes to the table) and
asks Sanity for the document, with one query per document type. For a route,
that query follows the links to both ports. It brings back each port's name, its
UN/LOCODE (the standard code for a port) and its country. It builds the route's
name from the two port names, and it lists the IDs and types of the documents
the route points at.
Sanity can attach some linked data to a webhook itself, so this was a choice
rather than a workaround. With the queries in our own code, we version and test
them like everything else. Every webhook stays the same small shape whatever the
document type. And the Lambda reads the document straight from Sanity's
published content at the moment it publishes the event.
It then publishes events to EventBridge, AWS's event router, and from there
onto Kinesis, AWS's streaming service, where other systems can read them. On
the BFF side, those references are used to tie the data together. Without them,
a route page couldn't show any specific port information for its two ports.
Why does each website part keep its own copy of the data?
The website is split into micro-frontends (MFEs): small, separately deployed
pieces, each responsible for one part of the site. Each MFE has its own
backend-for-frontend (BFF), a small backend that exists only to serve that one
MFE. A BFF listens to the Kinesis stream and keeps only what its MFE needs.
A concrete example: the port page MFE doesn't care about an update to a root
page or a root document, so its BFF filters those events out and ignores them.
It only stores the data that matters for ports.
Why is serving a page fast?
Because page requests never go back to Sanity. The page asks its BFF's API
(through API Gateway, AWS's front door for APIs), and the BFF reads from its own
DynamoDB table. The table is designed around exactly the lookups the page needs,
which makes those reads very quick. The pages themselves are built with
Qwik, a frontend framework, and rendered on the server in a Lambda wherever
possible. Anything that has to run in the browser is sent as JavaScript.