Information
- Rating
- 1,141-st
- Location
- Санкт-Петербург, Санкт-Петербург и область, Россия
- Date of birth
- Registered
- Activity
Specialization
Бэкенд разработчик
Python
Clean Architecture
Асинхронное программирование
Многопоточность
Базы данных
Высоконагруженные системы
CI/CD
Мониторинг
Тестирование ПО
C
да и операция бесшовного релоада nginx бесплатная по сути, как будто нет смысла это обсуждать)
Статья больше как туториал, скорее у кого-то есть активные ws или sse соединения - graceful reload nginx позволяет не обрывать их без необходимости
На счет бд - да временное, я это несколько раз повторил в статье. В заключении даже отметил отдельным абзацем, вы разве не заметили?
Да, Swarm тут подходит. Но переносить VPS кмк не так уж часто приходится, поэтому не хочу усложнять инфраструктуру ради такого случая. Также можно сделать скрипт, который подготовит новый сервер и сам переезд.
Бутылочное горлышко пока-что это ресурсы сервера)
Все упирается в контекст кейса. У меня никаким хайлоадом особо и не пахнет, никакого бутылочного горлышка у бд нет.
Про порядок: я осознанно оставил БД на старой машине и перевозил сначала stateless. Все очень легко и просто - приложение легко откатить, а базу нет. Пока новое приложение не подтвердило стабильность, я не хочу трогать данные.
Да, чтобы хорошо выделить контракты, нужно очень глубоко понимать бизнес домен. Без знания бизнес процессов, ограничений и т.д - очень легко промахнуться с осью изменчивости. Поэтому да, согласен, правило трех хорошо работает на практике.
Подписался, у вас отличные статьи!
передавать* =) что-то с утра плохо соображаю
но термин sessionmaker не такой явный, в разных контекстах это называется по разному - например, DbContext, EntityManagerFactory, etc. Uow более универсален и сразу понятен читателю
Репозиторий отвечает только за одну сущность - save, update_status, delete, etc.
Uow отвечает за транзакцию и координирует несколько репозиториев в рамках одной транзакции. Это тоже про зону ответственности. По идее можно упростить и передовать sessionmaker и нужные репозитории в юскейс и там уже координировать
может сделаю в рамках другой статьи, но тема слишком слишком широкая
можно решить с помощью ретраев, можно сделать outbox pattern с кроном, да много чего еще. Но это выходит за рамки этой статьи. Тут только про clean architecture без доп сложностей
и еще тут немного дополню - сущность никак не должна знать про какие-то инфрастуктурные евенты, это не ее зона ответственности
Я пошел спать. Давай продолжим завтра, чтобы обсудить детали спокойно и с ясной головой
Это очень спорная тема, думаю, что нет смысла продолжать ее.
Все просто, поток примерно такой:
Транспортный слой - consumer получает событие из внешней системы
Он вызывает use case, передавая данные события.
Use case выполняет всю бизнес-логику: проверяет состояние сущностей, модифицирует их, взаимодействует с внешними системами, при необходимости генерирует события для других сервисов
Entity внутри use case - только контейнер для состояния и локальных процессов, никаких интеграций нет
Сущность не знает про event bus или внешние сервисы
И пример антипаттерна, которого мы избегаем:
В этом антипаттерне сущность сама знает про внешнюю систему event_bus - это приводит к сильной связанности и ломает принцип Clean Architecture
примеры игрушечные, но концепция, наверное, стала понятной.
Еще раз пишу, в который раз.
Сущность может оперировать только своими собственными атрибутами. Она не имеет права обращаться к другим сущностям, которые не хранятся в её полях, и уж тем более использовать внешние ресурсы или инфраструктуру (БД, API, файловую систему и т.д.). Любая логика, требующая проверки или изменения других объектов, должна выноситься в сервис или use case. Это фундаментальный принцип объектно-ориентированного дизайна: сущность отвечает только за себя, а не за весь мир.
Разве в примере этого не видно?
Сервисный слой(use cases) в любом случае остаётся, вопрос тут скорее не "сервис или нет", а в том, где живут бизнес операции, относящиеся к конкретной сущности.
Если такая логика находится в сервисах, то при росте системы мы получаем дублирование правил или вынуждены выносить их в хелперы, что увеличивает связность.
Например, допустим у нас есть invoice.signers, и сегодня это set, потому что нам важна уникальность. Завтра мы меняем его на list, потому что появилась необходимость хранить порядок подписей.
Если логика проверки "уже подписывал или нет" находится в нескольких сервисах, нам придётся менять её во всех этих местах.
Если же локальные бизнес-операции сущности инкапсулированы внутри сущности (например, invoice.add_signer() или invoice.is_signed_by(user)), то изменение set -> list затрагивает только саму сущность, а внешний код остаётся стабильным
Спасибо, что заметили поверхностность. Сейчас исправлю с "Доменная модель описывает бизнес-сущность и её правила." на "Доменная модель описывает бизнес-сущность и её локальные правила". Также добавлю небольшое уточнение
Domain model в моём примере не пытается инкапсулировать вообще весь бизнес процесс системы. Её задача в локальных инвариантах сущности:
допустимых переходах состояний
защите собственного состояния
правилах, которые принадлежат именно этому объекту Entity не должна отвечать за то, что ей не принадлежит (Привет, первый pattern из GRASP)
Да, в статье сейчас написано это немного неудачно. Исправлю.
Не понял почему "не самодостаточна даже для соблюдения своих собственных инвариантов", можете подробнее раскрыть, пожалуйста.
Это можно оформить и функцией, и классом - это уже вопрос стиля и архитектурных предпочтений. Например, я использую DI фреймворк и мне так более предпочтительнее.
Бизнес-сущность должна отвечать только за своё состояние и свои инварианты. Это её граница ответственности. Всё, что выходит за пределы управления одним объектом, а тем более задействует какие-то инфрастуктурные компоненты - выносим в юскейс.
Я как раз это и разбирал в разделе “Нюансы написания Use Cases на практике”. И там оставил как раз вопрос про best practice написания юскейсов, может вы знаете?
и да, если пользователь условно отправит евент через час или день - ничего страшного не будет, его может обработать любой воркер. Т.е я к тому что мы выставляем graceful timeout как максимальное время выполнения стадии в приложении
В нашем случае “просто поднять новые воркеры и переключить трафик” проблематично, потому что система построена как event-driven DAG-пайплайн с 7-9 стадиями на каждый доменный агрегат. Т.е для обработки каждой стадии у нас есть consumer, который прослушивает определенную очередь. Это сделано для того, чтобы пользователь мог постепенно контролировать и взаимодействовать с процессом генерации.
В rabbitmq переключить трафик можно:
Через bindings (создание + удаление) - сразу отметаем по понятным причинам
через разные blue/green exchange + прокидывать в приложение current_deploy(blue/green) - но это может привести к такому состоянию, когда веб и воркер могут работать с разными версиями current_deploy
в любом случае в самом приложении(не на уровне инфры) мы должны будем реагировать на sigterm и ожидать выполнения текущих задач