Слоистая Архитектура на FastAPI / Onion Architecture

แชร์
ฝัง
  • เผยแพร่เมื่อ 16 ม.ค. 2025

ความคิดเห็น • 96

  • @artemshumeiko
    @artemshumeiko  ปีที่แล้ว +6

    🎓 Практический курс по Backend разработке: artemshumeiko.ru
    💡 Попробуй онлайн-тренажёр для подготовки к техническому собеседованию: clck.ru/3B5gwP 💡
    Забирай роадмап изучения самого востребованного фреймворка на Python - FastAPI здесь: t.me/ArtemShumeikoBot

  • @skyruptor337
    @skyruptor337 ปีที่แล้ว +50

    Хочу большой курс по sqlalchemy и alembic. Лайк стоит)

  • @FastAPIChannel
    @FastAPIChannel ปีที่แล้ว +21

    Хорошо когда есть кто-то, кто за тебя запишет видео по нужной теме)))))

  • @saitaro
    @saitaro ปีที่แล้ว +11

    Спасибо за видео! Артём, несколько моментов:
    1. В методе TasksService.__init__ в качестве tasks_repo мы ожидаем класс, поэтому аннотируем как type[AbstractRepository], а не AbstractRepository.
    2. Имеет смысл сохранять разные версии API в учебных (в этом случае) целях, чтобы можно было сравнить разницу архитектур.
    3. Может только мне, но при просмотре видео белая тема жёстко выжигает глаза, особенно ночью. Думаю, лучше использовать тёмную.

  • @TechnoBog-ov2mp
    @TechnoBog-ov2mp ปีที่แล้ว +3

    не канал, а находка 👍

  • @zoyaa9759
    @zoyaa9759 ปีที่แล้ว +1

    Понятно, спокойно, можно и за обедом послушать😊

  • @denvarenik4873
    @denvarenik4873 ปีที่แล้ว +1

    Познакомился с FastAPI 3 месяца назад. Начав работать сразу обратил внимание, что надо бы как то это все вынести подальше, в результате вышло, что то подобное на луковую архитектуру. Теперь знаю, как это правильно и читабельно упаковать, спасибо!
    Хотелось бы пример с фильтрацией, который можно так же переиспользовать!

  • @adammason482
    @adammason482 ปีที่แล้ว +1

    Спасибо за ролик, обязательно продолжай)

  • @AS-fk5fw
    @AS-fk5fw ปีที่แล้ว +2

    Превосходно 💯 даже несмотря на опыт коммерческой разработки на fastapi хочется поддерживать и смотреть с твоего канала для новичков, спасибо !!!

  • @begula_chan
    @begula_chan ปีที่แล้ว +1

    весьма интересно, однозначно лайк!

  • @АлександрСосо-щ1б
    @АлександрСосо-щ1б ปีที่แล้ว +1

    Спасибо, информативно! по большому счету данную практику можно применить и к другим языкам.

  • @victorkochkarev2576
    @victorkochkarev2576 ปีที่แล้ว

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

  • @Maks_travel
    @Maks_travel ปีที่แล้ว +1

    Спасибо за полезное видео!)

  • @JIJI-zv1qp
    @JIJI-zv1qp 2 หลายเดือนก่อน +1

    А чем отличается onion architecture от clean architecture?
    Там же еще есть mvc, mvp, mvt и возможно что то еще

  • @Olegt0rr
    @Olegt0rr 10 หลายเดือนก่อน

    22:51 указана неправильная типизация входного параметра tasks_repo, должно быть Type[AbstractRepository]. Тогда и подсказки для self.tasks_repo корректно будут работать без дублирования типа

  • @slava_zxz
    @slava_zxz 20 วันที่ผ่านมา

    Наконец-то я понял для чего нужны абстрактные классы

  • @МурадАхмедов-ч1с
    @МурадАхмедов-ч1с 27 วันที่ผ่านมา

    А где видео с unit of work?

  • @dronss1
    @dronss1 ปีที่แล้ว +2

    Артем, можешь пожалуйста записать видео с различиями между pydantic 1.* и 2.0?

  • @ЕвгенийБондарев-э1ц
    @ЕвгенийБондарев-э1ц ปีที่แล้ว

    А что с join делать? В смысле мы в методах получаем просто id для FK, а хорошо бы полную модельку получить с подтянутыми полями. Если в наследнике репозитория переопределить метод и добавить join, то смысл базового репозитория теряется

  • @povladis6940
    @povladis6940 ปีที่แล้ว +4

    ДАВАЙ КУРС ПО АЛХИМИИ И АЛЕМБИКУ, ПО ЧАСУ КАЖДЫЙ ДЕНЬ!

  • @СергейКим-ь9с
    @СергейКим-ь9с ปีที่แล้ว +2

    Артем, спасибо за видео! Лучший русскоязычный канал по FastAPI.
    Подскажите, а в каком слое правильнее обрабатывать исключения связанные с БД? Вроде бы логично в SQLALchemyRepository, ведь мы будем ловить конкретные ошибки SQLAlchemy, но, с другой стороны, мы ведь можем обрабатывать исключения в соответствии с какой-то бизнес-логикой, что кажется правильно держать в сервисном слое?

    • @AlexandrSpirit
      @AlexandrSpirit ปีที่แล้ว

      В FastApi есть HTTPException. Через него и выводишь ошибку пользователю в формате json

    • @anton6643
      @anton6643 ปีที่แล้ว

      Зависит от того, какие исключения. Если NotFound404 то я, например, бросаю это исключение из репозитория, но с возможностью управлять этим repository.get_one(raise_exception=True). При таком подходе можно заказать получение объекта либо None либо исключение в зависимости от логики. Все остальные исключения можно либо обрабатывать в сервисном слое, либо общим обработчиком fastapi, возвращая 500 server error и логируя ошибку куда-нибудь.

  • @ez9723
    @ez9723 ปีที่แล้ว +2

    Очень хороший и полезный урок, сам недавно на Java такое реализовывал. Но многие говорят, что это антипатерн и, действительно, сейчас работаю в месте, где проект на Flask и большинство запросов к БД реализованы внутри эндпоинта. Если для каждой из таблиц реализовывать CRUD репозиторий и сервис, то код МКСа вырастет для гигантских размеров

    • @СтаниславНектов
      @СтаниславНектов 3 หลายเดือนก่อน +2

      Ну так добро пожаловать на энтерпрайз уровень. Обычно когда много таблиц, большая часть CRUDа создаётся копипастой. А детали реализации вам так и так писать, разница лишь в том, где этот код лежать будет, только в одном случае у вас везде стандартный crud, а в другом "Вась, не помнишь где мы это написали?" И это, почему то люди предпочитают джипы и кроссоверы, а не малолитражки, хотя последние и проще в производстве.

  • @S1beria21
    @S1beria21 ปีที่แล้ว +4

    Что если нужно добавить некую логику присущую только для конкретного репозитория? Например в случае с тасками "удалить каждую вторую таску") Логично будет создавать на репозитории метод TaskRepository.delete_every_second_tasks. Однако дёргать из TaskService такой метод будет неверно (self.tasks_repo.delete_every_second_tasks) ведь абстракция AbstractRepository не имеет такого метода.

    • @maxrudn
      @maxrudn ปีที่แล้ว

      мне кажется ты в TasksService будешь принимать не абстрактный репозиторий, а TasksRepository просто. Ну и в TasksRepository ты можешь наследоватся и просто добавить нужные детали, или переопределить нужный метод.

    • @Chris-dx7oi
      @Chris-dx7oi 4 หลายเดือนก่อน

      Так, а ты решил этот вопрос для себя? Можешь поделиться, если да?

  • @МурадАхмедов-ч1с
    @МурадАхмедов-ч1с ปีที่แล้ว +1

    А подскажи можно ли сделать такую же архитектуру как в django с разделением на отдельные приложения или как аналог blueprint во flask, есть у fastapi какое то название для таких приложений и используют ли такой подход? Просто везде, где видел как пишут fast api, обычно всё роуты всех приложений кладут в одну папку routes, по другому с fast api разделение делают

    • @МурадАхмедов-ч1с
      @МурадАхмедов-ч1с ปีที่แล้ว +1

      Я разделил всю логику по отдельным папкам, но как разделить миграции, чтобы они брались из папок с приложениями

  • @MS-sf3pk
    @MS-sf3pk ปีที่แล้ว +1

    Каким образом лучше организовать код, если нужно написать специфичный sql запрос, который нужен только для определенного сервиса? Писать функцию внутри сервиса, либо создавать новый репозиторий?

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      Создавать репу и в ней писать запрос. Затем обращаться к репе внутри сервиса

  • @green1278dramost5
    @green1278dramost5 ปีที่แล้ว

    Спасибо за видео! Что ты лумаешь по поводу того, чтобы использовать Протоколы вместо абстрактных базовых классов? Протоколы позволяют задать имена и типы атрибутов класса/экземпляра, параметров методов, а также типы возвращаемых методами значений. Это не получится сделать с помощью ABC.

  • @TheFany666
    @TheFany666 ปีที่แล้ว

    Привет! Хмм а когда ты делаешь в find_all методе res = [row[0].to_read_model это не нужно никак обложить уровнем абстракции так же?
    Т.е такой кейс, ты меняешь алхимию на что то другое, откуда ты знаешь что у тебя модельки соответствуют колу to_read_model внутри реализации SQLNotAlchemyRepository?

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      Да, конвертацию модели бд в доменную модель приложения лучше вынести в отдельную сущность-класс

  • @iJaVolo
    @iJaVolo 3 หลายเดือนก่อน

    А почему в абстрактном классе в методах нет self, а просто пустые скобки?
    А что будет если добавить? Мне просто pycharm красным подчёркивает, но в ошибку не выпадает

  • @АлександрМайстришин-д7х
    @АлександрМайстришин-д7х ปีที่แล้ว

    круто! спасибо!

  • @PsychoBrain-c6h
    @PsychoBrain-c6h 2 หลายเดือนก่อน

    А почему скрыли основной курс по FaspAPI - 18 недоступных видео скрыто. Ранее было доступно. Верните пожалуйста.

    • @artemshumeiko
      @artemshumeiko  2 หลายเดือนก่อน

      Приходите на Практический курс по Backend разработке с 0 до production-ready кода: artemshumeiko.ru

  • @Johny-d4v
    @Johny-d4v ปีที่แล้ว

    Подскажите как импортировать класс через контекстное меню? Как называется такой плагин?

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      Вроде как встроен в Visual Studio code
      Я через CTRL + . вызываю его или правой кнопкой мыши

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      Возможно кстати это pylance

    • @Johny-d4v
      @Johny-d4v ปีที่แล้ว

      @@artemshumeiko спасибо за ответ. У меня одно из расширений vscode мешало работе. Удалил и все заработало!

  • @dzmitry2408
    @dzmitry2408 ปีที่แล้ว

    Подскажи, а нельзя в контроллере использовать что-то вроде:
    task_service: TaskServece = Depends(TaskService)?
    А в самом таск сервисе таким же образом получать репо?

    • @n_bazarov
      @n_bazarov หลายเดือนก่อน

      Можно, ищи Dependency Injector python

  • @best_coozy_dad
    @best_coozy_dad 8 หลายเดือนก่อน

    Спасибо за видео! Репозитории же по сути и есть DAO, или я что то путаю?

    • @artemshumeiko
      @artemshumeiko  8 หลายเดือนก่อน

      да, почти одно и то же

  • @linust5892
    @linust5892 4 หลายเดือนก่อน +1

    1. Переопределяешь сигнатуры дочерних классов, что не ок.
    2. Не показал, как работаешь с селектом и перегоняешь модели между слоями

  • @alexahdp
    @alexahdp ปีที่แล้ว

    А что насчет типизации? Можно ли получать из базы не абы что, а типизированный обьект/экземпляр класса?

  • @sergem6860
    @sergem6860 ปีที่แล้ว

    Скажите, а тайпхинт AbstractRepository в ините сервиса точно правильный? Судя по тому, что в ините tasks_repo вызывается (как конструктор), его тип не AbstractRepository, а класс этого репозитория (не знаю как правильно указать это в хинте)

    • @sergem6860
      @sergem6860 ปีที่แล้ว

      посмотрел, вроде так и указывается, Type[AbstractRepository]

  • @superpadush
    @superpadush ปีที่แล้ว

    А какой смысл каждый раз при вызове TaskService передавать в него TaskRepository? Разве не логичнее в конструкторе TaskService объявить что-нибудь а-ля self.repo = TaskRepository()?

    • @s1riys343
      @s1riys343 7 หลายเดือนก่อน

      Буква D из принципов SOLID - Dependency Inversion. В данном случае это значит, что TaskService не должен зависеть от конкретной реализации TaskRepository

  • @goodpins
    @goodpins ปีที่แล้ว +1

    Насчёт указания типа "абстрактный репо" - тоже такая себе идея. У тебя что, все репозитории одинаковы будут? Смысл тогда в них? Что будешь делать при специфичных задачах?

    • @Chris-dx7oi
      @Chris-dx7oi 4 หลายเดือนก่อน

      А какую альтернативу можешь предложить? Вместо указания абстрактного типа репозитория указывать конкретную реализацию? Думаю это может быть нарушением принципа D в SOLID, может есть ещё какие-то варианты или вообще иной подход к построению архитектуры?

  • @justyar5781
    @justyar5781 ปีที่แล้ว

    Хочу большой курс по алхимии и алембик =)

  • @suhanoves
    @suhanoves ปีที่แล้ว

    Зачем в абстрактных методах рейзить `NotImplementedError`?

    • @artyomklg915
      @artyomklg915 ปีที่แล้ว

      это нужно на время разработки, чтобы не забыть

    • @codEnjoyer
      @codEnjoyer ปีที่แล้ว +1

      На случай, если забудешь реализовать какой-нибудь метод у класса, который наследуется от абстрактного. Тогда при вызове не реализованного метода возникнет эта ошибка

    • @suhanoves
      @suhanoves ปีที่แล้ว +2

      @@codEnjoyer советую тогда попробовать не реализовать абстрактный метод у класса наследуемого от ABC и попробовать создать экземпляр)

  • @onikun2120
    @onikun2120 10 หลายเดือนก่อน

    Очень удобно получилось.
    Никто не поймет, пока не объяснишь.
    Люблю такие архитектуры

  • @sergeysergey421
    @sergeysergey421 ปีที่แล้ว

    Фабрика еще нужна что бы выбирать конкретную реализацию репозитория

  • @МурадАхмедов-ч1с
    @МурадАхмедов-ч1с ปีที่แล้ว

    Подскажите, кто в курсе, как он импортирует пакеты, чтобы они автоматически вставали вверх на свои места? Че то не попадался мне такой

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      Должно стоять расширение pylance и дальше при наведении на неимпортированную библиотеку появится всплывающее окошко, в котором можно будет импортировать, либо после наведения нажать CTRL + .

    • @МурадАхмедов-ч1с
      @МурадАхмедов-ч1с ปีที่แล้ว

      @@artemshumeiko ок спасибо, ато неудобно либо вспоминать либо копировать с других файлов приходилось

  • @teawithoutdonuts31
    @teawithoutdonuts31 6 หลายเดือนก่อน +2

    Какой-то Java EE. Нет никаких проблем писать sql в эндпоинтах. Он как раз гораздо понятнее и нагляднее там сидит. В крайнем случае для длинных запросов можно вынести выражение в отдельный модуль.
    Когда у вас с фронта просят ручки со вложенными сущностями вам нужно делать joinedload/selectinload. Где-то вам нужен User+Tasks, где-то только User. Вы на каждый такой вариант будете делать по функции в репозитории? Или передавать опции отдельным параметром, ещё больше всё усложняя?
    Модели и схемы можно очень быстро посмотреть одним кликом из эндпоинта, а вот прыгать по ORM надстройкам кастомным - это как раз разбираться нужно.
    Ну и по поводу притянутой за уши задачи уровня "смены sqlalchemy" - такое не происходит почти никогда. Да и на что? Вы скорее на другой язык переписывать будете.

    • @СтаниславНектов
      @СтаниславНектов 3 หลายเดือนก่อน +2

      Никто не может запретить писать плохо, каждый человек или команда пишет как ему удобно. Можно вообще все в одном файле написать, так просто сверх наглядно будет, я считаю. 😂 А уж как навариться на поддержке потом можно будет, долгими месяцами пытаясь найти баг - просто мечта. 😊
      При многослойной архитектуре вы не модель меняете в ручках, а вызов функции сервиса, или сервис правите не трогая ручку вообще. Работу со вложенными сущностями да и вообще при объединении данных нескольких таблиц вам так и так где то писать надо вне зависимости от архитектуры и ее наличия в принципе 😂 , можно конечно навалить все в эндпоинты, компьютер все стерпит.

  • @JIJI-zv1qp
    @JIJI-zv1qp ปีที่แล้ว

    а чем она отличается от чистой архитектура Дяди Боба

    • @JIJI-zv1qp
      @JIJI-zv1qp ปีที่แล้ว

      может про него тоже видос

  • @superninja2749
    @superninja2749 ปีที่แล้ว +1

    Здравствуйте, будет ли показан подход CBV?

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      В этом видео нет. В будущих - возможно

  • @xesax
    @xesax 8 หลายเดือนก่อน +1

    а это нормально что мы каждый раз будем писать with async_session_maker() ? в каждом методе

    • @artemshumeiko
      @artemshumeiko  8 หลายเดือนก่อน +1

      лучше объявлять сессию 1 раз уровнем выше. Часто объявляют сессию на уровне эндпоинта/ручки и прокидывают через Depends дальше в те модули, которым нужна сессия для работы с базой данных.

  • @romanbush5164
    @romanbush5164 ปีที่แล้ว

    Ахах спасибо Артем, хорошо было бы если бы ты в фишки pidantic посвятил)) и магия , классов настройки , валидации + у нас ещё используется dependancy_injector хорошо было бы осветить его. P.S. шарлотку поставил :DDD

  • @DangerSpliff
    @DangerSpliff 2 หลายเดือนก่อน

    Репозиторий не должен управлять транзакцией

  • @PlagueisMKII
    @PlagueisMKII ปีที่แล้ว +1

    class Config - тоже устаревший синтаксис. Новый : model_config = ConfigDict(**kwargs)

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

      Спасибо. Во втором видео про архитектуру поправлю это

  • @ammonjerro396
    @ammonjerro396 ปีที่แล้ว +1

    20к за курс? Оо

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว

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

    • @izpodvypodwerta
      @izpodvypodwerta ปีที่แล้ว

      уже 26к, скоро будет 400 000 минимум

  • @goodpins
    @goodpins ปีที่แล้ว +5

    Несколько вещей сделаны ужасно:
    почему в sqlalchemy модели метод для преобразования данный в пидентик? По твоим словам - что будешь делать, когда захочешь поменять orm? Также вопросы что будешь делать, когда некоторые связные модели подгружать не нужно будет
    Далее про инициализацию репозитория в инициализаторе сервиса + открытие сессии ради каждого метода репозитория - вообще орнул. Тебе не больно, когда ради 2-3 вызовов репозитория в одном сервисе ты будешь открывать на каждый новую сессию алхимии? Пропихнул бы в своем DI сессию ОДНУ и работала бы во время обработки запроса - так экономишь ресурсы

  • @artem1736
    @artem1736 ปีที่แล้ว +1

    нужно избавляться от глобальных переменных, в частности от глобального engine и async_session_maker.

    • @скриптослав
      @скриптослав ปีที่แล้ว

      зачем?

    • @artemshumeiko
      @artemshumeiko  ปีที่แล้ว +3

      engine используется только для одной цели - передачи в фабрику async_sessionmaker. Фабрика в дальнейшем используется для получения сессий, причем значение переменной, хранящей фабрику, нигде не изменяется. Зачем уходить от использования глобальных переменных в данном случае?

    • @andreyarefev445
      @andreyarefev445 ปีที่แล้ว +2

      Зачем нам множество фабрик сессий в проекте? Зачем тогда нужен будет async_session_maker если он не будет глобальным?

    • @wwqwqwwqwq
      @wwqwqwwqwq ปีที่แล้ว

      @@andreyarefev445 ты точно понимаешь во что вытекают глобальные переменные? 1. Ты не контролируешь жизненный цикл переменной, например в твоем случае engine (не можешь очистить пул коннектов, выполнив dispose). 2. Весь твой код, буквально весь код, где используется глобальная переменная становится зависим от 1-ой казалось бы переменной, что вытекает в сложную поддержку и тестирование (сложно написать юнит тест, сложно подменять объект, сложно подменять его конфигурацию). 3. Ты привел в пример множества фабрик на проекте, но ты же понимаешь, если ты захочешь в какой-то момент в репозитории иметь подключение к другой БД, следовательно ты должен будешь завести новый engine и async_sessionmaker и подменить их там где нужно, то тебе надо будет менять весь код, который использует предыдущие глобалы. Вот и тебе переиспользование кода, о котором говорится в видео)

    • @bocik2854
      @bocik2854 ปีที่แล้ว

      тишка, ты?

  • @k3l3vr444
    @k3l3vr444 ปีที่แล้ว

    Кажется, не хватает dipendecy injection

  • @chelseamurphy6799
    @chelseamurphy6799 ปีที่แล้ว

    *promo sm*

  • @RasulovENG
    @RasulovENG ปีที่แล้ว +3

    Предлагаю на английском языке вести чтоб пацаны развивались

    • @victorkochkarev2576
      @victorkochkarev2576 ปีที่แล้ว +1

      Зачем? Есть много курсов и материалов на английском.

    • @yevhenKaskov
      @yevhenKaskov ปีที่แล้ว

      на англ. весь интернет забит материалами. А на русском как раз мало. Особенно когда надо понять какие-то абстракции.

  • @anton6643
    @anton6643 ปีที่แล้ว

    Неплохо, но не до конца. Нужно добавить дженерик в объявление абстрактного репозитория и возвращать его в методах.