Pull to refresh

Comments 16

Знаю кейсы, когда люди тянули DjangoORM в проекты на FastAPI для удобства работы со знакомым инструментарием. Теперь изобретать велосипед нет необходимости, можно смело переходить на FastAPI, имея опыт работы с Django. Cтавлю жирный плюс и желаю развития проекту!

Довольно часто вижу ситуацию, когда к FastAPI параллельно тянут Django для удобной админки, что, как по мне, очень неудобно. Будем надеяться, что ещё увидем реализацию какой-нибудь удобной админки под FastAPI + Tortoise (FasAPI Admin крайне неудобна, к сожалению)

Очень хороший туториал для знакомства с библиотекой.
SQLAlchemy в любом случае mast-have решение для крутых high-load проектов.
Но новичкам осилить его с наскоку не так то просто, и да при всей прелести SQLAlchemy, подача материала в документации на мой взгляд ужасная.
Ни в коем случае не в упрек разразботчикам, просто контастирую факт.

Для хайлойда несомненно, но если нужно быстро собрать МВП проекта без боли, тортозка покрывает все*(ну почти)

Как ORMка связана с хайлоадом?

В тортозке более удобный, но менее гибкий Query Builder.

Алхимия же позволяет писать более гибкие запросы, считай, почти RawSQL, но не совсем, за счет чего в определенных местах можно выигрывать по производительности оптимизацией

В тортозке под совсем напряжные запросы можно писать RawSQL, но делать этого чаще всего не хочется.

Но новичкам осилить его с наскоку не так то просто

Зависит от того, что у новичков за плечами. Если они работали с SQL - то как раз SQLAlchemy будет более понятен и очевиден. А ORM для них будет какой-то магией, в которой неясно, что происходит под капотом и их знания SQL им вряд ли сильно помогут

Для того, чтобы работать с SQLAlchemy, приходится прокидывать db_session от контроллера вниз по слоям абстракции, что крайне неудобно

DI - я для вас просто шутка? Именно данная практика является верной при построении многослойной архитектуры приложения, где внешние зависимости (например в нашем случае, подключение к бд и сетевой запрос от клиента) используют БЛ, и БЛ вообще не должна знать, что и откуда ее использует!

И никто не заставляет вас прокидывать именно подключение или сессию к бд, нередка практика заворачивания всех таких объектов (или их геттеров с кастомной логикой) в какую-нибудь обертку с дальнейшим ее прокидыванием (например Axum.rs использует понятие State для подобных вещей). В итоге и волки целы, и овцы сыты)

Лично мне не нравится стиль написания запросов в SQLAlchemy. Приходится писать много ненужного кода, чтобы составить простецкий запрос. Да, повышается уровень контроля над ними, но даже самые простые могут занимать по 3-4 строчки

...и дальнейшие замечания

Опять же, SQLAlchemy (даже само ORM, а не Core) - менее низкоуровневый инструмент по сравнению с TortoiseORM или DjangoORM. Но не стоит обманывать себя - одни не лучше других, просто SQLAlchemy дает в разу больше уровней взаимодействия, настроек и прочих плюшек для работы с бд, а ORMки Tortoise и Django - высокоуровневые вещи, в которых как раз меньше контроля и шаг вправо - шаг влево -- сложно сделать.

Так можно сравнивать что угодно, например FastAPI и Django: зачем писать ручками в FastAPI, если есть DRF. Во втором просто написал модель - и всё, урлы, контролеры, сериализаторы готовы! А в первом - всё ручками-ручками)))

P.s.: в целом больше видно, что вы сталкивались с простыми кейсами и задачами быстрого прототипирования, а не с какими-то неординарными и хоть немного сложными кейсами. Вот там-то приходится изловчаться с ORM, в то время как на SQLAlchemy спокойно делается в несколько очевидных строк. Я не хочу вас как-то принизить, мой посыл в том, что универсальных инструментов не бывает и для каждой задачи нужно подбирать свой индивидуально

Я с вами полностью согласен по поводу замены алхимии/любой другой ОРМ тортозкой!

TortoiseORM ни в коем случае не рассматривается как полная замена алхимии, однако чаще всего (как мне кажется) на проектах сталкиваешься с чём-то простым, нежели неочевидным или сложным. Неочевидный запрос можно написать и там, и там, но на тортозке, без сомнений, придётся изощряться намного сильнее. Однако, на простых проектах (в плане запросов к БД) тортозка как раз таки выигрывает в скорости написания кода и простоте в сравнении с алхимией. Поэтому я и согласен, что нет лучших, есть хороший инструмент под нужды проекта.

А DI в FastAPI есть, но, насколько мне известно, только как раз таки на уровне контроллера. Я почти уверен, что можно что-то придумать со сторонними либами для DI. Так что чтобы получить DI на уровне бизнес логики, тебе ещё нужно докрутить логики и нести доп. зависимости в проект. Не минус, но такое есть. Либо ленишься один раз написать DI и кайфовать, либо дедовским методам прокидываешь по слоям абстракции, попутно жалуясь на это в отдельном пункте в статье, потому что за тебя что-то недодумали более умные и мотивированные люди (в частности имеется ввиду стек FastAPI+SQLAlchemy, понятно, что кастомный DI только кастомом)

А DI в FastAPI есть, но, насколько мне известно, только как раз таки на уровне контроллера

А где они еще нужны?

Еще по заветам незабвенного Дяди Боба, вы не должны позволять технологиям, которые вы используете, диктовать вам то, как слои проекта должны общаться друг с другом! И ладно бы речь шла о фреймворке вроде Django, но речь то о FastAPI, который чисто предоставляет роутинг и работу с запроса-ответами. Да, он также дает функционал и по хукам цикла жизни самого приложения (которые не обязательны и могут быть заменены), но смысл как раз в том, что вы и должны использовать FastAPI не более чем как слой роутинга со всякими удобными плюшками. БЛ не должно знать, что его вызывает - роутер, вы из консольки, gRPC или вообще кто или что угодно!

Я почти уверен, что можно что-то придумать со сторонними либами для DI. Так что чтобы получить DI на уровне бизнес логики, тебе ещё нужно докрутить логики и нести доп. зависимости в проект. Не минус, но такое есть

Всегда также можно написать что-то свое (обычно так делаю, там ничего особо сложного, если знаешь, что хочешь, + контроль имеешь и представление о том, как и что у тебя работает), или посмотреть в сторону продвинутого DI - IoC-контейнера

А DI в FastAPI есть, но, насколько мне известно, только как раз таки на уровне контроллера. Я почти уверен, что можно что-то придумать со сторонними либами для DI. Так что чтобы получить DI на уровне бизнес логики, тебе ещё нужно докрутить логики и нести доп. зависимости в проект. Не минус, но такое есть. Либо ленишься один раз написать DI и кайфовать, либо дедовским методам прокидываешь по слоям абстракции

Почитайте что такое DI и как правильно его использовать. Скорее всего вы путаете DI с Service Locator(DI vs Service Locator)
Зачем нужен "DI в бизнес-логике", если эту бизнес-логику вызывает контроллер, в котором и собираются(используются собранные) зависимости? Представим обычный флоу в архитектуре Порты и Адаптеры

Main -> Controller -> Service Layer -> Data Layer
1. Main - всё что связано с инициализацией проекта. Например в нашем случае это инстанс FastAPI, engine алхимии, etc. Так же здесь собираются все зависимости(Репозитории для Data Layer, Инстансы сервисов для Service Layer(будь это отдельный класс для отдельного интерактора в рамках юзкейса или один большой класс, где каждый публичный метод это интерактор)
2. Controller - получает зависимость в виде интерактора и вызывает её.
3. Service Layer - вызывает метод репозитория из Data Layer
4. Data Laye - использует зависимость(сессия/engine) которую прокинули при инициализации в 1 пункте

Зачем здесь, например в Service Layer, отдельный какой-то Service Locator, если изначально был резолв зависимости самого Service Layer?

К тому же, советую немного разобраться как работает Depends в FastAPI. Я бы не назвал это нормальным DI, т.к что бы он таковым мог называться, нужно очень много костылять(например у него один глобальный scope). Depends в FastAPI я бы скорее использовал для сбора запроса(query/body параметры и etc.) а для реальных зависимостей использовал более подходящие инструмент. Чего только стоит использование threadpool'а на резолв любой sync зависимости(у тебя 10 Depends, которые внутри просто парсят header/body/etc? Держи 10 потоков. У тебя больше 4 RPS? Пятый запрос будет ждать, пока у всех остальных запросов исполнятся Depends, т.к стоит лимит в 40 тредов).
Мы сейчас используем python-dependency-injector, но минусов у него пруд пруди. Сейчас "на хайпе" dishka, т.к активно поддерживается, написан "местным" разработчиком ну и в принципе адекватный продукт(как минимум он не написан на чистом C, как p-d-i)

Сравнивать с Алхимией — гарантированно нарваться на холивор) Но всё таки...

Что лично для меня стало killer features при выборе Tortoise вместо Алхимии для интеграции с FastApi?

Tortoise выигрывает за счёт изначальной асинхронности. Это тот же самый asyncio event loop что и в FastApi, безо всяких гринлетов. И когда ты изначально пишешь только в асинхронном контексте, минимизируя блокирующий код, FastApi, действительно, начинает работать быстро. Например, asyncpg, как в Яндексе утверждают, миллион строк в секунду читает. При этом, асинхронный код в Python — это отдельное умение, там до сих пор есть свои сложности и грабли, и когда у тебя и на обработке HTTP и на БД один и тот же async/await, жить с этим намного проще, чем с гринлетами в паре с asyncio.

Интеграция с Pydantic из коробки. Автоматическое построение моделей Pydantic из моделей Tortoise, включая вложенные модели для внешних ключей. То есть, буквально, прописываешь несколько параметров, и на выходе — вложенные JSON-структуры (причём вложенностью можно декларативно управлять в конфиге модели Tortoise).

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

PyPika под капотом — можно применять уже готовые решения, расширяя функциональность. Писать собственные сложные селекторы и так далее.

Aerich — действительно простой и понятный менеджер миграций. С Django, конечно, ни один не сравнится, но после иссушающего мозг Alembic — довольно круто.

Ну, то есть, да, Алхимия — это огромный треш-комбайн из аниме-боевика, который может вообще всё на свете. Только вот правило, что в 80% случаев вам будет нужно только 20% функционала, здесь тоже работает. И эти редкие возможности, которые есть в Алхимии и нет у других ORM, вам просто, скорее всего, на большинстве проектов просто не понадобятся.

Теперь про то, что в документации описано плохо и вызывает проблемы с пониманием:

1) Конфигурирование коннектов к БД. Понять, как это всё устроено, предлагается интуитивно — а интуитивно оно там далеко не всегда.

2) Тестирование с помощью pytest: тоже с полпинка копипастой из документации не заведётся.

3) Fetch данных из внешних ключей (и, особенно, M2M связи): с непривычки к асинхронному контексту поначалу будет многое подбешивать. Это похоже на Django, но это НЕ Django )

Но это и плюс, то есть, например, в отличие от Django, всё очень радует в плане тайпхинтинга.

В целом же, горячо призываю попробовать.

Спасибо большое за ваш комментарий! Очень интересно почитать про ваш опыт!

По поводу тестов с pytest - да, с докой большие проблемы. И очень жалко, что нигде не расписано по-человечески. Нужно угрохать большое количество времени чтобы просто завести под postgres)

  1. Интеграция слоя работы с БД со слоем фреймворка прекрасно делается с помощью DI-фреймворков. А в иделе вообще её нет, так как к БД вы обращаетесь из бизнес логики, а не фреймворка. Это позволяет сделать код гибче и более тестируемым

  2. Поддержка асинка в алхимии есть, не понимаю этот пункт вообще.

  3. Автоматическое построение моделей pydantic из моделей ORM - вещь немного проклятая. Сначала кажется крутой, но постепенно различия накапливаются, у вас появляется несколько версий модели пидантика. В конце концов вы все равно начинаете писать мапперы вручную.

  4. Поддержка несокльких субд как раз в тортойзе сделана хуже. Если у вас уже есть модуль работающий с БД, то вы не можете просто так взять и его юзать - он начнет обращаться не к той базе. В результате все такие модули надо параметризовать именем базы с которой они связываются, если вы действительно хотите их как-то независимо пилить (а иначе зачем вам несколько баз). Становится более похоже на прокидывание везде сессии алхимии только без её преимуществ

  5. Пробелма суждения "нужно только 80%" в том что вы не знаете какие именно 80%. Фреймворк должен уметь ~100%, ваш код - сколько вам надо. Менять фреймворк потому что понадобилась какая-то мелкая фича - неконструктивно

Это всё верные рассуждения, при условии, что мы согласны с убеждением, на котором они строятся. С убеждением, что возможно существование некоего идеального универсального подхода ко всему на свете, а если и невозможно, то надо к этому максимально стремиться. Этот взгляд имеет право на существование, конечно, почему нет. Но меня вот лично жизнь в этом разубедила.

Универсального "подхода" нет, есть универсальные принципы, они достаточно абстрактны и гибки. А есть ещё универсальные антипаттерны - их особенность в том что они не решают никаких проблем (кроме проблемы "мне не нравится другой подход"), а потом сами становятся проблемами.

Sign up to leave a comment.

Articles