Two seasoned salty programming veterans talk best practices based on years of working with Laravel SaaS teams.
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.
Joel Clermont (00:17):
Laravel gives you several different ways sometimes to accomplish a goal. And whenever there's a situation like that, I don't know, maybe it's just me, but I always like, "Well, what's the right one?" Like, what's the right way to do this? And sometimes right is just consistency but sometimes there actually is a technical reason to do or a team reason to do one versus the other. So let me make this concrete. Sometimes when you want a change to happen in response to some other change, you could do that by kicking off a job manually and making that change, firing an event, having a handler, whatever. Or, you could use an observer that watches the model directly and then it does its magic when that condition is met. So I thought maybe we could talk through why we might choose one over another and what the trade-offs are.
Aaron Saray (01:14):
I think I'll make a clarification right away. I think the conversation we maybe want to have is between events and listeners and observers. Because jobs are a thing that can be happening but they're sometimes the byproduct of a listener or observer or something, but they don't actually have a responsiveness nature to them. That's their scent.
Joel Clermont (01:39):
Okay, that's a fair distinction. Yeah, probably simplify the conversation a little bit too to just frame it that way. So on one hand you can manually fire an event and wire up a listener to do the work, or you can have this model observer that's kind of just there in the background and will do it automatically based on what happens.
Aaron Saray (01:59):
I think when you phrase it like that it's still like, "Well, it's obvious which one you pick." But I think some of the things that Laravel gives us make that a little bit more mudded. Let's not talk about events and listeners specifically... I mean, broadly. Let's go in specific. There's actually a type of event that gets kicked off that you can wire up basically or you can tell Eloquent is, "I want you to issue this event when you've been updated or created," or whatever. It's like a model event and then there's also observers that can observe that these things happened. That's really, I think, where the confusion comes in. When we talk about events and listeners unrelated to a model changing, I think that's pretty clear. Like, I have an event, I issue an event, and then there's a listener to it. It's really more so that would be like in like a workflow. But this is really more so I think the conversation that we want to dig in on is, what if the thing that happens is directly related to some data changing on a model?
Joel Clermont (03:00):
All right.
Aaron Saray (03:01):
And I think that's where the confusion comes in. Because Eloquent gives us the ability to, again, so you can kick off and edit. Like, this model of an edited event and you'd have to wire up a listener to that or not. Or, you can have an observer that observes that model and has a method for updated and watches that.
Joel Clermont (03:19):
Can I throw out a concrete example and maybe we can use that to sort of discuss it?
Aaron Saray (03:26):
Yeah.
Joel Clermont (03:26):
All right. Let's say you have a system where a user can post jobs and other people apply to those jobs. Let's say when a user is deleted they close their account. If they have an open job, you don't want those to be open anywhere because they're not around anymore. So let's use that as a frame of reference. Would you fire an event in the endpoint that deletes the user or deactivates their account? Or, would you make that a deleted event on the observer for the user model?
Aaron Saray (04:04):
Right. It depends, again, on context. That's why this is such an interesting conversation. Let me give a rule first then let's apply it.
Joel Clermont (04:12):
Okay.
Aaron Saray (04:13):
If you have something that needs to happen in your system that is data specific. So if a piece of data changes, another piece of data has to change. You can do that in two ways. You could do that in your database with a trigger or you could do it with an observer. I think that's the cleanest way. You're just saying, "A piece of data has changed and I observe that and here is a different piece of data that needs to change." Again, you said there's no right or wrong but that's just kind of the ruling I use is, if it's data only I'll use an observer. If it's data and then I have to interact with a third party, maybe it's a combination of things. I might have an observer that changes the data and then when it's done it fires off an event that is more so.
Like, I've altered all the data necessary for this action now let's deal with third parties. Because usually you don't want to deal with a third party until you have your own stuff kind of buttoned down. So I tend to use observers a lot in that case because one of the things is, if that data integrity is important... I mean, if it's integrity, I don't want to force other developers or myself to remember that every time I delete that model, wherever it is, if it's in the command or if it's in a controller or whatever, that you have to remember to issue an event by hand.
Joel Clermont (05:31):
Yeah, that's definitely a pro. So let's put that cleanly in the pro column. Is like you don't have to remember to do it, maybe you're using something else like Nova that you can delete a user and you don't have to remember to do it there too. That's a clear win for the observer approach.
Aaron Saray (05:48):
I mean, you could also say that, well, instead of using observer you can tell Eloquent then to fire off that event. You can specify events in NRA. I just think there's a lot of extra work because basically if you're in there you're pretty much saying, "Make an event," so you have to create an event class and you have to create in a listener. If you made one for each one of the actions, well, you just made a bunch of more extra events that you really don't need. Just put it all in one class, usually observer, and the chances are, in my opinion, in most cases, that if you're making an observer that has to do with data integrity and moving stuff around and edited and created all that kind of stuff, there's probably a little bit of code reuse that's going to happen there as well. So if you have an all-in-one class, it's just easy and it's all obvious.
Joel Clermont (06:33):
Yeah. I'll just interject because one of the things that I maybe struggle with or don't like about observers is they can kind of feel hidden or magical. Like, if you're just looking at the model class there's no hint this other action is happening. And if you just look at a controller that's changing the model, there's no hint it's happening. I think that is, I would say, a minor inconvenience but there's probably other ways to solve that. And some of it is just knowing the system. You can look at the event service provider and see what observers are wired up. I would say too, you probably don't use them for a lot so it's not like you have 50 observers you have to keep track of. You certainly wouldn't have one for every model in your system. That would be very unusual I think.
Aaron Saray (07:19):
That's a good point is, you shouldn't not know what code does, but the idea... Or you should not know what happens in your system. But the way I look at it is the idea of like the observer is simple things that it doesn't matter if you know it anyway. Like, if you're going to delete a user one would assume their jobs are not available. I don't really know how that happens and I don't really care most of the time until I have to troubleshoot it. Well, that's different though, because then you're in the mindset of actually looking at it. I mean, I get what you're saying because that does bother me too. Like I said, there's other ways to solve it. It could be as simple as even saying on the comments of your model, "This has an observer," if it really becomes a problem.
Joel Clermont (08:06):
Right. Yeah, I think it's more of an imaginary problem or it might... Especially in that debugging mode, you might be kind of temporarily a little confused but it's really not that hard to figure out. It's certainly within the norm of a Laravel framework application. It's not like you're doing some completely crazy thing that some other person would never guess you were doing. In that context, I think it's fine and it's a minor, minor inconvenience but outweighed by the data integrity aspects.
Aaron Saray (08:34):
Yeah. I'm not also saying that events and listeners don't have their place because they do.
Joel Clermont (08:38):
Sure.
Aaron Saray (08:38):
I think events and listeners are more based around... Well, I reach for them in two instances. First, when it's a workflow process that I'm marking the starter and the end of and there's probably a single way to get into that workflow and a single way to get out of it. When you talk about deleting a model and handling other data, there's a bunch of ways to get into there. But there's usually only one way to, I don't know, authenticate or register. There's a couple sort of... Or, if you have a service that does a thing that's unique. Like, when I post a job, I also post on these three websites and I make them pay for it and all this stuff. There's probably only one way that that's ever done so you could issue events at the beginning and the end of that.
Then the other reason I might reach for events and listeners and those sort of things is when there is a many to many sort of relationship within what has to happen. Or, what could have caused this or what needs to happen when that thing is caused. A lot of times we have a single event and a single listener wired up, but that doesn't mean it has to be like that. You can have three events that all have the same listener or you can have an event that has three listeners. There's reasons besides... You know, data integrity is for observers perhaps and then workflow stuff is for events and listeners, (inaudible 00:09:57).
Joel Clermont (09:57):
Yeah, I like that. And that's sort of like when I look at what Laravel itself does, you mentioned auth and registration, that workflow fires events that you can then hook in to, listen to and do things. You could log in, for example, or verify it, that is changing the model but it would feel really weird to make that an observer and stick logic there. Like, if last login changes do this thing instead of just listening to an authenticated event or something like that.
Aaron Saray (10:29):
Okay. As we kind of said in the beginning, there's no right or wrong way. And then Joel used my favorite word when we started out, which is consistency. So always doing things consistent as much as you can and also having kind of a reason for it. If you're not sure whether you should be reaching for an observer or event or whatever, it's half the technology and half your decision making process. If you're unsure, just take that moment that in this project to think it through and say, "How am I going to use events and listeners? And how am I going to use observers?" I mean, I guess that could change too but start out having an idea and kind of try to stick with that. And then the last thing I'll put to is don't be afraid to write it down in the README of the project. Have a section that kind of says, "This is why I do things this way." I have that a lot of my projects because I changed my thoughts over the years and when I go to it, I'm just like, "Oh, why am I doing it this way?" Or, a new programmer comes in and they don't know your logic, you know? So they can take a look at that and at least they know what you're doing and maybe they can ask you questions.
I sometimes have a hard time sleeping, I think a lot of people do. You get these weird thoughts... Or maybe not a lot of people, but some of us do. You get these weird thoughts in your head and it gets stuck in your head and you're like, "I just wish I could stop thinking and just go back to sleep." But I'm like, "No," the more I push against it, the more the thought won't go away. So I'm like, "I just have to think this through." So I started thinking about what if you were invisible? And if you close your eyes, could you still see them?
Joel Clermont (12:08):
Ooh. Is it like outward facing invisibility as well as inward facing?
Aaron Saray (12:12):
Yeah. Like, is it a one way or two way mirror? I mean, how does that work when people are invisible? So, yeah, if you close your eyes, can you see out? Can you see yourself but people can't see you?
Joel Clermont (12:22):
I'm a little more worried about if closing your eyes you can still see, I feel like that would drive you to never be able to sleep. It would make your sleep problems worse, like you can't close your eyes.
Aaron Saray (12:34):
Well, these things aren't related. I'm not saying be like... You said your sleep problem's worse. I don't think the invisible person was the person here with the sleep. I was saying I was deep in thought.
Joel Clermont (12:47):
Aaron, are you confessing that you have invisibility right now?
Aaron Saray (12:49):
Yeah.
Joel Clermont (12:49):
I don't know if everyone knows this. But when someone downloads our book it blows up Joel's phone. So you should probably go do that.
Joel Clermont (12:59):
Very funny, Aaron. But if you want to do it, you can go to masteringlaravel.io and find all of our books.