What are the domain modeling lenses?
I'm organizing my new book in terms of lenses. Each lens focuses our attention on one important aspect of software design. In this episode, I briefly introduce each lens.
[00:00:00] What are the domain modeling lenses?
[00:00:05] Hello, my name is Eric Normand and this is my podcast. Welcome.
[00:00:13] So I'm writing a book, another book. It's taking a lot of deep thought and introspection. A lot of it seems like new material to me, and so I'm just digging through and building this foundation
[00:00:34] The way I'm organizing it now is through this idea of lenses. Each lens provides a perspective on your software so that you can gather some information and make better decisions.
[00:00:55] I don't want to give a bunch of rules of thumb or harder design principles, things like that. I want to make it as much about just learning about what the software has to tell you as possible. Then you have to use your own judgment and your own intuition. And so I've organized it into a set of lenses. Now, these lenses are not a complete set. I'm am not saying that these are the only questions that you should ask or that asking these will give you a good design. They're simply ways to gain more information so that you can make better design decisions when you're writing your software.
[00:01:50] I want to go over them and briefly introduce each one. And I'll do episodes in the future about each one. And the order of these questions also is not fixed. There really is no order, but I do have to choose an order to put them in a book. There are seven of them.
[00:02:15] The first one is the domain lens. Now, this one I think is super important. It's trying to get you to ask what problem is your software trying to solve?
[00:02:34] I find more and more when I look at people's software, they are not really aware of what problem they're trying to solve, and this goes from the small levels to the bigger levels. So the small levels would be, they've introduced some abstraction and the question is why? Why did you put this indirection in your code? What problem is this code trying to solve? And sometimes they have an answer and that's great, but sometimes it's not a real problem. It's like an imagined problem. And sometimes it's a problem that's trying to anticipate other problems that may or may not happen.
[00:03:40] It's good to be aware that adding code to your system has a cost and that cost needs to be countered with the benefit of solving a problem. I'm putting it first because it's so important, but it is actually one of the harder ones to answer.
[00:04:00] And it gets at that idea of how do you go from a kind of informal understanding of a domain to a more formal one? How do you cut out all the stuff that's not relevant?
[00:04:21] The example I would give before was, you're starting a coffee shop. Coffee is a very free domain, right? You can roast beans from zero all the way to burned, just a totally continuous domain, and the size of the coffee you make is totally continuous. You can have a one ounce cup of coffee all the way to 16 ounce cup of coffee and everything in between. It's continuous, but because of logistical operational reasons, you're gonna have three sizes of cups. So you have to pick the three sizes. You're gonna have three kinds of roasts because you don't want to give people unlimited choice. You want it to be easy.
[00:05:13] There's this process of going from this undifferentiated domain coffee to breaking it up into discreet buckets. You're digitizing it, and that's really hard, right? It's similar to looking at a billiard table and you're trying to come up with what are the rules? What is the system of equations that dictates what is gonna happen on this billiard table? And how do you take that, and you come up with Newtonian kinematics, right? That is hard. But that's what we have to do very often when we're building software.
[00:06:03] That's the bigger level thinking, like what is the problem we're trying to solve? Are you trying to predict where each ball will be when it finally rests? Or are you trying to get a ball into a corner? Into the pocket. There's all these possible problems that you could be solving, but you need to figure out the problems you want to solve. It's not just figure out Newtonian mechanics. There's a subset of questions that we actually need answered.
[00:06:42] Similarly with the coffee shop example, you could say, we need to model all the possible coffees. But really the problem you're trying to solve is that you need the cashier to be able to type in a coffee on their screen, and then that coffee is communicated to the person making the coffee, and you want to make sure that they get it right. There's no information lost. That's the problem that you're trying to solve. It's not modeling coffee. The coffee doesn't matter. It's the coffee order that matters.
[00:07:23] Let's go on to number two. Like I said, we're gonna have other episodes where we can go deeper into each one. So number two is data, and in this lens we're asking this question: What are the relationships between data values?
[00:07:43] We've talked about this before in other episodes. Sometimes the relationship is very clear and systematic. It's something like an alternative. So you have different data values, small, medium, and large, and the relationship is you choose one of those.
[00:08:04] You can't have a small, large coffee, but sometimes you have a combination where you have a small coffee with a dark roast. You can and need to pick a size and a roast. You need to look at the data values and what is their relationship.
[00:08:27] Three operations: What are the use cases of your software? What are the specific actions that software needs to be able to handle? These are the operations.
[00:08:48] You need to be able to model someone typing in the order. And how are they gonna do that? And you could model it in different ways, right? But that's the question that you're asking. How are they going to do that? One way is, you're gonna have a plus button for soy, and you can add one soy shot, and a plus button for espresso, and that will add an espresso shot. And then if you hit the minus button, it'll remove it.
[00:09:20] You have to figure out stuff like what happens if it's at zero when you hit the minus button. These are the use cases that you're figuring out, and I like to represent those as actual functions because that forces you to think in terms of what are the inputs and what are the outputs? What are the arguments to this function? What is the return value, and how can I make it total?
[00:09:47] Four composition.
[00:09:51] So this lens is kind of dependent on number three. If you can list out your operations, how do they compose together? And I don't mean compose in some monadic way or anything like that. The more interesting use cases are going to be multiple operations in a row.
[00:10:20] They're gonna add soy, add espresso, accidentally, add a espresso again, and so now they have to remove one. And then change the size and then the customer changes their mind. They want a large, so now you've set it to large, right? How does your model incorporate sequences of operations? What is the model for that? How do they compose?
[00:10:50] Sometimes they might compose in parallel. I just want to be clear about that. It's not always a sequence.
[00:10:57] Five volatility.
[00:11:00] In this lens, we're asking: What things can change and how often? This needs a whole episode by itself, but I'll just say here: I think one of the, one of the things I would like to correct about the modern software movement is that change has become a boogeyman. It's totally unpredictable. It could happen anywhere. I mean, at the limit, yes, that's true. There is a possibility that it could happen in some random spot.
[00:11:41] It's nice if someone anticipated that and added a thing to make it easy. But those kinds of things are rare. It's rare that your random insertions of indirection to allow for a certain kind of change actually paid dividends, that they actually turn out to be correct. It seems like they're not rare because when they happen it seems like a great payoff, but you aren't counting all the times that you added indirection and they didn't pay off.
[00:12:22] My interpretation is that, like gambling, the dopamine release of winning makes you forget all the times you lost. The dopamine release of having an indirection that actually helped you when a requirement changed, it's so great, you forget that you actually put thousands of those in your software.
[00:12:47] The boogeyman is that you are afraid of change. So we put it everywhere. We put indirection everywhere to anticipate change.
[00:12:57] But that doesn't mean that change doesn't happen, and it doesn't mean that change is unpredictable. A lot of change truly is predictable. And you can just ask the business person, the straight up question, Hey, is this ever going to change? And how often? And they could tell you, oh yeah, that we change the sale price every week. You know? Oh, we change that text. Yeah. The marketers keep changing that one. That one has to be real easy to change. Or Oh yeah, we do change it, but it's like so rare, it's not worth worrying about it right now.
[00:13:37] These are the kinds of answers you can get, and you can often you can make these determinations yourself. You don't have to to ask someone often.
[00:13:50] Okay, so that is volatility. How often do these things change?
[00:13:58] Okay, number six, I'm calling it scope in my notes. I've got a bunch of crossouts cuz I don't really have this one nailed down in terms of the name.
[00:14:11] But this one is trying to ask: What arrangement of layers gives you the most leverage? I'm into stratified design, layering different parts of your software, and very often I will push something out of the scope of one layer because it just seems like it's cleaner to handle this somewhere else, right?
[00:14:42] This function can be very easily made total if I just don't handle this case. So, as an example, in a pizza restaurant, the business might have a hard limit of three toppings on the pizza. Whatever the reason is, it makes it hard to make your function total because now you have to wonder what happens if I add mushrooms when I've already got three toppings? Does it just not add it? But then if I remove mushrooms, it'll remove them, right?
[00:15:23] Let me lay out the scenario. Let's say I add artichoke hearts, mushrooms and bell peppers. So that's your three. It's full pizza now and I'm gonna add mushrooms.
[00:15:39] Now I've added the mushrooms, but it didn't do anything cuz I have a limit of three. And so now I realize, oh, I pressed the button, I want to undo it. I hit minus, it's gonna remove the original mushrooms that I had in there. So now I'm down to two toppings.
[00:15:54] I still get a valid pizza, but it violates this property that would be really nice to hold, which is that: if I do a plus and then a minus, I should get the pizza back that I started with. That's a really nice property. I mean, it just rolls off the tongue. It's something I want to have, but if I have this hard limit of three, it's not gonna work.
[00:16:21] So what I do is I say, I'm gonna push that limit to another layer. Someone else is gonna handle this hard limit. In this layer where I'm modeling the pizza order, I'm gonna allow pizzas of unlimited number of toppings it's not practical to make these pizzas, but I can imagine the pizzas, right? It's not like conceptually they don't make sense anymore.
[00:16:50] This scoping question is trying to get you to think about that. I'm getting more leverage by being able to have these stronger, cleaner properties if I push these things out, this particular thing, out of this layer and into a higher layer. And it makes sense semantically that the pizza domain is different from my pizza restaurant domain.
[00:17:22] My business has particular rules that don't apply to all pizza businesses, but the pizza itself, that model of the pizza as a circular bread with tomato sauce and toppings on it, that might work for other restaurants.
[00:17:43] Last one, runable Specification.
[00:17:48] I'm actually thinking that the title of the book might be Runable Specification, so it's weird to have it last. But this is the question, how can we test and learn from our code so that we can improve our model?
[00:18:06] This is where I'm trying to get iterative. How can we write this software so that we can learn from our code so that our model gets better?
[00:18:16] How can we write a use case out in code that calls the model and then we test that we've achieved the results we want. So kind of like a unit test, but at the prototype stage, right? So we're gonna create a little scenario of someone adding and removing toppings from their pizza, and by the end we expect it to look like this and this can test our model.
[00:18:57] We might find, oh, when they add too many toppings, such and such happens and we want to be able to, we want to avoid that. Or, you know, we didn't mean you could add three times the cheese. You know, we allowed you to put three mushrooms, but not three cheeses.
[00:19:18] So maybe cheese is different from the other toppings . You're using the code as a way to formalize your model, and then because it's runable, it's written in code, you can then run it, run a little scenario, and then you find a bug in your code, but it's also a bug in your model.
[00:19:51] And so now you can learn about how to make your model better. And that's kind of the, the magic that I would like to get to is this idea of runable specifications.
[00:20:09] That has been my summary of the lenses, and I will probably go into each one separately in a different episode just to dive deeper into them.
[00:20:23] I know these aren't exhaustive of all of software design and all the issues you need to take into account. These are just the ones that I have something to say about and have something that I can contribute to.
[00:20:44] I'll just leave it at that.
[00:20:47] My name is Eric Normand. This has been another episode of my podcast. Thank you for listening, and as always, rock on!