Surviving With "Thin" Controllers and Models

Lawrence Enehizena speaking at Laravel Nigeria in April, 2017
284Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

In this talk, we will see how we can leverage various techniques of reducing bloat from controllers and models using service objects in Laravel.


Transcript


Lawrence: Surviving with Thin Controllers and Models. My name is Lawrence Enehizena. I work with IROKOtv in the payments and billing engineering team. You can find me on twitter @enoma_lawrence. Just a quick show of hands, how many of us here is using Laravel for the first time? Or don't use Laravel at all? All right, quite a few number. Anyways, [00:00:30] this talk is more of a technique on how we [inaudible 00:00:34] our issues in payments or rather, how we remove some [bloats 00:00:39] in payments in IROKOtv. Laravel is an MVC PHP based framework. It's awesome. We all love Laravel. You'll really appreciate the awesomeness of Laravel if you know more about Laravel's goodness. Laravel is a clean and expressive framework. Laravel has a good community [00:01:00] and large ecosystem of tools, packages tutorial guides. Over time, if you use Laravel for a long time, you learn lots of best practises, standards, how to write clean and expressive code. Also, if you use Laravel in a team, it helps you really take away the pain and sanity of working with non really professional developers, or junior developers. Because they have to write their code to already defined structure of [00:01:30] Laravel. With all three, Laravel becomes really productive. [inaudible 00:01:36] productive framework. If you have an idea, and a good plan, you can actually have your production up. Ready in no time. Why this talk? Why Thin Controllers and Models? Before I can actually explain why we have this talk, we really need to understand what it means for Laravel to be MVC based. [00:02:00] "Laravel has an MVC based Framework," means Laravel implements the MVC design pattern. Which means Laravel comprises of Laravel's code based configuration file, a model, a view, and a controller. What is MVC really, as a design pattern? It's simply a concept that says, "You have a controller that receives requests from clients." It takes these requests, retrieves data from the data base using your model, [00:02:30] and subjects. If need be, gets the data from the data base using your model, takes your models' data to your view, renders the view, and return it back to your client. It's all good. That's basically what it does. What is the problem then? Why this talk, as I say? Really, if we look at it, if you use an MVC base framework like Laravel or other MVC based frameworks, [00:03:00] one of the quickest problems you run into is where do you fit your business logic? Business logic which is basically your own code, not the frameworks core code base or configuration files. That's the big question. When you use MVC framework, it forces you to think in a certain way. That obviously the MVC, you are left with the option of, where do you place your business logic. Either in the controller, [00:03:30] the model or the view. Of course we cannot place our controllers on the view. Because it's security breach. Two, it doesn't make any sense. You are left with only your controller and your model. Over time, your controllers and your model while it works, while your put your business logic there, it begins to grow. It grow in size, it grows in complexity, it becomes difficult to manage. Making changes, which [00:04:00] is one of the first software engineering rule, is write codes that are easily response to change. But once the bloat on your code base, on your controller begins to increase, remember, your controller simply takes requests from your clients. Your controller is supposed to only expose the public HTP methods for the various HTP methods like your post methods, your get method, delete, update, [00:04:30] and [end 00:04:33], yeah. Depending on ... End. Yes. For cross origin requests. So if we know that this is what your controller is supposed to do, why put your business logic in your controller? Because if you do this, it leads to bloats. While it works, while it's okay, as more features are required, as business begins to change, business requires more features, your ... Speaker 2: [inaudible 00:05:01] [00:05:00] . Lawrence: Okay. While business requirements begin to come, you notice how quickly your controllers and your models begin to expand. It makes you write very, very scary unit tests, because your controllers or your models, because they are so large, you have one large unit test for one large class. While you can split your unit tests [00:05:30] into smaller unit test to test different logics of your controller, it still doesn't make any sense because you are supposed to have one unit test to one class, which, in this case, your controller. Why thin your controllers and your model? As I initially said, to remove the bloat. We need to make sure our controller does what is supposed to do which is control the requests [00:06:00] and response, and sent in our model, simply is to get data from data base, and that all. If we remove the bloat, we remove code complexity. We remove code duplication. We have better and clean unit tests. We can easily make changes without breaking our code. How do we do this? It's simply a concept called service objects. Service objects is actually borrowed, it's a borrowed term from DDD, Domain Driven Design. But [00:06:30] because we find ourselves in an MVC based framework, we have to borrow the concept to solve most of our problems. Service objects simple is just a place folder for where you put your business logic. You need a place for your logic that controls or coordinates how interaction in your app happens. For example, if you need a business logic to handle, use that registration, and [00:07:00] while the user is being registered, you need to send out an email. The logic of creating the user sending out an email, they should reside in your service object which is your business logic and not in your controller or inside your model. Are there any downsides to this talk? Are there any downsides to thinning your controller or your model? Yes. While it's good, while you remove bloats, while you remove complexity, you might end up having too many classes. [00:07:30] It's actually a big problem and we developers fear having too many classes, but then again, it's OOP. Laravel is OOP based and what we do in OOP is create classes. Lets get into some code samples. Wow. All right, it's actually really difficult to see. I [inaudible 00:08:00] with [00:08:00] very low eye resolution so I could see my screen and content very well. That's why the font's actually small. That's how I like it. Sorry. I didn't know it would look like this. Anyways, what we have here is a charge controller for charging customers. Basically, there are four methods there. One is public, and three is private. The private methods are actually helper methods used by the public [00:08:30] method cost store, which is basically an HTP post request. Our mobile app makes an HTP post request to the charge APIN points to create a new charge. It simply sends the customer ID, the amounts, and some description because it's required by our billing providers. What do we do? We receive the amounts, the description, and the customer ID. This is a typical controller before thinning. [00:09:00] The customer is retrieved using a customer model, the customer debit source, the customer bank account, or the customer card. Any of them can be debit source. The customer debit source uses the customer to find the customer's active debit source. Their customers have multiple debit source. They could have four cards but one must be active. We retrieve the most active card then we use the card to get the payment provider. Depending on the card, we use a payment [00:09:30] provider which is actually a factory, to resolve which of the providers. Could be Stripe, [inaudible 00:09:35], GTB. We get the payment getaway, I will call debit on the getaway passing three parameters. The amount, description, and the debit source. The transaction goes, it's successful or it's failed, I cannot tell you what we do, but basically we get back a transaction ID. The transaction ID is then used to create a charge object in our service internally. [00:10:00] We use the charge model. We call the crates façade on the charge model which extends eloquence. We pass in the transaction ID, the amount, the description and of course, we generate a unique [inaudible 00:10:13] charge ID that we use internally to manage our charges. The charge web book is fired. That web book is simply a model using the engraved façade method on web model. We send in the charge objects and we do some stuff [00:10:30] in the background I can't expose. Anyways, the charges return back to the app for the mobile app to render. Now, but if you notice something about this particular controller, this is just one method and it has taken over 35 lines of your code. This method has just three helper methods, this if for one just the post request. We'll have the get request to get single charge, get all charges, [inaudible 00:10:58] charges, or [00:11:00] even cancel charges, that is not here yet but this is how big the controller is already looking. Currently for this controller, this is how the model looks like. The model simply exposes relationship between the charge customer and the charge metadata. We tie in some descriptions to charges we call metadata. If we don't want the bloats to be in the controller, we can as well move the bloats to the model. This [00:11:30] is how it looks like. In the bloat is in the model, this is a charge model that extends eloquent model. It does just one method called create charge, that simply does everything the controller would have done inside of it, and ask all the helper methods to generate customer ID, to get the customers' debit source and to get the getaway payment provider. Then the controller will now look like this. I know it's [00:12:00] really tiny but you can see that in the smaller image, where we have the charge controller, we have 3 methods. That's actually the store, the get, and the put method. This is how thin the controller looks like but now the bloat has moved from the controller into the model. But this talk is out to address removing the bloat from both the controller and the model. Lets thin both of them using the service [00:12:30] objects. First of all, we create a folder called services. You can name you folder however you like. Speaker 3: [inaudible 00:12:37]. Lawrence: Yeah, sure. Because of how the image looks like, I'll share the slide. This our folder structure, basically. You can name your service object folders however you like. We like to call ours services. In there we put all out service objects that holds our business logic. This [00:13:00] is a sample service object to remove the bloat from the controller and the model. This is just one class. All this class does is make sure we create a successful charge. It handles everything and abstracts these bloats away from the controller and the model. The next question is, "What? This guy is still big." Yes, it's still big but this is all it contains as just one class. The model and the controller you saw earlier [00:13:30] is just handling one method, there are going to be other methods and it's going to get bigger. But this is how big this guy will ever get. This is actually a create charge object. We will have a get charge object, a delete charge object. All saved as service objects to help us remove the bloat from the controller and from the model. This is how the controller now looks like. Clean and complete. This is the full controller. It creates a charge, [00:14:00] it gets a charge, it obeys a charge, and cancels a charge, and this is how the thin model look like. It simply has relationship which is what it's supposed to do of the charge model and relation to other models. That's all. Thank you.