Real Programming S01E07: A Spike and An EventBus
We make a big architectural change today, going from an update() method to an EventBus.
If you want to follow along in the code, the project is at github.
Hey, welcome to Real Programming, the show where the slogan is currently in a hardening sprint. We are taking out all of the wrong letters that we put in in order to give you a false sense of progress. What I want to do today is actually show you a pretty big architectural change to the system. And this is going to take us a little while, but I think it’s really going to be worth it. We’re going to switch off a little bit away how the different parts of the program talk to each other.
I’m going to start by talking to you about two problems that I’m seeing. One of them is already there, but only small right now. But it’s going to get progressively bigger and bigger. And the other one is kind of in my head. I’ve been thinking about it and thinking about it. And so we’ll take a look at those two problems.
Then, we’ll do a very short spike on a solution. We’ll toss the results of that spike. That’s really just our chance to do an explainer. And then, we’ll TDD the new architecture into place.
So let’s get started. OK, the first problem is actually right here on the screen. It is this function right here. This function is inside the model. Remember, we divided our code into view, and model, and domain. And you could also see that as saying we divided our model into UI and game. And what this function does is it transfers the results of doing things to the game in such a fashion that the view can actually render them.
Now, right now, our game is very simple. All it does really, then, in this update method, is make sure that the dice get their pips and images updated, and to control whether or not you are allowed to roll again when the program runs. That’s all well and good. All of this started when I started considering the scoring. Because that’s obviously where we need to go if we’re going to get a real game– scoring and turn taking.
And my idea is that the UI would look something like this. Not, maybe, exactly this, but something like this, except that where this says game 1, game 2, game 3, we would actually have the players. Because I wanted to do a multi-player version of this. So I’d have– probably, in my early versions, it’ll be GeePaw versus Molly, versus Wally. And we’ll play some three-player games. But there might be 5, or 2, or 1– any number of players, each one in a column.
Now there are a lot, as you can see, of possible score slots in here. Some of them are like this guy. They really are just full blown scores. Others are like the total, or the bonus, or the other total, and so on. And there’s even a special weird one which is the Yahtzee bonus. We’ll talk about all that at a later date. But essentially, these guys– there’s a lot of them. You’re looking at, I think, 16– 16 of these guys for every player that we have.
Now, when we come back and look at this update function, imagine what’s going to happen to this update, how it’s going to grow. It’s now, in addition to updating these five simple values, or six simple values, we’re now looking at it 16 times 2, times the number of players. And we’re going to do that every single time we take an activity.
I’m not wild about that approach. I’m not wild about it at all. That’s not the only issue. That’s just problem number one. And it’s small right now, and it’s going to get bigger.
Problem number 2 is a problem that has to do with some API.
[COMPUTER-GENERATED MUSIC PLAYING]
So if we divide things up mental into UI and game, the game has both function, and it has a bunch of state. And the UI currently calls the function. And then it fetches the state. That’s fine, but what it’s actually giving me– my two problems– is this channel here, this getting the state back out of the game, back into the UI.
So I started thinking about it. There are several different ways to do that. Obviously, the most common way is to simply use a functional approach, where you call the function and the function returns the state. But if we do that, that means that that update method is going to be essentially identical. Every function that’s called on the game is going to effectively return the entire game’s state, and we’d still be having that long and fairly complicated update function.
OK, that’s one approach. What’s another approach? Well, we could do some sort of observer pattern. So there’s a couple of choices there– JavaFX, my own, might even be a third choice– some library. So now, if we were saying JavaFX, write my own, use a library, these are all possible observer approaches. And yet, they don’t thrill me.
I don’t want to use JavaFX. We’ve already established that. Because why? Because if I use the JavaFX observation, then I’m tied to JavaFX forever. I will have a dependency on it. No one will be able to put a console command line on it or anything else, if– or make it a web app, or any number of possibilities, even though none of those things have to do with the heart of Yahtzee game.
What about rolling my own? Well, yeah, pain in the butt to roll your own. It’s not so much the simple observations as it is the more complex ones, like nested observables. Like when you have players, and the players have scores, and if you change the player, it changes the set of scores. Now, you’re into maintaining some fairly complex state in the game. And it’s a good deal harder to implement that stuff. Of course, I could liberate it from JavaFX itself and simply move it into my code base, but that feels like cheating.
What about getting some third party library to do that? Perfectly viable option, except I still don’t like nested observables very much. JavaFX just doesn’t use them very well. Well, come on, GeePaw, what are you going to do here? You gotta come up with some sort of answer to this.
There’s a third choice. That third choice is an EventBus. An EventBus is a back channel that allows me to call a function. I call the function. The function doesn’t return anything most of the time. Instead, what it does is it makes the change. And it sends events through the bus, out the back, that the UI can then catch.
It is just like subject-observer, except you don’t register with the object that is of interest. You register with the EventBus. Bus And the object that’s of interest, he doesn’t register with anybody, or know who the UI is, or know who is watching. Instead, he sends things to the EventBus, and the EventBus handles all of the updating and observational stuff.
All right, with that in mind, let’s try one of these EventBus things and see how well they work. Let’s try the one from Google’s Guava libraries and give that a shot. Now, this is a spike. And I don’t think we’ve done any spiking before. I don’t feel like I have time to explain to you in detail what a spike is or how it works. But I will give you just one description of it that captures what I mean by the word “spike.”
A spike is a period of programming in the computer in which there is exactly one rule. You can’t keep the code. So what I mean when I say, “I’m going to spike this” is I’m going to go play around in some code, run some programs, do this, and that, and the other, and then I’m going to throw it all away. This will be a very short spike, but you’ll get a sense. Sometimes we make much longer ones. So we’ll see.
Let’s go to it. First thing I’d have to do is, of course, go to my build and add the library. That should have Guava in there. Let’s find out. Let’s declare ourselves an EventBus. There it is. The default constructor is just there. I have no idea whether this works or not. We’re going to give it a try.
I now have this member bus, and what I’m going to do is, you post events to the bus when you’re the source of events. So this is going to be my event. My event is going to be HiMom. And that’s going to be its message. You don’t have to have a message, but I just put that field in there for my own purposes. And let’s say whenever you start, whenever you call start on this, we’re going to fire that event. We’ll do it after we do everything else.
And that’s all there is to that. You’re saying bus stop, post, here’s the instance of the actual event. Now, we’ve got get somebody to listen to it. Let’s make the model listen to it. The model is past a game object, so we can just look at that bus and say, I want to watch that bus.
We’ll do it in our constructor. That line of code is basically just telling the EventBus, hey, I’m interested in events that come from you. Guava’s EventBus code will now use an annotation on functions to essentially perform a filtering and know which particular events you are interested in.
And that looks like this. You mark it by Subscribe. And then, all you have to do is you have to have a method here that has a single argument. This is how you do that. You mark it with Subscribe, and then you say whatever name you want. This is how you say that. You mark it with Subscribe, then you say whatever function name you want. The function has to have one argument, and it has to be the particular– an instance of the particular kind of event you’re interested in.
So in this case, we’re saying event HiMom. And then, Guava does the work of figuring out, hey, YzModel is registered and is interested in HiMom events because he’s got this method and it’s marked with the subscription. And all we’re going to do with the method is print line event.message.
So let’s run this. And now, when I hit the start, we should be getting some blah blah blah. And sure enough, we do. Start calls game.start. game.start does the start of the game, and then says, hey, post, I got start an event. When he does that, Guava takes over and invokes handle HiMom with that posted event that is coming from the game.start. So it’s a back channel.
So that’s pretty quick and easy. Reminder, whoosh, all that code is now thrown away. When we look at code again, it’ll be back to where we started from. And let’s do this for real.
Now, when we look at this update, the first thing that is being done by the update is moving the data from the dice back out to the UI. For each die in the game, blah blah blah, assigned from the games to my particular pips image. So let’s head that direction.
We start by getting into the dice. Now, one of the cool things about this change is that we should be able to make this change without breaking anything for quite a while. That is, as long as– we can incrementally add support for the EventBus and keep the update until we’re sure that that support works correctly and put it back in again.
So to do that, what I propose to do is write some tests against the dice to make sure it uses the bus correctly. I say something like this– dice sends the pips change events. So it’s pretty easy to get a pip change to happen. That will do the trick, right?
And how do we find out what events got sent? Well, let’s see. First thing is we’re going to have to give the dice a bus when we construct it. And we’re going to have to listen to that bus. So let’s do that.
So in the dice test, that looks like this now. I have created a bus. I have passed it to the dice. That will make this test compile OK, because I went on over to the dice and changed it to take this argument. However, that is still going to break us when I actually try to do anything, because I’m sure the dice are initialized in places without getting that.
Sure enough, the game does it. So OK, let’s make the game get the bus. Now he gets the bus, and he passes it to the dice. Let’s see if we still have broken stuff. OK, no, that worked fine.
Now, now that we got this far, what are we going to assert here? We’re wanting to check that the bus has events in it. Let’s see. Let’s do this. Let’s catch all the events that get thrown from any of these calls. And we’ll stuff them in array, and then we’ll assert against the contents of that array, something like this.
So the init does bus.register. So in other words, my test will now register to catch events from this bus. It adds a handler that takes any kind of event and stuffs it into this events array at the end. That means– that means that I can easily assert against that list after anything. What we’ll probably wind up doing is embedding this in some sort of helper and use that technique. But for the now, this will do the trick.
Now, if you do a flat roll, you’re going to get five of those events that we send. Let’s see what they should be. They should all be– let’s call them PipChange. So we roll the dice, we assert that events contains all those events.
Now, what are these two guys? Well, a PipChange will be a simple data class that contains which die changed– could be 0 through 4– or, and what value? And we said– oh, I did it wrong. We said the values coming in are going to be 5, 4, 3, 2, 1. Good, that makes this a little easier to read, actually– like that.
Now, of course it doesn’t compile. There is no PipChange. We’ll put that into the dice. That’s all there is to that. I chose to use a data class just to guarantee that my comparisons are 100% of the field, and copy, construction, et cetera. Now this test should run, and it should fail, because we’re not doing anything with the bus that we put into the dice yet.
And we have a– well, is it a winner or is it a loser? We have a winner or a loser, depending upon what you were expecting to have happen. We’re not actually pushing any events. So let’s go post some events over in the dice. You’ve got your roll function, and it’s changing the values. But in addition to changing them, what we wanted to do is post an event.
Let’s give it a whirl. Ta-da! Like a boss we rolled that. All right, we just did our first piece of posting one of these events. So now, we’ve tested that our dice are correctly sending the event.
Who’s going to catch that event? Well, it should be, in my view, the model. You could actually catch them directly in the view, but you’re still probably going to need models for any sort of view-on-view violence, and any sort of complication that arises from catching an event. So I’m thinking the model is the right place to do that at.
In the model, we will first register in the bus and then add a handler function. Then there’s our handle function. It says, when you catch that event, you use the die– which die is it– to index into your die models. You change their pips to be whatever the event’s pips say they are. Now, we don’t have a test for this yet, but I’ll probably want to come back and figure out a test for this in a little bit. But I want to get to actually checking this out, and then we might pause for now.
Now, when we run, everything is going to work, because we didn’t change anything. I mean, we’re actually double setting. You might even be able to see it. Each time I roll– no, it happens too quick. We’re actually double setting that die, because we’re setting it in our update method and we’re setting it in our handlePipChange handler. So then, what we want to do is get rid of the part of the update that handles doing that. Let’s see how that goes.
Ha ha ha ha! Perfect. Now, I still would like to test against that model. So notice that in doing this, I don’t even actually have to manipulate the game. I just have to manipulate the bus. That seems a little odd. We’ll come back to that possibly after a while. But for the moment, all I’m doing is saying, suppose the bus says change the fourth dice to the value of 6. At that point, we should be able to say the model’s dice sub 4, their pips value is equal to 6.
Let’s see if we get that test to pass. And it sure does pass without any issue at all. So we are now successfully handling PipChange events. We’ve tested it, we’ve run the program, and we’re looking pretty good. So that’s pretty cool.
But I’m going to pause right here. I’m going to go ahead and push that code. I have green tests and a green working program, even though we haven’t really begun to do everything that’s necessary. I want to pause for a second and and blah blah blah a little more about what we did.
It is a significant architectural change. Before, remember, we were pulling state. And now we’re not. We don’t pull any state at all. Now, there may be parts inside game that actually still pull state from each other. But the UI is connected to the game positively only through the function, and then, on the back end, only through the EventBus. That’s a fairly hefty change.
I’ll be honest with you, it felt like it was a long time between the time I made the last video and the time I made this one. And I spent that time thinking really hard and playing a little bit, although not in the Yahtzee program, in another scratch code base I keep laying around for just such experiments. And I have to tell you that I feel kind of bad. Because the series is stretching out now– seven episodes, oh my gosh. And now you’re changing your architecture?
Each one of those episodes represents somewhere between a half an hour and 90 minutes of actual programming time. So we’re a day into this, maybe a day and a half? That’s a good time to make an architectural change, not a bad time. So I’m going to set that guilt aside.
Using this back door technique, in this very simple case, worked wonderfully. Whoosh– run around the back and hand the results. We no longer have to move all the state every single time. we can move just the pieces that actually change, which is very cool. Furthermore, the UI doesn’t have to know the internal layout of the game at all when we adopt that.
So those are two pretty big pros. And there’s a third one– how easy it was to roll that test. And we can make it even easier. All of my outflows are function calls and all of my inflows are events, and it’s very easy to compare events. The event classes are very simple. They can all be data classes. That makes everything a lot easier.
And isolation is guaranteed. All you have to have to test one of the guys on the model side is a bus. All you have to have to test one of the subcomponents on the game side is a bus. So that’s pretty cool. And with all of that wonderfulness going on, you know– you know you’re going to want to say, oh, my god, everything is an EventBus!
I’ve been a geek for 40 years, and I have said “everything is a” about a million times. And so far, I have been wrong every single time that I did that. So let’s not all go jump into bed with EventBus just yet, because there are some cons.
The biggest con is the swarm of events problem. Once you start down this road, it is possible to have too many events firing all through that EventBus. It will actually affect not just your performance, but it will affect your mentality. It’s hard to figure out and track what those events are.
The answer to this, in large-scale EventBuses is actually to create multiple buses, and to connect them together in pipes. That actually is a workable approach to that. But obviously, the complexity goes up quite a bit when you do that.
Now, another con to this is that we’re going to have to be very careful to define very simple, very detailed events. So the good part of that is it’s going to force us to be explicit. The bad part of it is that it’s going to force us to be explicit, right? It’s a constraint that we’re actually adding to the system. Furthermore, if we communicate across every boundary using an EventBus, we will quickly get overwhelmed with complexity.
The sequence in which events arrive at a queue is actually very important. It will become increasingly important. We probably are not even going to feel it in this dumb little Yahtzee game. But in real life, we could get into a very great deal of trouble. So sometimes, we use function returns. Sometimes, we use observers. Sometimes, we use EventBuses. In this case, I think the EventBus is going to be a net win.
I say “is going to be.” All we did was handle one tiny piece of it today. In the next session we have, I’m going to want to tighten things down a whole lot more. I’m going to make all the communication across from the game to the UI happen in events. I’m going to add events for things like the can roll.
I’m going to have to throw out the old way of doing things, which is no big deal, but will have to be done. And then, generally, I think there’s some functionality in some wrong places that I can now move around. In particular, I think the game is doing some stuff that really the dice should be doing. We’ll talk about that next time.
Anyway, I had a great time. Thanks for watching Real Programming. We’ll see you soon. Bye bye.
Transcript & Captions coming soon!