From 0983eb9eae4e059dcf702c04f7a1d6ae98197520 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Fri, 21 Jan 2022 14:01:53 +0200 Subject: [PATCH 1/4] Add a `Why not?` section --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 320d910..f285e8f 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 straight-forward "CRUD over a model" case. + +From our experience so far, this straightforwad 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 it's 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. + - Lets 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 for having custom managers and combine that with the idea of letting the domain live saparetely, 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 tool and should be used. + +--- + +> 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. From 702ca6d287be764eb9b8c68968095dd9e5bad015 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Fri, 21 Jan 2022 14:08:14 +0200 Subject: [PATCH 2/4] Add emojis --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f285e8f..beb4dbd 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ The general idea is to "separate concerns" so those concerns can be maintainable ## Why not? -> Why not put your business logic in APIs / Views / Serializers / Forms? +> 🤔 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: @@ -125,7 +125,7 @@ This styleguide aims to: --- -> Why not put your business logic in custom managers and/or querysets? +> 🤔 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. @@ -146,7 +146,7 @@ With all that in mind, custom managers & querysets are very powerful tool and sh --- -> Why not put your business logic in signals? +> 🤔 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: From fcb1a30ca5094fed7604d120e7a18fd08cf3c917 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Fri, 21 Jan 2022 15:40:02 +0200 Subject: [PATCH 3/4] Reword some of the paragraphs --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index beb4dbd..4e2fda2 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,9 @@ Relying on generic APIs / Views, with the combination of serializers & forms doe 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 straight-forward "CRUD over a model" case. +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 straightforwad case rarely happens. And once you leave the happy CRUD path, things start to get messy. +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. @@ -138,11 +138,11 @@ But trying to place all of your business logic in a custom manager is not a grea **The idea is to let your domain live separately, from your data model & API layer.** -If we take the idea for having custom managers and combine that with the idea of letting the domain live saparetely, we'll end up with what we call a "service layer". +If we take the idea for having custom queryset/managers and combine that with the idea of letting the domain live saparetely, 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. +**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 tool and should be used. +With all that in mind, custom managers & querysets are very powerful tool and should be used to expose better interfaces for your models. --- From b7a725b13e1cef16812e425fa86ab81221d58dc8 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Fri, 21 Jan 2022 15:48:11 +0200 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Ventsislav Tashev --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4e2fda2..dd68488 100644 --- a/README.md +++ b/README.md @@ -131,18 +131,18 @@ This is actually a good idea & you might introduce custom managers & querysets, 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 it's own domain, which is not always directly mapped to your data model (models) +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. - - Lets say you have a custom piece of logic that touches models `A`, `B`, `C` and `D`. Where do you put it? + - 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.** +**The idea is to let your domain live separately from your data model & API layer.** -If we take the idea for having custom queryset/managers and combine that with the idea of letting the domain live saparetely, we'll end up with what we call a "service 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.** +**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 tool and should be used to expose better interfaces for your models. +With all that in mind, custom managers & querysets are very powerful tools and should be used to expose better interfaces for your models. ---