CRUD stores only current state, losing history. Event‑Sourcing records every change as an immutable event, enabling you to answer "How did we get here?"
Growing Marijuana with Event‑Sourcing — Introduction
The story of myPlant
Three months ago I planted a marijuana seed in a small pot. I named it myPlant and put it near the window. The first week was exciting — I watered it daily, watched the first leaves sprout, and took photos to track its growth.
Then life got busy. Work deadlines piled up, I traveled for a week, and I forgot a few times — you know how it goes. I still watered myPlant when I remembered, but the intervals became irregular: two weeks would pass, then I’d water it twice in one week, then another long gap.
CRUD: the traditional approach
| id | height | health | lastWatered | trimCount |
|---------------|--------|--------|---------------------|-----------|
| myPlant | 15 | 30 | 2023-10-15T14:30:00 | 0 |
| id | height | health | lastWatered | trimCount |
|---------------|--------|--------|---------------------|-----------|
| grandmasPlant | 45 | 95 | 2023-10-30T08:45:00 | 12 |
Both plants are stored as simple rows in a CRUD database, showing only their current state.
The question CRUD cannot answer
Why is myPlant unhealthy while Grandma's plant thrives?
I can see myPlant is in poor condition, but I cannot answer:
- Was it watered consistently or sporadically?
- How many times was it watered in total?
- Were there long gaps between watering sessions?
- Did the health ever improve and then decline again?
With CRUD, I only have a snapshot of myPlant’s current state. The history is gone—overwritten with each update. I can’t debug the problem because I don’t know how myPlant got to this state.
Grandma's secret
"How do you do it, Grandma?" I asked.
She smiled and pulled out a small notebook. "I keep track of everything," she said. "Every time I water it, trim it, or do anything — I write it down with the date"
Her notebook looked like this:
2023-08-01T10:00:00: Seeded
2023-08-03T08:30:00: Watered
2023-08-06T09:15:00: Watered
2023-08-09T07:45:00: Watered
2023-08-12T08:00:00: Watered
2023-08-15T09:30:00: Watered, Trimmed
2023-08-18T08:15:00: Watered
...
2023-10-28T07:30:00: Watered
2023-10-30T08:45:00: Watered
But why does this make a difference?
Looking at her notebook I could immediately see the pattern: consistent watering every 2–3 days. My CRUD snapshot couldn’t tell me that. It only showed the last time I watered myPlant, not the pattern of care.
Grandma’s notebook is Event‑Sourcing — tracking every event that happened, not just the current state.
Event‑Sourcing: storing the full history
With Event‑Sourcing, instead of storing only the current state, we store every event that happened:
type PlantEvent =
| {
type: "Seeded";
plantId: string;
occured_at: Date;
}
| { type: "Watered"; plantId: string; occured_at: Date }
| { type: "Trimmed"; plantId: string; occured_at: Date }
| { type: "Died"; plantId: string; occured_at: Date };
const myPlantEvents: PlantEvent[] = [
{ type: "Seeded", plantId: "myPlant", occured_at: new Date("2023-08-01T10:00:00") },
{ type: "Watered", plantId: "myPlant", occured_at: new Date("2023-08-03T08:30:00") },
{ type: "Watered", plantId: "myPlant", occured_at: new Date("2023-08-20T09:15:00") },
{ type: "Died", plantId: "myPlant", occured_at: new Date("2023-10-15T14:30:00") },
];
const grandmasPlantEvents: PlantEvent[] = [
{ type: "Seeded", plantId: "grandmasPlant", occured_at: new Date("2023-08-01T10:00:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-02T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-03T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-04T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-05T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-06T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-07T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-08T08:30:00") },
{ type: "Trimmed", plantId: "grandmasPlant", occured_at: new Date("2023-08-09T09:00:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-09T09:15:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-10T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-11T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-12T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-13T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-14T08:30:00") },
{ type: "Trimmed", plantId: "grandmasPlant", occured_at: new Date("2023-08-15T09:00:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-15T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-16T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-17T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-18T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-19T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-20T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-21T08:30:00") },
{ type: "Trimmed", plantId: "grandmasPlant", occured_at: new Date("2023-08-22T09:00:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-22T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-23T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-24T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-25T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-26T08:30:00") },
{ type: "Trimmed", plantId: "grandmasPlant", occured_at: new Date("2023-08-27T09:00:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-27T08:30:00") },
{ type: "Watered", plantId: "grandmasPlant", occured_at: new Date("2023-08-28T08:30:00") },
];
The insight: now we can answer the question. myPlant was watered only twice in the first 19 days before it died from neglect; Grandma’s plant was watered regularly and trimmed twice per week. The events tell the complete story of how we got to the current state — my neglect led to death, while consistent care produced healthy growth.
Summary
Event‑Sourcing provides a fundamentally different approach to managing state:
- CRUD stores only the current state → loses history
- Event‑Sourcing stores all events → a complete audit trail; can answer “How did we get here?”
Storing events (Seeded, Watered, Trimmed, Died) lets us:
- Answer questions that CRUD cannot (for example, "Why did
myPlantdie?") - Understand the complete history of our plants
- Derive insights that would be impossible with just a snapshot
- Consistent care matters: daily watering and regular trimming lead to healthy growth; neglect leads to death
Further reading:
- Martin Fowler's foundational article on Event Sourcing provides a comprehensive introduction to the pattern and its applications.
- Shawn McCool's CQRS and Event Sourcing course - I bought this course back in 2018, but luckily it's now available for free on YouTube.