Have fun learning about Test-Driven Development with JitterTed's TDD Game

Live Coding Journal - Feb 4, 2026

Reflections, Learnings, and Mistakes from today's live coding with my JitterTicket Event Sourcing application


Notes from Today’s Live Coding Session

I’m back from vacation and resumed my live coding streams where I’m working on “JitterTicket”, an example Event Sourcing application in the Concert Ticketing domain. (Code is at https://github.com/jitterted/jitterticket-event-sourced.)

I’ve been working on the “available concerts” projection, which is used (as a read model) to show customers what concerts are available for ticket purchase. Before my vacation, we decided on a new event, “ConcertCompleted” that would stop ticket sales. Today I decided that this event name was too broad, so instead used TicketSalesStopped. The creation of that event was mostly mechanical, especially since it doesn’t need anything other than the Concert ID (which is already inherited from the base ConcertEvent).

I’m glad I did the work manually (instead of letting the Junie LLM do the work) as I discovered some weirdness with creating an otherwise “empty” event class. (Mostly on code-generating the equals and hashcode.)

I was also taking notes on the process so that in the future, I can create a code generator (no point in using an LLM for something that’s basically a template) for adding new events, since there’s more than just the event classes that need to be created.

Adding the new event was a straightforward TDD process, though the first “failing test” was a “fail to compile” due to the pattern-matching switch statements that I use. It had been a while since the last time I added a new event and forgot that I had JSON round-trip tests, which failed—thankfully! Adding the new event and the appropriate Jackson “mix in” classes was also mechanical.

Once the event itself was in place, it was time to update the “Available Concerts” projector to drop concerts from the projection when it sees a “TicketSalesStopped” event.

Since I was doing bottom-up development, i.e., creating the event first, and then adding behavior elsewhere, I decided not to modify Concert (the aggregate generating the TicketSalesStopped event and having a “canSellTickets” state), as I wanted to focus on the projection first.

Side note: Every time I think about a boolean state, I’m always thinking, is this really a boolean, or is it really a state machine with multiple states? Or maybe it’s an effective timestamp? I’ll have to write that up at some point.

With the event work done, I moved on to the first new (failing!) test of the projector (which is so easy to test as it’s stateless code): If there’s already a concert available in the projection (cache), stopping ticket sales removes it, leaving the Available Concerts empty.

The code for that was easy, but then things got tricky as I had to handle the catch-up projection scenario, where ConcertScheduled and TicketSalesStopped already happened for a concert, so the projection needs to just ignore that concert. What makes this complicated is the projector produces two results: a new projection cache as well as a “delta”, which is used for persistence and split into upserts & deletes.

Before the TicketSalesStopped event came on the scene, I didn’t worry about deletions, but also internally tracked inserts and updates in a single upserts map. However, I couldn’t tell when to put a concert into the “deletions” list because Scheduled and Rescheduled looked alike.

They’re not! If I see a Scheduled+TicketSalesStopped in the list of events being handled, it’s a no-op (cache remains the same, delta is empty). A Rescheduled+Stopped, however, is a deletion (because a Scheduled already happened some time in the past).

I ended up doing a refactor to split the internal upsert map into two lists to track inserts (Scheduled) and updates (Rescheduled) separately. I got the tests to continue passing, but it was clear that lists were the wrong data structure, so replaced them with maps (much nicer).

I’m pretty happy with my current design of the Projectors as it made tests relatively easy to write. I am feeling the pain of generating the stream of test events, so that’s something I’ll focus on once I work on the last component of the app: Processors.

I stream for 3–4 hours starting at 20:00 UTC on Monday thru Thursday, unless I’m at a conference or otherwise occupied, so I hope you’ll tune in at https://jitterted.stream to watch, help me out, ask questions, or even heckle.


Make Your Code More Testable™️

Don't miss out on ways to make your code more testable. Hear about new Refactoring techniques, improving the Test-Driven Development process, Hexagonal Architecture, and much more.

    We respect your privacy. Unsubscribe at any time.