С Django мы все дальше от Бога

в 11:16, , рубрики: django, drf, python

Дисклеймер

Данный текст не имеет цели кого-то оскорбить или высмеять и несет исключительно развлекательный характер.

Я долго работал в коммерческом проекте в роли backend-разработчика с всемирно известным фреймворком Django, а также с его альтер эго - Django Rest Framework. Всегда кажется, что работать с чем-то многозвёздочном на GitHub - это кататься сыром в масле. На входе имеем: отличную документацию, отзывчивое сообщество, множество решений одной и той же проблемы, так как все уже (пред)решено...

Предыстория

Знакомство с фреймворком произошло достаточно давно. Казалось, что такой швейцарский нож доступен в Java, C++, Go (иногда конечно встречается, но нужно рыть). Везде такая разработка API: вьюхи, модельки, в чем проблема? Поначалу интересно писать по официальному туториалу, как итог - ты сразу видишь визуальный результат. Не скажу, что я тщательно вкраплялся в Джангу при старте, но одни из первых пет-проектов написал на ней. Это продолжалось ровно вплоть до того момента, как пришлось в n-м году искать работу хоть как-то похожую на программирование, и тут я нарываюсь на довольно неплохой оффер для уровня trainee и участие в проектах немалого масштаба. Тут были и Django, и Flask, и FastAPI. Более того, в каждом удалось поучаствовать, но проект на Django c Flask'ом отвалились в недра техподдержки, а зачаток нового проекта пал на благоговенный FastAPI. Да, я люблю гибкие фреймворки, но это не главное. Главное, что Джанго исчез из моего поля зрения, запомнившись мне как панацея ото всего, но почему-то разработчики на тот момент ушли от данного экспоната.

Готовая архитектура = легко разобраться в другом проекте?

Думаю есть плюс, понятно что и где лежит, никаких самописных штук. Но когда проект разрастается, каждый начинает изворачиваться как хочет и разработчики отклоняются от django-way. Поэтому тут две стороны медали, в том же DDD ты видишь бизнес логику, а также адаптеры к ней, и у тебя также не возникает проблем.

Лицом к лицу. Django

Вернуться к пресловутому симбиоту Django + DRF удалось лишь с переходом на новую работу. Не сказать, что я молился на FastAPI или другую технологию, в большинстве своем мне было все равно. Я был готов и в Devops (теперь хайпово знать k8s обычному работяге CRUDоделу), и в Go, и в Fullstack. Но как-то опять повезло, меня вновь вербует определенная корпорация на классический бэкендерский стэк. Какие проблемы могут быть?Спустя долгое время работы по многослойной архитектуре (без прибамбасов, а обычные controller, service, repository) с type hinting в каждом файле и различными примочками, которые следят, чтобы код предсказуемо себя вел, у меня сложилось совсем другое впечатление от разработки. Не буду распинаться про DDD, TDD, но я понял одно: в разработке чем проще, тем лучше. С одной стороны, чем быстрее завелось, тем лучше, но в долгосрочной перспективе - чем более предсказуемо ведет себя код, тем меньше ошибок в нем можно допустить. Команде нужно дорасти до TDD, но я также считаю, что работать с Django может только сильная команда, которая знает все тонкости фреймворка (а их настолько много, что грамотно стартануть не получится), а не выбирает его только потому, что это быстрое решение для бизнеса. А еще придется идти "против дзена Django" чтобы код стал действительно хорошим и масштабируемым. Это борьба вечная и не всегда заканчивается успехом.

№1 Шок от исходников

Рандомный класс из исходного код Django

Рандомный класс из исходного код Django

Да, типизации здесь не видать, как и датаклассов, как и других удобных штук, которые повышают качество разработки в x10 раз вместе с читабельностью. Это понятно, потому что фреймворку достаточно много лет и тогда в python не было никакой типизации. Но сегодня мир изменился, python избавляется от своих первозданных идиом в пользу предсказуемости. Современные разработчики начинают привыкать к общепринятой практике ставить везде type hints, а взамен получать подсказки от любимого IDE. Лучше снижать когнитивную нагрузку и подкладывать "подушки безопасности", где это возможно. Конечно низкий поклон тому, кто сделал отказоустойчивую систему в таких условиях, но было бы круто, если бы Django получил адекватную типизацию, это дало бы некий буст и безопасность.

№2 Startproject - пошла магия

django-admin startproject my_project

Django создает такую структуру-матрёшку:

С Django мы все дальше от Бога - 2
  • Внешняя папка my_project с файлом управленцем manage.py

  • Внутренняя папка my_project, она же хранит все общие настройки и конфиги. Я еще не создал ни одного приложения, а уже должен осознать, что "приложений" почему-то будет много, хотя для новичка не совсем понятно, что за "приложения" такие, ведь мы создаем буквально веб-приложение (в единственном коли��естве).

  • my_project/my_project/settings.py что здесь храним?

    • магические словари DATABASES, CACHES, которые в недрах фреймворка превращаются в коннекторы к базам данных. Если движок базы данных не входит в стандартный набор Django, вы обречены на поиск сторонних библиотек, которые пытаются приклеиться к джанговской интерпретации, но не всегда обеспечивают ту же дефолтную магию.

    • магические переменные, которые, например, по одному писку меняют поведение транзакций во всем проекте. На днях я общался со знакомым разработчиком на Rust, и он спросил: "Что будет если в транзакции БД какой-то код вызовет таймаут?" Неважно какой здесь дать ответ, когда вовсе не работаешь с транзакциями, потому что весь UoW - это одна строчка в настройках, зато на собеседованиях спрашивают про ACID и уровни изоляции транзакций.

    • SECRET_KEY, который потребуется не забыть перенести в .env. Я частенько об этом забывал, но мешать секреты и конфиги в одном месте - плохая практика.

№3 Startapp

python manage.py startapp app

С Django мы все дальше от Бога - 3

Вроде бы ничего, появляется папочка заготовок, некоторые из которых разработчик сразу же удаляет, потому что не будет пользоваться основной спецификой фреймворка.
views.py tests.py models.py скорее всего превратятся в директорию, admin.py не будет использоваться вовсе, если пишется стандартный API, а вот urls.py наоборот не хватает в таком случае. Известна же плохая практика написания логики во View, получается нужно еще докручивать какие-нибудь services и в итоге многое захочется переименовать.
Ну, наверное, это мелочи, перейдем к конструктиву во время самой разработки

№4 kwargs.get('key1'), request.GET.get('key2') красиво?

Фреймворк Django предлагает море разных способов получить один и тот же параметр, заставляя нас гадать, какой из них "более ликвидный". А еще обязательно ожидает, что в функции вы укажете request, path_parameter, не говоря уже об обязательном нейминге get, post, patch и т.д., иначе все сломается.

class UserView(View): 
	def get(self, request, user_id, *args, **kwargs):
		# user_id - path параметр
		user_id_1 = user_id
		user_id_2 = kwargs.get('user_id')
		user_id_3 = self.kwargs.get('user_id')
		
		# А так вытаскивается query параметр
		group_id = request.GET.get('group_id')
		
		return JSONResponse(
			{
				"user_id": user_id_1,
				"group_id": group_id,
			}
		)

№5 Готовая админка с сессиями для Hello World

Если вас не пугает то, что сейчас будет мгновением ниже, наверняка вы сейчас где-то на туториале или разрабатываете сайт-визитку. Первичная миграция в базу данных заканчивается дефолтной высадкой целого десанта таблиц в вашей новоиспеченной базе данных:

  • django_migrations

  • auth_user

  • auth_group

  • auth_permission

  • auth_user_groups

  • auth_user_user_permissions

  • django_admin_log

  • django_session

  • django_content_type

Безусловно, это можно отключить:

# settings.py
INSTALLED_APPS = []

Надо кстати не забыть про MIDDLEWARE в settings.py и некоторый косметический рефакторинг, иначе приложение не заработает. Но оно же включено по умолчанию, лучше же ничего не трогать? Когда ты подходишь к бэкенду осознанно, такая "магия" из коробки скорее пугает, чем радует. В голову лезут вопросы: "Откуда это притянулось?", "Сколько там данных есть и будет, и как их чистить?", "Как это безопасно выпилить, чтобы всё не рухнуло?" Контроль над проектом потерян, ты начинаешь искать, кто тебе всучил это и убежал - для этого требуется лишние исследования.

№6 User призрак

Однажды приходит осознание, что пользователя за тебя уже кто-то создал (случай, когда из INSTALLED_APPS ничего не убиралось). За тебя не только сделали User, но и накидали своих полей. Выходит, чтобы писать на фреймворке, нужно заранее убрать медвежьи капканы, которые будут присутствовать примерно всегда. Так переопределим User и не забудем про settings.py, причем много переменных скрыто от пользователя, а за этим пожалуйста в документацию, хотя User для бэкенда - святая святых.

# users.py
from django.contrib.auth.models import AbstractUser 
from django.db import models 


class User(AbstractUser):
	# Полностью удаляем поле из модели
	username = None
	
	email = models.EmailField(unique=True)
	bio = models.TextField(blank=True)

	# Говорим Django, что теперь email — это логин 
	USERNAME_FIELD = 'email'
	
	# Говорим Django, что больше ничего не требовать от меня 
	REQUIRED_FIELDS = []
	
# settings.py
AUTH_USER_MODEL = 'users.User'

Лично для меня поля не совсем предсказуемы, то есть приходится снова брать готовое решение, и убирать лишнюю скорлупу, ведь нам не нужен "username" в качестве логина в 2026? А еще требуется почему-то сменить поле USERNAME_FIELD, а не LOGIN_FIELD или что-либо подобное.

№7 Явное лучше неявного?

Посмотрим внимательно на код. Есть тут название таблицы? А поле id в качестве первичного ключа?

import uuid 
from django.db import models
 
class Product(models.Model): 
	uid = models.UUIDField(default=uuid.uuid4, editable=False) 
	name = models.CharField(max_length=255)

В model.Model уже присутствует скрытый id, а если ваше приложение называлось app, то в базе данных появится таблица app_product, что в общем-то не совсем красиво, и хотелось бы получить некое наказание, а не поощрение за неявность.

Ведь с самого начала тебя не учат строить, тебя учат тому, что все итак работать будет.

№8 Шаблонизаторы - грех фуллстэка

Model - View - Template - такая структура Django до сих пор.
Стандарт, когда вьюха отдает HTML.
Однажды по молодости мне не засчитали тестовое задание потому, что я сделал не API, а нечто подобное:

class PollsDetailView(View):
	def detail(request, question_id):
	    question = get_object_or_404(Question, pk=question_id)
	    return render(request, "polls/detail.html", {"question": question})
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>

Не это хотелось увидеть от бэкендера, не так ли?
Думаю, совсем нет смысла комментировать. Для справки: в новой Django 6 разработчики и дальше прокачивают шаблоны.

Эпилог

К счастью или сожалению не буду раздувать дальше для компактности статьи. Я обязательно вспомню что-нибудь еще, если увижу отклик.

Django - это неподъемный монолит, который в основном кидается HTML. Да, моя ошибка, не понял истинное предназначение фреймворка 2005 года. В нем конечно есть JSONResponse и другие приколы, но ка�� оказалось, фреймворк не предназначен для построения API. То есть вся документация, на которую я опирался будучи новичком и услышав что "Django топ для бэкендеров", оказалась старейшим решением с шаблонизаторами и формами.

Конечно, не беда. Ведь есть Django Rest Framework! Однако чтобы с ним работать, нам потребуется вся родословная Django с ее кучей инструментов, которые лягут мертвым грузом при разработке API. Выглядит уже как-то не исчерпывающе, ведь так?

Продолжение следует за DRF...

Автор: nutsdonuts

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js