Crafting Code Podcast

~/podcast

$ cd episodes/059-software-patterns-and-pattern-languages

~/podcast/episodes/059-software-patterns-and-pattern-languages $ ls -1a
. .. episode-summary.txt references.txt themes.txt transcript.txt get-mp3.sh
~/podcast/episodes/059-software-patterns-and-pattern-languages $ cat episode-summary.txt

Software patterns are sort of like recipes which offer solutions for particular problems. Familiarity with patterns allows you to speak at high levels of abstraction, communicating a lot of information without having to dive into specific details. In this episode, your hosts share a bunch of patterns, explaining what each one is for and the consequences of using them. Along the way, we point out places where pattern language gives us insight for choosing which of these solutions we want to use.

~/podcast/episodes/059-software-patterns-and-pattern-languages $ cat references.txt ~/podcast/episodes/059-software-patterns-and-pattern-languages $ cat themes.txt ~/podcast/episodes/059-software-patterns-and-pattern-languages
$ cat transcript.txt

[00:00:16] Allan Stewart: Welcome to the Crafting Code Podcast, where we discuss the importance of doing the right thing at the right time with the right tools. I'm Allan Stewart, a software architect, and lately I've been thinking about the trade-offs between building a new gaming PC from scratch versus renting one in the cloud.

[00:00:32] Dave Adsit: I'm Dave Adsit, a VP of engineering, and recently I've been thinking a lot about team dynamics, collaboration, and the impact on work and process.

[00:00:40] Allan Stewart: Our topic for this episode is software patterns and pattern languages. So to kick things off, there's a question, what is a pattern? I remember being asked this question in one of my classes in college, and I don't think that I really understood it very well. At the time, I had kind of a vague notion that it was like, while you were drawing this, you know, you get the same kind of repeating something. I didn't know.

[00:01:10] Dave Adsit: Yeah, so that's where I would start as well, is that this is a repeating series of something. In software specifically, we talk about patterns being either created or even often just discovered as people build systems over and over and over. They start to see what repeats and then extract it and name it. And when we create a pattern, when we name a pattern, there's a few things that we want to do as part of naming it. Obviously, you want to give it a descriptive name, one of the hardest things in software development. But then also you want to describe the problem that it solves. You want to describe when it's applicable and when it might not be. There's a lot of cases where a pattern, it just isn't applicable at all. You want to talk about. How do you implement this solution? And then you want to talk about any consequences that come from having applied this pattern in this context to this problem.

[00:02:08] Allan Stewart: Right. And therefore, I feel like patterns, one idea that I've kind of glommed onto recently is I like the idea that patterns are more like a recipe than a stencil. Right. Like a stencil I might use and just repeat the same thing over and over and make some, you know, maybe designs or, you know, M3D. a weird visual analogy, I guess. But, but more that, Hey, I want to accomplish something. And, and this pattern is a, is a known way to solve a particular kind of problem. And if I have that

[00:03:01] Dave Adsit: problem, then it might help me. Right. Patterns are more like they're, they are a tool, not a goal. Yeah. Our goal is not to have a lot of patterns in our system. Our goal is to have a clear, concise system and often to communicate it about it. Right. We talk about pattern languages is like we have a collection of patterns that are in use. And so the pattern language is how we talk about the system. And when we have a shared understanding of these patterns, it allows us to talk or communicate at a higher level more quickly and still convey all of the essential complexity of the system at hand. We've talked about that in the past, having worked, together on several systems, we can say, Hey, we're going to apply MVC in the, for the front end of this application, we're going to use workflows and we're going to use repositories. And we're going to do put this in a service, you know, we can talk very quickly and at a high level, but still communicate a lot of the detail that is necessary to actually build a robust computer

[00:04:04] Allan Stewart: system. Right. And if I'm remembering correctly, the idea of pattern languages came out of building architectures. Yes. Somebody, somebody compiled, you know, here are a bunch of things that are common in, in buildings and, and created a way to communicate very quickly about, about those things. And we're doing, we're doing the same kind of thing with software. That's right. So to kind of illustrate how patterns exist and, and, and how we can discuss them in a pattern language, we decided we were going to talk through some patterns. Now there are hundreds, thousands of patterns. There are hundreds of patterns that have been documented. And we're just gonna pick a few out of those many, and we're not going to try and explain UML diagrams in an audio podcast, or you write, like, if you, if one of these patterns sounds like something that you might want to use, you should go and look up the details, because that's going to be a lot better, have a better experience that way. Exactly. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. That's right.

[00:05:34] Dave Adsit: That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. That's right. a thing and that thing must be globally accessible throughout your whole application. This pattern was originally created around the problem of managing hardware interactions, because you usually only have one com port or one keyboard port or whatever. And that, like none of that even makes sense in a modern computer, but that is the original problem. The context of applicability is that you're worried about violating single responsibility you're okay with some static coupling, and you're also worried about multiple things accessing the same physical resource at the same time. So we create as a solution, we'll create a class that has a private constructor and a static accessor. And so once it's been constructed, once it holds onto a reference to itself. And from that point forward, everybody who tries to access this thing goes through that static accessor. And you're therefore ensured that you only ever have one instance of this class in your system. Consequences are there can only be one. And you, by making the constructor private and by accessing it statically, you are making testing more difficult. Because if you're testing a class that utilizes your singleton, well, you're going to have to test with all the behavior of the singleton as well. You can't inject a mock for the singleton because it's accessed statically, etc.

[00:07:09] Allan Stewart: Right. And I think that one of the big problems with singleton really boils down to is it gets used in the wrong context. Because, yeah, maybe on your com port for your desktop, that makes sense. But you can actually have more than one in a computer. Right. And so it just adds all this complexity, it adds this coupling. But it's also one of the easiest to implement. And so it gets pulled out and misapplied quite a bit, right? Like, I've heard multiple stories from different places about being, you know, somebody in a software interview, and they're asked about patterns and singleton is the one that gets brought up, because that's the only one that they know. And, you know, that's right. It's kind of unfortunate. But it does have a place.

[00:08:02] Dave Adsit: And it does have a place. Maybe you are dealing with file access. Like I've built ecom systems where we had to use file access. To control things like the the tax calculation file or the IP to location, right, the IP, geolocating IP addresses, and you really only have one of those, and you should only have one, and it should be shared across processes, and maybe it's okay to use it as a singleton. And there, there are times when it's appropriate. And but more often than not, I think you're right, it mostly gets used incorrectly, because it's easy to understand. And it's the one we know, and it's the one we've heard.

[00:08:59] Allan Stewart: I think that there are a couple other ways that you can deal with it too, right? So the singleton pattern, I feel like doesn't work quite as well as using a singleton lifetime in like a dependency injection scenario. That's right. So then the next one, we have the flyweight pattern. And in this one, the problem is that you have many objects that take up a lot of data. And you can apply this pattern. The context of applicability is when you need quick access to all of these objects at once. There's not a really good way to offload or distribute them. And then the solution is, well, just share data across many instances of a similar thing. So an example is particle effects. There might be a lot of data that is similar across a lot of pieces. Or I mean, basically any time where you can share data across things because you've got, you know, I've got 700 of these objects and 200 of them all have this same data. And 300 have this same data and 50 have that, right? Like you can do that. But one of the consequences is, well, it might increase your CPU usage. The code will definitely be more complicated because you have to be careful now. Because anything that you touch, that is shared, if you make a change, you might affect a lot of instances.

[00:10:29] Dave Adsit: Yeah. And I don't think I've actually ever used the flyweight in a production environment. I always associate it with video game development, which I have not done. And so there may be times when this would be the right pattern for me. And I've just been ignorant of that fact. So kind of related to the flyweight is, but different in many ways is the object pool. And this is you have, uh, in for the object pool. Yeah. Yeah. So the problem that you're facing is that you have a lot of expensive to create objects, for example, database connections, HTTP connections, uh, and you are only using them for a short period of time in the current request. Um, but because they're so expensive, creating them each time they need to be used is going to slow down the performance of the application. So one of the things that you can do then is create a object, another object that manages a collection of these and keeps them around for longer than their actual use. And so, uh, your database connection pool is an object pool. You ask it for a connection. It's not going to create a new connection to the database. It's going to hand you a connection that it is already negotiated with that database. Right. And then when you are done with it, you release it back to the pool and the next person can grab it. Uh, so the consequences are the. The pool. The connections are reused multiple times or the objects in the pool get used for multiple reasons. Uh, so you may have positive consequences and that it gets. Your code gets faster, but you have negative consequences in that you have to manage another object that is complex. You have to be very cautious about the life cycle of these objects that none of the users put them in a bad state, right? If, for example, with database connections, I don't want somebody to close a connection and then hand it back to me, because now I've got a dead connection in my pool. Right. So I want you to release it to me. I don't want you to close it. Uh, I also don't want you to hand it back to me when it's executing a very long running query. That's going to take minutes or hours to complete. Um, so you have to be careful about not leaking anything between the users and any information or, or, um, you know, behavior, things that are happening. Um, and you have to be careful that you. Manage the right size pool for the objects that use creating too many of them, of course, is perform, uh, is unperformant and uses too much memory, too much CPU, not creating enough of them. You don't get the benefit of having a connection pool or an object pool to begin with.

[00:13:11] Allan Stewart: Right. You can get back to some of the singleton problems at that point, essentially.

[00:13:15] Dave Adsit: Mm-hmm.

[00:13:15] Allan Stewart: Um, this is another good example where. Because this is a pattern and it can be generally applied. There are various places you can do it, right? So database connection pooling is, is a great example. And a lot of people who do web development or, um, or API like backend development will be familiar with it. But, um, you, you mentioned video games with the flyweight pattern. Well, the object pool pattern can get used a lot in, um, in games as well. Right. So like if you have a bunch of things, you know, exploding on the screen and you're reusing some of the explosion. Things. So you don't have to. Create new ones every time. But if you're not careful, you could have some bad behavior or, or leak information. If you've got, you know, too many things that, that are interacting there, it's like, oh, you, you didn't reset that object correctly. And now it's got the wrong Sprite. Well, you're gonna have a bad time. Um, but my point is here that you can reuse this same pattern in very, very different contexts and what that looks like and like how, how it manifests might look very different. But it's the. Same underlying pattern. Yeah.

[00:14:25] Dave Adsit: And I think that that speaks to the overall generality of patterns is that they have many areas of applicability. Right. In different types of systems.

[00:14:33] Allan Stewart: So our next pattern is strategy. So with the strategy pattern, uh, you use this when you've got a problem that looks like you've got functionality that has multiple variations. They're each similar in a lot of ways, but they differ in significant ways. And trying to have them handled all. Together. Is just overly complex, right? It's too hard to maintain. So some examples for this might be, uh, getting directions for like, are you going to drive? Are you going to walk? Are you going to take public transit? Are you going to take an airplane? Essentially it's the same problem I want to get from here to there, but how you go about answering those questions is very different or payment methods. I want to pay something. Right. Like I just, I just want to pay and now own this. Thing that I'm buying online, but is it a credit card? Is it a debit card? Is it ACH? Uh, are you actually painting cash or check? So when those variations are similar enough, then we hit our context of applicability. We can, we can say, Hey, we're going to treat these the same at a high level of abstraction. And then our solution is, well, we're going to create an interface, an abstract interface. That covers the, the, that very high level of abstraction, those primary use cases. And then there's a separate implementation for every variation. So, uh, I've got the credit card processing implementation and I've got the ACH one separately, or I've got my, you know, driving versus public transit. Separate, separate classes that can be built and maintained separately. They just have to follow one common interface. And then you can swap. Them out, depending on your needs. Sometimes I've even seen cases where, where you use multiple strategies at the same time, and then you can compare and say, Hey, well, if you did it like this, or if you did it like that, you can give that kind of data back to the user.

[00:16:37] Dave Adsit: Yeah. And I would say there's other, a couple of other consequences there. Like we talked about, you have to manage all those strategies. Uh, one of the positive consequences is, uh, SRP. Like each of the implementations is a lot cleaner because it doesn't have to consider all of. The others, um, except through the interface and you, but you might also have to create a factory to generate the one that you need in this case. And then you have to track which strategy did we use for this, et cetera, et cetera. Right. So there's, there's, there's trade-offs there. Um, but overall, this is a very powerful and popular common use pattern across many software systems that I've worked on. Yeah. A related pattern that we've talked about. Yeah. Is the template method pattern. The template method pattern is a little bit more constrained than the strategy pattern. The template method pattern is we, for a case when you have a single algorithm that you have to use in different cases. Uh, one of the things, one of the examples that comes to mind for me is a sort algorithm. Uh, but I want to sort. Strings and integers and people and businesses. Uh, you know, I. I can sort. Sorting is sorting is sorting, right? There's like, there's a bunch of different sort algorithms, but I just need them to be sorted. One of the challenges that I have is how do I sort people? How do I sort my customers? What does it mean to sort a customer? Am I sorting by height or tenure with the company or amount of money they've spent with us or whatever? I don't know. So I, I, I'm going to have my basic sort algorithm, but then I'm going to have to implement parts of that algorithm differently based on what I'm doing. And so to implement the template method pattern, you create your base class. Maybe it's an abstract base class, and it has the. Default algorithm that you're going to use for everything. And then you inherit from it and override specific aspects of that algorithm. So in the case of sorting, you might override the compare. How the compare method and what that allows you to do then is. Create customized implementations from that template for each of the types that you're, you are doing that operation on it, performing that algorithm against. And you can do that through private or protected member classes. They're not exposed to everybody. You don't make what sort, you don't have to make the, uh, uh, the internals of the algorithm public. You access. Breaking encapsulation. Inheritance. Yeah. You don't want to break your, your encapsulation. So the solution, the template method pattern solution relies on inheritance. And so one of the consequences is that you had to use inheritance. Usually that's something you and I shy away from, uh, if we can. And of course there are ways to implement a template method pattern that do not necessarily rely on inheritance. If your language supports function pointers or Lambda functions, like, you know, ad hoc functions. Define in the, in the moment, you can pass those in to the constructor of the class at runtime or at, you know, as you are building out as you're implementing the code, but the template method pattern can be a very powerful tool in your tool belt. When you have to operate on. Many different types of things, but do kind of the same overall job to them. Yeah. Languages with templating or macros get away. Yeah. With doing these in a slightly different way, but, but if you're comparing a bunch of objects, very rarely do you want to compare the address of the pointers? That's usually not the most useful. That might in fact be the least useful thing that you could do with them in many cases. And so you want to be able to compare. Based on what the actual object is. And so the template method pattern is your friend.

[00:20:42] Allan Stewart: So this highlights another thing for me about patterns is that there are some patterns that are, that are similar. And so you can. Kind of. Evaluate between multiple patterns. Some patterns are very different. Singleton versus template method or object pool versus template method. Like they're not even in the same category of, of problem. Uh, why, why you're approaching them, but strategy and template method are, and then it becomes a different kind of question, right? So if you use strategy, well, you've got this interface, but no, no default implementation. With the template method, it gives you more of a skeleton and oftentimes. It has those defaults built right into it. And, and so it's like, oh, well, I don't have to create an entirely new thing. And depending on your problem, depending on your specific use case, one of these patterns might be a better fit than the other, or they might be interchangeable and it kind of doesn't matter. And it becomes a preference thing. Yeah. And, and as you, as you build up your skill and talking about patterns and pattern language, then, uh, it gives you more ability to kind of very quickly discuss. Yeah. And so that's a very different implementation that might be interchangeable.

[00:21:55] Dave Adsit: Yeah. And that that's true. And, and there may be some heuristics to help you help guide you. Like maybe you think if there's more difference between the things than similarity, then I would use strategy. So I know that I want to charge in an e-commerce situation. I want to charge PayPal. I want to, or I want to use PayPal to charge this customer, or I want to use Stripe to charge this customer. The APIs are going to be very different. And so maybe the interfaces. I just want to charge a customer. Uh, and maybe for a template method pattern, if we're thinking I want to sort well implementing, I don't know, proxmap sort or quick sort or merge sort or whatever. I want, I only want to do that once, cause I am likely to get it wrong, but I, I know that I need to sometimes sort my customers based on. Uh, how long they've been with our company, their tenure with us. And sometimes I want to sort them based on, uh, how much total. Total money they've spent with us because I'm doing different types of things with them in the inner, in the, you know, in the application. And so when it comes down to it, those are, you could create those two customer sorts. Very, very, very, very easily and quickly with very little code that has any chance for, for problems to enter into it. Uh, if you're using the template method, because most of the sort algorithm is already there and ready to go. And all I have to do. Is copy my customer by tenure sort class, make a new customer by total lifetime value. Uh, class and change which field on the customer I'm comparing. And so there's very little code to implement that new sort and the code is very, very focused on its purpose. So again, it depends. It depends on your context and what you're trying to. Accomplish, but patterns can either in either of those cases, using one of the patterns is going to accelerate your development. It's going to make you more productive and make you help you create code. That's easier to reason about and cleaner to work on.

[00:24:06] Allan Stewart: The next pattern we want to talk about is a model view controller. This pattern, there are a couple variations of it. I'm going to discuss kind of the, the original that is not used in API controllers. And the problem that you're having when you reach for model view controller is that you've got a user interface and it has a complex set of interactions. And your existing code is becoming more and more difficult to maintain with this complexity. The context of applicability is that you want to separate. Concerns between the data model, the presentation and the interactivity. Generally, this gets used when you have an in-memory data set. Uh, it's particularly useful for things like, uh, we're going to manipulate objects on a screen, right? Like we've got like a, maybe a drawing program, for example, or like vectors and things like that, where I can, I can click on an object and I can grab the handles and change the curve or reposition on, on the screen or things like that. Uh, although it can be used in a lot of other situations as well. And the solution is that you create a model class. That just represents your. Abstract data. Then you have a view class, which just takes the current state of the model and draws it, renders it. And then finally you have a controller which accepts user input from the view and acts accordingly on the model. So for example, if the view has a button, it will emit an event. This button was clicked and the controller is the one that will go and look at that and say, oh, well, according to the model, it says that that is currently disabled. And so. Tough. You don't get to work or an error message is going to appear or something. But if the model says it's okay, then it's going to take action on that based on what's going on. Right. And so the controller is making those decisions and saying, oh, well, they held down the shift key. And so therefore we're going to do something different than if it, if they hadn't been holding down the shift key and the model doesn't, doesn't keep track of that. It just says, this is the current state. One of the consequences of this is it makes testing. The three pieces easier. You've gotten rid of a lot of the complexity where they were all interconnected. When I would have used MVC in the past, it's often been in cases where there was kind of just like this massive God class that had way too much knowledge, way too much stuff in there. And the concerns weren't very separated. Um, so model view controller can be, can be really nice there. But one of the negative consequences is that you've added. Some additional complexity that was not necessary. One of the things I think is neat with a model view controller is that if you do it with the right level of abstraction, you can swap out any of the three pieces to get different kinds of variations. When I was working on educational software in the past, we would use this pattern to be able to build different kinds of interactive experiences for helping teach something. Right. So it could be as simple as something like a. A drag and drop. So I'd have different views. That do different things with drag and drop. Right. So, um, they might have various differences and, and, and usually they're just graphical kinds of differences. But the, but the controller might have some very different rules to it. Like, oh, well, if this is a grouping controller that allows you to group multiple things together and you're, you're dragging and dropping, and this other one says, no, you have to have one space for every single. Item. And it made it really easy to switch between those things.

[00:27:54] Dave Adsit: Yeah. I've used variations of the model view controller in several applications for good effect. When I was doing early asp.net web development, we were using the model view presenter pattern very closely related to model view controller, but focused more on abstracting the complexities of rendering things to HTML. So. The business logic of your system did not have to be concerned with generating proper, properly formatted HTML. You could separate that concern behind the presenter layer. Right. Later. I, I did systems that use the model view view model pattern, which MVVM is also very related to model view controller, but has some nuance to it as well. So there's a lot of these patterns that are again, related to each other, solving similar. Problems. And it's. It can be helpful for communicating to understand the nuances of the different patterns in the space where you are working. Right.

[00:28:58] Allan Stewart: And I think that this also highlights something about pattern language too, is that there can be significant drift. I work in. Net and a and. Net core. If you talk about MVC, then you're probably talking about an API framework. That uses a version of model view. But it has a very different shape. The classic. MVC has kind of a triangular shape. If you were to diagram it out where the model, the view and the controller. Are in a triangle contributors.

[00:29:31] Dave Adsit: Yeah.

[00:29:32] Allan Stewart: But in, in the web. And MVC looks more linear. The view talks to the controller. The controller. Talks to the model. So as you're thinking about patterns, as you're looking at them, there can be drift and it can, it can be different in different environments. So if you. Talk to a. Net programmer. About MVC. They might not be thinking the same thing as if you talked to say a graphics programmer. And so you do have to be a little bit careful. And, and especially because there's no, like there's no authority. Yes, this is, this is the, you know, one true registrar of patterns. And so. Some I've had, I've had experiences where. I've talked to cross purposes with people because. We were not thinking about the same thing. We were using the same word. But we were meaning something very different.

[00:30:22] Dave Adsit: Yeah. You can be certain that your. Net programmer and your react developer are not using MVC the same way. So the next pattern in our list is the repository pattern. Which is honestly a special case of the adapter. Which we didn't talk about in detail, but I think you'll get the, the gist of the adapter. If you're not familiar, just from the discussion of repository. Basically. The repository pattern. The problem that you face is that you have many places in code that need to know how to load or save an entity. And that can lead to important differences in. Probably. Spread database details across the whole code base. If they were all to access the database directly. So that's the problem. You want to. Centralize access to the database behind one interface. You want to simplify database access in a way that we're making sure that we're doing it correctly. We're not doing it all the right way everywhere. And so the repository is a good pattern to use in. To solve that problem. So. It is. The context of applicability is that you have. An effective way to share a single repository across all the places where you need it to be used. For example, you have a dependency injection controller, or you have some kind of a common factory pattern that you're using throughout your code base. So you can use the same repository everywhere. And. In your, you have a, an, a well understood domain. Right? Like we talk about domain languages. We talk about. The. Domain driven design, the concepts there around entities. That, that comes into play when you're building repositories, you want to make sure that you have well understood entities that you're using, as opposed to just like ad hoc querying the database all over the place. You know, I want to load a customer. I want to load a client. I want to load. A job. And so I want to work with the job. I'm going to load it, modify it and save it. So the solution is that you create an. An object that represents the interface that has the interface that represents loading and saving and querying. These objects. And you put that behind an abstraction. So. When we change how we interact with the database. For example, we don't have to change everywhere in the code. That. Loads or saves these objects. You and I have used this. Well, let's talk about consequences then that application, right? The consequences is you have to create these abstractions. And. You have to use them everywhere. Or you lose a lot of the benefit of having used a built a repository. Right? If we say, Hey, all the data access goes through the repository for this object. Oh, except for these three places that are different. Now I have. I, when I make a change, I have to change the repository and then I have to change these other three places where I didn't use it. There are additional challenges when you're using repositories. For example, if you need to do things that are transactional. I want to save this user and this job. But I only want them both to save if they both save. Like if there's something goes wrong in saving the job, don't make an update to the user because it references the job that doesn't exist. And if something goes wrong in saving the user, don't make updates to the job because it'll reference a user that's in the wrong state. Right? So transactions get harder when you're using repositories. And other consequences around using repositories is that for good or ill, you have to clarify your data model a little bit more than you may be used to. And. That can lead to some interesting discussions.

[00:34:11] Allan Stewart: Personally, I like the fact that when you're using the repository pattern, it can put some boundaries up in your data model. It's a consequence, right? Like for me, I prefer it when one of the things that we do is inside of our repositories, we limit how much joining we allow. Like where, where do you allow joining? And this is partly because I really like the version. Speaking of, you know, this is another pattern that has a lot of versions. I like the domain driven design concept where the repository is representing, hey, this is a collection of this kind of object. This is how you interact with these kinds of objects rather than a like very generic version. You could build a very, very generic version of a repository that basically can access anything in the database. Right. And at that point, it's kind of more, almost more like an ORM. Yeah. But, but I, I prefer the version where every repository has bespoke methods based on domain needs. And I like to limit when, when you're writing some SQL inside of this repository, hey, guess what? You don't get to access just any old table. That does create some boundaries and it can make some things more difficult because you have to start joining data in memory instead of joining in the database. But it also reduces the data coupling, allowing us to make changes in, in both. Yeah. In different parts of the system more, more easily, more readily.

[00:35:41] Dave Adsit: That's right. Well, and also that has an additional consequence downstream where we may be used to using joins in the database. For example, if we say we've got those, that same collection of users and jobs, if I want to load the, if, if I'm using an ORM, I might say load the user with the jobs. And then it'll just do all the joining and all the magic for me. Yeah. If I'm using repositories, I may say load my user and now load all the jobs for that user ID. Okay. Now that's multiple interactions with the database. And now I have to maybe put those together in memory. Like you said, I'm joining those together in memory and then I'm doing work downstream. However, one of the things, one of the perhaps unintended consequences is that I have reduced the overall query load on my database. Instead of having complicated joins going to the database that may take time to execute. It may lead to table locking, may just mean I have to scale the database sooner. Now I'm doing that in my application tier on the application servers where things may actually, it may be actually easier and cheaper to scale horizontally at the application tier than it is to scale vertically at the database tier. And so those are some of the constraints that I think about. Some of the things that I think about long-term as applications scale large. Things that you have to do is maybe reduce the load. Maybe reduce some of that load on the database and then handle that somewhere else. But again, that means you're moving the complexity. You're not reducing, you're not eliminating it. You're just moving it.

[00:37:12] Allan Stewart: Yeah.

[00:37:13] Dave Adsit: One of the other things I think about with the repository is repository just means place to store stuff. And so repositories can be an interface over original repositories were interface over memory collections that were not necessarily even persisted to desk. Right. Early in, in small talk, everything was held in your image. Which was presumably in memory. So if you are doing a repository, your repository can be an in-memory repository. It can be a relational database repository, a document repository. It can be a file, file access. All of any of those are possible. And it doesn't matter which one they are because you've put it behind an abstraction. Right. You and I have worked on systems where we implemented a repository to store objects in one database. And then later decided for some. Scaling needs. We needed to move to a different type of database for that data. And we've used the repository in combination with the decorator pattern, which we probably won't get into. To create a new repository for the new database. And then a repository that took the old database repository and the new database repository and some config and decided how to load and save data from each database. You know, we would say, Hey, for a while, we're going to. Save to both. And load from the old one. And then compare what we load from the new one to see if it's the same. And if it's the same for a while. Now we're going to save to both. And then use the new one for, for it's as the source of truth. But still keep saving to the old one in case we screwed something up. And then eventually we just stop writing to the old database. We just do a one, one time load save for every object in the table. And then now everything's in the new database. We're happy with that. And we had a very seamless migration from an old data store to a new data store without any customer downtime, without anyone even knowing or being aware that we were doing this kind of dual right type of a scenario. And we've used that to migrate fairly large data, data sources, data stores from one data database technology to another. And it was enabled by the fact that we were using the repository pattern consistently throughout. And we ended up for each of those objects. We ended up with three repositories, old database repository, new database repository, and migrating repository. And they all had the same interface.

[00:39:43] Allan Stewart: Yeah. I've also used it to just to version tables. Sometimes you get to a point where you say, you know what, this table structure just isn't working for me anymore. And maybe it's got some old columns in there that we want to drop. Maybe we want to rename some things. And so as long as everybody cares about the repository. Yeah. And it's like, hey, give me this object. I don't know. I don't care how you get it. I don't care where it was stored. Then that makes that possible. Right. Your example also illustrates ways that patterns can combine and like nest together. Repository did a lot of the work there. But there was also decorators that we use. There's also proxies. There's also like parallel run patterns going on there. So, yeah. Threading patterns.

[00:40:28] Dave Adsit: Yeah.

[00:40:29] Allan Stewart: It just illustrates that there's a lot of utility in these patterns. The next pattern that we're going to talk about, command query separation. In this case, you've got a problem that in your code, the act of getting information changes the underlying data or causes some kind of side effect, like maybe an email getting triggered. So the context for applying this pattern is that the changes or side effects that are happening are not important or essential. You're creating a separation between how you retrieve data and how you make changes to data, which includes any of those effects. It's the difference between queries and commands, or commands are sometimes called mutations. Queries must never change the underlying state. They can't have side effects. And so you can query all day long. You can say, hey, I know that if I make this query, it's not going to cause any bad thing to happen. If I call this method, it is safe. But if I call a command, then I have to know that I mean it because it's going to do something. And then the consequences of using this is that it can oftentimes make your code a lot more clear. You can understand where your data is being passively read versus where the changes are occurring. If you take this to the extreme, you can have a consequence that it is super inconvenient. You don't have to have a stack.pop method. You can have peak. And whatever the equivalent of pop that doesn't return something. But yeah, the original concept held that a command can't even return data. I oftentimes write commands or what I call commands as a version of this pattern that do return data. But it's not data like query kind of data. It's usually like result kind of data. The command was create a user. Okay, well, we're going to change state here. And by the way, you might want to know here was the new user ID. Or even here's the entire user object because I'm going to return some data just for making it useful.

[00:42:57] Dave Adsit: Well, and you see this pattern used all over the place. If you do anything with web development, then you know that people will sometimes complain about semantic HTML. You know, get in HTML is intended to be a query. It's not supposed to be mutating things on the server. It's just supposed to be returning them, which is one of the reasons why you can have default caching semantics around. And a lot of browsers and intermediate hardware, like a network appliances will do things with gets differently than posts or puts posts and puts being your commands and get being your queries. And that goes into tools like the tan stack and react is doing similar things with queries and mutators, right? When we are mutating data, we are doing. We are being intentional about the fact that this is a mutation or this is a command. It's doing something. It has side effects. Those side effects might be writing something to disk. They might be all kinds of other side effects. And so we see this pattern all over the place. And when we follow it, well, our lives get easier. And when we follow it poorly, things get more complicated, right? If you are not doing semantic HTTP. If you are not respecting. The verbs, then things get a lot harder. You're like, okay, I, if, if I treat, if I were, for example, to treat every single HTTP request as a post. Now I have to interrogate the post to determine, is this post really a get, is it really a put? Is it really a patch? Is it really. An options. I don't know. I don't know what this post is. Everything's a post. And so I have to do extra work. And so if you were working in a system where everything were a post, you'd be losing out on a lot of the semantics of HTTP. That we've developed over time in our industry. Yeah.

[00:44:54] Allan Stewart: And going against the grain of the pattern can be difficult, right?

[00:44:59] Dave Adsit: Okay. So the next pattern that we want to talk about is ports and adapters, which is from the sometimes called the hexagonal architecture. It's more of an architectural system level pattern where the problem is you have user interfaces, you have a. Services that you're using, you have repositories or databases that you're using, you've got all kinds of business logic that's become intertwined with interacting with these outside systems. And it's made things hard and you want to simplify. And so the context of applicability is that you want to separate your core business logic from interacting with external components, including UIs and APIs and data stores and network and who knows what. And sometimes you even want to maybe swap out some of those things. We talked about that with repositories. You might want to swap one database for another. And so the solution is that you create abstract interfaces, which are the ports that your application can know about and use to complete its work. And then you create implementations, which are the adapters that provide specific implementations for databases, user interfaces, et cetera. And so the consequence is that you now have the capability to swap out implementations and be able to use your core to build your core business logic in new ways. Like you can, your core business logic, now that it's separated, it gives you more flexibility. It becomes easier to maintain, easier to support because it doesn't have all of these unnecessary complexities baked into it. We've pushed those out behind an interface. And one of the, one of the other consequences is like, now we have more things to maintain. There's more pieces, right? We go back to the four rules of simple design. One of them is we use the fewest number of pieces. Well, pell pell

[00:47:17] Allan Stewart: There are a few different ways that you can think about this as you look at domain-driven design, but ultimately it is kind of a pattern. It might not have a UML diagram to it, but it is definitely a pattern that we can talk about. And the problem that you have when you reach for bounded context is that you've got a large complex system that is difficult to maintain and there are multiple subsystems and it's challenging. And so where does this apply? Well, this could apply if you've got multiple teams that are working on the same monolithic code base, or you've got multiple teams that are working on separate services like microservices in the Sam Newman style. The solution is that you create boundaries between the parts of the system. You don't share databases. You don't share models. Inside each bounded context, you have a ubiquitous language that says when we're talking about some entity, when we're talking about addresses or users or invoices or whatever it is, we know exactly what that means. We're ubiquitous in our use of language inside the bounded context. And then you've got these anti-corruption layers

[00:48:28] Dave Adsit: in between the contexts. Right. If I have an invoice and you have an invoice and I hand you my invoice, your anti-corruption layer is going to take the relevant fields off of my invoice and convert them to the relevant fields of your invoice. Right. So that you're always talking in terms of the Allan invoice, even if I'm talking in terms of the Dave invoice.

[00:48:51] Allan Stewart: Right. The consequence is that every bounded context becomes a little bit easier because everything is done in a particular way. Here in my Allan bounded context, I know exactly what we're talking about all the time because we're always talking about it in terms of the ubiquitous language of my context. And that helps teams be able to move faster. There's less If you have divided up your bounded contexts well, but the overall system is going to be more complex, more difficult to reason about. Oftentimes they have event characteristics to them. So like eventual consistency that you have to consider and worry about. And so it's a trade off. Are you going to allow this higher total complexity in order to get simplicity at the decision that you're making there with, with a bounded context?

[00:49:48] Dave Adsit: Well, one of the things I think about a lot with the bounded context is that higher global complexity that people will try to push that down. And again, that comes with trade-offs as well. One of them is that you have to do some kind of enterprise data modeling exercise, which I've been part of many times. I've, I've seen many of those started and I've never seen one successfully complete. So for me, I don't, I don't even bother. I want to go straight to like, instead of trying to define what the canonical invoice is that we all have to use, because you have different reasons for having an invoice than I do and different purposes. And so for me, in order to implement bounded context, one of the things you're going to have to give up is that concept of enterprise data modeling, where we're all going to share the same objects. And that feels ineffective and inefficient often, but it actually is something that enables our teams to deliver faster by having a narrower scope that each

[00:50:45] Allan Stewart: And in some ways it's more realistic too. In your example, you said the Allan version of an invoice versus the Dave version of an invoice. And right. Like when, when we're talking amongst each other as humans, I'm sure I'm certain that we have different things that come to mind when we say words like invoice or user or account. And that's okay. Like, even if we're working on the same system as humans, we can figure that out and bounded context kind of represent that. That's right.

[00:51:12] Dave Adsit: So all of these patterns that we've talked about so far have been patterns that you use in your production application for building your, you know, your enterprise grade software systems or whatever. But not all patterns are used in the production code. There are actually quite a few useful patterns in testing as well. And so I'd like to talk about a couple of those. The first one is the object mother, which at its, at its heart, at its core, the object mother is the object mother is a factory, but used for testing. So what's the problem you have done domain driven design or some other object or object modeling, and you have objects that you use throughout your system, but you need to create them inside tests to use them in tests. And maybe the object has a lot of data or there's different, different shapes to the data that you want to use. And so an object mother is a way for you to abstract the creation. Of test data into a single location. So like I said, the context where you would use it is setting up data or setting up objects for testing has become complicated and onerous. Maybe there's like 15 or 20 lines and most of them are incidental to the test, not in, not a core component of the test. And so maybe what we want to do then is abstract that away. And so we'll create a factory type object called an object mother that can create objects of different types while still supporting the, the randomness that you want for high quality tests, right? So you might have an address mother and the address mother might have methods like create valid us address, create valid UK address, create address that is invalid per UPS, USPS. Address validation API. You know, anytime you need an invalid address validating that it doesn't work with the USPS API could be complicated, but if you can put all of that, you can abstract all of that complexity behind an interface. You can leverage, you know, that SRP, right? We're going to have a single place that has this responsibility. It's going to, we're going to do it once. We're going to, we're going to simplify our test by saying, Hey, this is the type of thing I need. I don't know what it looks like and I don't care. Right? So that object mother is. Is doing some of the heavy lifting on that. So what happens is as a consequence, our tests become easier to read because the setup code has been abstracted away and the tests become easier to maintain because when rules for an object creation change, we only have to do it in one place. So, um, on the other hand, you might be ignoring something important that the tests are telling you about your code, namely that it is too hard to set up objects in order to test them properly. Right? So that's a, that is a potential downside to using this pattern is that you're ignoring some feedback that you're getting from the system. I, however, have used this to great benefit. I have found that if you say, Hey, I want an address and I don't care about the address. I'm like, it just, I want a valid us address. Great. Now I have that for my test, but I want a valid us address in Ohio. That might be more specific. And now I can say, okay, the, this, the state is Ohio. The. You know, the city is Columbus. The says is Ohio and the zip is whatever a valid zip for that place is. Now I've got something that I can use in my testing. And I know because I use the object mother as the base, and then I modified a few fields. Okay. So this is specifically, this is relevant in this test. If it weren't relevant in this test, I would have just used the one produced by the mother with no modifications. And so again, it helps your tests be more communicative about what they are trying to. Valid. Yeah.

[00:55:12] Allan Stewart: And how, how complex the mother is can also depend on your language. So when I do it in C-sharp, it looks different than when I do it in JavaScript or TypeScript. In TypeScript, I often will have only a few variations and because it's really easy to just splat in changes to an object. You just do it on the fly for the test rather than having a lot of named tests. The other testing pattern that we wanted to talk about was the page object pattern. Okay. This pattern helps with the problem that UI tests are hard to read and maintain. They're often brittle. And so our solution is to separate the page interactions from the test plan. So when you write a test, it's going to read like a bunch of instructions that you would give to a human tester. All the details of how to do each step are behind an adapter. So we can say things like we want to go to the sign in page and sign in. Rather than dealing with all of the details of how do you get to this page? How do we know when this page has loaded? Where is the sign in button on the page? Do you have to click on a thing that opens up and then you can sign in or is it just right there in the middle of the page? That level of detail is abstracted away.

[00:56:33] Dave Adsit: Well, and common things that you would have to deal with. And a lot of like. Wet testing is. A. Do I access this field by ID or by CSS class? How do I even get a handle on the text box where the user ID goes? So then I can do what? Set its text, type into it. There's various interactions that we can do there and they change over time. But by separating these two concepts of here's what I'm actually validating and how the test reads from here is how I actually have to do the nitty gritty interaction with the web page through the browser. When either of those change, it becomes a lot easier to maintain those tests and they can become more useful over time.

[00:57:16] Allan Stewart: Right. And so the context of applicability here is really around, well, what kind of things are If you are testing things that act, look and act like pages or screens, this might be really, really good. If you're trying to write unit tests, this is probably not the right pattern. To be using because it's kind of baked in the name, right? It's like, this is the page objects. There's some idea of pages or screens going on here. And the, you know, the consequence is that you can write better tests that are less brittle at the expense of having this additional complexity in the structure of the tests.

[00:57:52] Dave Adsit: That's right. So that's a very small subset of the total number of patterns that are available in our ecosystem, right? In software development. Very small. Instead of the beginning hundreds, if not thousands of named and identified patterns that can be used. But I just wanted to recap the fact that by having a shared vocabulary around these patterns, we can talk to each other more quickly and more efficiently say, Hey, something's wrong with this object mother versus, Hey, in this test and this test and this test, there's a problem where this is being constructed slightly wrong and blah, blah, blah. And like, you know, you can get, you can communicate a lot of detail very quickly by having a shared knowledge of patterns. I feel like it's allowed us to have, to build better systems by being able to talk about them at a higher level of abstraction.

[00:58:43] Allan Stewart: Yeah, absolutely. And I think that the patterns can also teach you as you go and investigate patterns, ones you haven't used, you might discover different ways of thinking about code that you hadn't tried before. It's like, Oh, I never thought to use this solution to that kind of a problem. And it might give you. A new tool in your toolbox that you hadn't used before.

[00:59:05] Dave Adsit: So one of the things, one of the warning signs or warnings when people get into interested in patterns is don't overdo it. You don't need to apply every pattern from the gang of four book. You don't need to apply every pattern from the headfirst design patterns book in every system that you build. Like we still value simplicity. And when a pattern helps us create code that is more readable, more maintainable and more, you know, overall simple, that is a good application of a pattern. And when the pattern creates unnecessary complexity, then it's a good time to refactor. Right? So there's a bunch of books that I recommend. Obviously you want to go read the original book on software patterns by the gang of four. Headfirst design patterns is a great one for getting started. Refactoring to patterns is a great intersection of the concepts of we have code and we have patterns and our code doesn't follow those patterns, but we sure would like it to. code that you use for the benefits that it could give us if it did. Right? So I would recommend all of those as, as places to start and also go look up. There is a implementation of hello world that uses all 23 of the gang of four patterns. And it's almost unintelligible. You, you almost can't follow it. It's, it's that complex. And so it's a good reminder that. The goal of patterns is not to create additional complexity. The goal of patterns is to actually help us communicate about the software we're building and do it in a consistent way.

[01:00:36] Allan Stewart: You could definitely identify or discover your own patterns and your code base. And they might be just something that is specific to your company and the way you write code. There's really no restriction with coming up with your own patterns. I think what makes a good pattern is that other people decide that they are good and memorable enough that they reuse them. But it's also a warning that you need to be a little bit careful when looking online at patterns, because not everybody thinks about patterns the same way. Not everybody holds to the same rules about them. Regardless, they can be a very valuable tool for you. And as you learn about new patterns, it can expand your ability to communicate complex ideas with other members of your team or other software developers.

~/podcast/episodes/059-software-patterns-and-pattern-languages $ cat ../../copyright.txt

Copyright © 2026 - Crafting Code Podcast