# Event-Driven Caching

# Purpose of Caching

As a *Consumer* of your shiny new service, I want my answers ***Now***. Real-time has become the de facto standard. It's no longer good enough to take our time digging through millions of possibilities and return ~500ms later. Don't believe, either, that this only applies to user interactivity.

Caching allows an application to retrieve required data from an in-memory data store. While quite faster than digging within a more persistent store, the in-memory call is not free.

# Cache On-Demand

Typically, caches are remote with respect to the application itself. Even if local memory stores the cache, each call to the store increases the contention. The typical three-step process: first checks the cache and can return early, otherwise, the second step loads data out of the database, and third we cache the data for future use with a heuristic Time to Live (TTL). This ***On-Demand*** style is common among CRUD apps with a cache layer on top of a database-first mentality.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672421631987/3f7b0c67-fa4e-4951-af6c-613858c49820.png align="center")

Cache On-Demand can encompass [Cache Aside, Read/Write Through, and Write Behind](https://learn.microsoft.com/en-us/azure/architecture/patterns/cache-aside), the patterns follow largely the same process. The owner of each step may vary or be done outside a hotter path. The key, though, is that each strategy requires ***Demand First and a Database Source of Truth (SoT).***

# Cache Ahead

***Cache Ahead*** is all about ***Knowledge Management***. ***Cache Ahead*** is more than simply *Write Through*. When we drive toward a realization of *real-time,* placing our slowest source of truth closest to our return is inefficient. In other words, even with a cache layer placed on top of a database, we've chosen the database as ***the true source*** of our data. We've relegated the cache to be little more than a band-aid on our architecture.

We need to take a better approach and address: *Writing*, *Lifetime*, and *Eviction* of cache data. Let's define this strategy by first dropping our CRUD preconceptions and viewing the problem through an Event-Driven lens.

## Widgets Inc.

To stage an exploration of ***Cache Ahead*** we'll use the e-commerce unicorn Widgets Inc. The backend we're working with is composed of the **Order Service,** an **Inventory Service**, and **Emal Service**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672497358161/34c9a64a-46a0-43ad-a66d-997d4e9d741e.png align="center")

Specifically, let's explore the **Order Service**. Our service is such that it's both listening to Events and responding to HTTP Requests. I've placed the cache first in line with the database updated either via the App itself or in a *Write-Through* from our cache. We'll see ***Cache Ahead*** laid out as it would fulfill the following requirements of the **Order Service**

1. Validate an `OrderRequest` has *Products* in stock
    
2. Permit the ad-hoc querying of current *Orders*
    
3. Maintain the Customers' *Order*
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672497968898/bbf0b4ad-0e0d-4b5e-86b3-79a1a69c2dbd.png align="center")

## Order Request - New Order

Keeping our **Knowledge management** perspective, a new *Order* must be validated and, if successful, persisted. This is the point in time when new knowledge enters our system.

We can assume that inventory has been maintained by our **Inventory Service**. We can confidently assume that, upon *Order* validation, we will hit the **Inventory Service** cache when checking stock levels. With a validated *Order,* we'll write to our cache.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672500273683/718dd300-4969-4aed-bb73-6464ab8d0f15.png align="center")

This was relatively straightforward, much the same as a *Write-Through* wherein our database is backing up our cache. Note, the confident **Inventory** cache hit foreshadows how we'll ***Always*** have a ***Positive*** cache hit, i.e. if it's not in the cache it's not the database, no need to check, and the database is no longer an SoT.

## Ad-Hoc Order Query

Permitting querying of any *Order* entails maintaining the *Order* during its active lifetime. Let's consider two external events that could occur affecting our *Order*: `CustomerNotified` and `InventoryUpdated` .

```apache
syntax = "proto3";

message CustomerNotified {
  string MessageId = 1;
  string CausationId = 2;
  string CustomerId = 3;
  Kind Kind = 4;

  enum Kind {
    Confirmation = 1;
    Cancellation = 2;
    Backordered = 3;
    Delivered = 4;
  }
}

message InventoryUpdated {
  string MessageId = 1;
  string CausationId = 2;
  string ProductId = 3;
  int32 Count = 4;
}
```

Each message causes a *State Change* to our *Order*, we maintain our *Owned Domain* perspective and emit that an *Order* has been updated via an `OrderUpdated` event. The **Order Service** will not drive downstream events but simply state its own actions. In this way we maintain the current state of the *Order*, keeping the cache always the SoT.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672501847019/c76af6f9-9394-46a6-a114-5b271bf2ed0f.png align="center")

We could have seen a variety of scenarios:

1. `Confirmation` ⇒*Order* is confirmed
    
2. `Cancelleation` ⇒ *Order was* canceled
    
3. `InventoryUpdated` ⇒ Business logic could drive a `Backordered`
    

Each of these cases would/could cause an `OrderUpdated` event. For instance, if a product becomes `Backordered` the Customer should be notified resulting in a `CustomerNotified | Backordered` event.

## Maintaining the Order - Eviction

So far, we haven't discussed anything about a Time to Live (TTL) or Eviction Policy. Keeping in mind that our objective of ***Cache Ahead*** is such that our cache is the primary Source of Truth we want to avoid any form of database fallback. The hard persistent store in this case is better suited as an Event Sourcing store and/or disaster backup, i.e. not an operational store.

The first scary thought is usually that this implies an unbound cache lifetime and bloated cache size, with the cloud spend associated. However, consider the **On-Demand** cache strategy against ***Cache Ahead*** in our **Order Service** context. If there's no or little *demand* for current *Orders* they're evicted from the cache but in the same breath ask; Does that make sense in the use case? In other words; Why are **active** *Orders* not interesting enough to remain cached, and if so can we be comfortable with little more than a best guess heuristic TTL?

### Business Eviction

***Cache Ahead*** is distinctly intended for an Event-Driven Architecture (EDA). Within our EDA and **Order Service** context; an *Order* has a real-world business defined *Operational* lifetime*.* That's what determines just how *interesting* our *Order* is and determines its lifetime within the cache.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672503922199/ada3b8c6-4b90-4d34-b692-82de2f5ffcb8.png align="center")

Our particular **Order Service** context can easily highlight `CustomerNotified | Delivered` as an End of Life (EoL) of the *Order*. Surely, we could have a **Delivery Service**, or live beyond via a 7-Day Return Policy, however;

> The key takeaway is that EoL is an event originating from the real life business context and our architecture reflects that reality.

# That's a wrap

***Cache Ahead*** is an Event-Driven caching strategy intended to meet the real-time needs of modern businesses. Oriented towards reflecting our real business operations and requirements. Databases don't go anywhere, but they begin to take a backseat operationally and serve analytics, backup, and recovery purposes.

What are your thoughts? What are the nuances I glossed over? Hit the comments and we'll chat!
