diff --git a/README.md b/README.md index 320d910..dd68488 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Django styleguide that we use in [HackSoft](https://hacksoft.io). - [Overview](#overview) +- [Why not?](#why-not) - [Cookie Cutter](#cookie-cutter) - [Models](#models) * [Base model](#base-model) @@ -77,12 +78,14 @@ Django styleguide that we use in [HackSoft](https://hacksoft.io). ## Overview +The core of the Django Styleguide can be summarized as follows: + **In Django, business logic should live in:** -* Model properties (with some exceptions). -* Model `clean` method for additional validations (with some exceptions). * Services - functions, that mostly take care of writing things to the database. * Selectors - functions, that mostly take care of fetching things from the database. +* Model properties (with some exceptions). +* Model `clean` method for additional validations (with some exceptions). **In Django, business logic should not live in:** @@ -90,12 +93,69 @@ Django styleguide that we use in [HackSoft](https://hacksoft.io). * Serializers and Forms. * Form tags. * Model `save` method. +* Custom managers or querysets. +* Signals. **Model properties vs selectors:** * If the property spans multiple relations, it should better be a selector. * If the property is non-trivial & can easily cause `N + 1` queries problem, when serialized, it should better be a selector. +The general idea is to "separate concerns" so those concerns can be maintainable / testable. + +## Why not? + +> 🤔 Why not put your business logic in APIs / Views / Serializers / Forms? + +Relying on generic APIs / Views, with the combination of serializers & forms does 2 major things: + +1. Fragments the business logic in multiple places, making it really hard to trace the data flow. +2. Hides things from you. In order to change something, you need to know the inner-workings of the abstraction that you are using. + +Generic APIs & Views, in combination with serializers & forms, is really great for the straightforward "CRUD for a model" case. + +From our experience, so far, this straightforward case rarely happens. **And once you leave the happy CRUD path, things start to get messy.** + +And once things start to get messy, you need more "boxes", to organize your code in a better way. + +This styleguide aims to: + +1. Give you those "boxes". +1. Help you figure out your own "boxes", for your own specific context & needs. + +--- + +> 🤔 Why not put your business logic in custom managers and/or querysets? + +This is actually a good idea & you might introduce custom managers & querysets, that can expose better API, tailored to your domain. + +But trying to place all of your business logic in a custom manager is not a great idea, because of the following: + +1. Business logic has its own domain, which is not always directly mapped to your data model (models) +1. Business logic most often spans across multiple models, so it's really hard to choose where to place something. + - Let's say you have a custom piece of logic that touches models `A`, `B`, `C`, and `D`. Where do you put it? +1. There can be additional calls to 3rd party systems. You don't want those in your custom manager methods. + +**The idea is to let your domain live separately from your data model & API layer.** + +If we take the idea of having custom queryset/managers and combine that with the idea of letting the domain live separately, we'll end up with what we call a "service layer". + +**Services can be functions, classes, modules, or whatever makes sense for your particular case.** + +With all that in mind, custom managers & querysets are very powerful tools and should be used to expose better interfaces for your models. + +--- + +> 🤔 Why not put your business logic in signals? + +From all of the available options, perhaps, this one will lead you to a very bad place very quickly: + +1. Signals are a great tool for **connecting things that should not know about each other, yet, you want them to be connected.** +1. Signals are also a great tool **for handling cache invalidation** outside your business logic layer. +1. If we start using signals for things that are heavily connected, we are just making the connection more implicit and making it harder to trace the data flow. + +That's why we recommend using signals for very particular use cases, but generally, **we don't recommend using them for structuring the domain / business layer.** + ## Cookie Cutter We recommend starting every new project with some kind of cookiecutter. Having the proper structure from the start pays off.