All about the architecture lens
In this episode, I introduce the architecture lens, its questions, and its goal of modeling architectural domains to manage complexity.
[00:00:00] All about the architecture lens.
[00:00:03] Hello, my name is Eric Normand, and this is my podcast. Welcome.
[00:00:08] So I'm writing a book about executable specifications as a way of doing domain modeling, and I'm organizing the chapters in terms of different perspectives on your code and its context, and each of those perspectives is called a lens.
[00:00:33] And in this episode I'm talking about the architecture lens.
[00:00:38] So every piece of software runs in an architecture. By architecture, I don't mean some technical definition, I just mean a casual way of talking about all the decisions that have been made that you're not gonna change. And these cause some constraints on the way you solve your problem.
[00:01:00] And I don't want to get too deep into what I mean by architecture, that's not the point, but I'll give some examples.
[00:01:07] My application is going to run in the web browser and talk to the server using Ajax. That's an architectural decision. You could of course change it, but I'm gonna say for this, for any problem we're gonna solve, we're not gonna change that. We're just gonna talk about, well, what if that was fixed and what can we do with that?
[00:01:29] Another might be we're using Postgres database. That's an architectural decision. And by architectural, I just mean it's outside the scope of the problem that you're solving.
[00:01:42] Now these architectural decisions do affect your code. Doing Ajax means you have to deal with the complexity of asynchronous communication with the server, with callbacks, Async Await, whatever way you want to do it, you are having to deal with that in a way that you wouldn't, if, for instance, you weren't communicating with the server, say you had all the data you needed loaded into the browser. If you were writing a mobile app and you could use a thread to communicate with the server, it wouldn't be asynchronous. You could block the thread while you're waiting for stuff to download. Okay, so these are constraints now that your system is putting on you because you've made these decisions.
[00:02:35] What each of these examples is showing is that there's actually a way to model what's going on, model the complexity of your architecture as a domain, as a separate layer that you're going to use. So as I've talked about before, I advocate for separating things out into layers, handling your domain logic purely in your language, in memory. Don't use a database, even though your application is gonna have a database, you don't want your domain model to have the database complexity baked into it because your domain model is not about your database. Database is a technical choice, has nothing to do with your domain. Likewise, Ajax has nothing to do with your domain.
[00:03:34] So once you've gotten your domain model, now you need to make it real. You need to say, well, yes, I have the domain model, but it's gonna run in the browser and somehow I need to get some of the data I need to run this domain model, right, to make some decision that my domain model helps me make. I don't have all the data. I need to get it from the server and I need to use Ajax.
[00:04:00] How does this lens help us? Well, one thing we can do is to model out the time. Remember, we've talked about that already, that there's a time lens that every domain model eventually, as it gets more sophisticated, starts to model time. So we can model the time of our Ajax request.
[00:04:26] Let's draw out the sequence diagram. We have two different actors in the sequence diagram. We have the browser and the server. The browser is going to make a request for a certain value. Let's make it concrete. Let's say we need to ask for the current list of add-ins for our coffee shop.
[00:04:52] So the browser says, I don't have this value. I'm going to make a request to the server for the current list of available add-ins. Then the server in the best case is just gonna respond with the list. And now you have the list in the browser. Now if you look at the sequence diagram, if you drew that out, it's like an arrow going to the server and then another arrow coming back from the server sometime later.
[00:05:18] From the browser's perspective, you can see there's actually three times. Those two arrows have divided the timeline into three. You got before the request, during the request, before it's the responses come back, and then after the responses come back. So those three times can become three different states as an alternative.
[00:05:45] What state are we in? We're in one of those three states. Those are your three choices in your alternative. Okay, so however you model alternatives, you can make it.
[00:06:17] In the loaded case there's a gotcha because you actually have a value. So in the loaded case, in the object, if the value of status is loaded, then there's going to be a second key called value that's gonna have whatever the response was from the server. So three states, one of them has a value.
[00:06:43] Now that was the best case. There's also more to model, right? It's more complex than that. What if the HTTP request has an error? If the HTTP request has an error, that's another state, right? That's a different status. And the error usually has a message, something like the server timed out or there was some unknown error. Even that's a message. And so we would have a fourth state. Status error, and then a message and it's a string.
[00:07:17] Before I go into more complexity, you can use this value to project your view, so the unloaded state, you're going to have some GUI element that represents I haven't even started loading yet. Okay. I don't know how you would do that. Sometimes you might just use a spinner just to say there's nothing yet. Because you know you're gonna make the request eventually, so then the loading and the unloaded look the same.
[00:07:50] The user doesn't need to know. Okay, so the loading also has that spinner, the loaded, you can make your list, you know, you modify the dom, you add this list of add-ins. So, The error might show a big X with an error message. Right now that's not great user experience. We could complicate this domain model a little bit more and we can say we that if we get an error, we're going to retry. We're gonna try to get the data again. Maybe we'll wait half a second, one second, and retry. So in that case, we wanna show that we're still, that we're loading, right?
[00:08:41] If we look at the sequence diagram, you know, we make the request. We get an error response and then we're gonna make a request again. And then in the best case, hopefully we get a response back this time.
[00:08:55] So we've actually divided it up into five. We've made four arrows, so we've got five states. So there's this other state of after I've gotten the error, but before I've retried, I'm going to have a state, and then after I've retried and got a successful response. Okay, now you might say, huh, that successful response is the same state as if I'd never got an error and got a successful response. Well, that's, that's a choice you can make and it's probably the choice I would make cuz I don't care about the error. Once I get the value, I'm good. Do you need to distinguish between, I got an error and I haven't retry yet and I got an error and I'm retrying. Now, you might need to, you might need that little bit of information to know whether to make the request again. You might have something that's already started the request and you wanna record that. So you don't make it again.
[00:10:01] This is the kind of thinking that you get into. Is this state the same or different? Is there some use case that needs it to be different? And if so, I'm gonna capture that. I'm gonna come up with a new name for this state and I'm going to capture all the data that I need to about it, you know, as keys and values in this object.
[00:10:27] There's another complication that you can add. What if I want to be able to reload this data? So I make a request, I get a value, and then I have some policy like I need to refresh this every five minutes. So you have a value, but you're also making a new request for a new value.
[00:10:52] What I would do is to have either a different state for this loading but I have a value, or I would make it so that loading has an optional value. Okay? That means the value can be there as a key in the map or not exist in the map. If it exists, it means I already had an old value. I'm looking at new values. But the same thing happens when I've got, when I'm done loading, when I've got a successful response, I'm just gonna go to loaded. Okay, but now I need to know how old that value is. So instead of just having the status and the value, I'm gonna put a time and like a timestamp of when that value was last fetched from the server.
[00:11:49] Now I can check, is this an old value? Okay. Now you gotta keep thinking about all these complicated scenarios, all these use cases. What happens if I get an error while I'm reloading? That's a possibility too. What if I retry three times and I still getting errors? I should probably stop loading or wait a really long time for the next one. There's all these possibilities.
[00:12:17] Those are the kinds of things you're gonna have to sit down and do. Now, what do you do with this? Alright, you've got this model of all the possible states and AJAX requests can be in that are meaningful for your application. And it can hold the value. And that value can be a value from your domain, from your domain model.
[00:12:41] So you've essentially created a way of composing your business domain, pizza or coffee shops, you compose that with the Ajax domain that you just created. They're independent. They don't know anything about each other, but some other layer can put the two together and make an Ajax aware list of add-ins for your coffee.
[00:13:11] We still haven't done the operations on this. Let's say in our final analysis, we've got six different states that we can be in. Six different states, maybe five because you can collapse some with some like optional values. Let's call it five. I like that number. Sounds right. There's five different states that your Ajax request can be in. We've done that analysis.
[00:13:39] There's actually only so many operations that you can do on this data structure.
[00:13:48] One, you can make the request. That will modify what state you're in. You can have a successful request complete. That's another operation. And so that would go from loading to loaded. You can have an error. There's an error in the Ajax request, and so now you need to mark that. It's gonna change your data structure.
[00:14:18] So there's five states and there's three operations. That's it. That's all that you've got. Now you have to figure out what happens to each state for each operation. So that's 15 different cases you have to handle. Okay? Remember, we're trying to make a total function for each of these three operations.
[00:14:44] Each operation has to be total. Request, succeed and error. Those three operations, they have to handle all five states. So there's easy ones. If you're an unloaded and you do request it goes to loading and records, whatever other data needs to be recorded in there, depending on your analysis. If you're in loading and you make another request, you're still in loading, gonna be essentially a no op. If you're in loaded and you do a new request, then what does that mean? Are you going into a reload state? Depends on your model. But you have to figure out what's gonna be next . I would probably just keep it as loaded. I have a value. I'm gonna display that until I get a new value. Finally, if you're an error and you do request, I would probably keep it an error because we don't have a value yet, and so I would just display that error until we resolve the error with a successful request.
[00:15:54] Okay. Now this is all up to you. Like I'm not saying this is how to do it, just saying that these are the kinds of thinking you have to be doing. You could say, well, we could go back to loading. Once we make the new requests, we'll display the error for one second and then go back to a spinner. Right? That's a way to do it. Or you could have another state that represents a retry. And that retry would show the error and a loading spinner to indicate, look, we got an error, but we're, we're gonna try again.
[00:16:26] Remember, part of these perspectives is to get you to see more possibilities. More options, get more information so that you can choose from those options in a better way.
[00:16:41] Okay, so that's the first operation. The second operation is succeed. We got the value. The request came through. Okay, what do we do if we're in unloaded and we succeed? Well, we're just gonna skip loading. Something happened, we missed it, but that's okay. We have a value. We're just gonna jump right to loaded. If we're in loading, we're gonna jump right to loaded. If we're in loaded and we got a new loaded, probably we should replace the value. Stay in loaded, but replace the value. And if we're in error, we're going to go back to loaded. Sounds good. There's other possible ways to do it. You can do the analysis yourself.
[00:17:31] All right. Finally, if we get an error, if we're in unloaded or loading and we get an error, we're going to want to go to the error state, so. If we're in loaded and we get an error, we have a hard choice to make. Do we go to error, or do we say who cares? I already have a value. I'm in loaded. I'm just gonna stay in loaded if we're already in error. So that's a choice you can make. If we're already in error and we get another error, we'll stay in error.
[00:18:03] Okay? So these, this is it, right? This is all we've got. Maybe you could have another operation called retry or re-request something like that. But I tend to want to not complicate it too much. The GUI is gonna get too complicated.
[00:18:21] You don't have to represent all of these states. One use case is what do you wanna show to the user? Especially if there's nothing they can do about an error. Like why are we showing these complicated errors? This complicated setup of, Hey, we're on the seventh retry. Do they care? Right. All they know is, it's not working. There's that question.
[00:18:42] The user probably doesn't want to know so much about what's going on, but what do you need to do your operations correctly, to perform the way you want to perform, to execute what you want to execute? And like I said before, you might need to keep the time that the successful response comes back so that you can know when it gets old and you can reload it. Stuff like that.
[00:19:07] There's no right way to model this. There's only the way that works for the semantics that you need for your application. That's another important thing.
[00:21:03] I replaced the dom, and then the second request comes back. But it timed out. So now it's an error. I replaced the list with the error because there's no logic to look, Hey, do you already have a value? Okay, now I can add that logic, but it's an if statement in this callback that for this one Ajax request.
[00:21:26] It's starting to get complicated again. To get it correct, you have to do all 15 of those cases anyway. You're just gonna do them one at a time. Haphazardly all smeared all around your code. But if you can get it correct in an isolated spot in a module, then you don't have to worry about it. You can just do the analysis. You can do it on paper. Just draw out your sequence diagram and get it correct. Just make sure you handle all the cases. It's basically the same as making the functions total and you're good. It will just work.
[00:22:11] And now all you have to do is focus on what do I display for each of these cases. There's each of these states, we have five states. You would have to figure out, well, which ones have a loading spinner and which ones have a value to show, and how do I show this error? And that's it. You're done with your view. Your view can be very straightforward, doesn't have to have any extra state itself. It can just look at the current state of that Ajax value and it's done.
[00:22:48] Now I'm going to make a leap here and say that you can do this for any architectural constraint that you have, any architectural complexity. You can model it. So you could have a data model that represents a SQL request. What can happen in a SQL request? Maybe you wanna handle the case where it gets zero results back differently from where it gets 10 results.
[00:23:22] You can do that. You can have a different state. Maybe the database times out. It can happen. You get an exception or something, maybe an error. And so you might wanna handle that.
[00:23:33] And you can just go down the list of all your different architectural choices and what complexity they have. And as you notice, hey, this is actually causing a lot of complexity in my code and it's getting smeared out all around, and I kind of know now how I want it to behave to separate that out. And you can make it composable, just like how the Aax value had a value that was part of your business domain. Make it composable.
[00:24:07] All right, this has been the architecture lens. My name is Eric Normand. This is my podcast. Thanks for listening, and as always, rock on!