 Thank you, and hello everyone. Good to see you here. I'm Radoslav Georgiev. It's really hard to pronounce. Eastern European name. So just call me Raddow. It will do. I'm the CEO of a software development company where we mainly do Django and React. This is like our bread-and-butter. So if you want to find more about me and those beautiful people over there. This is the hacks of team. We went to DjangoCon in Heidelberg this year and if you happen randomly to pass by us while we're taking this picture, you're going to say those people are from Eastern Europe. Definitely. Yeah. And... Let's talk about scaling images. Yeah. So the red arrow pointing at Istanbul. This will create some historic turmoil, but Bulgaria is right next to this red arrow, and we are somewhere over there in the United Kingdom, so it's far away, but in case you don't know where Bulgaria is. And we... the final thing that we do is we do a lot of free courses for students, mostly in Python and Django, where we prepare people for their first job. We have like a highly successful rate of placing people in companies, and it's pretty good. So, but we are here for something different. We want to talk about Django. So let's see by rays of hands who here is working with Django daily. Alright, and now the people who are working weekly, but just weekly, not daily. They have to touch Django from time to time. Alright, it's all pretty pretty good audience. I hope this is going to be interesting and actually valuable and resourceful for you. So what do we want to achieve? First thing, we want to enable productive Django teams. As mentioned in the keynote this morning, it's really hard when you have like a big software, and it's not using a popular framework, and it's really hard to set up in bootstrap. It's really hard to get people to contribute because there's a high barrier to actually running any piece of code. So this is one of the things that we want to do when we're developing Django applications, and we're developing Django applications that are going to be used for like next five to ten years, and we need to support it, and we need to add features, remove features, fix bugs, write bugs, and so on. Which is the same thing. We want to have a stable software, and we don't want to introduce random bugs, which we are going to see in the presentation, and also avoids any kind of needless abstraction. Avoid this thing where someone writes a really nice piece of abstraction, and only he or she understands it, and then everyone else who has to touch this code is like, just ask him or better, make him or her write this piece of code because we have a needless abstraction. So this we are going to try to eliminate this. And the most important thing for me is to have a repeatable pattern, and this applies not only to Django, but applies to software development. The best thing we can do is to have a repeatable pattern of adding new features or fixing existing features or removing existing features. We don't want to invent the wheel every time we have to do something new, and this is what we're trying to achieve today. All right, so the main question because I'll need an entire day of DjangoCon to talk about the entire topic because Django is a big framework. So the question that we're going to focus on today is where to put business logic in Django, and the first thing that we need to ask ourselves is how do we define this mythical beast called business logic? Any suggestions? How do you define business logic in less than five sentences? Yeah, I'll try. So for me, this is everything that's specific to the domain of the software that we're writing. If we're writing a software that deals with hotel management and booking, everything related to how we book and manage a hotel is a business logic. Or, more generally speaking, the constraints and the relationships in our code. Every time you see an if, and this if is not in a utility method that checks some strings, this if probably is a business rule that needs to be applied in the software. So almost everything that's not framework or utility is business logic. It's the thing that we're writing the software about. All right. So let's talk about boxes. I really love this how to say example. Framework or frameworks in general, they give us boxes and those boxes are places where we can put our code in. We have boxes and in Django, we have several boxes where we can put and structure our application and actually put our business logic because our business logic is like 80% of the entire code base. The rest is views, models, and so on. And the very first box that people actually hit when writing Django is the models. And I think models are like the big and integral part of Django and we kind of use Django because it has a really nice URL. So models. They define the so-called data model. We have like entity relationship diagram. People love creating those diagrams. I don't know why. And we have to turn this ER diagram into models with the proper relationships with the proper constraints. As I said, they're a very important part of Django. And they define the relations that later we're going to use in the so-called business logic. And models can have properties and static or class methods. And we usually start putting some small part of our business logic in those model properties or in those model methods, either class method, static method, or instance method. And the thing where where people start tripping over and things start to get like bad is when they start putting business logic in the save method. So this is a lot of tutorials do this. It's like you'll really find save method and you start adding code there. You start adding tasks that send email and you run a test suite where you bootstrap a lot of users and you have production settings on your local machine and while running your test suite, you send emails to randomly generate emails, which is a pretty fun experience. So models. Let's see an example. Let's have a course model that defines a start and end date. So business logic can be distinct here. Has started or has finished. It depends on the now. We have some get now that returns the proper date in the proper time zone. And we say true or false. The course has started or course has finished and we return this in some API and DUI updates accordingly to this. So this is an example of business logic is pretty straightforward and is the right place to add it. Another thing that models allow us to do is to define custom validation because sometimes the validation needs to be expressed as a Python code and not just as a database constraint and this custom validation is usually written in the clean method and the thing that we usually do is redefine save call full clean not to forget to call it in the other place and trace validation errors if something's wrong and this is also a good place to put your business logic. It's like additional validations and models keep those two things together properties and additional validation. And this is kind of ugly and it's according to me and this presentation not the right way to do it. So don't try to read the code. I've just pasted some call from a system that we use for our our learning management system that we use for the courses. When you create the course you need to generate weeks for the course because we organize content around weeks and this suddenly starts to feel heavy because I have a really core thing defined in my model safe. It's going to work, but it's not going to scale. And the thing is if I put it here I have to put everything else related to this here tasks, more logic, whatever you imagine related to course creation. And for me models should take care only of the data model, have simple properties and find extra validation and this is where we draw the line. We don't do like everything is going to be in the model. We just don't do this because then we get a fat models which don't scale very well because you have to put everything there. So business logic in models, three simple rules that we have is like it's okay to do it in properties and we'll see later when it's not okay. It's okay to define additional validation in clean and it's totally not okay to start adding a bunch of code in your safe method because you want to keep some kind of separation of concerns models, data model, not business logic. All right. Carry on. The next integral part of Django is views and APIs. I'm going to talk about APIs using REST framework. For us REST framework is Django. You install it as a separate library, but it's the go-to library that you're using when doing APIs. The same thing complies for views. So you don't have enough time to cover everything. So the views and the APIs, they're like the HTTP interface to the rest of the world and they call things on the inside. And can hold business logic which is usually shown in tutorials. We'll take a look at your tutorials. It's like if you don't do it in the models, you're going to do it in the post method of your API or your view of your view. And as I said, I'll focus on APIs. So this is copy-pasted example from REST framework which again is a great framework. And the thing that's being defined here is two APIs that do a crud over some snippet model. And even more if you scroll, if you scroll down in the tutorial, you're going to end up with this example. This is a model view set that needs two things to work. A query set and a serializer. You can then use a router and you have a crud API over a model in around five lines of code, which is pretty neat. It looks really good. But then what's happening? Do you know three lines of code? Three lines of code. Where in the three lines of code is your model being created? For example, if you use the create API. Who knows? You can shout, raise a hand. Where? In the serializer. So what happens? You hit this API. The create model mixing from the REST framework create method is called. The serializer is instantiated and validation is run. And then the snippet serializer method create is called. So we have three lines which look neat for an API. But then the heavy weight needs to be done by the serializer. And it's a third-party library thing which is really nice. But it has to create our objects. So if you want to put additional business logic around creating something which is like almost all of the cases. This is really good for hooking people up. Look three lines of code crud. We're awesome framework. But then reality comes and you have to do five additional things when you're creating a snippet. And then you go to the serializer and you really find create method and you start dumping code there. So let's talk about serializers. They're like really, really great and I wish some somehow forks them out of REST framework as a separate library because the plain serializer is really nice. Model serializers, Django-related. Plain, you can use them for a lot of things. So they're great to transform Python and RM objects to JSON. Or whatever you want, XML and so on. JSON is the usual thing. They're great to do the other thing, transform JSON coming to an API and make it Python data or even RM object data. Make the query for you. And this is where we draw the line. Two things that serializers are really good at and should be used. Okay. The last thing that people often do is to use serializers to create objects. It's like you define create and you have like 500 lines of a create in a serializer. And it's again a separation of concerns thing. Serializers, the first two things, not the third thing. It's not a serializer's job to create your objects. It's your job to create your objects. It's not the API's job to create your objects deep in some abstraction because we have three lines of code. It's again your job. All right. So here's an alternative then. You say, Rado, okay, I won't do it in the safe method. I won't do it in the serializer. But I will definitely do it in the post of the API, which is also shown in a lot of tutorials. Again, just a dump, a cold dump, which not even sure if it's correct. We need to refactor this, but this is what people also do. And this may be okay. If you think hard about it, there are not a lot of bad reasons to do it. And then again, business logic and API's is great until you need to do the same business logic. In a view, you have API for React and you have some old Django view with HTML and you have to do the same thing. Or you have to do it in a task. You have salary and you have to do the same thing in a task. Or you have to do the same thing in a command. Or you have to do the same thing somewhere internally in your system because you have a chain of automation where snippets or courses are being created as part of other flows. And then we have five places where we can do the same thing. And the bad approach here is to start instantiating the API to use the post method in order to have the same business logic. It's like red flags everywhere. So if you have only API, perhaps it's okay. If you have something very simple, perhaps it's okay to use the model view sets of the rest framework. But we're talking about projects that evolve and scale with time and we need to be flexible and we need to have teams working on them. So this is not the box we're looking for. None of the things that I've shown so far, it's not the box that we need for business logic. And oftentimes we need more boxes. How to say, it's wrong to assume that all the things that Django is giving us are all the things that we have to use. And we try to put things to boxes where they don't belong. And when we do this often and time goes by, then we end up with a bad design. And then we end up with random bugs and then we end up with unmaintainable code base because you have a serializer somewhere in your code base. And you just add a simple property because your API needs it and you break five other APIs because five other APIs are using the same serializer. And you don't know that. And such things can be really, really bad developer experience. So we need a new box. And this box is like, it's pretty simple. A personal note on abstraction. I don't feel really good when I have three lines of code and then 100 lines of abstraction underneath it that creates my object in the database. And then I'm not sure what I have to redefine. I have to read the rest framework code, which is nice, done it many times. But I don't feel good when there's a lot of layers of abstraction between me and the ORM, which actually saves the object to the database. That's why I don't like the model view sets or the generic APIs or views at all. They should not be used unless you have a really simple project. And on the other hand, sometimes when reading things from the database, you have additional business logic. You have to do filtering based on other objects in the database or filtering based on your internal business logic. It's not like just a list API view which dumps all objects, define a pagination and we're done. It also happens. We can get away with abstraction here, but when the thing comes that we need more flexibility and control, we have to again redo everything. So those are the existing boxes that Django is giving us. Models, views, templates, tasks, for example, and none of those boxes are suitable for heavy business logic. We've got time to show you some nice template examples where you put a lot of business logic in templates and you do a lot of queries and you can't understand from where these queries are coming, but templates are like the worst idea to add business logic. Just no time for this. So the thing that we're doing at our company, since we're writing Django every day and all of our clients or our projects were using Django, we're developing a style guide which we are constantly debating and updating because we want to have a repeatable pattern of writing Django apps that is nice, easy to use. It's not a burden. It's not something that you have to learn as an abstraction. And that's why I'm here actually to share where we put our business logic. And the first new box that we introduce, which is the fancy name service, is called services. It's like services.py module in your app. Each app has a services py. You can split the module inside however you want. But this is the general unit that deals with the business logic, meaning creating ORM objects, doing additional stuff with those ORM objects, calling color services, and so on and so forth. We'll see an example in a second. And the other thing that we're doing with the services is we have like a specification. It's a keyword only function with type annotations, which helps a lot for documenting what you're actually doing with this function. It speaks the domain language, can call other services, can call other tasks. It's like all the heavy lifting is isolated in a core part called services and selectors. We'll see them in a second. And the rest of the Django is either communicating with the service like APIs or being communicated from the service like ORM. Yeah, as I said, can handle everything that needs to be done. And it works mostly and mainly with models because Django without the ORM, it's like no use to use Django at all. So here's the good stuff. Here's the example. Pretty simple service called create user. As you can see, we're doing the keyword only thing, which means you can't call a service with 10 positional arguments and wonder which is which or what is what. You have to pass the keywords by name explicitly. This is the first thing. The second thing is we have type annotations. What are the arguments? What is the return type? And even if we don't use my pie yet, it's still need something for Django support. It's really nice because it lets you understand what this service is doing. It lets you jump around the project. I see that this is returning to user model and I want to see what the user model is. I hit some combination in my editor or IDE and I jump there and I see and I return. It's really, really nice. And what's happening inside is I create a model and I go to other services. The first one creates a profile. The second one calls a task. Again, we don't have time to talk about services and salary tasks because there is a lot of interesting stuff there. But the thing that we do is we wrap all of our tasks in a service because this way it's isolated nicely. We can mock, we can test, so on and so forth. All right. So this is it. This is a pretty simple service. Here is a little bit more complicated service. When creating a course, I need to generate weeks. So this is the right place to do it. A bunch of codes can be tested. We'll talk about testing in a minute. And the rule of thumb is that every non-trivial operation that touches the database should be done in service. It's like you should have no ORM code in your API, in your model properties. Talk about this in a second. Or anywhere else except your services and selectors. This is the core that defines how your software is behaving. On top of that core, you have the shell, which is Django, which communicates with the core, exposes API to the rest of the world. I will probably add a link to Gary Bernhardt's talk about boundaries. It's really nice. He did it on RailsConf way back in the time. And this is actually the thing that we're doing because it's inspired by this, by this talk. All right. And services take care of writing to the database. Selectors, take care for things that needs to be fetched from the database. Again, selectors.py, we have, let me get all the bullets. They take care of the business logic when fetching, which is usually some kind of filtering. They can call arbitrary services, selectors or tasks, whatever needs to be done, and can handle permissions, filters and so on. Permissioning, filtering should be also happening inside the core and not on the shell, not from the API. Of course, there are some exceptions, again, which we don't have time for this. And here's a simple selector from a project that needed this simple selector. You want to list all users for a specific user, for a specifically logged in user, which is called fetched by. But there are restrictions from the business, from the domain, about what you can actually see. And this code was firstly written in a get of an API. And it was not really scalable, because after this, all other interactions for fetching from the database needed some kind of permissioning and filtering. So selector, you say, give me the visible users the ID so I can filter by them so you don't accidentally see something that you don't have the permission to see. And as you can see, having a plain Python code just using the Django RM is much better than having an API. And then this is like the asterisk star on the model properties for using business logic. If your model property is a simple function on several of your fields, it's good. If your model property starts making queries outside of its relations or even with its relations, and let's say something like this, this model property, it better be a selector, because right now we have business logic, we have not present students is making queries. And the general rule of thumb, the general rule of thumb here is if you have a model property and you just add it in the list API and you get M plus one problem, it should be a selector. This is a bit too deep in Django, but if you have a property that's making, okay, then you're going to say select related, okay, this can solve the problem. But if you add a property which cannot be solved with just select related to be to solve the M plus one queries problem, then it should move to a selector. So another rule of thumb. And then you're going to ask, Rado, but what about the APIs? We started with a three line API. Model view sets, and now I bet you imagine some huge APIs or how our APIs are going to look. And the good thing about our APIs is that they're going to look absolutely the same. They're going to be five to seven lines long. And we're going to use zero abstraction over them, which is actually the same thing that we achieve with model view sets, but without the abstraction. So there's no hidden or complex. And the thing that we achieve with the APIs is this repeatable pattern, because if you open or pick a random API in a project that follows strictly this, it's going to look the same. You're not going to be surprised, or if you are surprised, then there's a good exception to everything, and there should be a good comment why this is a good exception from everything. And you suddenly see the boundaries between things, and you suddenly start to navigate the code base much better, because you know what to expect. This is what your framework is for. All right. So let's make a coarse, crude API. First, we have the list API. We'll talk about why we define serializers inside in a minute. We have a get. We use a selector. We use the serializer to transform the result to something that's JSON serializable, and we return three lines. That's all. Nothing more, nothing less. Detail API. All right. We have get, but with coarse ID. We use selector, which we'll probably fetch. Or the other thing that we can do here is to use an input serializer that's using primary key-related field that's going to fetch the coarse ID for us. There are nuances and options here, but we call a selector and we return. And all lists and get APIs, they look like this. You can call generate them, if you want. There's nothing in this API that's actually hidden or complex. It's a stupid thing that calls selector. Again, create API. We need to create objects in our system. For list and detail, we define output serializers. For create and update, we define input serializers. And what we do, get the serializer, validate it. We probably need some additional mixings to catch validation error and through... No, this won't result in a 500. This is actually okay because it comes from REST framework. Never mind. We call createCourseService, which usually just duplicates the serializers as keyword argument. So I can just expand it with two stars and then return whatever I want to create it. So on 10 minutes remaining. Thank you. And finally, an update API, which looks basically like create and detail. We use updateCourseService and we use input serializer again. And this is, for me, is also neat. But it's neater than the other thing because there's no hidden abstraction. There's no additional knowledge of the framework that you need to have. And let's talk about the elephant in the room, which is not me. It's the serializers. We nest our serializers inside the API. And we do this because otherwise it's really easy to reuse serializers. I really can't say this word, serializers. All right. And if you reuse serializers, then someone can add or remove something from a base serializer that's inherited five times down the road and break who knows how many APIs, which if you don't have a good integration test coverage, the users will catch it. It's the user-driven development where they do the final testing. So we try to inline serializers and try to not reuse them. It's like you need a good reason to reuse. And we still haven't figured out how to freeze a serializer that's being reused. It's like you're reusing it but you cannot modify it anymore because you're going to break APIs. And this happened way too many times and that's why we started doing it. We have a rule of thumb which says for output serializers, it's okay to use model serializers. Sometimes it saves lines of code. But for input serializers, it's strictly forbidden you don't use model serializer because this kind of says let the model serializer do the creating. Now, it's a plain serializer and you define all the fields that you need. No model serializers there. And this saves a lot of problems and a lot of headaches. This simple rule of thumb. And to avoid reusing, sometimes you kind of want to have, like in Ruby, anonymous classes in Python in order just to create in place an anonymous class which extends a serializer and I can define a few fields. Sadly, we don't have the syntax but we have the tools to do it. So we have this really neat utility method called inline serializer which takes all the keywords for a serializer and defines the fields as a dictionary. And then there's no need to reuse a weak serializer defined elsewhere. You can just do it here. In place, in line. I don't know why I've put the implementation here. It just creates a new type from the base serializer. In the resources to this presentation, there is a code for this. It's pretty neat. It's pretty neat to use it. And to end everything, let's talk about testing because it's really important. When we do this service selector thing, the core and the shell, testing models comes down to testing the properties and testing the clean method. And oftentimes, testing properties that are functions on fields, you don't have to instantiate models. Fast tests, good tests. Also clean, you don't have to instantiate models. If your clean method just uses fields. So set of the fields and do something around them. Which is really nice. Testing services and selectors, this is the heavy lifting. This is where you actually hit the database. You use a lot of mocking because you isolate different parts. For example, you've tested this service a lot, and you want to test just the integration between four services. You mock the services that you've tested, or you mock the email sending that we've done three times to randomly fake generated emails. You use a lot of mocking here, and it's like the heavy lifting is down here. Your brain capabilities go here. And testing APIs, with this approach, there's usually no need to test the APIs, because they're just an interface, and it's usually delegated to integration tests, five minutes from any correct, usually delegated to one of my last slides. Delegated to integration testing, which is also really nice. But my talk's not about testing. If you want to hear more about Django testing and what approaches we use, you can listen to Martin, who is here in Lemmermere. Well, how do you pronounce this? Lemmermere, all right. In this whole, at 405. All right. So, through a cap, and to finish. Avoid business logic in. Model safe method. Forms, any kind of forms in any kind of serializers, just don't do it. Again, views and APIs, there's no need to do their business logic. And templates, tags, and utility methods. Utility methods should work like pure functions on pure Python data structures, on Python data structures. You should not have a utility which speaks your domain. Or if you have it, don't call it a utility, just create a simple function with leading underscore and don't use it elsewhere. So, utilities should not be doing business logic. Again, tasks, tasks is an interesting topic. Sometimes you have to do it. I don't have the time for this, sorry. Selectors and services. Again, use services for creating update actions. Use selectors for lists and detail actions. It's really simple approach, but it pays a lot in the long run. There's more to it. I've just published this morning the style guide, because we have it internally, and we're going to update it a lot. I'm going to tweet it, add it to the presentation. It's already added. And so on, in case you're interested how we're doing this. Again, I'm not saying this is the best way to write Django, because there are many ways that are actually valid, and you can build software with them. This is just the way that's working for us, and it's working from experience. It's working from shipping software that's being used by a lot of users every day, and we have to be flexible with the features. So, that's it. Thanks. Any questions or welcome? Hello. Hi. I used to have my business logic in the managers, like I extend the bulk update manager and have the business logic here. Do you think this is a bulk method? So, you say you put your business logic in custom managers and custom query sets. That's actually good, but it's not the best, how to say, it's too orange-specific. You can achieve absolutely the same thing with services and selectors with custom managers and custom query sets, but this kind of makes you want to use them in APIs and tags and templates and so on. So, it's basically the same, but we just define a nice boundary around it. It's still good to, if you feel comfortable doing things in managers, just wrap the thing that you've done in a manager in a service and call the service. Just have one entry point to the database and one entry point outside of the database. That's the general idea, to have the boundary. So, yeah. I hope this answers it. Very nice talk. Thank you. In all the examples we saw, everything seemed to work out okay. Are there any best practices for how you handle exceptions raised from within services and transactions within the services? Yep. Let's hope I've copy pasted the writing. I missed it, all right. So, let's talk about exceptions first. Since we're doing validation and permission checking in the services and selectors, we usually define a mix-in that goes in all of the APIs that just handles the exception that we define as in our core, in our business and just translate it to rest framework exceptions. This is how we handle it to avoid 500. And then for transactions, we usually, if you have a service, you decorate it with transaction atomic. That's it. And if there are special edge cases where we have some special edge cases like you need to be sequential in some ordering, for example, invoice numbers you lock, but you do this in the service. Yeah. Yeah, I was wondering about, now you have all the logic in selectors and in services. That's awesome. What do you think about then providing shortcuts to those from a model, for example, as a property? And if yes, what do you do about the circular import problem? Oh, this is pain. So circular imports, you either import the module and use the dots or you import it inside the function which makes it really hard for testing. So I'm not sure I could give you a general solution for this because I still struggle every time I hit a circular import. As for shortcuts, we usually do the other way around. We have some methods and properties that are simple enough on a model and we expose them via service. We do the other way around because again, we try to have the boundary between the API and the database with the business logic in between. We try to have no other way of using the application aside from using a service or a selector. Yeah. Okay. Did you mention that when we have a property that calculates something in the relation, we have attended just today another speech talking about how to optimize the OREM to do queries and so on. So will be the best practice to do that calculation as a property, doing a selector to do that calculation as a property and the property called that? Yeah, so actually this is... Evil stock right in front of you for the query optimization. This is a nice edge case. In case you need this, you better preserve them as properties or you have to use serializer methods fields in order to use the selectors. But this is an edge case and when there is an edge case, you have to decide we should gather a team, make a decision and add a comment why we've done this... why we did this and why it's not like the rest of the system. But this is context-specific. Sometimes you can do it with a selector. It's pretty easy. Sometimes you have to use the cached property because you're doing some annotation, grouping and aggregation in the query set. Okay. Thank you. And just to say we don't follow 100% of this in our projects because we developed this over time. Last week we made some decisions that are looking good but we tried gradually to turn our entire code base to be like that. The last project that I did, which was a two-month-long project it was entirely like this and it works like magic. It's really good. But we learned along the way also. Hey. I haven't used enough Jaguar REST framework so I'm just going to double-check this. Is there anything like a model form serializer that would basically serialize it to the form that could then get clean and verified that way? So there's a plain serializer which just takes, for example, JSON and turns it into Python objects. And there's this thing called model serializer which takes JSON and turns it to ORM object. So yeah. Would you use forms at all with this method? So most of our projects are using, for example, React as a front-end so we don't use forms. But the thing that we had, we had duplicate forms and model serializer sends. For me, the best thing is to drop one and just use model serializer's render as forms. Yeah. Because you have, again, duplication and it's really painful. Thank you. I have a couple of questions. The first one, why not put selectors into services? Because you get way too many services. You get way too many services and you make the separation visually in the file. It's like there's a comment in the middle which says things that create and update, things that list and do details. Yep. My gut feeling would be that if I'm able to create something with a service, I should also be able to select it with the same service. I guess, yes. Same good feeling here. Perhaps it's a naming thing because we started with services which were doing both and then we decided to start factoring the selectors. So perhaps the entire thing should be called service and we should have subnames inside of it for the two different types of functions. So yeah, I agree with you. And then what's your take on using the main driven design approach towards structuring your project? This is basically the main driven design. It's like you do the data model, you expose the business logic and you do the APIs. From my point of view, of course. Yeah. Okay. All right. That was all the time we had for questions. Give one last round of applause, please.