API caching
A connector that talks to the same upstream API five times in a row for the same id is a connector that should cache. Caching does not change topology behaviour: it only stops you from paying for work you have already done.
When to cache #
Cache when all of the following are true:
- The upstream response is the same for the same input within a known time window (a customer's address won't change in the next 60 seconds).
- The cost of the upstream call is non-trivial: latency, rate-limit budget, or money.
- A stale answer is acceptable for the configured TTL.
Don't cache:
- Mutations. Cache reads, never writes.
- Per-request authentication tokens (the SDK already handles those).
- Anything where freshness is the whole point (live stock, payment status, OTP codes).
Three tiers, three time scales #
Tier 1: in-process memoization #
Inside a single processAction() call, just keep a Map. Useful when a custom node iterates over many records and would otherwise call the same upstream lookup many times.
public async processAction(dto: ProcessDto): Promise<ProcessDto> {
const items = dto.getJsonData().items as Array<{ ownerId: string }>;
const ownerCache = new Map<string, Owner>();
for (const item of items) {
if (!ownerCache.has(item.ownerId)) {
ownerCache.set(item.ownerId, await this.fetchOwner(item.ownerId));
}
// use ownerCache.get(item.ownerId) ...
}
return dto;
}
Lifetime: one node call. No infrastructure, no risk.
Tier 2: short-TTL shared cache #
For lookups that recur across messages and processes, use a shared cache (Redis, Memcached) with a short TTL: minutes, sometimes seconds. Wrap the upstream call in a "get from cache, fall back to upstream, write to cache" helper.
The right TTL is the longest staleness your business can tolerate. For "what's the customer's email" inside a sync that runs every 5 minutes, a 5-minute TTL is usually fine.
Tier 3: ID resolution cache #
A special case of tier 2. When you constantly translate "the upstream identifier for our internal customer X", that mapping rarely changes and is exactly the ID mapping pattern. Treat it as a permanent cache that you maintain explicitly, not as a TTL'd one.
Invalidate on write #
The moment you mutate something upstream, drop the matching cache key in the same node. Otherwise the next read returns the stale answer for the rest of the TTL.
await this.updateContact(id, payload);
await this.cache.delete(`contact:${id}`);
Operational notes #
- User in the key. Always include the user identifier (or Application install id) in the cache key. Otherwise one user may see another user's data.
- Auth token in the key, never the value. Don't accidentally store credentials in the cache value; key them out.
- Observability. Log a hit/miss counter so you can see whether the cache is paying for itself.