Real Programming S01E04: Into the Frying Domain!
We have two parts of our walking skeleton, the View and the Model. This time we’ll move a tiny amount of work into the domain classes
If you want to follow along in the code, the project is https://github.com/geepawhill/yz
Welcome to episode four of Real Programming, the show where the slogan is still a work in progress. Last time, we had our view and our model and I remarked that that was two of the three parts of our architecture. Today, we’re going to throw in the third part, the actual domain. What we’ll do is, let’s take a look at what we’ve got right now then I’ll lay out the change that I have in mind and then we’ll go to it. OK, here’s our working app right now. You have to say start once and then you can roll the dice three times, once, twice, three, and then you have to say start again.
Now of course that’s not Yahtzee, not yet, not even close to Yahtzee. But what we’re doing is we’re pushing out what we call a walking skeleton. I’ll tell us more about that in a little bit. Underneath the hood, this screen is represented almost entirely by a view, a YZ view that we’re looking at right here. Now it’s actually, it’s a very simple beast. It really is just a little tree of things that are directly from JavaFX’s windowing land. It has, at the top a root, which is a kind of pane inside the window, and then a horizontal box. It’s got two buttons and then for each one of the five dice, it’s got an image, which is showing either the question mark like we saw in the start or after you roll it shows the number of pips.
And that’s all the view does and that’s how we want it. We want that view to stay simple and stupid. Now what it does is as it’s created, it also creates its own model that’s actually backwards but it’s a constraint of JavaFX that it has to be done this way. So it makes this model, what’s the model doing? Well the model over here is actually right now doing the work. It is doing the work and it is stuffing various properties like can I roll right now or what are the values of the pips right now into JavaFX properties. The same properties that are being watched by the view.
Now here’s the thing, that’s 2/3 of the way there but there’s another step that we need to take to get our full architecture in place because in almost all serious applications, we have a view watching a model but we have a model working with domain classes. Let’s take a second, draw ourselves a little picture and see how that works in theory. What I’m proposing is an arrangement where I have a view, a model and a domain. And right now, let’s say we don’t have this domain yet, we have the view and we have a model and the view is observing the model. Furthermore, he is sending commands. Right now, the model is doing those commands and that’s actually the problem and the problem has to do with both isolation and with dependency.
The reason we separated view and model was so that we could test the model easily, which is great, and why does this make it easier to keep them separate like this? Well because the view, the view depends on windowing and windowing systems are very difficult to test in any reliable stable way. The model though, what does the model depend on? Well remember, we’re observing properties. Where do those properties come from? Where are the subject observer relationship? Well in fact, the model depends on JavaFX. Now it’s different and this one depends on JavaFX plus windowing, whereas the model depends just on JavaFX effects without all the windowing.
Nevertheless we learned the last time, you still have to have that weird JFX required thing that I showed you in order to really test that the model works and it’s because of that dependency. Now in real life, what we don’t want to have happen is to have the actual business logic, the actual core of the problem and its solution dependent on the UI. Both of these are UI and it’s true that there’s a line between them around testability. We isolated some of the part from the other parts so that we could test it and that’s great. But we need one more layer of isolation and that isolation separates the UI completely from the work.
So in our proposed brave new world, right now the model is actually doing stuff like rolling and things like that. We don’t really want it to. We want the models job to be strictly telling the domain to do things, looking at the results from the domain and translating them back to the windowing system. That’s why we introduced this third layer to our architecture. It’s one more layer of isolation. The working down here is going to move there. It’s going to be in the domain and the domain classes. If we don’t do this, what we’re going to wind up with is an application that is inextricably bound to JavaFX.
So how do we go about this? If you take a look here, what we’re doing in this model with this roll is exactly illustrative of what I’m talking about. There’s real business logic in here. Stuff like the fact that the die or six-sided dice, stuff like understanding that there is an unknown setting inside the die models. That’s good stuff but you can see us, we’re already starting to grow this die a little, a little hefty and at the same time, we still have that problem with the JavaFX dependency. So how could we do otherwise? Well, in the same way that we gave the view a YZ model, we could give the YZ model a YZ game and the YZ game is now finally down to the bedrock of Yahtzee.
Let’s do that now and see what it looks like. Well goodness, that’s awfully simple isn’t it? We simply declared an object of type YZ game, we put a field of type YZ game into the game. That may be enough or it may not be enough. How would we use such a thing? Well these guys are going to stay being the dice for the model but really dice are part of the game itself. It would be interesting if the YZ game had its own set of dice and then our role command would be literally tell the game to roll. Now take for each die inside the game set our die model is pips to whatever the new current value is after the roll and that’s going to be the first step we take.
So part zero of that is let’s get ourselves some dice and let’s get ourselves dice tests. Now I’m going to do something interesting with this. Instead of making the dice be a raw array inside the game, I’m going to actually make them a whole thing. What I’m essentially doing is wrapping that array into a class called dice. There will be no individual die object inside the array. We’re going do something a little different and the reason we’re doing that is simply because of predisposition. In my long painful life, I have learned that keeping things as arrays or other raw containers very often is a slippery slope leading us straight down the garden path.
So let’s jump to getting ourselves a dice test. It’s going to very much resemble the test that we wrote against the model. Well that makes sense. There will however be some differences starting with that difference about the array. OK, well there wasn’t much to it, frankly. Val pips, it goes an array of unknown. We’ve seen now usage of unknown before over in the die model, now that’s a hole. We’re going to have to unify those two usages but we’ll get to that in a minute. Instantiate the object of type dice now. For every guy in the pips array, assert that it is equal to that unknown. Well does it run? Oh baby, of course it runs. No problem there at all.
Now what? Well I’d like to implement a roll and I’d like to know after I’d do a roll, that the pips are in a certain arrangement. We’ll see if I can sketch that test. OK, we ran into this before. After a roll, the dice have to have some values but those values are random. Now I could just assert that they’re not all unknown, but I think I’d rather go a little deeper this time. I’d rather know that they were set to a correct value. What’s a correct value in this context? Well over there in the die model, we do this by calling a randoms and getting the next and doing the Mod Trick, which certainly seems like something that would be useful inside our dice.
But I can see an issue almost immediately because I’m a test-driven developer. If I pass a randoms in here or if I lock a randoms into dice and use it in that way, then I’m not, I’m still not actually going to know what those guys are. I need the dice to take, as part of their construction, a particular thing, a roller and then I can pass in a roller that has a random generator as part of it or I can pass in a roller that has pre-stuff values in it. Let’s see what that might look like. Well that’s not so bad. We have an interface. The interface would have a roll, would pass it in at the time we make the dice and then in here, we can go through the loop and do the roller and get a value, one value at a time back from the roller.
That works for me. In order to test it, I need a specialized roller don’t I? Need a roller that will give me, that will let me stuff values into it at the time I make it. That way, I’ll know for sure what the dice are actually going to be after you do a roll. OK this is, kotlin really doesn’t want you to say if return, else return and it gives you warning and it wants you to say return if. I prefer the when construction so I use that here. Essentially what we’re doing is this. When we construct a testing roller, we stuff in a bunch of pending dice. Dice that are going to come out and we keep track of them in our little private variable pending and then when we roll, what we say is if the pending is not empty, pending remove out the zero of item, that way they come out in the same order I put them in.
And if in fact it is empty, then false is equal– Not really very happy with the look of that code. Maybe it’s just because I’m not totally comfortable with Kotlin yet. I don’t like the return when or return if construct very much. We’ll see about that later. Meanwhile though, I’m hot to try. I want to get this thing to do something for me so I’m not going to mess with that code any further. I think I have it right. Let’s get ourselves a test. In order to test down here, what we’re going to have to do is we have to make sure that the dice are constructed with one of our testing roller guys that we’ve loaded up and then, we’ll say the dice change during a roll and because we’ve preloaded this guy, we’ll know for sure what these values should be.
I’m going to do it like this. OK, so I’ve passed in a testing roller and I gave him the new rolls will be 5 or 4 or 3 or 2 and a 1. That means after I do the roll, the dice stop pips, should contain exactly 5, 4, 3, 2, and 1. Let’s run and see what happens. Well no, of course it doesn’t work yet. You didn’t implement roll. Well let’s go do that. There it is. For die, the zero to 4, pips of die is equal to roller dot roll. Let’s see if that gets us through the test and it does. Now we have some dice and the dice know how to roll and I’ve even secretly asserted that changes happen in order. 5, 4, 3, 2, 1 and 5, 4, 3, 2, 1.
We’ll think later about whether we need to make that a little more explicit. Right now I’m not super paranoid about what could happen with this code. The reason being it’s a little too light. But now, we have to look it up. So what are we going to do? Well go to the model and, right. Don’t go to the model, go to the game. In the game, we’re going to instantiate dice. We’ll put them right inside the game, we’ll make our roller at the same time that we do that and then we’ll dereference them when we need them in the model. To do all that, I’m going to have to extract things into files and get them in the right area for the testing. So quick like a bunny.
OK, we got this simple game class with nothing in it. We’ve got the dice extract it out. We’ve got the roller interface. Right away I see an issue. There is no real roller. There’s no random roller. Maybe I should start by putting myself a random roller into place. It’s not remotely complicated. We already wrote the code. It’s actually right over here. It’s this business, get yourself a random object and do that and that’s all you have to do for a random roller. Let’s go to it. OK, that was easy enough. Just a little bit of copy and paste. Now what do we need to do? Well now we can put some dice into the game object.
Again, that was pretty straightforward. Now let’s use them in YZ model and there it is. Our loop is still a perfectly simple loop and all we’re doing is going through and we’re saying for each dice we have to deal with, the die models pips needs to be equal to the game dot dice pips. Let’s see if we broke any tests along the way just in case, I’m not sure. Now we’re still good on tests. Let’s try the game. Game comes up. You’re still set to unknown. When I say start, when I say roll works very well as far as I can tell. What did we accomplish? Gosh, it was a really tiny step but a little cleanup here will help us be a little bit clearer.
Notice the model no longer uses a random at all. He just uses the game object. Everything else that he’s doing is going out the other side, that is, it’s for the view to work with. This didn’t get super simple because we’ve got that code in place to test for can roll. Makes me wonder if can roll might actually need to be also moved into the game. We’re going to think about that and we’re probably going to do something about it. But for right now, we’ve actually reached a fairly straightforward stopping point. It’s a little short but I actually want to show you a picture to make it clear to you, as clear as possible, exactly what we have accomplished.
Going back to my hideous drawing, we now really have the D, OK? We have a domain class, the game class. So YZ view watches YZ model, YZ model works with YZ game. Now the working part, at least for the roll, has now been moved. It’s over here. And what is this working, what’s the other name for it? Well it’s the business logic. So what we have accomplished in doing this is we’ve now implemented all three parts of our architecture. The windowing is implemented by view, the business logic is implemented by domain and the model, his whole job has now gone from instead of doing the work, he tells the business logic to do the work.
Then his job is to simply front the results of that back into the model. So he takes care of the can roll logic, he does all of that. Now at this point, you might be thinking to yourself wow, that is really over engineered. We’ve got these three layers, it’s just a dumb Yahtzee game. Couldn’t I put that all in windowing? Yes, you certainly could. You could have done everything with the view. We’d have one nice model. However in real life, we almost never work that way. The reason we don’t work that way is because this stuff is almost completely untestable. So the purpose of this line between view and model is precisely to give us testability.
It’s precisely to make it possible for us to test all the complications. And what about this line, the second line? The second line is actually to separate business logic from UI. So we’ve actually achieved some pretty significant isolation and remember, we don’t have an actual Yahtzee game yet. Because we don’t have that game, we don’t know what directions it’s going to be. And then my last point to make in this is this, remember we’re now what, probably 90 minutes maybe, maybe 2 hours into development of our brilliant Yahtzee app. So what I did then, what you’ve seen over these first four episodes is what you watched me do was rush into the architecture.
What I wanted was end-to-end. What I wanted was from pixels to bits one time, something dumb that takes us all the way down to the middle, all the way back out to the screen. That is called, in Alistair Cockburn’s term, making a walking skeleton. I have a crude walking skeleton. Noticed by the way, it doesn’t play Yahtzee. No, it doesn’t play Yahtzee at all. It has really a very little Yahtzee-ness to it except for the fact that it has pictures of dice and they change. It’s not very like Yahtzee. That will of course be where we go from here but the point of having a walking skeleton is that at this point, we can take any feature or aspect of our code at any time and as soon as that’s done, we can ship it because all three layers of the architecture in place, they all three know how to talk to each other.
They all three have clear responsibilities. So we’re actually ready now, in the next episode, to start pushing hard and I’ll give you a little foreshadowing. I think we’re going to start by pushing really hard on that new domain object. Let’s get the domain object to play Yahtzee and we’ll hook up the model in the view a little bit here and there, now and then but the driving force will be get our business logic to be the business logic we really want it to be. Anyway, thank you very much for watching. I look forward to seeing you next time. By which time, surely we will have some slogan in place. See you later.