Ga door naar de hoofdinhoud

Whist with a decider

Back in June, I attended DDD Europe 2023. It was the first time I was at this conference, but I liked it a lot, and I was intrigued by Jérémie Chassaing's talk, in which he used a decider to keep track of the internal state of aggregates.

I don't know a lot (yet?) about functional programming, but I was charmed by this elegant way to work with commands and events. So I wanted to experiment with deciders as well. As it turns out, I have an ideal project for doing this kind of experiments: dikdikdik, my web based score sheet for the wiezen (whist) card game. It already had commands and events, so using a decider should not be too hard.

someone deciding what to bid with their cards

Dikdikdik has two aggregates: a table (as in a piece of furniture, the table is where the players sit down, and play their games) and a score sheet. Initially those aggregates had methods that emitted events, and other methods that applied those events to their internal states. The latter ones were also used to rebuild the aggregates based on their event streams, since dikdikdik is also an event sourced application.

In a first step, I replaced the table entity by a TableDecider and a TableInternalState class. The idea is that when you pass the internal state of a table and a command to the decide-method of the TableDecider, it produces events, describing what happens. When passing those events with the internal state to the evolve-method of the decider, it applies the events, creating an updated internal state.

I could use this decider pattern to implement event sourcing, and it also allowed me to create a kind of testing framework, that made it easy to create given-when-then-unit tests in a quite elegant way, see e.g. this test that tests joining players.

I created the decider in a more-or-less test driven way, by converting my old tests for the Table aggregate to new tests for the table decider, and then make them pass by converting the old logic to the decider based logic.

This conversion was interesting, because it made me look back into the existing code for the write side of the table. And whenever I see code I wrote a couple of months or years ago, I am reminded about the things I learned since then. That's a good thing, I presume. Other pieces of the code have become less relevant, since the project has changed as well during the last couples of years. I created a couple of issues on gitlab for the oddities I enountered, e.g. #304, #308, #302.

When all unit tests passed, it didn't take a lot of work to make the integration tests and e2e tests to be all green as well. Which made me happy, because I think this was an indication that the degree of decoupling in my project is low. If you look at the merge request (which I reviewed en merged myself, since I am the only developer on the project 😉), you will notice that almost all changes are in the Domain\WriteModel\Table namespace, and almost no other things needed to change. The other aggregate, the ScoreSheet, still uses my old way of working, and guess what: that doesn't matter. The score sheet doesn't need to care about the internal workings of the table, and vice versa, the events are the only things that pass the boundries between them.

All with all, there were two other things I needed to change. One thing, was the validation of the games that can be logged. Now I can use a service for this, that I can nicely inject into the decider, which is way more elegant than what I did before to let the aggregate class validate the logged games. (The validation service still has some issues in its current form, but it is already injected; that's something.)

Another thing is that the decider is aware of the initial state of the aggregate. Previously, I prepared the initial state by handling the event TableCleared. Now this event is not really needed anymore, but in my integration tests I still (ab)use it to reset the read models.

Another interesting change that happened, when introducing the decider, is that I don't have a dedicated handler for each command anymore. Which feels a little strange to me, I always learnt that each command should have its own handler. Now the TableDecider takes every TableCommand, and calls the handler function that corresponds to the command. But I guess these functions are the handlers now, which may be fine.

Anyway, I am happy about the result I've got so far. I think I will already release this to at the end of this month, so that we can try it out during the next meetup of our wiezen club. (Of course everything should just work, since all tests are green, but I want to use the application quickly after the release, so that we would notice any bugs that are not covered by the tests.)

So what's next:

Now that the write side for the table is handled by a decider, I will also replace the one for the score sheet. Since the ScoreSheet class is much smaller than the Table class was, this should take less time than converting the table, but I also have little free time to develop these days, so we will see how this turns out.

And then there's the process manager, that passes commands to the score sheet when games are played at the table. I think Jérémy did something like this in his talk, by combining deciders, but I will probably have to watch a recording of his talk again, because I'm not sure anymore about how this works.

Another thing I want to do, is add the generic decider classes I created to the krakboem libray I created for my own event sourced projecs. That library needs some updates as well, because it was created when php 7.4 was a thing.

For a short period of time, I had timelines for the new features of dikdikdik. But not anymore, because life happens, and I will see when I have time to code. Anyway, I want to thank Jérémie for his inspiring talk. I'm glad I got this far already, and we'll see what the future brings.

Until then: have a nice time playing whist.

Debian 11, XFCE and a bluetooth mouse

Last week, I gave an old laptop a second life. It's an old ASUS, I think it came with Windows 8 back in the day, and I installed Debian 11 with XFCE on it. This looks very retro, but it works like a charm. I can now use the battery for serveral hours, while with Windows it would die after 5 minutes. And today I got my bluetooth mouse working.

a bluetooth logitech mouse, sitting on the laptop

Here is what I did:

Lees verder…

Running apache and php-fpm as services in a gitlab-ci job

I've been using gitlab-ci to automate the end-2-end tests for my PHP-applications for several years now, but I wasn't really happy about the way I got it to work: it involved injecting IP-addresses into configuration files, and starting a web server as a part of the test job.

an elephpant, and the apache and gitlab logo's

Today I can run apache and php-fpm as services, so that the job's script doesn't have to care about the web server, and can fully concentrate on running the actual tests.

Lees verder…

Working around a Pop!_OS networking issue

Update (2021-11-26):

The solution I describe in the original post, did work for a while, and then stopped working. Then I got it working again by removing the kernel modules iwlmvm and iwlwifi, and modprobing iwlwifi again. Worked for a day, and stopped working again. So I guess it just sometimes worked, and sometimes, it didn't.

the TP-Link TL-WR1043ND v2

I think it was just the router. It's a TP-LINK TL-WR1043ND v2, and I installed OpenWrt on it (v19.something), a couple of years ago. Now I upgraded it to v 20.02.1, and I think - hope - that that will finally have solved the problem. I was a little reluctant to flash a firmware update, but it turned out to be very easy. The page for my device on the openWRT wiki had a direct link to the 'upgrade firmware', which I could easily flash using the openWRT web interface (using another device than my laptop, of course).

Lees verder…

Migrating a web app from VueJS to Symfony UX: retrospective

The last couple of weeks, I have been refactoring the frontend of dikdikdik, one of my pet projects, that we use to track the scores when we play the card game 'wiezen'.

twig and class for DelaerSelectComponent

The release of version 3.0, in which I threw out VueJS in favour of Symfony Turbo and Symfony Live Components was announced yesterday, and in this blog post I look back on the refactoring, making some kind of roundup on what I like and dislike about the new frontend I created with those new symfony-ux tools.

Lees verder…

Trying out Symfony Twig Components

Last summer, I attended SymfonyWorld Online 2021 Summer Edition, and I was fascinated by Ryan Weavers talk about Symfony Twig Components.

As the README says on the github project page

Twig components give you the power to bind an object to a template, making it easier to render and re-use small template "units" - like an "alert", markup for a modal, or a category sidebar.

I found the presentation very promising, and I wanted to try using twig components in combination with stimulus for the frontend of dikdikdik, a hobby webapp I maintain, to keep track of the score for the wiezen card game.

Lees verder…

Updating your Symfony app from Mercure 0.10 to Mercure 0.11

Last wednesday I updated dikdikdik, a score sheet app for solo whist, so that it now uses Mercure v0.11, instead of v0.10. I use docker-compose for the development environment of dikdikdik, and GitLab CI/CD to run the end-to-end tests with codeception and selenium.

Mercure v0.11

This was not a trivial update, because v0.11 was a major milestone for the Mercure project. In retrospect, I didn't have to change much, but it took me some time to find out what exactly I had to change, especially to make the end-to-end tests running again.

Lees verder…

Iemand zin om mijn code te reviewen?

Laat ons het hebben over code review. Waarschijnlijk bestaan er verschillende definities van 'code review', ik zal eerst even verduidelijken wat ik met code review bedoel.

In grote lijnen: alvorens er aanpassingen gebeuren aan de broncode van je software, is het de bedoeling dat één van je collega's er eens naar gekeken heeft. Zodat die collega vragen kan stellen, of opmerkingen kan maken.

speelkaart en broncode

Lees verder…