Транзакции, синхронизация с read базой данных

Обсуждаем, как правильно строить приложения
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

zelenin писал(а): 2017.10.30, 14:08
anton_z писал(а): 2017.10.30, 14:02 Я не о подводных камнях, а о полезности/неполезности в конкретных условиях
в конкретных условиях инкапсуляция сервисного слоя в одном месте с возможностью добавления общего поведения (логирование, валидация, транзакции, обработка событий), имхо, даст только плюсы. Из практики минус только один - медленнее дебажить, но это так себе.
Предлагаете везде применять шину команд?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Транзакции, синхронизация с read базой данных

Сообщение zelenin »

anton_z писал(а): 2017.10.30, 14:44
zelenin писал(а): 2017.10.30, 14:08
anton_z писал(а): 2017.10.30, 14:02 Я не о подводных камнях, а о полезности/неполезности в конкретных условиях
в конкретных условиях инкапсуляция сервисного слоя в одном месте с возможностью добавления общего поведения (логирование, валидация, транзакции, обработка событий), имхо, даст только плюсы. Из практики минус только один - медленнее дебажить, но это так себе.
Предлагаете везде применять шину команд?
по вкусу. в моих проектах везде локальная шина.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

Ну что ж, о вкусах не спорят
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Транзакции, синхронизация с read базой данных

Сообщение noLogicOnlyWar »

Еще вопрос в догонку по шине возник (взял tactician если это имеет значение)

Что если я хочу применить мидлварю не ко всем обработчикам команд (к примеру логирование)? Я вижу несколько вариантов:
1) вместо мидлвари - декорировать обработчик, но я написал удобный (как по мне) локатор - заменяем Command на CommandHandler в имени класса и обращаемся к dic за екземпляром, то есть придется от него отказаться и мапить все команды - хэндлеры вручную.
2) использовать две шины с лог-мидлварью и без (но тогда будет неудобно инжектить в конструктор контроллера зависимость на шину)
3) занести в обработчик сведения о том надо ли его логировать или нет, например чтобы он имплементил интерфейс и разрешать вопрос прямо в мидлвари (теоретически неверный вариант я так пологаю)
4) ...

Как вы решаете?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Транзакции, синхронизация с read базой данных

Сообщение zelenin »

разрешать в мидлвари. либо по паттерну какому-то определять, либо описывать список команд ручками в конфиге или аннотациями. Можно и имплементить интерфейс, но мне кажется команда должна быть чистым классом.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Транзакции, синхронизация с read базой данных

Сообщение ElisDN »

noLogicOnlyWar писал(а): 2017.10.30, 18:58 Как вы решаете?
Маркетным интерфейсом проще всего.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

noLogicOnlyWar писал(а): 2017.10.30, 13:38 Отпишу еще 1 вопрос возникший по пути. Как писал выше сделал декоратор для обработчика который коммитит события. Соответственно надо в голове держать что все обработчики команд должны быть обернуты в этот декоратор, что конечно неудобно, тем более для нескольких программистов. Я так понимаю очевидное решение использовать шину команд + мидлвар? Почему спрашиваю - в соседней теме как раз разговор о подводных камнях шины, правда оборвался на середине.
Никому не хочу ничего доказывать, надеюсь, люди в соседней ветке сами себе могут объяснить, зачем им шина команд и запросов. Если хотят шину и все тут - пусть пользуются.

Инкапсуляцию и прочие хорошие вещи можно сделать и без шины. Она не обязательна для хорошего ООП. По мне шина команд нужна только тогда, когда есть несколько приложений, работающих по отдельности, которые как-то нужно между собой интегрировать. Например есть интернет-магазин на PHP и две CRM на С#. Надо, чтобы CRM также что-то выполняли при действиях пользователя. Да и для этого в основном шины событий хватает. Локальная шина команд - которая ничего не интегрирует - по мне это лишнее. Не надо разделять контексты там, где это не требуется. Это не бесплатно и пользы может и не принести. У завсегдатаев этого форума может быть противоположное мнение, но проана\лизируйте свои задачи, это вам лучше всего скажет нужна шина или нет. Я не говорю, что шина команд это вообще хрень и не нужно ее применять, нужно, но на это должны быть четко сформулированные основания.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Транзакции, синхронизация с read базой данных

Сообщение zelenin »

anton_z писал(а): 2017.10.31, 02:26
noLogicOnlyWar писал(а): 2017.10.30, 13:38 Отпишу еще 1 вопрос возникший по пути. Как писал выше сделал декоратор для обработчика который коммитит события. Соответственно надо в голове держать что все обработчики команд должны быть обернуты в этот декоратор, что конечно неудобно, тем более для нескольких программистов. Я так понимаю очевидное решение использовать шину команд + мидлвар? Почему спрашиваю - в соседней теме как раз разговор о подводных камнях шины, правда оборвался на середине.
Никому не хочу ничего доказывать, надеюсь, люди в соседней ветке сами себе могут объяснить, зачем им шина команд и запросов. Если хотят шину и все тут - пусть пользуются.

Инкапсуляцию и прочие хорошие вещи можно сделать и без шины.
и получится локальная шина
anton_z писал(а): 2017.10.31, 02:26 По мне шина команд нужна только тогда, когда есть несколько приложений, работающих по отдельности, которые как-то нужно между собой интегрировать. Например есть интернет-магазин на PHP и две CRM на С#. Надо, чтобы CRM также что-то выполняли при действиях пользователя. Да и для этого в основном шины событий хватает. Локальная шина команд - которая ничего не интегрирует - по мне это лишнее
локальная шина - это способ организации сервисного слоя.
anton_z писал(а): 2017.10.31, 02:26Не надо разделять контексты там, где это не требуется. Это не бесплатно и пользы может и не принести
шина не разделяет контексты
anton_z писал(а): 2017.10.31, 02:26У завсегдатаев этого форума может быть противоположное мнение, но проана\лизируйте свои задачи, это вам лучше всего скажет нужна шина или нет. Я не говорю, что шина команд это вообще хрень и не нужно ее применять, нужно, но на это должны быть четко сформулированные основания.
Антон, мне кажется ты под шиной подразумеваешь что-то монстроподобное типа ESB, в то время как мы про тупой сервис, инкапсулирующий сервисный слой - в простейшей реализации один класс из 10 строк и реестр хэндлеров в виде массива - да, тут конечно есть о чем задуматься.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

zelenin писал(а): 2017.10.31, 10:19 Антон, мне кажется ты под шиной подразумеваешь что-то монстроподобное типа ESB, в то время как мы про тупой сервис, инкапсулирующий сервисный слой - в простейшей реализации один класс из 10 строк и реестр хэндлеров в виде массива - да, тут конечно есть о чем задуматься.
Ну не такие они и монстроподобные. Так вы про штуку, внутри которой есть что-то типа этого:

Код: Выделить всё


$handler_class = get_class($command) . 'Handler';

//или

$hanler_class = $this->handlers[get_class($command)];

$handler = $this->container->get($handler_class);

return $handler->execuite($command);

Верно?
Не подскажете, этот паттерн описан в каком-нибудь справочнике паттернов? Для меня это что-то типа сервис-локатора или доменного фронт-контроллера.

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

$response = $bus->execute($command);

Тип аргумента нестрогий, тип возвращаемого значения также нестрогий.

Я понимаю, что это может быть и более быстрая локация сервисов, чем непосредственная их инжекция в контроллер, но все же, минусы есть - runtime локация сервисов, нестрогая типизация. Альтернатива - инжекция сервисов в контроллер по интерфейсу, декорирование этих интерфейсов вместо декорирования шины - все можно сделать статически, типизацию сохраним, но писанины больше. Не спорю, палка о двух концах. Поправьте, если что-то не понял.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Транзакции, синхронизация с read базой данных

Сообщение zelenin »

anton_z писал(а): 2017.11.01, 14:05
zelenin писал(а): 2017.10.31, 10:19 Антон, мне кажется ты под шиной подразумеваешь что-то монстроподобное типа ESB, в то время как мы про тупой сервис, инкапсулирующий сервисный слой - в простейшей реализации один класс из 10 строк и реестр хэндлеров в виде массива - да, тут конечно есть о чем задуматься.
Ну не такие они и монстроподобные. Так вы про штуку, внутри которой есть что-то типа этого:

Код: Выделить всё


$handler_class = get_class($command) . 'Handler';

//или

$hanler_class = $this->handlers[get_class($command)];

$handler = $this->container->get($handler_class);

return $handler->execuite($command);

Верно?
Не подскажете, этот паттерн описан в каком-нибудь справочнике паттернов?
выше - это вариант реализации локатора хэндлера, по большому счету к шине отношения не имеющий. есть паттерн шина, есть паттерн команда (лучше сообщение).
anton_z писал(а): 2017.11.01, 14:05Для меня это что-то типа сервис-локатора или доменного фронт-контроллера.
шина - это скорее что-то куда ты отправляешь сообщение, а подписчики с ним что-то делают. Да, в простом варианте это одно сообщение - один обработчик.
anton_z писал(а): 2017.11.01, 14:05Видел раньше такое, но от использования этого меня останавливала неявность при вызове такой шины, так как конкретный получатель будет определяться не статически, а динамически, во время исполнения.
в таком случае $container->set(PostRepository::class, PostMysqlRepository::class) или подписчики на события тоже неявно, т.к. принцип один в один.
anton_z писал(а): 2017.11.01, 14:05 $response = $bus->execute($command);

Тип аргумента нестрогий, тип возвращаемого значения также нестрогий.
есть такое. Но этого перестаешь бояться, когда шина из локальной превращается в межсервисную - там сообщения нужно по другому маркировать и в разные языки интегрировать.
anton_z писал(а): 2017.11.01, 14:05 Я понимаю, что это может быть и более быстрая локация сервисов, чем непосредственная их инжекция в контроллер, но все же, минусы есть - runtime локация сервисов, нестрогая типизация. Альтернатива - инжекция сервисов в контроллер по интерфейсу, декорирование этих интерфейсов вместо декорирования шины - все можно сделать статически, типизацию сохраним, но писанины больше. Не спорю, палка о двух концах. Поправьте, если что-то не понял.
альтернатива - да. Явность - да. Но раздробленность всего слоя, и соответственно невозможность применить к сервисному слою общее поведение.
Tommi
Сообщения: 90
Зарегистрирован: 2013.08.01, 13:44

Re: Транзакции, синхронизация с read базой данных

Сообщение Tommi »

Так а в чем точный смысл для существования шины? какова ее ответственность?
Такое:
- получить команду (команда в виде dto) как сообщение
- определить обработчик команды
- заинжектить в конструктор обработчика и метод handle зависимости
- выполнить команду
- какие то еще сообщения у себя вызывать в этом workflow чтобы к шине можно было поведений или слушателей накидать?
Как все это выглядело бы в коде, не одна шина а разные потомки одной шины, не на одну же шину все вешать. Чтоб из конфига нужной шине навесить нужных ей обработчиков.
?

И еще - вы бы использовали вот этот команду DTO чтобы из сервисного слоя возвращать результат работы? Что то как так:

$command = new CreatePostCommand(['model' => ...]);
...
$commandBus->handle($command);

if ($command->data['result'] == CreatePostCommand::RESULT_SUCCESS) {
\Yii::$app->session->addFlash($command->data['successmessage']);
return $this->render('success', ['model' => $command->model]);
}
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Транзакции, синхронизация с read базой данных

Сообщение zelenin »

Tommi писал(а): 2017.11.02, 17:37 Так а в чем точный смысл для существования шины? какова ее ответственность?
обработка сообщений. частный случай - обработка команд.
Tommi писал(а): 2017.11.02, 17:37 - заинжектить в конструктор обработчика и метод handle зависимости
это ответственность контейнера
Tommi писал(а): 2017.11.02, 17:37 Как все это выглядело бы в коде, не одна шина а разные потомки одной шины, не на одну же шину все вешать. Чтоб из конфига нужной шине навесить нужных ей обработчиков.
это плохой русский. я не понимать.

Tommi писал(а): 2017.11.02, 17:37 И еще - вы бы использовали вот этот команду DTO чтобы из сервисного слоя возвращать результат работы? Что то как так:

$command = new CreatePostCommand(['model' => ...]);
...
$commandBus->handle($command);

if ($command->data['result'] == CreatePostCommand::RESULT_SUCCESS) {
\Yii::$app->session->addFlash($command->data['successmessage']);
return $this->render('success', ['model' => $command->model]);
}
я использую контекст
https://github.com/zelenin/message-bus#example
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Транзакции, синхронизация с read базой данных

Сообщение ElisDN »

Tommi писал(а): 2017.11.02, 17:37 Как все это выглядело бы в коде, не одна шина а разные потомки одной шины, не на одну же шину все вешать. Чтоб из конфига нужной шине навесить нужных ей обработчиков.
Либо через декораторы, либо через middleware c __invoke($command, $next): Context.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

zelenin писал(а): 2017.11.02, 17:54 обработка сообщений. частный случай - обработка команд.
Про разделение контекстов. Некоторое разделение она все же дает. Так как сущности нельзя приравнивать к сообщениям из шины их не получишь, в шину не передашь. Только сообщения можно. Таким образом, получаем разделение на контексты передатчика/приемника сообщений.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

Пока для меня локальная шина - попытка втащить шину для передачи сообщений удаленным получателям в локальный код без достаточных на то оснований. Мои аргументы/невыясненные вопросы:
1. В шину можно передавать только сообщения. Сообщения по сути являются dto. Они проектируются такими для того, чтобы быть сериализуемыми, чтобы их можно было передавать по проводам. Если нет никакой передачи, зачем они нужны? Чтобы выбрать обработчик? Можно его и так из контейнера взять. Используя шину, придется всегда сначала сделать сообщение. Даже для одного-двух значений, которые бы прекрасно стали аргументами вызова метода.
2. В ООП сообщенями по сути являются вызовы методов. В Smalltalk, кстати, не говорят "вызвать метод объекта", а "отправить объекту сообщение". В локальной шине это делается с помощью пачки инфраструктурного кода. Да, он небольшой, но он лишний. Он станет действительно нужен только тогда, когда появится необходимость в передачи команд в удаленные контексты через очереди (а это может и вообще никогда не понядобится в конкретном проекте). С локальной шиной команд вы фактически используете инфраструктуру для RPC там где его нет. Есть такой принцип YAGNI.
3. Не видел, чтобы шину так описывали (как локальный способ организации сервисного слоя) или использовали хоть в одной сколько-нибудь авторитетной книге. Можете привести, если знаете такие? Или это ваш собственный паттерн?
4. Вы говорили про "невозможность применить к сервисному слою общее поведение". Возникает вопрос, какое поведение имеется ввиду, что такого общего может понадобиться всему без исключений сервисному слою?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Транзакции, синхронизация с read базой данных

Сообщение zelenin »

anton_z писал(а): 2017.11.05, 02:05 1. В шину можно передавать только сообщения. Сообщения по сути являются dto. Они проектируются такими для того, чтобы быть сериализуемыми, чтобы их можно было передавать по проводам. Если нет никакой передачи, зачем они нужны? Чтобы выбрать обработчик? Можно его и так из контейнера взять. Используя шину, придется всегда сначала сделать сообщение. Даже для одного-двух значений, которые бы прекрасно стали аргументами вызова метода.
одно-два значения - тоже сообщение. Любой сервисный слой - это сообщения. ООП - это о поведениях и сообщениях.
В шину можно кинуть ['type' => 'deletePost', 'id' => $postId]
anton_z писал(а): 2017.11.05, 02:05 3. Не видел, чтобы шину так описывали (как способ организации сервисного слоя) или использовали хоть в одной сколько-нибудь авторитетной книге. Можете привести, если знаете такие.
сервисный слой знаете? вот это оно и есть. кидаем сообщение в сервис.
anton_z писал(а): 2017.11.05, 02:054. Вы говорили про "невозможность применить к сервисному слою общее поведение". Возникает вопрос, какое поведение имеется ввиду, что такого общего может понадобиться всему без исключений сервисному слою?
так мы собственно в этой теме и обсуждаем такое общее поведение. Одна транзакция на сервисную операцию, публикация событий, логирование сообщений.

anton_z писал(а): 2017.11.05, 02:05 2. В ООП сообщенями по сути являются вызовы методов. В Smalltalk, кстати, не говорят "вызвать метод объекта", а "отправить объекту сообщение". В локальной шине это делается с помощью пачки инфраструктурного кода. Да, он небольшой, но он лишний. Он станет действительно нужен только тогда, когда появится необходимость в передачи команд в удаленные контексты через очереди (а это может и вообще никогда не понядобится в конкретном проекте). С локальной шиной команд вы фактически используете инфраструктуру для RPC там где его нет.
мы буквально обсуждаем в стиле "- мне нравится. - а мне нет". У меня и автора есть проблема - я ее решил и посоветовал ТС. Не используя никакой инфраструктуры rpc, а лишь задекорировав сервис в более общий сервис с подмешиванием фнукционала (классика декорирования).
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

zelenin писал(а): 2017.11.05, 02:34 мы буквально обсуждаем в стиле "- мне нравится. - а мне нет". У меня и автора есть проблема - я ее решил и посоветовал ТС. Не используя никакой инфраструктуры rpc, а лишь задекорировав сервис в более общий сервис с подмешиванием фнукционала (классика декорирования).
Получается у вас шина - это декоратор. Это не классика декорирования. Классический декоратор имеет с декорируемым классом общий интерфейс. У вас его нет.
zelenin писал(а): 2017.11.05, 02:34 так мы собственно в этой теме и обсуждаем такое общее поведение. Одна транзакция на сервисную операцию, публикация событий, логирование сообщений.
А вы не думали, что в некоторых сервисах, прицепленных к такой шине, может быть не нужны транзакции? А может в сервисе нужно сделать две транзакции? Слишком широкие обобщения. Большой риск, что станет мешать или потребует костылей.

zelenin писал(а): 2017.11.05, 02:34
сервисный слой знаете? вот это оно и есть. кидаем сообщение в сервис.
Можно решить вызовом метода сервиса. Если нужен декоратор, декорируется сам сервис, его интерфейс не становится при этом обощенным. Не понимаю зачем терять типизацию, если нет сериализации и передачи сообщений в другие контексты.


Про задачу ТС - я тоже решал подобную задачу. У меня работает так - удаляется объект. У него есть метод delete() - в нем записываем событие в базу в одной транзакции с удалением данных. Потом в другом процессе достаем события из БД по очереди и обрабатываем.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Транзакции, синхронизация с read базой данных

Сообщение ElisDN »

anton_z писал(а): 2017.11.05, 02:05 1. ... Чтобы выбрать обработчик? Можно его и так из контейнера взять. Используя шину, придется всегда сначала сделать сообщение. Даже для одного-двух значений, которые бы прекрасно стали аргументами вызова метода.
DTO удобны не только для передачи по сети, но и для передачи данных из слоя в слой. Так что если из контроллера в прикладной сервис и так передаются DTO, то изначально разницы нет, сами Вы достаёте обработчик из контейнера и вызываете:

Код: Выделить всё

$this->userServise->signup(new SignupRequest(...));
либо его достаёт и вызывает шина:

Код: Выделить всё

$this->commandBus->handle(new SignupCommand(...));

Код: Выделить всё

class SimpleBus implements CommandBus
{
    public function handle($command) {
        $handler = $this->resolver->resolve($command);
        $handler($command);
    }
}
Типизация здесь теряется только один раз в контроллере.
anton_z писал(а): 2017.11.05, 02:05 1. Если нет никакой передачи, зачем они нужны?
2. Он станет действительно нужен только тогда, когда появится необходимость в передачи команд в удаленные контексты через очереди...
3. Не видел, чтобы шину так описывали. Или это ваш собственный паттерн?
Снова смешиваете концептуально понятия. В данном случае это "локальная шина", "шина команд", "шина сообщений", "очередь событий". Поэтому и путаница.

Суть CommandBus здесь - достать привязанный обработчик и выполнить его за нас. А как только говорим о передаче сообщений по сети - то это уже как раз про сеть и очереди.

Есть паттерн Command с данными и методом execute() прямо в классе команды. Это неудобно с точки зрения DI. Удобнее разбить на DTO Command с данными и на сервис Handler с handle($command). Но теперь для автоматизации нужен какой-то менеджер CommandBus, который бы сам искал нужный обработчик.

Поэтому и спорим опять, что мы говорим про инкапсуляцию централизованного выполнения прикладных сервисов в классе CommandBus, а Вы всё время про передачу по сети через RabbitMQ.
anton_z писал(а): 2017.11.05, 02:05 4. Вы говорили про "невозможность применить к сервисному слою общее поведение". Возникает вопрос, какое поведение имеется ввиду, что такого общего может понадобиться всему без исключений сервисному слою?

Можно решить вызовом метода сервиса. Если нужен декоратор, декорируется сам сервис, его интерфейс не становится при этом обощенным. Не понимаю зачем терять типизацию, если нет сериализации и передачи сообщений в другие контексты.
В ответе выше по ссылке про декораторы приводил пример с валидацией и логгированием. В случае с шиной можно навешивать общее поведение прямо на шину:

Код: Выделить всё

class Bus implements CommandBus
{
    public function handle($command) {
        // код, выполняемый до обработчика
        // вроде валидации команды, открытия try {}, старта транзакции и т.п.
        $handler($command);
        // код, выполняемый после обработчика
        // вроде отлова catch, закрытия транзакции, логгирования исполнения, релиза событий и т.п.
    }
}
Намного проще для общего инфраструктурного кода один раз сделать декораторы ValidationBus, LoggingBus и тот же TransactionBus (или соответствующие Middleware для MiddlewareBus) для самой шины, чем, как предлагаете, декорировать каждый UserService.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Транзакции, синхронизация с read базой данных

Сообщение anton_z »

ElisDN писал(а): 2017.11.05, 09:38
Суть CommandBus здесь - достать привязанный обработчик и выполнить его за нас. А как только говорим о передаче сообщений по сети - то это уже как раз про сеть и очереди.

Есть паттерн Command с данными и методом execute() прямо в классе команды. Это неудобно с точки зрения DI. Удобнее разбить на DTO Command с данными и на сервис Handler с handle($command). Но теперь для автоматизации нужен какой-то менеджер CommandBus, который бы сам искал нужный обработчик.

Поэтому и спорим опять, что мы говорим про инкапсуляцию централизованного выполнения прикладных сервисов в классе CommandBus, а Вы всё время про передачу по сети через RabbitMQ.
Хорошо. убедили. Паттерн рабочий. Не говорю, что буду использовать, но буду знать. Спасибо.
Ответить