Crafting Code Podcast
$ cd episodes/022-fending-off-frameworks
~/podcast/episodes/022-fending-off-frameworks $ ls -1a ~/podcast/episodes/022-fending-off-frameworks $ cat episode-summary.txtSoftware frameworks are so useful we can hardly imagine working without them. Yet they have an insidious tendency to invade, corrupt, and wrest control away from us. In this episode, your hosts discuss the good and bad of frameworks then offer some suggestions for keeping them at arm's length.
~/podcast/episodes/022-fending-off-frameworks $ cat references.txt- Cobra. Steve Francia.
$ cat transcript.txt
[00:00:16] Allan Stewart: Welcome to episode 22 of 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 product management concepts I think are most important.
[00:00:34] Dave Adsit: I'm Dave Adsit, VP of Engineering, and recently I've been thinking about the annual budgeting process in an agile organization.
[00:00:44] Matt Baker: My name's Matt. These days I'm a startup coder. Lately I've been thinking about LISP, specifically in LISP, linear recursive processes versus iterative processes.
[00:00:56] Allan Stewart: Our episode topic today is fending off frameworks. Dave, what is this? What are we talking about today?
[00:01:03] Dave Adsit: So I think you're right that we should start with a definition. And I think the easiest definition for a framework is code that calls your code instead of your code calling it. So a library is code that you call that somebody else wrote, and a framework is code that calls into you that somebody else wrote. I think it might be helpful to use a couple of examples. Some that I've used are Rails. Rails is a framework that helps manage the whole HTTP stack in Ruby and calls into your code to do certain things. ASP.NET Web Forms is code that manages the whole HTTP lifecycle in .NET and calls into your code through a series of event handlers. A lot of what a framework does often looks like and feels like magic because you have to get things in the right place with the right name and you have to use special strings here and there and everywhere. And then when you do, it works like magic. And when it doesn't, it feels like you can't understand why. At a high level, that's kind of what frameworks end up being.
[00:02:19] Allan Stewart: And they're very useful. You want to use frameworks. I would not suggest to anyone. Hey, go and write your own HTTP server from scratch. You're going to waste a lot of time doing that kind of work and it's not going to help you get anything done that was valuable.
[00:02:34] Dave Adsit: I think that's right. I think that a good framework can help accelerate your development. It helps you with that zero to one, that early phase, that rapid application startup phase where you're like, okay, I need logging. I need logging. And you need logging. And you need logging. And you need logging. And you need logging. And you need logging. And you need logging. And you need logging.
[00:03:09] Matt Baker: And you need logging. And you need logging. And you need logging. And you need logging. And you need logging. And you need logging. And you need logging. gate. As long as you observe the idioms of the framework, it can be nice to continue on the React Rift. The unidirectional data flow in React, it's nice. And you're sort of forced into it by
[00:03:37] Dave Adsit: the API and the conventions of the framework. Well, and you think about it, if you're writing a command line app, you probably aren't reaching for a framework. But if you're writing a web app, especially if you haven't done very many web apps before, reaching for a framework is probably your only hope of getting anything useful done. Because handling HTTP, or even worse, handling sockets is not for the faint of heart or the uninitiated, right? That's a lot to deal with. And handling, even handling things at the level of HTTP, it's going to be a lot of work to manage, all of the requirements of a basic HTTP handle. When it comes to things like routing and verbs and response types and response bodies and all of that, it seems really trivial until you start doing it and you realize how much it actually is. I think even in CLI world, you can find some value,
[00:04:40] Matt Baker: like I'm thinking about, I wrote a little CLI tool in Go recently. I can't remember the name but there's a framework that allows you to take command line arguments gracefully. It handles the parsing, it handles if they fail to provide an option, it gives them a message that you're able to just put into this little declarative stub to say, this is one of the arguments coming in from the CLI and this is another one. And then it just handles all the goo of getting those arguments gracefully into the rest of your code. And it's a nice little jumpstart to the CLI app, for sure.
[00:05:16] Dave Adsit: Yeah. If you've used tools like that, and in fact, I think I may have used the one that you're talking about. The one that I've used is called Cobra. Cobra. That's right.
[00:05:26] Allan Stewart: So then if frameworks are so important and we're saying, yes, you need to have them and you should use them, why is our topic fending them off? For me, I can't tell you how many times I've seen code that is so blended into the framework that it's just difficult to use. Early on, it's probably not. Probably fine. Early on, it gets you going really quickly. But the tighter you get your coupling to that framework, the harder it is to make changes over time.
[00:05:57] Dave Adsit: For me, one of the things, the first time that I realized that a framework was doing something to me instead of just doing something for me was when I started trying to unit test web applications in .NET. And what I learned is that there is a problem with the way the framework hooked into my code. So instead of using inversion of control, the framework was calling new on my classes, my controller classes. And by calling new on my controller classes, it took control out of my hands for what I could do to test that. And it would inject a whole bunch of things, make a whole bunch of stuff available, like an HTTP context, which I couldn't mock for a few reasons. I mean, first of all, you shouldn't mock what you don't own, but I couldn't own it because I couldn't even get to where it was instantiated, right? So in that case, the framework, which was written for ease of use rather than for high testability, was impeding my ability to write high quality tested code. And so we had to, as a team, adopt some patterns to invert control ourselves. And that led to us using the Model-View-Presenter pattern, which was specifically an attempt to put control of how the code works back in the hands of us as developers and our code, and take it out of the hands of the framework. What happens when you do that is you're now no longer following the standard patterns. You've gone off the reservation and you're going to have to find your own pathway.
[00:07:46] Matt Baker: Yeah. Yeah. They oftentimes are a substrate among many companies, right? They're born of a particular context and then you might find something useful out of that context. So you bring it on board. But if you're lucky, it works for you forever. But oftentimes you find that either the framework diverges over time or there's just corners of it that don't really fit your case or your company or whatever you're writing the code for, the needs diverge. And then it sours and then you're left with this problem of, well, I've married my code so close to this framework and now it's not fitting the bill anymore. So I've got to do all these little contortions to work with it, like Dave was saying, adopt these new patterns and all the overhead. And I guess there's an argument you can make like, well, it's dealing with the troubles of, Dave, what you just described versus writing it all yourself. Maybe it's more practical and economically sound to just deal with the problems of the framework. But I know just in practice, it's not as good as it sounds. But I think it's But often when I see a framework come into a code base or I've worked at companies where they've rolled their own frameworks or something and it just, Dave, I thought you captured it succinctly when you said it. At first, it feels like it's doing something for you and then it starts doing something to you. And so going back to what Allan said at the beginning of the episode, if frameworks are so good, why do we have to fend them off? And it's a dance. It's a dance of saying, I want to get the good out of here. But at the same time, I don't want to just be so beholden to you. You don't want to put all your eggs in one basket and then have no control over that basket. It's an unfortunate position to be in. And when it gets you, especially when you're in the crunch to deliver a product and then you're 90% there and then you realize that there's some show stopper because of the way the framework works, that's painful. That sucks. And making those decisions of how to fend off the framework in the 11th hour, those are really hard. I don't envy you if you're in that position. It's not fun.
[00:09:50] Dave Adsit: Well, I would say that one of the things about a framework is that because it's someone else's code, typically, even if it's being developed internally, it's probably being developed by some other team. Because the code belongs to someone else, they can make changes to it. And when it comes time to upgrade, you may find yourself in a situation where the upgrade costs more than what you saved by adopting the framework early on.
[00:10:15] Matt Baker: Yeah. Or like the upgrade,
[00:10:16] Dave Adsit: breaks something, right? The upgrade is likely to break some stuff, right? So the last two full-time jobs I've had, we've had front-end frameworks that had never been kept up to date and were years behind, which means they're full of bugs and vulnerabilities and all kinds of problems. And we've had server-side code that was in a similar place. You need to be constantly upgrading so that you can maintain the performance and the scalability and especially the security of your system. But it's expensive to upgrade because the framework is changing. One of the things about keeping the framework at a distance is that you do get the benefit, but you don't have to pay quite so much cost for it. One of the things that I've been thinking is that the superpower of software development is encapsulation or abstraction, however you choose to think about it, right? It's So I want that thing, but I want it behind this interface, whatever that means. I don't want that thing to dictate how my code is shaped. I don't want my domain controlled by a framework, but I want my application that is written in my domain to leverage the power of
[00:11:35] Allan Stewart: that framework to deliver functionality. There's also a disconnect between the framework, or the framework authors, and what you're doing in your code. The framework authors, they don't know what you're trying to accomplish. In general terms, they're solving a problem, but frameworks tend to get more and more generalized. They don't know what domain problems are going to be solved, building on top of what they're providing. And so they get more and more generic and there's more and more options and toggles and things, and even opinionated ones that say, oh, you have to do it this way. They're still a lot more generic than the specific thing that your code is trying to accomplish. And so they're not incentivized. So they're incentivized to solve the problems that you are likely to have, because it's unlikely that you're going to have the same problems as their other customers or their other users. But on the flip side, they are incentivized to try to keep you coming to their code. There's not a great reason for them to show you in the example, in the tutorials about how does this framework work. They're going to just show you the easiest thing that highlights the great part of their framework. But they're not going to say, hey, here's how to keep us at bay from you. This is how you should add a little bit more work so that there's a boundary between what we're doing, what we're providing for you, and what you want to do, because there's no incentive for them to do that. Ideally, you do marry right into the framework and then you're there with them forever. And if they have like some kind of support model or something else, then they can continue getting money doing all the things that they're wanting to do with the framework that they have provided.
[00:13:38] Dave Adsit: And there was like, I don't know what the community called themselves, if anything, but like the alt rails community who tried very hard to create persistence, ignorant models and divorce their business logic from their data store so that they could write unit tests that ran more in a more performant way. Right. And so that was, that was a hot debate in the rails community forever ago, which way should we go, which way is right. And if you take the framework path, you're going to have slower tests that touch databases. And if you take the uncharted path, you would have faster tests that don't touch databases and are subject to all kinds of extra complexity, not least of which is teaching every new rails dev that you hire how to work in this. When the community at large is teaching people that the easy short path is write a test that does an integration with the database and that's fine, which kind of gets me to one of my other questions around frameworks. Is your ORM a framework?
[00:14:45] Matt Baker: Oh, I want to say yes, just because I don't like the ORM. I think they are though, like bias aside, I, to a degree, I think that there's, I suppose it depends. I have some friends that every time I go on an ORM rant, they will bring up SQL alchemy out of Python and it does some things differently than like a typical ORM. So it probably depends, but I do think that an ORM to a degree constrains your domain model. If you're not careful, like if you don't do what we're talking about, like making sure you fend off the framework. And I think the way you persist your code in a framework like Ruby on rails naturally will bleed into the way you model your domain that you're persistent.
[00:15:28] Allan Stewart: Yeah, I can see some arguments for saying that certain ORMs are libraries and they're not frameworks. But going back to that definition of, is this code that calls your code? I guess it kind of depends on what you define as your code, right? Like ORMs are code that calls your database and they have certain interactions with things that you've created in the database, you know, schemas and hopefully not stored procedures. But I think that the, it's kind of a different thing. It's kind of borderline and it depends on how you're using it. And it depends on how or whether you've abstracted yourself away. But I've definitely seen what Matt was talking about. You can get into a mess so quickly by just saying, oh, here is this ORM. It's going to help me use my database. And before you know it, you're starting to use the ORMs models as your business entities, blurring the line between how you persist your data and how you create code. And then pretty soon you're starting to do things based on how the ORM wants you to do it rather than anything that necessarily makes sense in your code. And I've seen this so many times, especially when it just, it's so easy. You say, oh, well, I just need this data. Oh, and if I do this, then I can connect and get that other piece of data. I like to think about it in terms of graphs. Instead of having a directed acyclical graph. You have a crazy fully cyclical graph where you can almost pull out every piece of data in the database from any entry point. Just take any old record and you can get to almost every other table, almost every other record in the whole database. Because it was easy. And you fall into that trap and pretty soon you have all the problems that cycles bring to any kind of graph problem that you're trying to solve in computer science.
[00:17:22] Matt Baker: Yeah. And to keep on the ORM riff, there's definitely ORMs that you can give resolvers to, like resolving functions to that will run your code. So going back to our definition of framework and whether or not an ORM is, it probably depends, but they're messed around with ORMs that will call your resolution functions to give you the domain objects you're looking for. And on ORMs specifically, just to examine a few of the variants, there's your domain that you're working in that could change the way you're working. So you can have a database change that might cause you grief, depending on the way you've implemented your ORM. Your database tech can change, right? Like ORM is a once removed variant. So what I mean by that is there's no guarantee that when the database revs or comes out with a new version, the ORM supports it. And there's also no guarantee that you're not going to really need that database version for something, right? Like I realize this is contrived, but let's say for the sake of conversation that you are on a particular database. That has a security vulnerability that affects you. What if you can't upgrade to the patched version of that database because the ORM doesn't support it? So it's just an example of it. It's bringing in a set of constraints, you know, that you have to work with and you just don't think about them upfront. When you have a green field, you're not really analyzing the trade-offs per se until you've done it a couple of times of why should I bring this ORM? Why shouldn't I bring in this ORM? Or why should I or shouldn't I bring in this ORM? framework is just it's all win. It's all butter, right? Like I'm just going to get up. I'm going to go fast. I'm going to impress the people around me. And here we go. And as the constraints you didn't think about start to emerge, then you're going to be at this crossroads. We're talking about where the degree to which you fended off the framework from the start is the degree to which you've got a problem on your hands.
[00:19:13] Dave Adsit: Well, I think it's interesting that with some of these frameworks that we've talked about, you can treat them as a library. You can treat them as a tool that you call into to facilitate features you need if you are intentional about it. And if you are not intentional about it, you'll end up with the framework becoming the dominant factor of your code. When I think about like, how do you represent the architecture of your system? The primary components of your system should reveal what the system is about. And your system is probably not about controllers. Or views. Views. Or I don't know, some data access-y thing.
[00:19:59] Allan Stewart: Yeah. Ultimately, I think it kind of boils down to if you want to fend off your framework, you have to figure out where are the places that you separate. Because you need that framework. It's doing something important for you. Or you need a framework. Maybe it doesn't have to be that specific one, but you need something. Are you separate? Can you run your logic in new ways? And I think that there's a couple of easy litmus tests that you can ask yourself to figure out, hey, am I too coupled to my framework? And it's things like, can you publish a message to a message broker easily and get some piece of code, some logic, some business process to happen? Or do you have to call the API because the logic was so entangled with the API framework that that's the only way that it works? Or could you create a command line version? Can you swap your persistence to another table? Or even to another database? The degree to which you can say yes to those things, then you have fended off your framework successfully. And the degree to which you say, no, there's no way that we could do that, you are locked in. In some ways, kind of similar to the idea of the automated testing pyramid, there's this idea that as you're creating automated tests at the bottom, you've got your unit tests and they try to cover as much of your code base as possible. And then you have higher level integration or component tests that are testing more things together, but you're testing less overall. It gets so complicated to test all the edge cases that you don't do that anymore. And you test certain critical paths. And at the top, you've got automated UI tests. They tend to be the most brittle. And so you do fewer and fewer of those. I think that's a similar concept to how you should keep your framework separated from your code. Most of your code should be tested and you're testing your own stuff. If you have to test the framework in order to test your code, then you've probably missed the separation somewhere because you can no longer live without that framework. Or at least the thing that was important that you're creating that is different, that is not just this can be done in any framework. It's not already provided out by some other library or thing out in the world. What is it that you are trying to accomplish? You should be able to take your code and move it around and change it and be able to own that thing that you do.
[00:22:28] Dave Adsit: I think one of the things there is that frameworks do a lot. And typically we are using a very tiny portion of the full capability of the framework. When you see that, there's a temptation to say, oh, well, we could get X, Y, and Z for free. It's always for free. Minus the... The implementation costs, the maintenance costs, the testing costs, upgrade costs, and the cost of removing it later when you realize that nobody actually cared. So you get all these extra features for free if they're in the framework. So if we are more purposeful about what we're doing with the framework, we will limit our interactions with it or limit our entanglement with the framework just by being intentional about the features that we're delivering and the value we're creating in the system. Okay. I think Go does a good job of this. Like you can't include a package in Go that you don't actually use. The compiler will get real mad at you and you don't get an executable out of it. JavaScript has gone a different direction where they have the concept of tree shaking where you're like, it turns out we're not using most of the framework. And so when we create the output package, the deliverable, we left out a bunch of stuff that was unnecessary. And so... I think that we're coming to this as an industry and we're seeing, hey, frameworks, they help you do a bunch and they almost always do too much for your use case. I should be clear. For your use case. If you take the aggregate of all the use cases, then the framework is probably still underpowered. But if you look at your use case, your product, your feature set that you're building, that framework does too much.
[00:24:15] Allan Stewart: I like the connection there to dependencies. Dependencies are a kind of a liability. You need them. But if they have a problem, then it's your problem too now. So you have to be judicious about what you're including. Be judicious about what you're depending on and how stable are those things that you are depending on? How stable is this framework? And is it going to be around for the long haul so that you can continue using it and you don't have to make massive changes later?
[00:24:45] Matt Baker: Yeah. There's this recent emergence of... I guess not recent, but considering the last few years may be recent. There's this emergence of supply chain attacks and software, right? So now you, in addition to everything Allan just mentioned, you have to ask questions like, do I trust the delivery mechanism of this framework or do I have to take steps to isolate that? So you don't upgrade to some version of the framework that has malicious code in it. It's a lot. It's a lot to adopt a framework. And maybe there are people... There are people out there that when they adopt a framework, they read all the documents, all the manuals for it, and they thoroughly understand what they're getting into before they do it. But every time in practice that I've seen a framework discovered and adopted, it's someone Googles framework to do this for me. They find the framework that does that thing for them, and then it just comes on in. And the results of that decision or the trade-offs you just made are off. They emerge a little bit later, right? And if you're lucky, they're not bad. And if you're unlucky, they can be showstoppers. I can't remember the... There's just like this idiom. What is it? The 90% rule? The first 90% or the back 10% takes longer than the first 90%? Something like that. Am I ringing any bells?
[00:26:03] Dave Adsit: The first 90% takes 90% of the time and the last 10% also takes 90% of the time. Yeah, something like that. Because that's what you discovered. That's how I like to say it because it always catches people off guard.
[00:26:14] Matt Baker: Yeah. When you really get into it and really get going, just all of a sudden you realize like, oh man, like a month ago, it was great that it did this thing for me. But like, look at these jumps and leaps and hurdles I'm doing now to deal with the goodness that it's given me.
[00:26:29] Dave Adsit: Well, and frameworks are interesting. I think about it from a system level. You could conceive of your service mesh as being a framework. Sure. Because the service mesh is a toolkit that you adopt in order to frame. So you facilitate creating a microservices mesh network type of a thing, right? Depending on how you're trying to deploy. But again, it doesn't actually have anything to do with what you're trying to deliver to customers. The value you are. It's purely a technical tool. And so it may or may not be the right one and it may or may not be helping you. You certainly wouldn't want to have to write all that functionality yourself over and over and over for every one of those little microservices. But I think there's a thing that you could consider. How do I deliver value by treating this framework as a library? How do I limit it to basically its surface area in my code so that my code can be flexible and I can be flexible about whether or not to continue using this framework or adopt the new one? There's always a new one. With ASP.NET web forms, it was superseded by ASP.NET MVC. But if you were using all the web forms, you were going to have a hard time moving to MVC. And if you were using MVC, you were going to have a hard time moving to FUBU MVC when that was really popular for a minute. Or I think Nancy was the .NET equivalent of Sinatra. Super light web framework. And so depending on what you're trying to accomplish, you may want to move forward to these better and better tools or newer and newer tools at least. And you may or may not be able to. I'm thinking that different communities have different levels of control. And I think that's where the comfort with adoption of massive frameworks. The Ruby community was all about Rails. It almost was exclusively Rails for a while, even though Ruby existed before and will exist after Rails. The .NET community was always uncomfortably comfortable adopting giant frameworks for everything.
[00:28:35] Matt Baker: Like what, .NET?
[00:28:38] Dave Adsit: Started with .NET and working. .NET is just a runtime. Come on, Matt. Runtime is fine. But ASP.NET or WCF or WF.
[00:28:51] Matt Baker: I'm just going to point out that Microsoft calls it the .NET framework.
[00:28:55] Dave Adsit: I'm sure that you're right. They do. But what matters is what the .NET consortium calls it, which is also the .NET framework. What's in a name, though? What's in a name? .NET. .NET does a lot for you and to you. Right. But then you think you look at other communities, like the Go community. The Go community is all but allergic to frameworks. When someone suggests we should adopt this big web framework to make our Go web API better, the experienced Go programmers tend to get really gun-shy really quickly. And they're like, what if we adopted the absolute simplest library? that could handle HTTP.
[00:29:43] Matt Baker: What do you think does that? I've experienced the same thing, Dave. What do you think does that? Why is that Go? And why doesn't that happen elsewhere?
[00:29:52] Dave Adsit: I think that Go, well, Go is, it's an evolution of C. And Go is arguably a harder language to pick up than JavaScript or C Sharp or PHP. And I think it attracts the kind of people who have been around the block a couple of times and have been burned by touching hot things a lot of times.
[00:30:19] Matt Baker: Yeah, I totally agree, man. There's always been this thing in Go that I like, which is just go read the source, right? When you open up Go's source code to see what's going on, it's so, it's written in Go, which is nice. And it's just clean. It's simple. There's not a lot of, in direction people highlight Go and like the fight to get generics in it as an example of like the culture of the language, you know, and I think that does, it shows you that, like you were saying, Dave, people that are more averse to big hammers, big frameworks, big complex code bases, they might find a home in Go that they like. I've always appreciated that Go, albeit like verbose at times, I think that maybe that's part of the trade-off. It's, yeah, it's pretty straightforward. I'm not going to say there's not complicated Go code out there, but I don't come across a lot of Go code that's complicated because Go is complicated.
[00:31:14] Dave Adsit: Right. Go is pretty straightforward language. I actually did the day one of Advent and Code last night using Go. Oh, cool. That was a fun experience. I spent probably half my time in the docs trying to figure out how to do different things that would come naturally if I were using a language I was more experienced in.
[00:31:34] Matt Baker: Or a language that like has some more, like, you know, like, you know,
[00:31:47] Dave Adsit: like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, like, you know, just have to do it. Yeah. And other toolkits would do it for you. Go chooses not to, which might be part of what keeps more novice developers away. When you don't have a lot of experience, having a system that's actually helping you is super helpful. Yeah, absolutely. And when you have gotten tired of that system spoon feeding you, you might get really mad and go do something crazy like code in a C derived language that is super fast and creates super tiny executables, but also is going to make you handle your own errors on every single line. Yeah, it would be
[00:32:41] Matt Baker: fun. Like an experiment, a fun experiment would be to go some software conference and say like, you know, put a question on the table. We're going to rate in each one of these programming languages and the ecosystem that surrounds it. We're going to rate how tolerant they are of through the list, say maybe like, you know, to a room of engineers is Ruby. What degree do you think they're tolerant of frameworks would be on rails or maybe like the Django devs or maybe Django is not fair, but I think you catch my drift. I think there would be consensus. I really do that. Like if you said go, I don't think a lot of people are going to say like, no, they'd love frameworks or like if you said, but if you say JavaScript, who uses PHP without it? Yeah, yeah, yeah, exactly. PHP. Or if you said JavaScript or
[00:33:27] Dave Adsit: PHP, you're like WordPress or Lara,
[00:33:29] Matt Baker: or something. Yeah. What's the big one in PHP, the symphony. But anyway, yeah. Like there's our, you just said three, like right off the cuff, you're not going to name three and go. And so I wonder what causes you mentioned one thing, David seems like level of experience. I'm not as convinced on that. I know some like pretty novice engineers that have found pleasure and go. And so it leaves me wondering a little bit what, what transpires that causes some languages in their ecosystems to get so open to frameworks. Whereas,
[00:33:59] Dave Adsit: others are so completely shut down. Well, it's a good point that there are a lot of novices learning go now. I was thinking that it was not just experience, but like bad experience. That's probably true, man. Being boxed in by something that did something to you that you
[00:34:16] Matt Baker: wished you didn't have done to you. Yeah. It only takes like a year to work at a hellscape with like a really bad framework to hate frameworks and whether or not you do that year, year one in your career or year 20, like you're going to get it. And so maybe it's instead of experienced, it's how traumatized are you? How bad has your
[00:34:35] Dave Adsit: experience been with the framework? Did the previous architect roll their own framework? Cause that that'll do it in one go. Oh yeah, absolutely. I definitely did it for me twice.
[00:34:48] Allan Stewart: As if I needed convincing. Well, regardless of the language and like the community around it, I feel like there are some general, general concepts that help us to keep those frameworks at bay. One of them that I think is really important is the open closed principle. If you write some code and now you're going to come along and make a new feature, do you have to go in and edit all this existing code? So you created a report that prints out some HTML. Can you make the CSV version of it? Can you make the PDF version of it without going in and changing everything? If you've done open separate classes or modules or functions or whatever it is specific to your language that do those business work. If you're going to create a CSV report from the HTML report, or, you know, there's a peer to the, why are you touching the database? You shouldn't have to do that again. Why are you marshalling the data into a presentable fashion? You shouldn't have to do that again. You should be able to use these closed classes, ones that you're not touching in order to rebuild that thing. Another principle that relates to that is interface segregation. Are you actually depending on things that you don't need? If you are, there's a tendency to get you into trouble. And now you're more connected to the framework. And because the framework requires something, then you have to change a lot more code where you have to make another change because you weren't depending on just the little thing that you needed. You depended on like four or five things all at once and conflated them just because, I mean, there's probably a reason why those five things are together in the framework, but that doesn't mean it goes together in your code and what you're trying to accomplish. Those are more like low level, like coding principles that I think relate to this, but then higher up, there are concepts around like domain driven design. And it has patterns like repository or gateway pattern that help you keep those frameworks away from what you are trying to accomplish. You're creating these ideas of contexts and where does your business. Logic live within those. There's also hexagonal architectures, which are very similar to the, the onion architecture or the clean architecture. I think those are really good places to go to get ideas. I don't necessarily subscribe to, you should pick one. And like, that's the thing, like hexagonal is the only way to live and all of your code from now on has to be ports and adapters. But at the very least, you should go and understand those architectures to get an idea of what's possible. And why they do the thing. And why they do the things that they do so that you can get some of the benefits that come with the patterns that they have in place.
[00:37:34] Dave Adsit: I think it's interesting that we have a lot of terms, a lot of names for similar patterns that are all about take that thing over there and don't let it get into my code. In DDD, we talk about the anti-corruption layer and you'd have an adapter pattern or a facade pattern or gateway. Okay. Proxy. Proxy. Proxy. Proxy. Proxy. Proxy.
[00:38:18] Matt Baker: Proxy. Proxy. Proxy. Proxy. Proxy. Proxy. Proxy. Proxy. Proxy. Proxy. prioritized work or the way that they're going to set goals oftentimes that comes with some framework right like okay our framework the rocks framework the jobs to be done whatever when you're an engineer trying to write code in that company it's just kind of noise right it's like okay so now i have to i have to change some things about the way i've implemented my day to day i have to report my progress this way or i have to express this goal this way or whatever and it's just it's extra cycles that arguably may or may not be necessary right because if you change a framework that causes a lot of changes in your code it's that's what we're talking about trying to avoid here right so like with the onion architecture you you mask what's being done and you expose the intent right so with the repository pattern you mask how the repository is communicating with the database and you may even mask in some cases what type of what database it is and you expose what needs to be done that's relevant to the domain right like dave's talking about with the anti-corruption layer but as soon as your transaction or your interaction with the database becomes aware of how it's working uh whether or not it's wrapped in a transaction what what are the retry semantics like all that stuff getting into your domain is exactly what we're talking about because that stuff changes as the as the way you access as the way your repository execute changes and what i one thing i really like about some of the architectural perform a file upload somewhere? Is it SSH? Is it FTP? Is it HTTP? Like it shouldn't matter. Like Allan was saying that those semantics are not pertinent to your domain. And if you're implementing these, these architectures, right, then you'll, you'll see in your code, like, oh, yeah, I can, because I've abstracted this away, I can, I can implement it any which way I want. And I don't have to change my domain at all. And then I think you're really, I don't want to oversimplify, emphasize this, but I think you're riding some lightning a little bit there because you've, you've freed up your domain to be the center of focus. And these little implementation details, they're not getting in your way a day to day. Like I'm thinking about some code bases I've worked on where you make a change and then it just, it's pulling on a thread, right? And you start to see like all the places you have to go. Like if you're making a change to your Postgres driver and you're editing like five, 700 files and sure, maybe that's relevant or maybe that's correct for some code bases, but it's a smell. Like, make a change. And so going back to this people example that I think was a stretch too. And maybe some people will take this wrong way. I don't care what prioritization framework you're using. You know, like, I don't care about how you want me to set my goals, like outside of like, I'm going to do it saying, I'm going to go do this. I'm going to do it. And like, all that stuff is just getting in my way. Like, if I have to go read some stupid book that says like, this is the new way you express, like how you're going to accomplish what I'm going to pay you for and what time you're going to do it in. It's like, just cut it out. Like I, I, you know, I don't care about it. So maybe another thing I'm saying is that I wish organization designers would take a cue from the onion
[00:42:01] Allan Stewart: architecture. I like that a lot. It's why things like onion or hexagonal come up so much is just because there are common places, right? The database is a common, you know, common place. Boundary where you're going to do some stuff and nobody care, like nobody outside of your company cares. Nobody's going to pay you more because you were using Oracle. You're going to get charged more because you were using Oracle, but nobody's going to pay you more because you use that versus the competitor who's using Postgres or something else. And if you can switch that out, that's great. Another place is the HTTP APIs that we're talking about and other common places out on the front end code and what kind of framework, because we are more, you know, more limited there. You can really love go, but if you were going to serve a web page, you're going to have to do JavaScript now, probably in some form or fashion. Like you might get really, really clever with rep web assembly or something like that, but for most people, most of the time, that means you're going to be in JavaScript and it's really sad, but it happens all the time where those things get mixed up and the way that your front end works is apparent in your server side code, which then has the same shape as your database, because in the controller method that you opened up in that endpoint, it's got all of that stuff just splattered around their SQL statements or ORM commands, invoking loads and saves and you're mutating data. And then you spit it out into some form that the front end can process and consume. And all of those boundaries are all mushed together and you don't have any of the separation.
[00:43:37] Matt Baker: What are some ways we've talked about onion architecture? I also think the actor model. Introduces a flavor of this talk about imperative shell functional core, but can we expand on those just a little bit and talk about the technical mechanics within those that enable fending off a framework? I can start with like the onion architecture, right? It, it does what it does because of dependency injection and heavy usage of interfaces to extract away the technical bits. And then out of the furthest most edge, you decide what implementation of a given interface you're going to inject. Into a, it's usually like the root of a program, right? Like if you take a, like a.net web app, there, those are great examples of a dependency injection framework. And each of the controllers receives the implementations of different interfaces that are expressed in their, their constructors. And so that allows you to do exactly what we're talking about. If you have a repository that says save foo, you can pass it the Postgres implementation. You can pass it the Cassandra implementation, whatever it is. So onion architecture. Relies on depend heavily on dependency injection in order to accomplish this, this installation from the framework. What other technical bits are interesting to call out in the architectures that we've listed?
[00:44:57] Allan Stewart: I think one that comes up a lot is some layer of mapping because a lot of these problematic code bases, they don't have any kind of mapping. How did we decide to write this model that informs the table structure or vice versa? And that. That informs how the front end is going to receive the data and how it's going to transmit data back to us. But if you identify those boundaries and say at this boundary, there's going to be a mapping, even if it's just a one-to-one mapping. Now you've got a new type. You've got like this pivot point at which you can add stuff. There are stories about these kinds of things where everything was tightly coupled together. And so you add a new column in the database and a new thing appears on the web app. It just appears on the screen magically, which is kind of cool in a way. But what it means is that now you don't have any ability to change the database without also changing the front end. And so you're constrained, you're more constricted. But if you put those mapping layers in place and a lot of them are so simple, like there are some pretty great tools to do these kinds of mapping concepts, but a lot of the time it's just so dead simple. You don't even need that. Or the mapping tool will be overzealous. And try to map too much. And now all of a sudden you have to do like all these annotations into your, your domain. And it's getting polluted again when you could have had something that's separate and it, you know, it took you 10 minutes to write it the first time. And now it's there for you creating that pivot point that you never have to go back and revisit again.
[00:46:32] Matt Baker: Another example I like it's CLI tools composed via pipe in star Nix style systems like Unix, Linux. What have you. You think of a series of commands composed via pipe, the furthest left command or the command that'll run first might be the command that goes out and scrapes a website for you and get some data that you need. Right. And then it might pipe that thing forward into a, a function that knows how to munch the particular data you scraped. But maybe it's agnostic of its format on the web. And so you, in this sense, like when you're, when you compose these. I mentioned earlier that I think actor models are a version of this and trying to get at that here without going too much into actors, but you can compose these chains of operations with the pipe that are ignorant of one another. Right. You hope they are, they should be like, I guess a test would be, can I just pluck one out and put another one in? Cause it's the same thing as like, they're just their interfaces that take data. They don't know where the data came from. They don't know where it's going and standard in standard out. And I can take the first one that scrapes a webpage and maybe say, no, that's not. Now just get that data from an FTP and the rest of the process. You know, the rest of the domain that's expressed further to the right in this composition of commands is unaware, but even there you see like this visualization of the edge. Right. At least I do any way where the leftmost command is the edge or like the shell. Let's go with that imperative shell. And then as you pipe that data in, you get more and more into the functional core, but if at any point one of those CLI tools that's composed. Further to the right on that chain, if they know about the first one, the whole thing breaks, just completely falls down. Like if the goal is to be able to swap them out, independent of one another.
[00:48:22] Allan Stewart: Related to that. I think one of my favorite concepts coming out of functional programming is immutability of data. If you treat your certain data structures as immutable, you're getting rid of a lot of state and you can easily transition from one place to another. And you can say, Hey, here, here is an object that has the data that is. That represents a thing. Now you can do all kinds of things in those kinds of pipelines and say, it doesn't matter. I'm not worried about in the middle of the pipeline. We're going to mutate some state. Each pipeline thing might have some like IO that it's doing, but like if you're saving changes and then there's some other state that has to also go and save changes or load other changes, it makes it more complicated. And if you can get less stateful, it makes it easier to compose those things with the. The imperative statefulness at the edge.
[00:49:15] Matt Baker: Yeah. I not to be too much of a reductionist, but there's a definitely a pattern here of side effects. Keep them out, like keep them as far out to the edge as possible, and then get to a state where you can just have a series of peer functions that your data runs through going back to your, you know, your functional program and call it. Yeah.
[00:49:36] Allan Stewart: I also like DDDs aggregate roots concept. Especially when you apply. Yeah. That to something like an ORM, knowing that you have the thing here is the entity and it is populated with its entire state, or this is all the data that represents a thing is super valuable. Multiple times as a. Net developer, I have fought against entity framework. And one of the biggest reasons why is that it makes it so easy to just load arbitrary data and they all go into the same types, but they're not fully hydrated. You don't know. Just because you're given a user object, that doesn't mean it has all the data that you need, because maybe the emails aren't loaded. Maybe the customer list isn't loaded. But when you have this idea of the aggregate roots, like these things all belong together and they can only be accessed through certain predefined ways. Then, you know, that when you're handed this object, it has what you need. It actually fully represents a concept without missing things that may or may not be there today. Yeah. Well, with that, I think we've covered pretty much everything that we wanted to address in this episode. Any parting thoughts on frameworks?
[00:50:50] Dave Adsit: The main thing about frameworks is that they're super useful and super dangerous because of what they do to you. And so if we want to leverage a framework, we should definitely keep it at arm's distance. And we should follow some of the good coding conventions of putting in place adapters. Or putting in place proxies for the part of the code that needs to talk to that framework. And if we do that, we're going to have a good time because we get a lot of functionality that somebody else has built and tested. And if we don't do that, we're going to have a bad time because the functionality that somebody else has built is changing the way our code behaves with or without our permission.
[00:51:32] Matt Baker: There's some joke I want to make just because the person that bags your groceries at the store is useful doesn't mean you invite them into your home to live with you permanently. There's something there.
[00:51:43] Allan Stewart: I don't quite know what it is. Music for our podcast has been generously provided by Todd Fisher. And as always, we recommend that you join up with a community of professionals by attending a Software Crafters group or meet up near you. The Utah SC group at utasc.org has a virtual meeting the first Wednesday of each month. Perhaps we will meet with you there to discuss keeping your frameworks at bay.
Copyright © 2026 - Crafting Code Podcast