Обновить
51
0.6

Architect | Lead | Senior Developer

Отправить сообщение
Что-то меня бомбануло с этой статьи.

Отдельных удаленных сотрудников не рассматривали — с ними сложно поддерживать коммуникации, и у них пониженная вовлеченность в проект, т.к. они не видят того, что происходит внутри команды, работающей в офисе. И живут только от таска до таска.

Да, есть компании, для которых удаленщики (да и в целом сотрудники) расходный материал, а есть такие, где удаленщики — самостоятельно ведут проекты и команду. Вряд ли в таком случае можно говорить о пониженной вовлеченности. Вы сами в своей голове делите людей на касты — вот это офисник, он красавчик, а вот удаленщик, ему таску, не возвращайся пока не сделаешь.

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

Т.е. до вас только так дошло, что в офисе вы тоже все друг другу мешаете? Но там нельзя «просто уйти».

Первое время было сложно без разговоров с коллегами у кофепоинта и без совместных походов на обед.

Так вы на работу ходили чтоб кофе/чай гонять?

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

Даже до пандемии в офисе было такое понятие как гибкий график. Кто-то приходит в 8 утра, кто-то к обеду. Но обязательно гарантировалось например 4 часовое пересечение для всех сотрудников днем. Это легко переносится и на удаленку. У вас там в компании совсем совок что ли? Давайте сломаю вам шаблон — можно продуктивно работать, когда часть команды на той стороне планеты, и разница во времени порядка 7-10 часов.

Приятным бонусом для меня стало то, что у нас повысилась производительность.

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

Неужели? Вы не знали, что лучше не дергать людей умственного труда каждые 5 минут? И планировать задачи на несколько дней/спринт вперед?



Итого — пандемия и удаленка, походу, научила вас работать. Это плюс.
Не совсем, основной тезис — куда складывать переиспользуемый код, который может быть как инфраструктурным, так и бизнес-кодом.

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

С сервисами — я уже говорил, что они начнут пухнуть. А потом внутри сервисов захочется обратиться к хранилищу за дополнительными данными (см выше в статье пример с EntityService). Но обращение к хранилищу — это ведь тоже есть запрос или команда. Да и с domain сервисами — вы кажется начинаете затрагивать DDD. В случае с DDD команда будет дергать модель предметной области, а та в свою очередь начнет молотить бизнес-логику внутри себя.

С infrastructure и persistence — да, все так, но вот там такой бизнес кейс, а не инфраструктурный:
1. Покупатель делает заказ в интернет магазине на несколько товаров.
2. Возвращает один из товаров — под этим скрывается некий бизнес-процесс для единицы товара. Это возврат товара на склад и возврат части денег.
3. Но покупатель может так же вернуть все товары полностью. Но ведь внутри этого бизнес-процесса будет повторяется бизнес-процесс для единицы товара в цикле + некоторые оптимизации (иначе например будет N+1 запрос к БД).
доселе невиданные методы появились у стандартных библиотечных классов

Посмотрит исходники или документацию.

расширения своих собственных классов

Зачем их расширять, если и так есть доступ к исходникам и можно написать по обычному, внутри самих классов? Вот с интерфейсами проблема, хотя в последней версии появилась дефолтная реализация.

Насколько я понял идею методов расширения, они используются для:
1. Добавления новых (более удобных) методов для уже закрытых для изменения классов.
2. Для массовой реализации нового поведения для заданного интерфейса. Т.е. чтобы не прописывать в каждом классе реализацию, можно сделать метод расширения на интерфейс и все классы наследники получат это поведение. Мне это напоминает реализацию интерфейса по умолчанию, которое появилось в C# 8.0.
Есть интересные моменты, благодарю.
Его задачи: аналитика требований бизнеса

Не очень понятно — он что бизнес-аналитик? Или ему спускают готовое ТЗ (описание) и он анализирует именно это описание, чтобы разбросать по технической части?

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

А можно тут подробней рассказать? В саппорт прилетают различные вещи и для начала их надо идентифицировать (это бага, это надо инструкцию обновить, это мелкая доработка, это похоже на эпик, это пользователь тупой). И ведь кто-то этим должен заниматься. Таким образом, разработчикам будет уходить только часть этих тикетов. Мало того, порой непонятно бага это или фича и требуется время разработчика, чтобы глубже копнуть. А такие задачи кому уходят? Дежурным? Да и еще — бывают такие тикеты, что без поллитра не обойдешься, нужно еще привлечь бизнес-аналитиков и созвониться с пользователем.

И еще не понятен жизненный цикл feature команды в рамках проектов. Они что сделали проект и свалили (распались)? А кто поддерживать будет?
Удаленка — это хорошо для айти сферы, потому что это асинхронная работа. Все знают (но не все соблюдают), что работников умственного труда не стоит дергать каждые 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.

А еще был набор техник 8480 — деталей там меньше, но выглядит более функциональным

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

Я надеялся, что в конце Сергей откроет свою фирму и наймёт себе директора и бухгалтера, а сам будет кодить. Этакий серый кардинал.

По-моему с методами расширения неудачный пример. Я тоже запилил такие методы для удобства чтения кода:

((IMyInterface)myObject).Field1

myObject.To<IMyInterface>().Field1

var stringArray = new [ "a", "b", "c"];

string.Join(", ", stringArray);

stringArray.Join(", ");

public void MyMethod(IList<int> list)
{
  if (list != null && list.Count > 0)
  {
    // ...
  }
}

public void MyMethod(IList<int> list)
{
  if (list.IsNotNullOrEmpty())
  {
    // ...
  }
}
Можно переименовать, не проблема. С медиатором — да, косяк, это фабрика, поправил.

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


Я пока нигде не видел функции «предложить новый маршрут». Т.е. пользователь включает этот режим и едет (указав машину — легковая, газель, фура) или идет. Раз он это смог, значит там и другие смогут.

Я читал CQRS Documents by Greg Young. И там есть некоторые вещи, которые меня смущают:


  1. Изначально Бертран Мейер вводил понятие CQS для объектно-ориентированного дизайна. И определяет read-only и write-only методы, которые меняют объект (состояние, данные). Это ближе к понятию чистой функции, без побочных эффектов.


  2. Грег Янг адаптировал CQS под DDD. Я же адаптирую под анемичную модель. И у него точно такая же сборная солянка:


    public void Handle(DeactivateInventoryItem message) // <-- обработчик команды и dto
    {
    var item = _repository.GetById(message.InventoryItemId); // <-- запрос внутри команды
    item.Deactivate(); // <-- собственно бизнес-код по изменению состояния
    _repository.Save(item, message.OriginalVersion);  // <-- старый добрый save-changes
    }

  3. Грег Янг вводит понятие task-based команд. Т.е. это такие штуки, которые запускают сложный бизнес-процесс, а не просто обновляют часть данных. Моя Story похожа на task-based команду. Там тоже все намешано, но я не называю ее командой, которая изначально призвана только менять состояние объекта.


  4. Далее, в случае с DDD команда будет дергать входную точку доменной модели. Но сначала надо загрузить ее состояние из хранилища в оперативную память. Формально при вызове команды внутри не будет query, так как команда обращается к уже загруженной доменной модели. А по факту чем ее будут грузить? Методами-запросами, которые не изменяют состояние. Почему об этом ни слова?


  5. CQRS — это отдельная тема про разделение модели (и следовательно хранилищ). Например, одно для записи с 3НФ, и несколько оптимизированных для чтения с 1НФ (кстати аналог view в реляционных БД). В моем случае — внутри Story можно отправить dto в очередь сообщений и на той стороне несколько 1НФ хранилищ его подхватят и изменят свое состояние. Там это будет контекст обработчика сообщения из очереди с вызовом команды. Причем это будет чистая идеальная команда — она только поменяет состояние и ничего не возвратит. Story там излишняя.


  6. Event Sourcing — тоже отдельная тема. Не обязательно этим пользоватся в составе CQS/CQRS/DDD. Любой компонент, который отслеживает баланс (денежный или складских остатков) по сути и есть event sourcing.


Тем самым мы просто перенесем подвисший код из IXYZService в IXYZRepository, хотя по сути этот код тоже является query/command.
выносить повторяющуюся логику в свои сервисы и вызвать их в нужных обработчиках

Вот это и есть «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.

Информация

В рейтинге
2 092-й
Откуда
Россия
Зарегистрирован
Активность

Специализация

Бэкенд разработчик, Архитектор программного обеспечения
Старший
C#
.NET Core
SQL