Понимаю проблему, классно, что получилось начать соблюдать ограничения. Но честно о результатах можно будет сказать через полгода хотя бы, а лучше через год.
Я человек с 6:40 экранного времени, лимиты не помогают вообще. Очень похоже на сигареты на самом деле, ты можешь придерживаться ограничений, когда все ок, но как только малейший стресс или плохо себя чувствуешь, то тут же на все ограничения забиваешь.
В 2024 году, когда в любой момент могут порезать способы оплаты господам из нашей страны, оплачивать помесячно просто смешно. Предоплата на год минимум на любом +- серьезном проекте. Ну и бекапы, конечно, надо скачивать себе.
Раньше тоже обмазывался тоталкомандером, но в какой-то момент он у меня слетел, я пожил с обычным проводником. Оказалось, что никакого критичного недостатка в нем нет.
Эстетически он на 10 голов выше. Практически все повседневные действия выполняются с такой же скоростью, смотреть все так же удобно, панель быстрого доступа слева супер полезная, а сделать "две панели" на фулскрин элементарно (а с двумя мониторами можно еще и на два фулскрина). А часто еще нужно и три панели, что тоже легко делается.
Для поиска everything намного удобнее, для переименований есть ant renamer, хеши считает hashtab, а архивы прекрасно архивятся из контекстного меню 7z.
В тоталкомандер захожу сейчас для 2 вещей, подключится к удаленному компу по sftp, что-то перекинуть. И посчитать в файл\проверить чексумы для директорий.
Не отрицаю, что если есть профессиональная необходимость в ручном режиме постоянно воротить файлами, то продвинутые файловые менеджеры могут быть полезными, но вот в моем случае они оказались избыточными.
Это ничего не стоит, потому что 90% кодовой базы использует private readonly _field; как codestyle полей, а primary constructors этот codestyle ломают.
Да, CQRS скорее использован в обывательском смысле, чем в правильном. Речь шла про разделение логики на команды и квери, нужно подобрать какой-нибудь более подходящий термин. CQS?
Я довольно часто использую при отладке. Включаю логирование определенных команд через конфиг, рестартую контейнеры и смотрю в логи. HTTP реквесты логировать бывает не очень удобно. Часть данных может быть в теле и не видна, часть лежать в хедерах. Команды, которые могут быть инициированы и из message bus-а, и из контроллеров, тоже выглядят в логах одинаково и аккуратно.
С интерфейсами интересно, но только для включения/отключения. В атрибуты же можно добавлять свойства, и настраивать таким образом поведение bеhavior. Например, указать уровень изоляции транзакции для конкретной команды. Можно конечно наделать интерфейсов под каждый уровень, но... такое.
Ну вот я постарался в самом начале статьи и написать, где тут медиатор. С изначальным шаблоном тут связь скорее историческая, чем реальная.
На вопрос, почему не стоит все строить на медиаторе отвечал в комментариях выше, да вы и сами ответили в своих рассуждениях. Границы выбираются эмпирическим путем. В разных проектах они могут быть разные, возможно, эти границы даже можно сдвигать какими-нибудь хитрыми решениями!
Я думаю, что этот подход может работать, но в каких-то ограниченных случаях. Например, если вы фулстек команда и фронт разрабатываете сами рядом с бекендом. Потому что вместо какого-то независимого контракта, у вас получается контракт который строго продиктован бекендом. Вы уже не сможете по просьбе фронтов что-то там подправить, поменять название поля, потому что править команды и квери из-за запросов UI слоя это максимально неправильно. Еще подход хорошо будет работать, если вы пишете контроллеры не для сторонних клиентов и ходить в них будете исключительно из кода других сервисов, а может и вообще сгенерите себе клиента.
Из плюсов код становится проще. Не нужно ничего мапить, перегонять туда сюда модельки, в принципе эти дтохи создавать и синхронизировать с изменениями в кверях\командах. Приняли объект в контроллер, его же и кинули в медиатор.
Минусы тоже есть:
Вы не сможете контекстные данные из контролера добавить в команду. Например, id пользователя, который лежит в токене, или его роль, или какое-нибудь значение из header-а. Точнее сможете, но костылями, какими-нибудь with на рекорде.
Вложенные модельки будут по умолчанию криво мапиться в квери, с точкой в названии, тоже мало кому из фронтов понравится. С командами все хорошо, потому они просто json в теле.
Не получится делать красивые REST урлы, потому что в REST айди ресурсов прописываются в пути до ресурса, а у вас принимается моделька квери. Или делать урлы некрасивые, либо костылями перед передачей в медиатор подсовывать параметры из пути в команду через with.
Но кто-то и так не заморачивается с REST, ему не нужны контекстные данные, и все равно на названия квери параметров. Тут зависит от задачи, и от ваших предпочтений.
Большинство архитектурных инструментов, в том числе и медиатор, сделаны чтобы бороться со сложностью. Если сложности нет и она не планируется, то инструменты использовать не нужно, даже скорее вредно.
В вашем примере, представьте что помимо HTTP вызовов вы начнете слушать очередь в rabbitmq, и из обработчика сообщений дергать тот же самый сервис. Теперь логика, которую вы писали и тестировали на middleware не будет работать, эти вызовы будут для нее невидимыми. А может быть это будет не очередь, а простенький BackgroundService, который что-то там чистит раз в час. За него aspnet тоже ничего не залогирует.
Или может быть другая ситуация, что у вас так и остаются простенькие сервисы, но вот один работает с aspnet-ом, а другой слушает сообщения из очереди. Поведение Медиатора, которое бы вы написали 1 раз в общей либе, работало бы одинаково для этих случаев.
Плюс поведения работают все таки с конкретными командами или кверями, это больше уже абстракция бизнес действий, с явными аргументами. А middleware в aspnet с запросом пользователя, другой уровень абстракции, другие возможности.
Квери и хендлеры не составляют большую часть кодовой базы нашего проекта. Они скорее составляют большую часть определенного слоя, и в этом слое дублирование кода, возможно, и присутствует, но я не считаю это чем-то плохим. Мне кажется, что выделять просто общий код в общие места это плохая практика, нужно работать на уровне абстракций, и если код можно вынести как адекватную абстракцию, то делать это не медиатором.
При этом у нас существуют еще инфраструктурные сервисы, доменный слой, инфраструктурные поведения, конфигурации, которые тоже позволяют избегать дублирования. Экстеншены, в конце концов. Варианты для удовлетворения потребности в DRY присутствует.
Почему бы не вынести в квери и команды вообще все? У меня были такие проекты и лично мне показалось, что именно при таком использовании медиатор создал больше проблем, чем принес пользы. Откуда-то оттуда наверняка и растут ноги критики.
Сложно стало договариваться с коллегами, что в принципе может быть командой или кверей, а что нет. Например, кто-то может сделать пулреквест с крутой командой сохранения агрегата. Назвать ее CreateSomethingGood. И вроде пока мы знаем, что это просто сохранение в базу, но через полгода легко можно и самому вызвать этот самый CreateSomethingGood из контроллера, забыв что мы пропустили таким образом кучу логики (например по отправке событий). Без четкого места медиатора становится сложно аргументировать коллегам, да и себе, где же эти команды можно создавать, а где нельзя. Очень сильно размываются границы между слоями. Можно попробовать бороться с этим неймингом, но.. не хочется.
Аспекты, которые в случае с одним местом медиатора, почти всегда исполняются один раз в понятном месте, могут начать исполняться в непредвиденном месте. Сложность написания поведений увеличивается в разы. Если с подходом описанным в статье, нам надо только помнить об одном вызове поведения, на входе в слой приложения, то с кучей команд в разных местах это предположение будет неверным.
Это больше интуитивное решение, не отрицаю что можно построить классную архитектуру исключительно на медиаторе, но я столкнувшись с проблемами, решил их таким вот ограничением. Мне показалось это хорошим компромиссом.
Понимаю проблему, классно, что получилось начать соблюдать ограничения. Но честно о результатах можно будет сказать через полгода хотя бы, а лучше через год.
Я человек с 6:40 экранного времени, лимиты не помогают вообще. Очень похоже на сигареты на самом деле, ты можешь придерживаться ограничений, когда все ок, но как только малейший стресс или плохо себя чувствуешь, то тут же на все ограничения забиваешь.
В 2024 году, когда в любой момент могут порезать способы оплаты господам из нашей страны, оплачивать помесячно просто смешно. Предоплата на год минимум на любом +- серьезном проекте. Ну и бекапы, конечно, надо скачивать себе.
Раньше тоже обмазывался тоталкомандером, но в какой-то момент он у меня слетел, я пожил с обычным проводником. Оказалось, что никакого критичного недостатка в нем нет.
Эстетически он на 10 голов выше. Практически все повседневные действия выполняются с такой же скоростью, смотреть все так же удобно, панель быстрого доступа слева супер полезная, а сделать "две панели" на фулскрин элементарно (а с двумя мониторами можно еще и на два фулскрина). А часто еще нужно и три панели, что тоже легко делается.
Для поиска everything намного удобнее, для переименований есть ant renamer, хеши считает hashtab, а архивы прекрасно архивятся из контекстного меню 7z.
В тоталкомандер захожу сейчас для 2 вещей, подключится к удаленному компу по sftp, что-то перекинуть. И посчитать в файл\проверить чексумы для директорий.
Не отрицаю, что если есть профессиональная необходимость в ручном режиме постоянно воротить файлами, то продвинутые файловые менеджеры могут быть полезными, но вот в моем случае они оказались избыточными.
Это ничего не стоит, потому что 90% кодовой базы использует private readonly _field; как codestyle полей, а primary constructors этот codestyle ломают.
Никто не хочет оформить серверную часть в докер, чтобы оно запускалось по docker run без любви с конфигами? Для openvpn полно таких решений.
Спасибо за отзыв!
Да, CQRS скорее использован в обывательском смысле, чем в правильном. Речь шла про разделение логики на команды и квери, нужно подобрать какой-нибудь более подходящий термин. CQS?
Я довольно часто использую при отладке. Включаю логирование определенных команд через конфиг, рестартую контейнеры и смотрю в логи. HTTP реквесты логировать бывает не очень удобно. Часть данных может быть в теле и не видна, часть лежать в хедерах. Команды, которые могут быть инициированы и из message bus-а, и из контроллеров, тоже выглядят в логах одинаково и аккуратно.
С интерфейсами интересно, но только для включения/отключения. В атрибуты же можно добавлять свойства, и настраивать таким образом поведение bеhavior. Например, указать уровень изоляции транзакции для конкретной команды. Можно конечно наделать интерфейсов под каждый уровень, но... такое.
Ну вот я постарался в самом начале статьи и написать, где тут медиатор. С изначальным шаблоном тут связь скорее историческая, чем реальная.
На вопрос, почему не стоит все строить на медиаторе отвечал в комментариях выше, да вы и сами ответили в своих рассуждениях. Границы выбираются эмпирическим путем. В разных проектах они могут быть разные, возможно, эти границы даже можно сдвигать какими-нибудь хитрыми решениями!
Я думаю, что этот подход может работать, но в каких-то ограниченных случаях. Например, если вы фулстек команда и фронт разрабатываете сами рядом с бекендом. Потому что вместо какого-то независимого контракта, у вас получается контракт который строго продиктован бекендом. Вы уже не сможете по просьбе фронтов что-то там подправить, поменять название поля, потому что править команды и квери из-за запросов UI слоя это максимально неправильно. Еще подход хорошо будет работать, если вы пишете контроллеры не для сторонних клиентов и ходить в них будете исключительно из кода других сервисов, а может и вообще сгенерите себе клиента.
Из плюсов код становится проще. Не нужно ничего мапить, перегонять туда сюда модельки, в принципе эти дтохи создавать и синхронизировать с изменениями в кверях\командах. Приняли объект в контроллер, его же и кинули в медиатор.
Минусы тоже есть:
Вы не сможете контекстные данные из контролера добавить в команду. Например, id пользователя, который лежит в токене, или его роль, или какое-нибудь значение из header-а. Точнее сможете, но костылями, какими-нибудь with на рекорде.
Вложенные модельки будут по умолчанию криво мапиться в квери, с точкой в названии, тоже мало кому из фронтов понравится. С командами все хорошо, потому они просто json в теле.
Не получится делать красивые REST урлы, потому что в REST айди ресурсов прописываются в пути до ресурса, а у вас принимается моделька квери. Или делать урлы некрасивые, либо костылями перед передачей в медиатор подсовывать параметры из пути в команду через with.
Но кто-то и так не заморачивается с REST, ему не нужны контекстные данные, и все равно на названия квери параметров. Тут зависит от задачи, и от ваших предпочтений.
Большинство архитектурных инструментов, в том числе и медиатор, сделаны чтобы бороться со сложностью. Если сложности нет и она не планируется, то инструменты использовать не нужно, даже скорее вредно.
В вашем примере, представьте что помимо HTTP вызовов вы начнете слушать очередь в rabbitmq, и из обработчика сообщений дергать тот же самый сервис. Теперь логика, которую вы писали и тестировали на middleware не будет работать, эти вызовы будут для нее невидимыми. А может быть это будет не очередь, а простенький BackgroundService, который что-то там чистит раз в час. За него aspnet тоже ничего не залогирует.
Или может быть другая ситуация, что у вас так и остаются простенькие сервисы, но вот один работает с aspnet-ом, а другой слушает сообщения из очереди. Поведение Медиатора, которое бы вы написали 1 раз в общей либе, работало бы одинаково для этих случаев.
Плюс поведения работают все таки с конкретными командами или кверями, это больше уже абстракция бизнес действий, с явными аргументами. А middleware в aspnet с запросом пользователя, другой уровень абстракции, другие возможности.
Квери и хендлеры не составляют большую часть кодовой базы нашего проекта. Они скорее составляют большую часть определенного слоя, и в этом слое дублирование кода, возможно, и присутствует, но я не считаю это чем-то плохим. Мне кажется, что выделять просто общий код в общие места это плохая практика, нужно работать на уровне абстракций, и если код можно вынести как адекватную абстракцию, то делать это не медиатором.
При этом у нас существуют еще инфраструктурные сервисы, доменный слой, инфраструктурные поведения, конфигурации, которые тоже позволяют избегать дублирования. Экстеншены, в конце концов. Варианты для удовлетворения потребности в DRY присутствует.
Почему бы не вынести в квери и команды вообще все? У меня были такие проекты и лично мне показалось, что именно при таком использовании медиатор создал больше проблем, чем принес пользы. Откуда-то оттуда наверняка и растут ноги критики.
Сложно стало договариваться с коллегами, что в принципе может быть командой или кверей, а что нет.
Например, кто-то может сделать пулреквест с крутой командой сохранения агрегата. Назвать ее CreateSomethingGood. И вроде пока мы знаем, что это просто сохранение в базу, но через полгода легко можно и самому вызвать этот самый CreateSomethingGood из контроллера, забыв что мы пропустили таким образом кучу логики (например по отправке событий). Без четкого места медиатора становится сложно аргументировать коллегам, да и себе, где же эти команды можно создавать, а где нельзя. Очень сильно размываются границы между слоями. Можно попробовать бороться с этим неймингом, но.. не хочется.
Аспекты, которые в случае с одним местом медиатора, почти всегда исполняются один раз в понятном месте, могут начать исполняться в непредвиденном месте. Сложность написания поведений увеличивается в разы. Если с подходом описанным в статье, нам надо только помнить об одном вызове поведения, на входе в слой приложения, то с кучей команд в разных местах это предположение будет неверным.
Это больше интуитивное решение, не отрицаю что можно построить классную архитектуру исключительно на медиаторе, но я столкнувшись с проблемами, решил их таким вот ограничением. Мне показалось это хорошим компромиссом.