Написать пост

Как не допустить свалки в Django-проекте: MTV, services.py, новые приложения

Аватарка пользователя Илья Осипов

В этом материале мы обсудим, как писать чистый код, и какие концепции и типовые ошибки превращают утончённые проекты в заросли и свалки.

Привет! Меня зовут Илья Осипов, я методист курса программирования на Python «Девман» и больше пяти лет пишу код на этом языке. В этом материале мы обсудим концепции и типовые ошибки, которые превращают утончённые и «правильные» проекты в заросли и свалки.

Когда вскрывается проблема

Представьте, вы пишете проект на Django в команде из пяти человек. На старте все замотивированы «сделать хорошо», ведь это шанс сделать проект «идеальным» с самого начала. Но проходит год или два, проект обрастает парой десятков тысяч строк кода. Разрабатывать становится всё сложнее: файлы ломятся от кода, навигироваться по проекту без продвинутых инструментов из IDE просто невозможно.

В этот момент разработчики обычно говорят, что причина возрастающей сложности — рост проекта. Или грешат на «коллег-говнокодеров» / «плохой фреймворк». Хотя с ростом проекта навигация действительно естественным образом усложняется, чаще всего виноваты отнюдь не фреймворки или количество строчек, а плохие решения.

Основная цель фреймворка — задать «фреймы», то есть «границы», в рамках которых будет работать программист. Для этого внутри Django предусмотрено множество механизмов, и многие из них описаны в документации. Некоторые — прямо в исходниках фреймворка. А какие-то детали разработчики Django не предусмотрели, и программистам приходится придумывать новые границы самостоятельно.

Зачем нужны эти границы? Разве без них не лучше?

Ответ на этот вопрос сходу поймёт любой пользователь фреймворка: не лучше. Границы пусть и создают дополнительные «препятствия», но взамен они обеспечивают понятное пространство для работы: сюда писать такой код, а сюда – вот такой, и всё будет работать. Не нужно каждый раз «изобретать велосипед» и пытаться придумать, как будет работать ваш сайт. За вас уже подумали и оставили «места для дописывания кода». Вот эти места для вашего кода — это и есть фреймы.

Архитектура MTV

Самый «высокоуровневый» фрейм в Django — это архитектурное разделение логики на Model, View и Template.

Да, архитектура Django называется именно MTV, а не MVT. Разработчики используют в документации именно такую аббревиатуру. По сути, архитектура почти полностью повторяет другой подход — MVC, Model-View-Controller (Модель-Представление-Контроллер).

Суть такая:

  • в Model хранят данные сервиса;
  • View отвечает за отображение данных пользователю;
  • Controller отвечает за то, чтобы сообщать Model, когда пора менять данные.

В Django же отошли от этой концепции и использовали архитектуру Model, View и Template. Сами разработчики Django пишут, что не видят никакого конфликта с MVC. Model по-прежнему отвечает за хранение данных и работу с ними. View — за «отображение» данных. Просто этот слой они разделили на два: View отвечает за то, чтобы решить, какие данные отображать, а Template — как отображать. Куда делся Controller из MVC? В аббревиатуре MTV он пропал. 

Разработчики считают, что это сам фреймворк.

Возможно, вы хотите спросить, зачем я об этом рассказываю. Разве возможно накосячить на этом уровне? Вообще-то, да.

Бизнес-логика, или слой Services

Многие разработчики пытаются переизобрести MTV и добавляют в Django дополнительный слой: Services. Вы найдёте файл serivces.py во многих туториалах и видео по Django.

Необходимость добавления этого слоя обычно объясняют так:

  • В моём проекте появились функции, которые не укладываются ни в Model, ни во View;
  • Я хочу сделать «бизнес-логику» реюзабельной между приложениями, и, чтобы не импортировать функции из одного View в другой, храню их отдельно.

Эти проблемы действительно существуют. Зачастую в проекте появляются вспомогательные функции, которые не принадлежат стандартным сущностям Django, вроде Model или View.

Но решение складывать весь этот код в файл services.py, на мой взгляд, плохое. Вот почему:

Слой Services слишком широкий, это плохая рамка. При определении рамок важно не определить, какой код можно класть в этот файл, а решить, какой код в этот файл класть нельзя. Для сравнения, Model хорошо справляется с этой задачей: если код не относится к работе с БД, то ему не место в файле models.py. А в services.py часто кладут всё, что не влезло в остальные.

Файл становится свалкой. Это ожидаемое следствие плохой рамки. Я часто интересуюсь у друзей и у кандидатов на собеседованиях, есть ли в их текущих или прошлых проектах такая проблема: в services.py более 2 тысяч строк кода и по нему тяжело навигироваться. Если в проекте более 10 тысяч строчек кода, разработчики почти единогласно говорят, что файл превращается в свалку.

Файл размывает слой View. Есть мнение, что в файл services.py нужно выносить бизнес-логику. Термин «бизнес-логика» кажется конкретным — это всё, что определяется правилами бизнеса. Но на поверку он размыт куда сильнее, чем кажется. Разработчики, глядя на функцию, иногда не могут сказать, бизнес-логика это или нет.

Куда девать общий код?

Альтернативное решение — оставить фреймворк в покое и не добавлять в него новый слой. Если появилась новая логика, то создать файл с говорящим названием и положить код в него. Если логики вынести нужно много — создайте несколько файлов. Искать нужную функциональность проще в одном мегафайле на тысячу строк или в пяти небольших по двести строк? Так будет куда понятнее, где, например, искать функцию, которая форматирует дату или преобразует цену товара:

Как не допустить свалки в Django-проекте: MTV, services.py, новые приложения 1

Писать запросы в слое View — это нормально

Я так и не разобрался, откуда взялся миф о том, что во View нельзя писать запросы в БД, и в чём аргументация его сторонников. Это правило просто откуда-то «взялось», и теперь в него верят как в аксиому.

Тем не менее в документации Django для слоя View очень простое определение:

The view itself contains whatever arbitrary logic is necessary to return response.

Соответственно, в этом слое можно делать ORM-запросы, если это нужно для ответа. Не обязательно пытаться «прятать» этот код куда-то ещё.

Часть запросов будут нести бизнес-смысл, то есть они станут частью бизнес-логики. Иногда это будет даже не один запрос в БД, а несколько, или даже пара строк кода между ними.

По принципу MVC «Fat models, skinny controllers, simple views», большую часть такого кода стоит стараться класть в слой Model. Один из самых недооценённых, на мой взгляд, способов — переопределение QuerySet. Этот способ позволяет взять здоровый запрос на 10 вызовов методов ORM и дать ему простое, бизнесовое название.

Со слоем Services получается «Skinny Models, Skinny Controllers, Fat Services». На мой взгляд, это очевидное отхождение от принципов, которые изначально закладывались в фреймворк.

Приложения (app)

Сейчас эпоха популярности микросервисов над монолитами. Из-за этого сама архитектура Django-монолита стигматезируется, некоторые мои знакомые даже не пытаются пилить Django-монолиты на приложения. Но ведь «монолит» не равно «свалка»! За ним всё ещё стоит ухаживать.

Чуть больше о микросервисах и монолитах

Это будет шокирующей мыслью для многих читателей, но дробление на микросервисы на всегда оправдано (если посмотрели и заинтересовались докладом, советую ещё этот доклад). 

Приложения — это следующий уровень абстракции в фреймворке, который тоже отвечает за разделение кода на кучки, но уже по другому признаку.

Можно представить, что MTV разделяет код на «красное», «синее» и «зелёное», а приложения — это разделение на «круглое», «квадратное» и «треугольное». Важное правило в делении логики на приложения — Cohesion and Coupling. Если коротко, это о связности между приложениями: нужно добиваться состояния, когда у вас мало связей между приложениями и много внутри них.

Вот к этому нужно стремиться:

Как не допустить свалки в Django-проекте: MTV, services.py, новые приложения 2
Картинку взял из всё той же статьи

Между «разделёнными группами» связей мало, почти все связи спрятались внутри групп. Кружочки одного цвета — это части кодовой базы, которые сильно связаны друг с другом.

А вот такого стоит избегать:

Как не допустить свалки в Django-проекте: MTV, services.py, новые приложения 3

В первом случае связи хороши, но связанный код разбросан между «приложениями». Придётся часто заходить в разные приложения, чтобы внести правку в логику «жёлтых кусочков кода», например.

Справа совсем всё плохо: код разделён на такие маленькие группы, что каждый кусочек кода сам по себе стал «группой». Это называется ‘Destructive decoupling’.

Чтобы не попасть в одну из ситуаций выше, логику приложения нужно группировать по юзкейсам, а не по сущностям. Часто эти два способа дают одинаковые результаты, но так происходит не всегда.

Когда вы группируете логику в приложения, в первую очередь смотрите, какая логика постоянно совместно используется. Переводя в идею про Cohesion and Coupling — какие из кружочков связаны большим количеством стрелочек.

Если смотреть на разделение по приложениям так, то, если потянуть за одну функцию, за ней подтянутся и другие, сильно с ней связанные. Иногда часть связей этих функций будет неожиданной, хотя будет казаться, что складывать их вместе «нелогично».

В такой ситуации советую задуматься: а почему они оказались связанными? Может, вы неправильно выбрали название для приложения и оно должно включать в себя больше.

Не стесняйтесь создавать новые приложения.

Что делать, если одна модель не помещается ни в одно из приложений? Создайте для неё отдельное!

А что, так можно? В целом ПРИЛОЖЕНИИ будет всего одна жалкая моделька?!

Да, можно. Даже нужно.

Лучше создавать приложение сразу, как только стало заметно, что оно понадобится. Ведь когда у вас наберётся много логики, разбросанной по разным приложениям, будет уже поздно.

Процесс выделения этой логики в новое приложение будет болезненным, и, делая это, вы будете задаваться вопросом: «какой м**ак раскидал эту логику по приложениям?».

Очевидно, слишком увлекаться тоже не стоит. Иначе столкнётесь с Destructive decoupling. Но и бояться создания приложений не нужно.

Файлы

Ещё один слой разделения логики по фреймворкам — это файл. Мы касались темы файлов выше, давайте резюмируем уже сказанное:

  1. Свалка в файлах вредит навигации по проекту.
  2. Cohesion and Coupling поможет разложить логику по файлам.
  3. Не стесняйтесь создавать файлы, даже если кода внутри будет мало. Это поможет потом не страдать, выкорчёвывая функции из кода.
  4. Название файла должно задавать чёткую однозначную границу, говорить о том, какому коду здесь не рады. Иначе свалки не миновать.

На третьей мысли хочется остановиться подробнее. Страх создания файлов зачастую иррационален. У разработчиков редко бывает страх создания новой функции: если это нужно, вы без проблем вынесете кусок логики в новый, небольшой «контейнер» — функцию.

С файлами аналогично, это тоже контейнер для кода. Просто функции хранят в себе строчки кода, а файлы хранят в себе функции.

У ситуации «переборщили с количеством файлов» есть надёжный симптом — читая интересующий код, вы не задерживаетесь в одном файле больше, чем на 30 секунд. Всё время приходится двигаться по файлам туда-сюда. А это и есть нарушение принципа Cohesion and Coupling.

Итоги

Если во время чтения статьи вы успели потерять основные тезисы, то вот небольшой конспект. ?

  1. Services – не часть MTV;
  2. Плохое название файла приведёт к свалке;
  3. Можно писать запросы в слое View, не бойтесь этой практики;
  4. Монолит – не повод сваливать всё в кучу;
  5. Группировать по юзкейсам лучше, чем по сущностям;
  6. Не стесняйтесь создавать новые файлы и приложения, даже если кода в них мало;
  7. Разложить логику по файлам поможет Cohesion and Coupling.
Следите за новыми постами
Следите за новыми постами по любимым темам
3К открытий3К показов