Информация
- В рейтинге
- 5 315-й
- Откуда
- Санкт-Петербург, Санкт-Петербург и область, Россия
- Дата рождения
- Зарегистрирован
- Активность
Специализация
Фулстек разработчик, Архитектор программного обеспечения
C#
SQL
ООП
.NET
Visual Studio
Git
PostgreSQL
REST
React
TypeScript
«Медленная разработка: Любое изменение требует полного регресса (везде ли всё поменяли) и согласования со всеми.»
«Масштабирование невозможно: При пиковой нагрузке на распродажах «падает» весь монолит, включая складской учёт т.к. передаётся слишком большой объём данных из-за разросшегося агрегата.»
«Когнитивная перегрузка: Новый разработчик должен понять все 50+ полей и методов, даже если работает только над каталогом.»
Абсолютно не согласен с этими утверждениями.
Команда из 10 человек может прекрасно развивать сложный монолит, тогда как для распределённой системы с той же логикой потребуются сотни людей. Разработка в монолите быстрее: только на согласование контрактов между сервисами уходят недели, плюс отладка взаимодействий.
Вместо одного лога и стектрейса — вам нужна распределённая трассировка по десяткам сервисов. Поиск причины сбоя превращается в детективное расследование.
Любой вызов теперь уязвим к таймаутам, сетевому лавинообразованию, проблемам DNS. Вы тратите больше времени на написание устойчивого к сбоям клиента, чем на бизнес-логику.
Независимое развёртывание — миф, если изменение API одного сервиса требует синхронного обновления трёх других. Версионирование API, устаревшие клиенты, graceful degradation — это отдельная инженерная дисциплина.
Масштабирование более чем возможно. Появились распределённые SQL-базы данных нового поколения, такие как YugabyteDB, CockroachDB, Google Spanner. Они предлагают строгую согласованность, горизонтальное масштабирование на уровне данных и распределённые ACID-транзакции — всё то, ради чего мы дробили монолит и отказывались от транзакционности.
В мире микросервисов разработчику необходимо понимать не меньше, но только он не может быстро посмотреть код, который происходит где-то там за границами сервиса
Микросервисы это не способ борьбы со сложностью, это про другое: разный технологический стек, независимые команды с разными циклами разработки и деплоя (при условии что контракты не меняются), жесткие нормативные или юридические требования, четко выделенные ресурсоёмкие специфичные задачи.
Государство всегда было есть и будет инструментом в руках правящего класса. Вряд ли кто то на серьезных щах будет отрицать классовый характер общества. Рабы и рабовладельцы, крестьяне и феодалы, капиталисты и наемные рабочие. Это антагонистические классы, и пока они существуют, будут войны и эксплуатация человека человеком
Да просто автор недостаточно упростил, представьте мир, в котором есть только 1 цех по производству хлеба и больше вообще ничего не существует, ни моря ни солнца ни луны ни других планет. Этим цехом владеет капиталист, один единственный и на него работают 9 рабочих. Пусть цех в день производит 10 буханок хлеба чтобы накормить каждого. Получается что капиталист должен платить каждому рабочему столько, чтобы хватило на хлеб и один хлеб он присваивает себе. Вот и весь капитализм. Тут стоит отметить, что если капиталист становится директором, управляет производством и прочее, то он такой же работник как и другие, в этом случае нет никакой эксплуатации, если же он не участвует в производственном процессе, то это явная эксплуатация основанная на частной собственности на средства производства
Вот смотрите простой пример: у вас есть товар на складе, кол-во товара не может быть меньше нуля. Теперь есть поступление товаров на склад, при создании поступления нужно увеличить количество товара на складе, при удалении - уменьшить. Вот вам первый сервис, который занимается поступлениями и меняет объем товаров на складе. Так же есть к примеру отгрузка со склада, при создании отгрузки, товар на складе уменьшается, а при удалении увеличивается, второй сервис. В первом вы описали логику что при удалении поступления нужно проверить хватает ли товаров на складе, а во втором сервисе при создании объекта забыли.
В моем случае это забыть невозможно, в самом товаре на складе свойство количество имеет приватный сеттер, и есть 2 публичных метода добавить товар на склад или убрать его, в которых есть все необходимые проверки.
Смысл повторять из раза в раз одни и те же проверки в каждом сервисе? В этом ведь и заключается бонус от ООП, чтобы данные и действия над данными были ассоциированы и лежали в одном месте. В конце концов никто не заставляет вас писать экземплярные методы, можете использовать статику для работы с коллекциями.
Ну и напоследок, проверить изменения домена на ревью сильно проще, чем проверять все возможные места, где вы могли бы изменить сущность, в вашем примере это любой доменный сервис и любой контроллер. Да и изменение существующего бизнес правила будет проще.
Да ну вы тоже так говорите, как будто никогда не видели как это может быть, когда программисты напишут вам прямо в контроллере доменную логику? Да ну сколько угодно.
Сервис на сущность это конечно здорово, но будет сервис на другую сущность и в нем забыв о каком то бизнес правиле изменят первую сущность, такое встречается? Да сплошь и рядом.
Конечно можно наплодить Client2 и Client3, обратиться к бд из контроллера, но за такое скорее всего если и не уволят, то явно притормозят карьерный рост. А вот за то что ошибся и забыл бизнес-правило явно так никто ругать не будет, а забыть то не трудно, если предметная область сложная.
Опять же для чего тогда вообще на уровне сценария получать сущности из репозитория, если все бизнес действий в сервисах, не проще ли вообще закрыть доступ к сущностям и наружу светить только методами сервисов, хоть какая то изоляция домена.
Разница в том, что код по изменению сущности в вашем подходе может быть разбросан в любых сервисах и в любых слоях по всей программе, а в моем он будет сосредоточен именно в этой сущности. И сущность будет обеспечивать инварианты, в вашем случае обеспечить инварианты не получится, т.к. нет явной ассоциации между данными и действиями над этими данными.
Проще говоря вы можете изменить сущность на уровне приложения обойдя все бизнес правила, не меняя код домена. В моем подходе этого сделать не получится, придется изменить сам домен.
Вы же не сервис получаете из репозитория, а саму сущность. Я так понимаю что на уровне сценария (в моем понимании это уровень команд) существуют обращения к сервисам и к репозиториям. Из репозитория вы можете достать сущность и поменять свойства как хотите и сохранить сущность. Для этого не нужно менять код сервиса или самой сущности.
В моем примере вообще никаких сервисов домена нет, логика написана в самой сущности, более того client.Remove() наследуется от BaseEntity, там он помечен как protected, т.е. на самом деле вы не сможете вызвать его где то еще и только сама сущность будет определять свое поведение.
Теперь по поводу изменений самого класса Client, во первых изменение доменных сущностей сразу бросается в глаза, это будет очень заметно на ревью, это намного проще чем скрупулёзно изучать что там разработчик написал в сценарии/сценариях. Во вторых наличие приватного сеттера явно указывает на то, что просто так это свойство меняться не должно. В третьих проектирование доменных сущностей чаще всего ответственность опытных разработчиков. В четвертых обеспечивается инкапсуляция не только на уровне сценария, но так же и на уровне самого домена, анемичная сущность ничего о себе не расскажет, где гарантия что на уровне другого доменного сервиса вы не измените ее как то не валидно, забыв о том или ином правиле? В моем понимании доменные сервис - редкий случай, когда бизнес действие нельзя напрямую отнести к какой то сущности, по тем или иным причинам.
Давайте попробуем, при условии что вы не будете использовать отражения, а в IData будете передавать реальный uow.
Вы пишете, что используете анемичную модель, а бизнес правила сосредоточены в доменных сервисах, также вы пишите что на уровне сценариев есть обращения к репозиториям и к доменным сервисам, при этом консистентность обеспечивается за счёт uow. Это непонятно, что мешает получить какую то сущность из репозитория, изменить ее поля без оглядки на любые бизнес правила (сами сущности же не содержат поведения и проверок) и сохранить через репозиторий?
Вы знаете, у нас действительно бывают сложные сценарии, где множество сущностей проверяется для того чтобы принять решение о результатах выполнения этого сценария.
Скрытый текст
Пример реального сценария в нашем проекте: мы должны сохранить (создать/отредактировать) карту раскроя (раскладка деталей на листе металла). Карты раскроя входят в программу раскроя, программа раскроя просто объединяет несколько карт раскроя c одинаковым материалом и организацией. Так вот, при создании/редактировании карты раскроя необходимо проверить:
Проект (а значит и его детали) относятся к той же организации что и программа
Что проект находится в работе
Детали либо не находятся в других картах, либо объем необходимый для их добавления доступен.
Что добавляемые детали имеют тот же материал
Что детали не являются давальческими (т.е. не передаются в субподряд)
Что если деталь является стыковой деталью (условно половинкой целой детали), то ее второй стык уже добавлен в любую из карт этой программы, либо пока что еще вообще не добавлен ни в какую другую карту.
Что детали проходят определенный производственный цикл
Что карта раскроя не была передана в производство.
Если что то не так, то создать/изменить мы ничего не можем. И таких сценариев сотни.
Я не очень силен в распределенных системах, но предложил бы разобрать на простом примере. Есть 2 сущности проект и заказ, заказ может создаваться в рамках проекта (может и без него) но если в рамках проекта, то проект должен быть в работе. Пусть это будут 2 микросервиса проекты и заказы. Сценарий создать заказ по проекту. Какие тут возможны решения:
(плохой) Обратиться напрямую в БД проектов (только на чтение) из микросервиса заказов. Схема взаимодействия довольно простая, плохая масштабируемость и производительность из за пессимистичных блокировок и удерживания транзакции, нарушение инкапсуляции. Но архитектура может быть такой же как и предложенная в статье.
Один из стандартных вариантов, обратиться к сервису проектов через REST/gRPC API. Честно говоря, кроме разве что самих контрактов, которые предоставляет API на данные которыми обмениваются сервисы, для того чтобы изменения БД/сущностей не касались другого сервиса, ничем от первого варианта не отличается. Те же блокировки, только теперь репозиторий обращается не к БД а API другого сервиса.
Event-Driven, тут если проект изменил свой статус, то он публикует событие, и сервис заказов сохраняет у себя в БД свою локальную копию проекта (упрощенную в рамках контекста заказов). Тут уже у меня не совсем хватает опыта, для того чтобы ответить как обеспечить транзакционность на публикацию этих событий, т.к. условно в данный миг времени сервис заказов может принимать какое то решение на основании текущего состояния. Но опять же архитектура такая же, т.к. это обычные локальные данные, с которыми работает микросервис. Т.е. со своей БД через свои репозитории.
Сервис заказов создает заказ, условно наплевав на все проверки и публикует событие что заказ создан, тогда проект подписан на это событие и проверяет уже свое состояние и пишет в шину, например что произошла ошибка. И тут могут быть два подхода ожидать подтверждений от всех сервисов и потом сохранять в базу или сохранять в базу и потом использовать компенсирующие действия. Но разве это влияет на архитектуру домена? Все те же сущности, все те же (ну почти) события, нет делегатов да, но это и так понятно, да разница в инфраструктуре, в приложении, в каких то дополнительных слоях. Опять же в рамках сервиса чаще всего не одна сущность, взаимодействие между ними такое же.
На самом деле я буду рад конструктивной критике и объяснению в каких аспектах я ошибся в этом ответе
Да конечно, необходимо использовать блокировки, можно довольно удобно добиться требуемого поведения через уровни изоляции транзакций. Но это справедливо что для богатой, что для анемичной модели.
Смысл статьи в том, чтобы показать один из возможных подходов к проектированию системы, который может обеспечить достаточно высокую производительность, сохранить инварианты, обеспечив при этом чистоту и полноту модели предметной области. В реальном проекте мы наоборот уходим от хранимок и логики на стороне БД. Если хотите, можем попробовать разобрать какой то пример.
К сожалению я и правда не подскажу, у меня нет настоящего опыта работы с распределенными системами, но судя по всему какое то звено должно быть ответственно за консистентность