Как не допустить свалки в Django-проекте: MTV, services.py, новые приложения
В этом материале мы обсудим, как писать чистый код, и какие концепции и типовые ошибки превращают утончённые проекты в заросли и свалки.
4К открытий5К показов
Привет! Меня зовут Илья Осипов, я методист курса программирования на 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 нужно выносить бизнес-логику. Термин «бизнес-логика» кажется конкретным — это всё, что определяется правилами бизнеса. Но на поверку он размыт куда сильнее, чем кажется. Разработчики, глядя на функцию, иногда не могут сказать, бизнес-логика это или нет.
Куда девать общий код?
Альтернативное решение — оставить фреймворк в покое и не добавлять в него новый слой. Если появилась новая логика, то создать файл с говорящим названием и положить код в него. Если логики вынести нужно много — создайте несколько файлов. Искать нужную функциональность проще в одном мегафайле на тысячу строк или в пяти небольших по двести строк? Так будет куда понятнее, где, например, искать функцию, которая форматирует дату или преобразует цену товара:
Писать запросы в слое 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. Если коротко, это о связности между приложениями: нужно добиваться состояния, когда у вас мало связей между приложениями и много внутри них.
Вот к этому нужно стремиться:
Между «разделёнными группами» связей мало, почти все связи спрятались внутри групп. Кружочки одного цвета — это части кодовой базы, которые сильно связаны друг с другом.
А вот такого стоит избегать:
В первом случае связи хороши, но связанный код разбросан между «приложениями». Придётся часто заходить в разные приложения, чтобы внести правку в логику «жёлтых кусочков кода», например.
Справа совсем всё плохо: код разделён на такие маленькие группы, что каждый кусочек кода сам по себе стал «группой». Это называется ‘Destructive decoupling’.
Чтобы не попасть в одну из ситуаций выше, логику приложения нужно группировать по юзкейсам, а не по сущностям. Часто эти два способа дают одинаковые результаты, но так происходит не всегда.
Когда вы группируете логику в приложения, в первую очередь смотрите, какая логика постоянно совместно используется. Переводя в идею про Cohesion and Coupling — какие из кружочков связаны большим количеством стрелочек.
Если смотреть на разделение по приложениям так, то, если потянуть за одну функцию, за ней подтянутся и другие, сильно с ней связанные. Иногда часть связей этих функций будет неожиданной, хотя будет казаться, что складывать их вместе «нелогично».
В такой ситуации советую задуматься: а почему они оказались связанными? Может, вы неправильно выбрали название для приложения и оно должно включать в себя больше.
Не стесняйтесь создавать новые приложения.
Что делать, если одна модель не помещается ни в одно из приложений? Создайте для неё отдельное!
А что, так можно? В целом ПРИЛОЖЕНИИ будет всего одна жалкая моделька?!
Да, можно. Даже нужно.
Лучше создавать приложение сразу, как только стало заметно, что оно понадобится. Ведь когда у вас наберётся много логики, разбросанной по разным приложениям, будет уже поздно.
Процесс выделения этой логики в новое приложение будет болезненным, и, делая это, вы будете задаваться вопросом: «какой м**ак раскидал эту логику по приложениям?».
Очевидно, слишком увлекаться тоже не стоит. Иначе столкнётесь с Destructive decoupling. Но и бояться создания приложений не нужно.
Файлы
Ещё один слой разделения логики по фреймворкам — это файл. Мы касались темы файлов выше, давайте резюмируем уже сказанное:
- Свалка в файлах вредит навигации по проекту.
- Cohesion and Coupling поможет разложить логику по файлам.
- Не стесняйтесь создавать файлы, даже если кода внутри будет мало. Это поможет потом не страдать, выкорчёвывая функции из кода.
- Название файла должно задавать чёткую однозначную границу, говорить о том, какому коду здесь не рады. Иначе свалки не миновать.
На третьей мысли хочется остановиться подробнее. Страх создания файлов зачастую иррационален. У разработчиков редко бывает страх создания новой функции: если это нужно, вы без проблем вынесете кусок логики в новый, небольшой «контейнер» — функцию.
С файлами аналогично, это тоже контейнер для кода. Просто функции хранят в себе строчки кода, а файлы хранят в себе функции.
У ситуации «переборщили с количеством файлов» есть надёжный симптом — читая интересующий код, вы не задерживаетесь в одном файле больше, чем на 30 секунд. Всё время приходится двигаться по файлам туда-сюда. А это и есть нарушение принципа Cohesion and Coupling.
Итоги
Если во время чтения статьи вы успели потерять основные тезисы, то вот небольшой конспект. ?
- Services – не часть MTV;
- Плохое название файла приведёт к свалке;
- Можно писать запросы в слое View, не бойтесь этой практики;
- Монолит – не повод сваливать всё в кучу;
- Группировать по юзкейсам лучше, чем по сущностям;
- Не стесняйтесь создавать новые файлы и приложения, даже если кода в них мало;
- Разложить логику по файлам поможет Cohesion and Coupling.
4К открытий5К показов