Удаленка — это хорошо для айти сферы, потому что это асинхронная работа. Все знают (но не все соблюдают), что работников умственного труда не стоит дергать каждые 5 минут. Поэтому разница в часовых поясах — только в плюс. Никто не отвлекает от работы. Отсюда рост производительности труда.
Удаленка обнажила некомпетентность/закостенелость руководителей и несовершенство процессов в компаниях. То что компании не планируют задачи, не тратят время на тесты и документацию — это не проблема удаленки. Все это нужно было настраивать и делать 5-10 лет назад.
Единственная реальная проблема — что к удаленке резко принудили сразу всех. Чтобы более менее спокойно работать/учится дома двум взрослым и 2-3 детям — это надо 4-5 комнат/рабочих мест/компов.
У российских провинциальных городов две основные проблемы:
1. Деньги. Им не дают деньги просто так. Почти все налоги уходят в столицу. Поэтому города разрушаются — инфраструктура приходит в упадок, метро закапывают, трамваи и троллейбусы убирают, дворы не чинят, грязь не убирают. Если в регионе появляется более менее честный чиновник, и который умеет выбивать деньги из столицы — регион будет жить относительно лучше (Белгород). Или если регионом руководит олигарх (были примеры на Дальнем Востоке). В последнее время (олимпиада и подготовка к ЧМ) появились всероссийские программы заливания баблом регионов. Поэтому в некоторых городах были построены стадионы и инфраструктура приведена в более менее приличный вид. Но этого недостаточно.
2. В следствие перманентного разрушения в регионах — наиболее экономически и социально активное население, по возможности, уезжает в столицу / за границу. Они не хотят жить в городах-трупах, в постоянном упадке, в отсутствии перспектив и депрессняке. Грубо говоря общество расслаивается. Алкоголиков и тунеядцев в регионах становится относительно больше.
Все это очень заметно, когда можешь сравнить по конкретным пунктам. Из последнего — вакцинация от ковида. В регионе входишь в поликлинику и с порога заявляют — у нас вакцин нет. Приходится буквально с боем прорываться, ждешь одних врачей, потом других, все друг на друга сваливают ответственность, но в итоге, показав подтверждение с гос услуг, вакцина внезапно находится. В Москве записываешься, одеваешься, идешь и через 20-30 минут без вопросов получаешь прививку.
И вот это все влияет и на парки/тротуары/дороги, и общественный транспорт, и на уровень сервиса, и отключат ли во всем районе воду 31 декабря вечером во время подготовки к новому году. А уже из этого, в том числе, складывается общий комфорт жизни в городе.
По существу — допустим, но вопрос так и остается — куда складывать переиспользуемый код? В query/command нельзя, если это ни то ни другое. В бизнес-сервис? Есть вероятность, что он начнет пухнуть. Можно конечно принудительно на ревью кода ограничить один класс-один метод. Но тогда это и будет моя Story, просто другое наименование.
Собственно в этом и суть — один класс-один метод. Как их назвать — да как душе угодно. Но метод, по возможности, не должен иметь побочных эффектов, делать только одну вещь (только брать из хранилища, только сохранять в хранилище, только вычислять, только обращаться к внешнему ресурсу) и быть законченным логическим блоком (т.е. делать полную операцию на текущем уровне абстракции/инкапсуляции, а не ее часть). Не везде получится это сделать, но надо стремиться увеличить количество таких методов. Например GetWeatherStory из статьи делает кучу вещей. Но каждая ее составляющая часть делает одну маленькую вещь. А целиком она законченный логический блок — выдает погоду, оставляя хранилище в согласованном состоянии, без дублей и так далее.
Можно даже пойти дальше и прокидывать Action/Func. Я об этом думал в свое время, но там начинаются проблемы с подтягиванием зависимостей. Надо тогда все прокидывать как параметры функции и это всплывает на уровень фреймворка/платформы. Недавно кстати была статья по этому поводу, которая в более полной мере описывает этот подход habr.com/ru/company/jugru/blog/545482.
Насчет UseCase — да, соглашусь, есть условно верхнеуровневые story (которые вызываются из контроллеров) и вспомогательные (рассчитать что-то по алгоритму, обратиться к внешнему сервису, отправить уведомление и так далее — у них как раз вероятность переиспользования выше). Не проблема разделить их с помощью имен. Например GetWeatherUseCase, но внутри нее GetWeatherFromExternalServiceStory (-> и теперь можно сократить имя до GetWeatherStory или RequestWeatherStory). Но тут палка о двух концах. Если работа ведется с бизнес-аналитиком и соответствующей документацией — имена story должны совпадать с блоками в документации, чтобы потом новому программисту было легко их найти и сопоставить. В таком случае стиль именования должен быть согласован на более высоком уровне. Так же возможен случай, когда код в UseCase вдруг станет переиспользоваться. Тогда его придется раскидать заново по новым UseCase/Story.
Вы молодцы конечно… Но, не очень понятно — вы все таки изначально айтишник? Когда айтишник меняет направление — это обыденная вещь. Мы все бываем джунами по несколько раз за карьеру. Вы же сами перечислили таксиста, курьера и хирурга.
Круто, попробую. Но меня всегда интересовал другой вопрос — все эти маршруты между двумя точками это очень трудоемкое дело. Откуда вы черпаете информацию о появлении новых дорог и проходов?
Я пока нигде не видел функции «предложить новый маршрут». Т.е. пользователь включает этот режим и едет (указав машину — легковая, газель, фура) или идет. Раз он это смог, значит там и другие смогут.
Я читал CQRS Documents by Greg Young. И там есть некоторые вещи, которые меня смущают:
Изначально Бертран Мейер вводил понятие CQS для объектно-ориентированного дизайна. И определяет read-only и write-only методы, которые меняют объект (состояние, данные). Это ближе к понятию чистой функции, без побочных эффектов.
Грег Янг адаптировал CQS под DDD. Я же адаптирую под анемичную модель. И у него точно такая же сборная солянка:
public void Handle(DeactivateInventoryItem message) // <-- обработчик команды и dto
{
var item = _repository.GetById(message.InventoryItemId); // <-- запрос внутри команды
item.Deactivate(); // <-- собственно бизнес-код по изменению состояния
_repository.Save(item, message.OriginalVersion); // <-- старый добрый save-changes
}
Грег Янг вводит понятие task-based команд. Т.е. это такие штуки, которые запускают сложный бизнес-процесс, а не просто обновляют часть данных. Моя Story похожа на task-based команду. Там тоже все намешано, но я не называю ее командой, которая изначально призвана только менять состояние объекта.
Далее, в случае с DDD команда будет дергать входную точку доменной модели. Но сначала надо загрузить ее состояние из хранилища в оперативную память. Формально при вызове команды внутри не будет query, так как команда обращается к уже загруженной доменной модели. А по факту чем ее будут грузить? Методами-запросами, которые не изменяют состояние. Почему об этом ни слова?
CQRS — это отдельная тема про разделение модели (и следовательно хранилищ). Например, одно для записи с 3НФ, и несколько оптимизированных для чтения с 1НФ (кстати аналог view в реляционных БД). В моем случае — внутри Story можно отправить dto в очередь сообщений и на той стороне несколько 1НФ хранилищ его подхватят и изменят свое состояние. Там это будет контекст обработчика сообщения из очереди с вызовом команды. Причем это будет чистая идеальная команда — она только поменяет состояние и ничего не возвратит. Story там излишняя.
Event Sourcing — тоже отдельная тема. Не обязательно этим пользоватся в составе CQS/CQRS/DDD. Любой компонент, который отслеживает баланс (денежный или складских остатков) по сути и есть event sourcing.
выносить повторяющуюся логику в свои сервисы и вызвать их в нужных обработчиках
Вот это и есть «command1 -> IXYZService -> query2/command2». Как-то я работал на проекте, где IXYZService обращался к БД напрямую (через EF context). И все эти запросы были по сути подвисшими в нигде query/command.
Наводящие вопросы, на которые мне приходилось как-то отвечать при работе в рамках подхода query/command (т.е. запрос — это выборка данных, без модификации, а команда — только модификация данных):
1. Куда деть дублирующийся бизнес-код, который используется в нескольких query/command? Например генерация и отправка уведомления. Генерация — здесь query или command? А отправка?
2. Если этот код выделить в IXYZService, то внутри таким сервисам может потребоваться добрать что-то еще из базы или даже сохранить (номер попытки отправки в БД? что-нибудь в лог?), т.е. вызов такой: command1 -> IXYZService -> query2/command2. И в какого монстра это все превращается?
3. Обращение к внешнему сервису — это query или command?
4. Как делать, если сначала надо получить сущность из базы через query, а потом сохранить через command? В query вызывать command или в command первым делом вызвать query? Может перенести это в контроллер? Тогда бизнес-логика размажется, да и контроллер начнет толстеть
5. Разрешать ли использовать внутри query вложенные query, а внутри command вложенные query/command. По канону нельзя
6. Как вообще ложатся методики описания бизнес-процессов (IDEF0 и BPMN) на одиночные query/command, которые нельзя вкладывать друг в друга?
7. А что у нас там с SOLID и другими принципами? Отдельные простые query/command еще могут отвечать этим принципам, а когда внутри все усложняется?
Изначально CQRS был создан для разделения хранилищ данных (основное для записи и несколько реплик для чтения). С тех пор как его стали использовать в качестве паттерна организации кода — проблемы полезли из всех щелей. И в оригинале он только и годится для CRUD. Но по другую сторону у нас те самые IXYZService сервисы, у которых есть риск превратиться в god-объекты. И query/command хорошее подспорье, чтобы это упростить и разделить.
PS Да и это я не имел ввиду EventSourcing — потому что это совсем отдельная тема.
А можно подробней про остальные варианты — что имелось ввиду? И было бы здорово теперь все тоже самое, но когда бизнес логика посложнее обычного CRUD? Во внешний сервис пойти, в кэш, в очередь что-нибудь отправить. Или хотя бы получить из базы сущность, что-нибудь сгенерировать на основе того что в базе, что пришло с клиента и только потом сохранить. Ну и тяжелая артиллерия — какая-нибудь распределенная транзакция, охватывающая несколько query/command.
Это лично мне нужен js код и после T4 я решил попробовать именно source generator. Но те, кто будет использовать его для C# кода могут столкнутся с аналогичными проблемами. А в статье уже есть ответы на некоторые вопросы, на которые я сам ответа сходу не нашел.
MSBuild я пока не копал, но там точно так же может быть куча проблем. Если вы активно им пользовались — поделитесь своим опытом. Возникают ли там озвученные мной проблемы? Они решаемы?
Супругу так пытались развести из сбербанка, я рядом стоял слушал. До меня не сразу дошло, что это мошенники. Потом выхватил телефон из рук. Работает прям как наваждение. Мне потом тоже звонили, но обломались на том, что я никогда не имел счетов и карт в сбере. О чем им заявил и у них скрипт сломался. Потом просто нахер послал. Но не известно как бы я себя повел, если бы типа позвонили из банка, в котором реально счет есть.
А вот друг мой смог их разговорить и узнал что там типа колл центра или какой то группы, и зарплату узнал, то ли 200, то ли 400 тыщ в месяц, не помню.
Очень зашла статья. У меня были примерно те же ощущения, только никак не мог оформить мысли текстом. Но я начал с того, что демократия, возможно, не очень то и эффективный режим. Демократией (опросы, голосования) можно собирать фидбек с пользователей (граждан, жителей). Но вот рулить принятием законов...?
Так а что надо чтоб по прямой дететь? Много невесомого топлива? Или новые двигатели, у которых удельный расход топлива гораздо меньше чем у ракетных? На ядерных получится напрямую лететь?
Удаленка обнажила некомпетентность/закостенелость руководителей и несовершенство процессов в компаниях. То что компании не планируют задачи, не тратят время на тесты и документацию — это не проблема удаленки. Все это нужно было настраивать и делать 5-10 лет назад.
Единственная реальная проблема — что к удаленке резко принудили сразу всех. Чтобы более менее спокойно работать/учится дома двум взрослым и 2-3 детям — это надо 4-5 комнат/рабочих мест/компов.
1. Деньги. Им не дают деньги просто так. Почти все налоги уходят в столицу. Поэтому города разрушаются — инфраструктура приходит в упадок, метро закапывают, трамваи и троллейбусы убирают, дворы не чинят, грязь не убирают. Если в регионе появляется более менее честный чиновник, и который умеет выбивать деньги из столицы — регион будет жить относительно лучше (Белгород). Или если регионом руководит олигарх (были примеры на Дальнем Востоке). В последнее время (олимпиада и подготовка к ЧМ) появились всероссийские программы заливания баблом регионов. Поэтому в некоторых городах были построены стадионы и инфраструктура приведена в более менее приличный вид. Но этого недостаточно.
2. В следствие перманентного разрушения в регионах — наиболее экономически и социально активное население, по возможности, уезжает в столицу / за границу. Они не хотят жить в городах-трупах, в постоянном упадке, в отсутствии перспектив и депрессняке. Грубо говоря общество расслаивается. Алкоголиков и тунеядцев в регионах становится относительно больше.
Все это очень заметно, когда можешь сравнить по конкретным пунктам. Из последнего — вакцинация от ковида. В регионе входишь в поликлинику и с порога заявляют — у нас вакцин нет. Приходится буквально с боем прорываться, ждешь одних врачей, потом других, все друг на друга сваливают ответственность, но в итоге, показав подтверждение с гос услуг, вакцина внезапно находится. В Москве записываешься, одеваешься, идешь и через 20-30 минут без вопросов получаешь прививку.
И вот это все влияет и на парки/тротуары/дороги, и общественный транспорт, и на уровень сервиса, и отключат ли во всем районе воду 31 декабря вечером во время подготовки к новому году. А уже из этого, в том числе, складывается общий комфорт жизни в городе.
По существу — допустим, но вопрос так и остается — куда складывать переиспользуемый код? В query/command нельзя, если это ни то ни другое. В бизнес-сервис? Есть вероятность, что он начнет пухнуть. Можно конечно принудительно на ревью кода ограничить один класс-один метод. Но тогда это и будет моя Story, просто другое наименование.
Собственно в этом и суть — один класс-один метод. Как их назвать — да как душе угодно. Но метод, по возможности, не должен иметь побочных эффектов, делать только одну вещь (только брать из хранилища, только сохранять в хранилище, только вычислять, только обращаться к внешнему ресурсу) и быть законченным логическим блоком (т.е. делать полную операцию на текущем уровне абстракции/инкапсуляции, а не ее часть). Не везде получится это сделать, но надо стремиться увеличить количество таких методов. Например GetWeatherStory из статьи делает кучу вещей. Но каждая ее составляющая часть делает одну маленькую вещь. А целиком она законченный логический блок — выдает погоду, оставляя хранилище в согласованном состоянии, без дублей и так далее.
Можно даже пойти дальше и прокидывать Action/Func. Я об этом думал в свое время, но там начинаются проблемы с подтягиванием зависимостей. Надо тогда все прокидывать как параметры функции и это всплывает на уровень фреймворка/платформы. Недавно кстати была статья по этому поводу, которая в более полной мере описывает этот подход habr.com/ru/company/jugru/blog/545482.
Насчет UseCase — да, соглашусь, есть условно верхнеуровневые story (которые вызываются из контроллеров) и вспомогательные (рассчитать что-то по алгоритму, обратиться к внешнему сервису, отправить уведомление и так далее — у них как раз вероятность переиспользования выше). Не проблема разделить их с помощью имен. Например GetWeatherUseCase, но внутри нее GetWeatherFromExternalServiceStory (-> и теперь можно сократить имя до GetWeatherStory или RequestWeatherStory). Но тут палка о двух концах. Если работа ведется с бизнес-аналитиком и соответствующей документацией — имена story должны совпадать с блоками в документации, чтобы потом новому программисту было легко их найти и сопоставить. В таком случае стиль именования должен быть согласован на более высоком уровне. Так же возможен случай, когда код в UseCase вдруг станет переиспользоваться. Тогда его придется раскидать заново по новым UseCase/Story.
А еще был набор техник 8480 — деталей там меньше, но выглядит более функциональным
Я надеялся, что в конце Сергей откроет свою фирму и наймёт себе директора и бухгалтера, а сам будет кодить. Этакий серый кардинал.
Круто, попробую. Но меня всегда интересовал другой вопрос — все эти маршруты между двумя точками это очень трудоемкое дело. Откуда вы черпаете информацию о появлении новых дорог и проходов?
Я пока нигде не видел функции «предложить новый маршрут». Т.е. пользователь включает этот режим и едет (указав машину — легковая, газель, фура) или идет. Раз он это смог, значит там и другие смогут.
Я читал CQRS Documents by Greg Young. И там есть некоторые вещи, которые меня смущают:
Изначально Бертран Мейер вводил понятие CQS для объектно-ориентированного дизайна. И определяет read-only и write-only методы, которые меняют объект (состояние, данные). Это ближе к понятию чистой функции, без побочных эффектов.
Грег Янг адаптировал CQS под DDD. Я же адаптирую под анемичную модель. И у него точно такая же сборная солянка:
Грег Янг вводит понятие task-based команд. Т.е. это такие штуки, которые запускают сложный бизнес-процесс, а не просто обновляют часть данных. Моя Story похожа на task-based команду. Там тоже все намешано, но я не называю ее командой, которая изначально призвана только менять состояние объекта.
Далее, в случае с DDD команда будет дергать входную точку доменной модели. Но сначала надо загрузить ее состояние из хранилища в оперативную память. Формально при вызове команды внутри не будет query, так как команда обращается к уже загруженной доменной модели. А по факту чем ее будут грузить? Методами-запросами, которые не изменяют состояние. Почему об этом ни слова?
CQRS — это отдельная тема про разделение модели (и следовательно хранилищ). Например, одно для записи с 3НФ, и несколько оптимизированных для чтения с 1НФ (кстати аналог view в реляционных БД). В моем случае — внутри Story можно отправить dto в очередь сообщений и на той стороне несколько 1НФ хранилищ его подхватят и изменят свое состояние. Там это будет контекст обработчика сообщения из очереди с вызовом команды. Причем это будет чистая идеальная команда — она только поменяет состояние и ничего не возвратит. Story там излишняя.
Event Sourcing — тоже отдельная тема. Не обязательно этим пользоватся в составе CQS/CQRS/DDD. Любой компонент, который отслеживает баланс (денежный или складских остатков) по сути и есть event sourcing.
Вот это и есть «command1 -> IXYZService -> query2/command2». Как-то я работал на проекте, где IXYZService обращался к БД напрямую (через EF context). И все эти запросы были по сути подвисшими в нигде query/command.
1. Куда деть дублирующийся бизнес-код, который используется в нескольких query/command? Например генерация и отправка уведомления. Генерация — здесь query или command? А отправка?
2. Если этот код выделить в IXYZService, то внутри таким сервисам может потребоваться добрать что-то еще из базы или даже сохранить (номер попытки отправки в БД? что-нибудь в лог?), т.е. вызов такой: command1 -> IXYZService -> query2/command2. И в какого монстра это все превращается?
3. Обращение к внешнему сервису — это query или command?
4. Как делать, если сначала надо получить сущность из базы через query, а потом сохранить через command? В query вызывать command или в command первым делом вызвать query? Может перенести это в контроллер? Тогда бизнес-логика размажется, да и контроллер начнет толстеть
5. Разрешать ли использовать внутри query вложенные query, а внутри command вложенные query/command. По канону нельзя
6. Как вообще ложатся методики описания бизнес-процессов (IDEF0 и BPMN) на одиночные query/command, которые нельзя вкладывать друг в друга?
7. А что у нас там с SOLID и другими принципами? Отдельные простые query/command еще могут отвечать этим принципам, а когда внутри все усложняется?
Изначально CQRS был создан для разделения хранилищ данных (основное для записи и несколько реплик для чтения). С тех пор как его стали использовать в качестве паттерна организации кода — проблемы полезли из всех щелей. И в оригинале он только и годится для CRUD. Но по другую сторону у нас те самые IXYZService сервисы, у которых есть риск превратиться в god-объекты. И query/command хорошее подспорье, чтобы это упростить и разделить.
PS Да и это я не имел ввиду EventSourcing — потому что это совсем отдельная тема.
А можно подробней про остальные варианты — что имелось ввиду? И было бы здорово теперь все тоже самое, но когда бизнес логика посложнее обычного CRUD? Во внешний сервис пойти, в кэш, в очередь что-нибудь отправить. Или хотя бы получить из базы сущность, что-нибудь сгенерировать на основе того что в базе, что пришло с клиента и только потом сохранить. Ну и тяжелая артиллерия — какая-нибудь распределенная транзакция, охватывающая несколько query/command.
MSBuild я пока не копал, но там точно так же может быть куча проблем. Если вы активно им пользовались — поделитесь своим опытом. Возникают ли там озвученные мной проблемы? Они решаемы?
Раньше программистов тоже спрашивали про шары. Если не знаешь, что спросить — спрашивай про шары xD
Супругу так пытались развести из сбербанка, я рядом стоял слушал. До меня не сразу дошло, что это мошенники. Потом выхватил телефон из рук. Работает прям как наваждение. Мне потом тоже звонили, но обломались на том, что я никогда не имел счетов и карт в сбере. О чем им заявил и у них скрипт сломался. Потом просто нахер послал. Но не известно как бы я себя повел, если бы типа позвонили из банка, в котором реально счет есть.
А вот друг мой смог их разговорить и узнал что там типа колл центра или какой то группы, и зарплату узнал, то ли 200, то ли 400 тыщ в месяц, не помню.
Так а что надо чтоб по прямой дететь? Много невесомого топлива? Или новые двигатели, у которых удельный расход топлива гораздо меньше чем у ракетных? На ядерных получится напрямую лететь?