Live Coding Journal - Feb 18, 2026
Reflections, Learnings, and Mistakes from live coding my JitterTicket Event Sourcing application
Notes from Today’s Live Coding Session
Refactoring to Granular Reader and Writer
On today’s stream, I spent some time refactoring the use case/command/application layer code, i.e., the command and query for rescheduling concerts.
Instead of the typical case where I’d pass in a service class, or perhaps a full Repository (or in this case, the entire EventStore), I now pass in more specific interfaces for doing a query for concerts (read) and the command object for rescheduling it (write).
Before the refactoring, the code looked like this:
copy@GetMapping("/reschedule/{concertId}") public String rescheduleConcertView(@PathVariable("concertId") String concertId, Model model) { Concert concert = concertEventStore .findById(new ConcertId(UUID.fromString(concertId))) .orElseThrow(() -> new RuntimeException("Could not find concert with id: " + concertId)); model.addAttribute("concert", ConcertView.from(concert)); model.addAttribute("rescheduleForm", RescheduleForm.from(concert)); return "reschedule-concert"; } @PostMapping("/reschedule/{concertId}") public String rescheduleConcert(@PathVariable("concertId") String concertId, RescheduleForm rescheduleForm) { CommandExecutorFactory commandExecutorFactory = CommandExecutorFactory.create(concertEventStore); var command = commandExecutorFactory.wrapWithParams( (concert, reschedule) -> concert.rescheduleTo( reschedule.showDateTime(), reschedule.doorsTime())); Reschedule rescheduleParams = new Reschedule( rescheduleForm.newShowLocalDateTime(), rescheduleForm.newDoorsLocalTime()); command.execute(new ConcertId(UUID.fromString(concertId)), rescheduleParams); return "redirect:/reschedule/" + concertId; }
After the refactoring, which included adding a factory method to ConcertId that takes a UUID as string, it now looks like this:
copy@GetMapping("/reschedule/{concertId}") public String rescheduleConcertView(@PathVariable("concertId") String concertId, Model model) { ConcertId id = ConcertId.from(concertId); Concert concert = concertQuery.concertQueryFind(id); model.addAttribute("concert", ConcertView.from(concert)); model.addAttribute("rescheduleForm", RescheduleForm.from(concert)); return "reschedule-concert"; } @PostMapping("/reschedule/{concertId}") public String rescheduleConcert(@PathVariable("concertId") String concertId, RescheduleForm rescheduleForm) { rescheduleCommand.execute(ConcertId.from(concertId), rescheduleForm.asCommandParams()); return "redirect:/reschedule/" + concertId; }
So much cleaner!
And just as easy to test as before, if not easier, because the ConcertQuery has a single method that’s easier to replace with a test double if needed.
I moved the creation of the command object to a new class, Commands, where all new command objects will live:
copypublic class Commands { private final CommandExecutorFactory commandExecutorFactory; public Commands(CommandExecutorFactory commandExecutorFactory) { this.commandExecutorFactory = commandExecutorFactory; } public CommandWithParams<ConcertId, Reschedule> createRescheduleCommand() { CommandWithParams<ConcertId, Reschedule> command = commandExecutorFactory.wrapWithParams( (concert, reschedule) -> concert.rescheduleTo( reschedule.showDateTime(), reschedule.doorsTime())); return command; } }
I still have some package reorganization to do, but I’m pretty happy with how this is set up.
All Concert Aggregates Projection
I then (around 3h04m) worked on fixing the problem where my event viewer was only showing Concert objects that were available, i.e., tickets can be purchased.
That’s fine for showing the list of concerts to a customer, but for viewing the underlying events, I want to see all Concert aggregates that ever existed.
For that, I needed a new projection.
It’s almost exactly like the AvailableConcertsProjection, so I cheated a bit and copied it, leaving only the event it cares about: ConcertScheduled.
It’s tempting to reuse the same projection for different situations, but since projections are cheap to code and create, it’s better to avoid potentially dangerous coupling.
I didn’t even bother test-driving it, since the code I extracted it from was well tested.
After updating the configuration to use the AllConcertsProjection, everything just worked.
Event Modeling Tool Tryout
I’ve been using tldraw.com for my diagramming, which is a great tool, but since it’s a generic drawing tool, it can be a bit painful to create more structured diagrams, such as event models. I spent about 90 minutes (starting around 1h30m into the video) trying out two tools: one was from @Suigi (aka Daniel Ranner), who create a tool called Slicr. Like PlantUML and Mermaid, it uses the text-to-diagram method, where you type structured text and the diagram is drawn from that description. I think it’s so much better to create diagrams that way, since they’re much easier to make global changes to and easily diff between revisions. It turns out LLMs are pretty good at it too, since it’s “just text”. I built out the Purchase Tickets slice of JitterTicket (you can see it at 2h20m) and once I got the syntax down (though it uses YAML, sigh), it was straightforward to use.
I then (around 2h32m) looked at Ismael Celis’ EventLanes app. (It was neat to report a bug on stream and have Ismael fix it right away!) Event Lanes is in beta, but has some really nice features around schemas that define the data elements used in read models, commands, and UIs. I also created the Purchase Tickets slice and added the Concert Started Processor to the diagram (see 3h02m). The tool itself is event-sourced, so you can look at the history and time-travel to see what the diagram was at any point. It’s also multi-user and (like many tools these days) has an MCP Server so an LLM can control it more directly. I look forward to spending more time with this tool, especially when looking at how to use the diagram text for code generation.
Next Steps
I want to add some concert scheduling validation, where the system ensures that you can’t schedule two concerts for the same date. This will need yet another projection, but those are straightforward to create. That will require implementing the “creator” command, which will be tricky because creation happens via static methods, so I’m unsure how to fit that into the current code.
Before I do any of that, however, I’ll be upgrading (finally!) to Spring Boot 4.0.3.
Join me on my next stream, which I usually do Monday through Thursday, starting at 20:00 UTC on Twitch: https://jitterted.stream.