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

Live Coding Journal - Feb 12, 2026

Reflections, Learnings, and Mistakes from live coding my JitterTicket Event Sourcing application


Notes from Today’s Live Coding Session

Don’t Use an SDK When a POST Will Do

I spent far too long trying to replace the email service in my Ensembler tool from SendGrid (who started charging even for minimal usage) to Brevo. It was a terrible experience, with inconsistent documentation, a vendor-supplied Java SDK with many problems, a third-party SDK that was overkill, and a support chat LLM that provided completely wrong information. I really should have looked closer at what I needed, because a single HTTP POST would have been sufficient. However, I’m extremely displeased at Brevo’s use of LLM for support chat, since it is well known that they provide wrong answers. It’s likely I’ll try out Postmark (using straight HTTP requests) unless Brevo decides to kick the LLM chatbot off the site—I refuse to support companies that waste my time in that way.

Generics and Lambdas Break My Brain

The main task for the rest of the stream was to expand the use of lambda-based Command Objects for executing command methods against the Concert aggregate. I had a nice wrap method that returned a “unit of work” composed object:

copy
public Command<ConcertId> wrap(Command<Concert> concertCommand) { return concertId -> { List<ConcertEvent> concertEvents = concertEventStore.eventsForAggregate(concertId); Concert concert = Concert.reconstitute(concertEvents); concertCommand.execute(concert); concertEventStore.save(concert); }; }

However, this was only for methods (like .stopTicketSales()) that take no parameters. I wanted to implement the Reschedule Concert command similarly, but it takes two parameters, and it took a surprising amount of time for me to get that working. Around 1:47:42 is when the struggle began and, after a number of false starts, @Suigi comes through (again) and reminds me to revert the lambda I was trying to write into an anonymous inner class. That made everything so much more clear, and was able to get where I wanted:

copy
public CommandWithParams<ConcertId, Reschedule> wrapWithParams(CommandWithParams<Concert, Reschedule> command) { return (concertId, reschedule) -> { List<ConcertEvent> concertEvents = concertEventStore.eventsForAggregate(concertId); Concert concert = Concert.reconstitute(concertEvents); command.execute(concert, reschedule); concertEventStore.save(concert); }; }

I wasn’t ready to ship it yet, because (around 2:28:40) as I was writing a test for the code (a rare code-first episode) I started to realize something was wrong with the unit work of work code wrapping the command, i.e., reconstituting the Concert by loading the events and calling the .reconstitute() static method: if the Concert didn’t exist, we’d try to reconstitute it from an empty list of events! Oops, that won’t work, and, in fact, that’s an important missing validation in the reconstitute() method! I added a quick validation for that:

copy
public static Concert reconstitute(List<ConcertEvent> concertEvents) { if (concertEvents.isEmpty()) { throw new IllegalArgumentException("Can not reconstitute from an empty list of ConcertEvents."); } return new Concert(concertEvents); }

I then updated the code to properly call the event store’s findById() method instead:

copy
public CommandWithParams<ConcertId, Reschedule> wrapWithParams(CommandWithParams<Concert, Reschedule> command) { return (concertId, reschedule) -> { Concert concert = concertEventStore .findById(concertId) .orElseThrow(() -> new IllegalArgumentException("Could not find Concert with ID " + concertId)); command.execute(concert, reschedule); concertEventStore.save(concert); }; }

Once again, thinking through things to predict how the test would fail revealed a problem even before I had a failing test, but then the test still failed in a different way, which revealed a problem with the test.

One thing I hadn’t realized until I was writing these notes is that I need to go back and fix the no-arg wrap() to also use findById()!

Event Modeling for Reschedule Concert

Around 3:00:30, I do a quick event model for rescheduling concerts, and then decide not to build a completely new UI for it. The list of concerts to display for scheduling is the same as the list of concerts for purchasing, so I decided to just add another button for “reschedule”. Then I did the usual test-driving of a new UI endpoint, though it had been a while since I wrote one of those, so I made some silly mistakes. I ended the stream with an Olive test (fails as expected) that I’ll pick up on the next stream.

Next Steps

For next time, I’ll:

  • Fix wrap() to use findById()
  • Finish the Olive test for the reschedule controller
  • Look at a tool for doing event-modeling?
  • Maybe figure out in which package the CommandExecutorFactory should live
  • Generify CommandExecutorFactory so I can use it with the Customer aggregate
  • And maybe look at how to design aggregate-slices (Deciders) that can leverage the command objects, which might help in converting the PurchaseTicketsUseCase to use the CommandExecutorFactory

Join me on my next stream, which I usually do Monday through Thursday, starting at 20:00 UTC on Twitch: https://jitterted.stream.


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.