Реклама
Комментарии 26
Маленькая поправка инверсия зависимостей это буква D в SOLID — dependency inversion
Я уже год хожу вокруг да около этой идеи со слоями. Но я спотыкаюсь о структуру папок.
Можете свою показать?
В простом случае будет практически тоже самое, что в примере github.com/zueve/rentomatic:
project/
   services/
   adapters/
   rest/

причем если следовать заветам, то содержимое services можно прямо в корень project кидать — ибо «приложение должно кричать»
Да, именно, entity — выделяются из services, по мере необходимости, но начинаю проект именно с такой простой структуры

А в чём заключаются преимущества этого подхода по сравнению с выстраиванием сервиса вокруг данных и операций над ними?


Я возможно смотрю с какой-то странной колокольни (у меня в основном всякие упоротые сервисы: поиск по картинкам, антифрод Авито из сотен rabbitmq-воркеров, просто inference ML-моделей и тому подобное), но никогда не понимал смысла этой движухи.


Она начинает работать только с какого-то определенного объёма проекта или как?

> Она начинает работать только с какого-то определенного объёма проекта или как?

И да и нет, в общем случае действительно можно констатировать, что актуальность проблемы снижается в эру микросервисов. Программист физически не может импортнуть в свой микросервис модуль из другого микросервиса. Тем самым установив между ними связь в обход API.
Другой момент, что многие микросервисы несут утилитарный характер и содержат минимум бизнес-логики, во некоторых моих сервисах, services занимает около 10% от общего объема кода.
Но, даже в таком случае я вижу все преимущества о которых говорится в статье, к тому же, микросервисы также имеют свойство расширяться, и стало быть применение Сlean Archetecture это задел на будущее.
Еще один бонус — в микросервисной архитектуре сервисы в основном взаимодействуют друг с другом посредством обращения к их АПИ, и это очень хорошо вписывается в предложенную модель, если ORM нас как-то абстрагируют от БД, то обвязки API придется писать самим.

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

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


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


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


Так недавно к нам приезжал Крис Ричардсон и рассказывал про то, как правильно делать микросервисы: он говорил, что должен соблюдаться принцип "один микросервис на команду", а у нас на команду из 7 человек уже полтора десятка микросервисов и это действительно удобно.

На всякий случай добавлю, что необходимость включать мозг при добавлении новой функциональности ни один из подходов не отменяет :)

На самом деле, один из основных посылов, который я бы хотел донести, что сейчас python позволяет реализовать данную модель без особого оверворка, да, результат получился больше чем могло бы быть решение в лоб, но совершенно минимально, зато теперь определен вектор дальнейшего развития, во что бы он в конечном счете не превратился

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

Думаю для вашего примера нужно просто нафигачить sql в контроллере и не парится. После этого можно задаться такими вопросами:


  1. Что должно измениться в проекте, чтобы sql в контроллере перестал быть самым простым и понятным решением?
  2. Какие есть вообще варианты ответа на возникшее изменение?
  3. Чем выбранный вариант, лучше всех остальных?

Если на эти вопросы отвечает человек с хорошим техническим кругозором и знанием предметной области — будет вам хорошая архитектура. А clean, onion, MVC, все что угодно это попытки предложить фиксированный набор примитивов, в терминах которых нужно описывать ваш бизнес. Это никогда не работает в долгосрочной перспективе и всегда добавляет уйму ненужного кода.

> нужно просто нафигачить sql в контроллере и не парится

Совершенно верно, пример (не мой) совершенно гротескный, зато в нем хорошо видно тот минимальный оверворк, который несет предложенный подход, посмотрите еще раз он совершенно не большой.

> Что должно измениться в проекте, чтобы sql в контроллере перестал быть самым простым и понятным решением?

Мой опыт говорит что ответить на этот вопрос в сколь-нибудь отдаленной перспективе совершенно невозможно, даже в данном примере, бизнесу может потребоваться все что угодно:
— ранжирование на основе предпочтений пользователя
— добавление в топ партнерских материалов
— расчет скидок,
а еще отправить евент о событии и тд.
И спрогнозировать во что это может вылиться совершенно невозможно, сколько не погружайся в предметную область, потому, что потребности бизнеса имеют особенность постоянно меняться, такова его природа

> всегда добавляет уйму ненужного кода

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

Кажется бизнес тут совсем ни при чем. Вот моя попытка ответить на эти вопросы. Исходная ситуация — есть функция, которая мапит запрос в sql, выполняет его и возвращает результат.

Что должно измениться в проекте, чтобы sql в контроллере перестал быть самым простым и понятным решением?

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

Какие есть вообще варианты ответа на возникшее изменение?

— Сложный мапинг — специфический builder объекты, query object, просто вытащить часть логики в другой объект, назовем его «сервис», «репо», «контекст» — не важно, лишь бы другие члены команды были согласны, что все по фен-шуй.
— Повторное использование мапинга — опять же builder/criteria, возможно репо. Мой личный фаворит для C# — extension method для DbContext или builder/criteria.
— Хитрое «выполнение» с кешами, шардингом и т.п. — можно все прямо в контроллере сделать, дернуть кеш или из shard manager получить соединение с нужной базой. Но тут возможны варианты.

Сложные объекты с логикой внутри ala DDD появятся только если
— Есть логика, эквивалентная машине состояний с хотя-бы десятком состояний.
— Нужно обрабатывать в памяти сложные объекты. Пример — счет клиента на бирже, связанные с ним ордера, балансы и всякие настройки комиссии и прочее.
Привет. Спасибо за подробную статью!

Для всех интересующихся чистой архитектурой и DDD могу посоветовать посмотреть на проект dry-python — набор библиотек для написания сложных бизнес приложений.

Пример как это выглядит на живом репозитории: github.com/dry-python/bookshelf

Доклад clean architecture для django приложений: www.youtube.com/watch?v=tKEv9Enhm1Q
Этот живой репозиторий — по сути просто CRUD-обёртка, для него точно требуется так много папочек с абстрактными именами? :)

Дорогой @profit404, я даже добавил ссылку на dry-python в конце статьи, но почему же Вы выкатили свои ссылки, не добавив столь нибудь существенного комментария по тексту статьи (

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

Прочитал про data classes и подумал: почему ничего про них не помню? Оказалось, всё просто: потому что, если используешь attr.s, то они нафиг не нужны :)

Узнал про этот модуль в монументальном цикле статей "asyncio: We Did It Wrong ", проникся и с тех пор без него как без рук.

В оф. документации написано, почему эта штука лучше, чем data classes, named tuples и т.д.

Очень коротко: нам предлагают больше плюшек при том, что хотят от нас меньше бойлерплейта и оперативки
Если посмотреть пример, то там используются не нативные датаклассы, а pydantic, и в от личии от attrs они обеспечивают валидацию входных данных, сериализацию, и генерацию схемы для openapi. Must have pydantic-docs.helpmanual.io
О как Не слышал про них, а в примерах не понял, что это шаблоны классов. Спасибо, поизучаю.

Выходит, статья ещё полезнее, чем мне показалось сначала :)
Вот хороший пример реализации — мне сильно помог разобраться в теме. Жаль только, что автор забил на него.
Раз Вы уже разбирались, можно совсем кратенько, что там хорошего? А то исходники представляют из себя дерево с миллиардом веток и никаких пояснений
Да, проект Леонардо Джордани весьма запутан, согласен.
Однако, в приведенном Вами «плохом» примере от Леонардо присутствует валидационая обвязка, которая в Вашем «хорошем» примере неявно скрыта внутри FastAPI. Не знаю, что мешало Леонардо ввести в его код это логичное усовершенствование.
Если за определение MVC брать статьи habr.com/ru/post/321050 и habr.com/ru/post/322700, то что в старом примере, что в новом MVC один и тот же.
Только полноправные пользователи могут оставлять комментарии. , пожалуйста.