Я ищу умное решение для упрощения лямбда-выражения, которое становится все длиннее и длиннее. Выглядит это примерно так:
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
cfg.ReceiveEndpoint(host, "some_endp_1", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<Class1>(e.InputAddress);
});
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<Class2>(e.InputAddress);
});
// ... a lot more of these here ....
cfg.ReceiveEndpoint(host, "some_endp_n", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<ClassN>(e.InputAddress);
});
}));
Это часть простого проекта веб-API, но проблема здесь не в том, как настраиваются службы, и не в том, как работают конечные точки API. Просто этот фрагмент кода становится многословным и избыточным, чего я бы хотел избежать.
Может быть, это что-то простое, как я могу обобщить это и просто передать список строк конечных точек с соответствующими именами классов без этого большого фрагмента кода?
Спасибо - я попытался изучить это, но не нашел ничего полезного для этой конкретной ситуации.
Обновление 1
После ценных предложений от всех вас код переписывается многими способами. У меня есть помощник:
void ConfigureEndPoint<T>(IRabbitMqHost host, ref IRabbitMqBusFactoryConfigurator cfg, IServiceProvider provider, string endPointName)
where T:class
{
cfg.ReceiveEndpoint(host, endPointName, e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<T>(e.InputAddress);
});
}
Кроме того, в Startup.cs
есть некоторые раздражающие части, которые также довольно избыточны, но обязательно легко перезаписываются, например:
services.AddScoped<MyConsumer1>();
services.AddScoped<MyConsumer2>();
// a lot of them here
services.AddScoped<MyConsumerN>();
Почему это не просто? Каждый потребитель наследует MassTransit.IConsumer<T>
, но здесь T - это либо команда, либо запрос, от которого команды наследуют тип интерфейса и запрашивают интерфейс совершенно другого типа. Следовательно, я действительно не уверен, как это можно сделать общим? Возможно, разделить его на 2 части, 1 часть для команд, 1 часть для запросов?
Я также не знаю, как правильно заменить e.LoadFrom
на e.Consumer<SomeConsumer>(provider);
, потому что способ, которым я настроил службы, по умолчанию несовместим с этим вызовом.
Потребители также добавляются в MassTransit после добавления .AddScoped
как такового:
services.AddMassTransit(x =>
{
x.AddConsumer<MyConsumer1>();
// the rest of the consumers all here, all added the same way
});
Опять же, из первоначального вопроса, CreateUsingRabbitMq
немного лучше, но все еще имеет N строк:
ConfigureEndPoint<SomeCommandOrQuery>(host, ref cfg, provider, "endpoint_name_here");
Я уверен, что есть хороший обходной путь, чтобы сделать код короче и читабельнее, но каковы ваши идеи, как я могу это сделать? Как я могу также заменить e.LoadFrom
на e.Consume<SomeConsumer>()
?
Спасибо, очень ценю сообщество и ваши идеи!
ОБНОВЛЕНИЕ 2
Я не уверен, что мне не хватает - все мои потребители реализуют IConsumer<T>
, который исходит из пространства имен MassTransit.
Отказ от ответственности: Я ничего не знаю о MassTransit - я никогда раньше с ним не работал и даже не слышал о нем, пока не прочитал вопрос.
Вам, вероятно, следует проверить Ответ Алексея Зимарева, поскольку он предлагает другой способ работы с конфигурацией MassTransit. (Не имея опыта работы с этим инструментом, я даже не могу сказать, лучше он или нет, но, вероятно, вы можете проверить это сами.)
Мой ответ чисто с точки зрения C#.
Первое, что приходит в голову, - это создать метод настройки ReceiveEndpoint
- что-то вроде этого (я не знаю, какие типы задействованы, вам, вероятно, придется их изменить):
void ConfigureEndPoint<T>(Host host, Config cfg, Provider provider, string endPointName)
{
cfg.ReceiveEndpoint(host, endPointName, e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<T>(e.InputAddress);
}
}
А затем в своем лямбда-выражении вы просто используете его так:
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
ConfigureEndPoint<Class1>(host, cfg, provider, "some_endp_1");
ConfigureEndPoint<Class2>(host, cfg, provider, "some_endp_2");
// ... a lot more of these here ...
ConfigureEndPoint<Classn>(host, cfg, provider, "some_endp_n");
}));
В то же время исходный код (который не использует другой вспомогательный метод) работает отлично, как и ожидалось.
«он вылетает при первой попытке вызвать связанную с этим конечную точку API». Я никогда не работал с MassTransit, но, возможно, сообщение об ошибке, которое вы получаете при сбое, поможет вам понять, что не так ...
И, на самом деле, НЕТ использует .LoadFrom () - особенно когда присутствует несколько конечных точек приема с одним провайдером. Вы просто потребляете все везде. Посмотрите ответ Алексея ниже, чтобы узнать правильный синтаксис.
Спасибо за ваш вклад, Крис - я знаю, что у вас есть более глубокие знания об этих вещах. Как мне тогда сделать это правильно? Я проверил ответ Алексея. У меня несколько потребителей подключены к одной шине. Затем каждый потребитель также вернет ответ с .RespondAsync()
, который я затем жду от конечной точки API, чтобы затем отобразить сообщение (для команд) и результат (для запросов). Я делаю это, чтобы придерживаться принципов CQRS, так как это гигантский проект со множеством сервисов. Какова наилучшая практика для конечных точек приема и общего обмена данными?
Я не уверен, почему это принятый ответ, поскольку код для настройки конечной точки просто неправильный. Соглашения о конечных точках не настраивают способ доставки сообщений на конечную точку, они только указывают MassTransit, где отправлять (не публиковать) сообщение при использовании Send(message)
, без предварительного получения экземпляра ISendEndpoint
.
@AlexeyZimarev Вот о чем спросить ОП. Лично я никогда раньше не работал с MassTransit и думаю, что публикация этого ответа могла быть ошибкой, но теперь я не могу удалить его, потому что это принятый ответ.
@ZoharPeled Я понимаю вашу точку зрения с точки зрения языка C#. Исходный код вопроса был неправильным и соответствовал вашему ответу. Я не виню вас, но мне просто нужно, чтобы люди знали, что код не будет делать то, что от него ожидается.
@AlexeyZimarev Я добавил отказ от ответственности в своем ответе - это действительно все, что я могу сделать на данный момент.
Предположение: Мой ответ предполагает, что существует перегрузка для .Map()
, которая принимает Type
вместо Generic
. (например, .Map(typeof(Class1), e.InputAddress)
), поскольку это довольно распространено среди таких общих функций.
Если это не так, проигнорируйте этот ответ. Я чувствую, что это может быть полезно для кого-то еще в аналогичной ситуации, который найдет этот вопрос.
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
var mappingTypes = new Type[] { typeof(Class1), typeof(Class2), ... etc };
foreach (var mappingType in mappingTypes)
{
// I did mappingType.Name for simplicity, but you could get the mappingType's index in the collection and use that as well if you need this to be a number.
cfg.ReceiveEndpoint(host, "some_endp_" + mappingType.Name, e =>
{
e.LoadFrom(provider);
// See note at the top of answer.
EndpointConvention.Map(mappingType, e.InputAddress);
});
}
}));
EndpointConvention.Map(mappingType, e.InputAddress)
не работает - неуниверсальный метод метода не принимает тип в качестве параметра. Так было бы проще :-)
Похоже, вы неправильно поняли концепцию конечных точек, потребителей и условностей.
Я очень обеспокоен тем, что вы используете LoadFrom
для каждой конечной точки. Это означает, что все ваши потребители, зарегистрированные в контейнере, будут прослушивать каждую конечную точку.
Обычно, если вы хотите разделить потребителей на конечную точку, вам необходимо явно настроить конечную точку, вызвав ep.Consumer<MyConsumer>(provider)
.
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.Consumer<SomeConsumer>(provider);
});
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.Consumer<SomeOtherConsumer>(provider);
});
Когда вы используете LoadFrom
, каждая из ваших конечных точек будет иметь потребителей все из контейнера. Если вы используете LoadFrom
для каждой конечной точки, каждая конечная точка будет подписана на все ваши сообщения, и вы будете получать каждое сообщение столько раз, сколько таких конечных точек. Определенно, это не то, что вам нужно.
Вы, наверное, неправильно поняли значение EndpointConventions
. Соглашения используются только для отправки сообщений, а не для получения сообщений. Конечные точки будут получать все сообщения, которые они могут использовать.
Решение 1
Если вы не ожидаете большого трафика, вы можете поместить всех потребителей в одну конечную точку, а затем использовать LoadFrom
.
cfg.ReceiveEndpoint(host, "my_service", e =>
{
e.LoadFrom(provider);
});
Решение 2:
Если вы хотите разделить потребителей с одним потребителем на конечную точку и использовать что-то похожее на однострочную конфигурацию конечной точки, это можно легко сделать с помощью этого кода:
using System;
using MassTransit.RabbitMqTransport;
namespace MassTransit.TestCode
{
public static class BusConfigurationExtensions
{
public static void ConfigureEndpoint<T>(this IRabbitMqBusFactoryConfigurator cfg,
IRabbitMqHost host, string endpointName, IServiceProvider provider)
where T : class, IConsumer
=> cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<T>(provider));
}
}
Затем вы можете использовать его как:
cfg.ConfigureEndpoint<SubmitOrderConsumer>(host, "submit_order", provider);
cfg.ConfigureEndpoint<MarkOrderAsPaidConsumer>(host, "mark_paid", provider);
cfg.ConfigureEndpoint<ShipOrderConsumer>(host, "ship_order", provider);
При использовании этого расширения вы не должен используете метод AddMassTransit
из MassTransit.Extensions.DependencyInjection
, но вам необходимо зарегистрировать все ваши зависимости потребителя в коллекции сервисов.
Однако я действительно не вижу смысла в таком расширении, если вы все еще можете использовать этот однострочный код в коде конфигурации шины.
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<SubmitOrderConsumer>(provider));
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<MarkOrderAsPaidConsumer>(provider));
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<ShipOrderConsumer>(provider));
Тот факт, что в моем первом фрагменте кода я использовал группы методов, не означает, что вы не можете просто использовать выражение лямбда.
В следующем выпуске будет новый пакет MassTransit.AspNetCore
для лучшей интеграции MassTransit с ASP.NET Core. Он настроит хостинг шины, правильно зарегистрирует экземпляр шины, а также применит ведение журнала. Тогда конфигурация будет выглядеть так (этот код работает, я только что написал и протестировал его):
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ReceiveEndpoint(host, "message_one", ep => ep.Consumer<MessageOneConsumer>(provider));
cfg.ReceiveEndpoint(host, "message_two", ep => ep.Consumer<MessageTwoConsumer>(provider));
}));
}
Помните, что у каждой конечной точки будет своя собственная очередь. Если вы только начинаете свою работу и хотите попробовать что-то, вы можете выбрать первое решение, так что у вас будет одна очередь. Со временем вы можете разделить свои конечные точки.
Не могли бы вы проверить мою обновленную информацию по этому вопросу и рассказать мне несколько идей / решений? Я считаю, что то, что вы сказали, правильно, но мне нужен правильный способ его использования, чтобы служба не зависала / не становилась бесполезной.
Спасибо, я попробую - я думаю, он должен работать, как ожидалось, мы свяжемся с вами и сообщим вам об этом в ближайшее время. Ваше объяснение было действительно полезным, поскольку, очевидно, я не настраивал MassTransit, как предполагалось. Итак, большое спасибо!
@ user1639822, пожалуйста, подумайте об удалении отметки о принятом ответе, потому что код там не даст желаемого результата и запутает людей, которые попытаются использовать этот код для своей конфигурации MassTransit.
Да, я сделаю это сейчас. Пожалуйста, проверьте мой обновленный вопрос - у меня возникла ошибка, и я не знаю, почему. У меня тоже есть правильные операторы using, но эта ошибка все равно появляется. Мои потребители реализуют MassTransit.IConsumer<T>
Почему вы используете Autofac? Ясно, что ошибка исходит из пакета интеграции Autofac.
Этот подход не работает с MassTransit и тем, как он себя настраивает. Хотя он синтаксически правильный (я настроил void ConfigureEndPoint для соответствия типам), он также компилируется и запускается. Однако он дает сбой при первой попытке вызвать связанную с ним конечную точку API.