Some reasons to write a down method in your migrations
Joel Clermont (00:00):
Welcome to No Compromises, a peek into the mind of two old web devs who have seen some things. This is Joel.
Aaron Saray (00:08):
And this is Aaron.
I'm a big fan of doing things "the right way" but I also don't like doing things just to do things when it comes to programming, you know? Like, "Oh, well, that's just always the way we did it," or, "That's what the tool does and tells us to do so we do it." I can think of a good example of this. What I think is important to talk about is for the longest time looking at database migrations and wondering why I am doing the down side of it and testing it. Why is that? Because I know that when I do a migration the database has changed and I might have new columns. And what about all that data then?
Joel Clermont (00:59):
Yeah. I think this is something that comes up a lot just within the Laravel community. Some firmly take the stand like we never write down methods. Because for that reason, is it a waste of time? I've never used the down method, why would we do that? I think it's a good topic and I'd be willing to talk through how we do it and kind of the reasoning behind it too. Because like you say, "You don't just do it because it's there." It's like, "Well, I got to fill in this method, it was in the stub. I mean, I guess I got to do it." But there's that expression cargo culting, where you do something and you don't know why but just because it's always been done.
Aaron Saray (01:39):
Or everyone else does it.
Joel Clermont (01:40):
Right. Obviously, for anyone listening to this, make your own decision as a team or as a developer but we can kind of share where we're coming from.
Aaron Saray (01:50):
Well, just to be on the same page with everyone. We wanted to real quick touch on the migrations because I know everyone has different experiences of what these are for. When we talk about the migrations, really what we're talking about is the tool inside of Laravel to move the database schema to the next level that you need. And then when we talk about the down method, the theoretical point of that is to completely undo all of the changes that you had just made with the up method so that the database is exactly the same structure as it would've been before you ran the up.
Joel Clermont (02:24):
No, that's a good kind of foundation to build upon. When you're writing a migration, the reason is because you want to change the database schema, right? That obviously you fill out the up method because that's the whole point of the migration. But the down method, as you said should do what the up method does in reverse. But why do that? I'll kind of go to the obvious use case and maybe we start there, and then we can add in some additional use cases where it is valuable. Probably the classic scenario everyone thinks of is, "Oh, I deployed this in production, it went horribly wrong for some reason, doesn't matter why, and I want to roll it back." So by having a down method, you're able-
Aaron Saray (03:11):
When you say horribly wrong, what went down?
Joel Clermont (03:14):
The site is down.
Aaron Saray (03:15):
Immediately or there's a big bug that we discovered like in a week?
Joel Clermont (03:20):
Sure. Okay. I mean, that's an important bit of nuance. I'm thinking of the immediate catastrophic. The site is down, I don't have time to even figure it out. I just want to roll it back I'll figure it out later. That's the scenario I think most people think of. I think it's easy to dismiss that as a valid use case because, number one, that should hopefully be pretty rare. I'm thinking for ourselves, I can't even think of a case where we've done that, right?
Aaron Saray (03:52):
Wow. You sound cocky.
Joel Clermont (03:54):
Well, I've only been programming two weeks so. But I mean, maybe it has but I can't think of that. That's one use case. Before I address your question, I just want to share one other thing too. Which is some teams have a strategy where they only roll forward. If there's a failure, well then you write another migration in the up method to change what you need to change. In fact, I think... Is it PlanetScale? There's some newer database engine I've seen where that's literally the only way to do it. Obviously, your circumstances, if that's what you're using, maybe this won't apply but I just wanted to address it.
Aaron Saray (04:35):
Yeah. When you have a journaled system, for example, that you can't undo things in.
Joel Clermont (04:39):
Right. That doesn't apply to us. You know, we're typically deploying on MySQL and we can do whatever we want, we can roll things back if we want to. But you bring up a good point. Because let's say you deploy it on Monday and Wednesday a customer service issue comes in which says, "Hey, this data looks weird." You look into it and it's like, "Oh man, I messed up this migration." We would not roll back in that case because now it's been in production for days. And I would even say, depending on the system even if it's hours, your window of rolling that back maybe has closed. Because now you've captured new data in the new schema, you're going to roll it back and delete it. That feels more catastrophic and messier than dealing with the situation a different way. You would agree with that, right? Do you think hours is maybe even a window that's too long to roll back?
Aaron Saray (05:34):
Yeah, I think hours is right on that line depending on the traffic that you have or the data. If it's only read-only, then it's probably fine.
Joel Clermont (05:40):
Sure.
Aaron Saray (05:40):
But if it's where you've added something where there is now data generated by a user that you can't reverse engineer really, then I think that's an issue. Really what it sounds like you're saying is that the down migration is basically your insurance if something goes very bad at the time it goes very bad. Like, at the time of deployment but that's it. That's kind of the point of this is, if something goes really bad within those few minutes you can put everything back. But after that, it loses its value by huge orders of magnitude.
Joel Clermont (06:16):
Yeah, it might create more problems than it's fixing at that point. I think that's the common use case. In production, something goes horribly wrong we want to undo it. But the other use case and I would say the one that probably applies to most teams unless you have a lot of catastrophic deployments, is it helps you in two other ways for local development. I'll take the first of those two that I'm thinking of. Which is if you're working with a team, or to a certain extent if you're a single developer working on multiple branches, having a down method is nice if you want to check something out that isn't deployed, that isn't merged. Run the migrations to test it and then you want to go back to something else, right? I know, Aaron, you kind of sold me on this. Here again not having worked on a lot of large teams in Laravel projects I haven't felt that benefit. But you've said to yourself doing pull request reviews you want to play with it. This is something you did a lot.
Aaron Saray (07:15):
Yeah. A lot of the times too if you think about it imagine that you're about halfway through a large feature, you set up a ton of test data that maybe isn't already in your seeders. It's a whole new feature, you created all the test data through interface and then someone says, "There's a issue in the main branch, you have to check this out." But you've done a lot of work to build that test data, but you haven't had a chance yet to build the seeders. You don't want to lose that, you know you're almost done if you could just keep this data.
So maybe you do a rollback on your current branch to undo a couple of different columns and things and changes, maybe on a different part of the project that doesn't really affect the bug. Then you can go back to the main branch and check it out. You maybe still have a lot of extra user records or whatever from your test data from your other branch but at least the database structure and stuff is back at kind of how it was. You can see that, then you can fix the issue, deploy, and you can go back to your own branch and spin back the database structure to where it should be. So you don't have to remake all that data again.
Joel Clermont (08:21):
Yeah. Just kind of quickly being able to reconcile like, "Well, what version of the database does this pull request or this branch need?" and switching in and out of that. That's one use case for local development. Here's one that I personally find the most beneficial and that is writing a down method, especially for a little more complicated migration, forces you to think about it differently. Like at a deeper level. I'll give an example. We recently, this was a long-running project, a lot of legacy data and there were some columns we just weren't using anymore. It involved removing some columns, renaming some columns, even things across multiple tables that had foreign key dependencies.
There was quite a bit going on in the migration, maybe four or five tables were being affected. Then to roll that back I kind of had to do it in reverse order. Like, "Okay. I got to recreate this thing that was dropped last." And in the process of writing the rollback, the down, I realized, "Oh, this thing I did in the migration is kind of dumb." That wasn't the optimal way to do it and it actually wasn't having the desired effect. You could run the migration and then see, "Oh, it didn't work." But it's not quite a unit test, but it almost was a mental unit test of my logic to work it backwards. I found a bug and I was able to fix it before I even ran it. I mean, that was one I found. I don't know if you've ever run into that yourself.
Aaron Saray (09:57):
That's interesting. I mean, I could see where you could use that in various different parts of your application not just even the migrations. But what happens when you follow your logic backwards? Does it still make sense if you're thinking about it? As silly as it sounds, you can write the alphabet as fast as you possibly can.
Joel Clermont (10:15):
Sure, yep.
Aaron Saray (10:15):
But if you read it backwards, you can see maybe you missed a letter or two because you were just trying to type it as fast as possible. But I can see that... I mean, that's maybe a bad example. As I'm saying it I'm Iike, "What-"
Joel Clermont (10:28):
Well, I was like, "Isn't that like a sobriety test?" Say the alphabet backwards.
Aaron Saray (10:29):
Yeah.
Joel Clermont (10:29):
If you're coding under the influence we want you to try this. But, yeah, it forces your brain to think of it in a different way. Just that context shift of doing it in a different order might help you to see something that wasn't quite right. I mean, you mentioned testing I think kind of in the intro and I wanted to come back to that because we're not writing a unit test for a migration. But testing in the sense of you should be running your migration locally obviously, but also running the down and then looking at the data and looking at the schema and seeing, "Did it in fact put it back the way I want?"
Aaron Saray (11:08):
Yeah, I've seen a lot of people that will go and make the changes in the database with a GUI tool until it works. Then they'll try to write their migration after that to match what they did. It's like you don't actually know if you wrote the migration correctly. Also when you think about it, if you know you want to do two, three different things but you're not sure if the first one's going to work write the first up and the first down part of it and run that and then roll it back. Because it's easier to only have one failing migration at one point versus, I don't know, I've had this before where you have a migration that fails third out of the fifth step. It's like I'm in a weird spot where it's sort of migrated now, I don't know what to do.
Joel Clermont (11:48):
Well, just a side point. MySQL makes that worse because it can't do schema changes in a transaction. I learned this recently, other databases can. If you have a migration that fails with Postgres or Microsoft SQL Server, it won't leave it in a weird state. Anyways, that's just a side point.
Aaron Saray (12:05):
Oh, I didn't know that.
Joel Clermont (12:05):
That was one thing I was like, "Oh, man, that's kind of a convincing reason to look at Postgres again." But anyways, side point. But, yeah, testing it, rolling it forward, rolling it back just gives you the confidence that this thing is doing what you want. And really, if you do have that catastrophic scenario you really want to have tested that down method because you don't want the down method to make something broken in a different way and now you're really stuck. I think that's good advice to test it manually.
Aaron Saray (12:39):
You know, when you're a kid people ask you, "What's your favorite color?" or a lot of times, "What's your favorite number?" But I realized that the larger someone's favorite number becomes the weirder it is.
Joel Clermont (12:53):
Okay, give me an example. How large are we talking?
Aaron Saray (12:58):
Well, if you said to a son or daughter, if you're like, "What's your favorite number?" And they're like, "Six." You're like, "Oh." What if they say 672,127? You're like, "What's wrong with you? Why would that be... Why is that?" Maybe that's their favorite number.
Joel Clermont (13:14):
Maybe the question itself is confusing. Because you know there's a difference between number and numeral. But I think most people are thinking single-digit numeral, right?
Aaron Saray (13:25):
Oh, yeah.
Joel Clermont (13:25):
And as a kid, when you said that I never had a favorite color. I'd picked one just to not be weird but I definitely had a favorite number.
Aaron Saray (13:34):
What was your favorite number?
Joel Clermont (13:36):
It was eight.
Aaron Saray (13:38):
Oh, I like that.
Joel Clermont (13:39):
What? It's too big like six is the limit?
Aaron Saray (13:41):
No.
Joel Clermont (13:41):
I just liked the shape of it and I thought it was cool that you could turn it sideways and it became infinity so that's your deep thought for today.
Aaron Saray (13:56):
About this time in every podcast episode, we take a small little second and tell you to do something.
Joel Clermont (14:01):
But today we're not going to. You enjoy the day, take it for yourself. Go, find a quiet place and relax.