Ядро c# net с умным упрощением лямбда-выражения - обновлено

Я ищу умное решение для упрощения лямбда-выражения, которое становится все длиннее и длиннее. Выглядит это примерно так:

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

Error message on using <code>services.AddMassTransit(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq()</code>

Я не уверен, что мне не хватает - все мои потребители реализуют IConsumer<T>, который исходит из пространства имен MassTransit.

1
0
611
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Отказ от ответственности: Я ничего не знаю о 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");        
}));

Этот подход не работает с MassTransit и тем, как он себя настраивает. Хотя он синтаксически правильный (я настроил void ConfigureEndPoint для соответствия типам), он также компилируется и запускается. Однако он дает сбой при первой попытке вызвать связанную с ним конечную точку API.

user1639822 31.10.2018 13:42

В то же время исходный код (который не использует другой вспомогательный метод) работает отлично, как и ожидалось.

user1639822 31.10.2018 13:44

«он вылетает при первой попытке вызвать связанную с этим конечную точку API». Я никогда не работал с MassTransit, но, возможно, сообщение об ошибке, которое вы получаете при сбое, поможет вам понять, что не так ...

Zohar Peled 31.10.2018 13:58

И, на самом деле, НЕТ использует .LoadFrom () - особенно когда присутствует несколько конечных точек приема с одним провайдером. Вы просто потребляете все везде. Посмотрите ответ Алексея ниже, чтобы узнать правильный синтаксис.

Chris Patterson 31.10.2018 14:53

Спасибо за ваш вклад, Крис - я знаю, что у вас есть более глубокие знания об этих вещах. Как мне тогда сделать это правильно? Я проверил ответ Алексея. У меня несколько потребителей подключены к одной шине. Затем каждый потребитель также вернет ответ с .RespondAsync(), который я затем жду от конечной точки API, чтобы затем отобразить сообщение (для команд) и результат (для запросов). Я делаю это, чтобы придерживаться принципов CQRS, так как это гигантский проект со множеством сервисов. Какова наилучшая практика для конечных точек приема и общего обмена данными?

user1639822 31.10.2018 15:00

Я не уверен, почему это принятый ответ, поскольку код для настройки конечной точки просто неправильный. Соглашения о конечных точках не настраивают способ доставки сообщений на конечную точку, они только указывают MassTransit, где отправлять (не публиковать) сообщение при использовании Send(message), без предварительного получения экземпляра ISendEndpoint.

Alexey Zimarev 01.11.2018 09:40

@AlexeyZimarev Вот о чем спросить ОП. Лично я никогда раньше не работал с MassTransit и думаю, что публикация этого ответа могла быть ошибкой, но теперь я не могу удалить его, потому что это принятый ответ.

Zohar Peled 01.11.2018 09:42

@ZoharPeled Я понимаю вашу точку зрения с точки зрения языка C#. Исходный код вопроса был неправильным и соответствовал вашему ответу. Я не виню вас, но мне просто нужно, чтобы люди знали, что код не будет делать то, что от него ожидается.

Alexey Zimarev 01.11.2018 10:03

@AlexeyZimarev Я добавил отказ от ответственности в своем ответе - это действительно все, что я могу сделать на данный момент.

Zohar Peled 01.11.2018 10:13

Предположение: Мой ответ предполагает, что существует перегрузка для .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) не работает - неуниверсальный метод метода не принимает тип в качестве параметра. Так было бы проще :-)
Rui Jarimba 31.10.2018 13:37
Ответ принят как подходящий

Похоже, вы неправильно поняли концепцию конечных точек, потребителей и условностей.

Я очень обеспокоен тем, что вы используете 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));
    }));
}

Помните, что у каждой конечной точки будет своя собственная очередь. Если вы только начинаете свою работу и хотите попробовать что-то, вы можете выбрать первое решение, так что у вас будет одна очередь. Со временем вы можете разделить свои конечные точки.

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

user1639822 31.10.2018 18:55

Спасибо, я попробую - я думаю, он должен работать, как ожидалось, мы свяжемся с вами и сообщим вам об этом в ближайшее время. Ваше объяснение было действительно полезным, поскольку, очевидно, я не настраивал MassTransit, как предполагалось. Итак, большое спасибо!

user1639822 01.11.2018 10:35

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

Alexey Zimarev 01.11.2018 10:37

Да, я сделаю это сейчас. Пожалуйста, проверьте мой обновленный вопрос - у меня возникла ошибка, и я не знаю, почему. У меня тоже есть правильные операторы using, но эта ошибка все равно появляется. Мои потребители реализуют MassTransit.IConsumer<T>

user1639822 01.11.2018 10:47

Почему вы используете Autofac? Ясно, что ошибка исходит из пакета интеграции Autofac.

Alexey Zimarev 01.11.2018 11:41

Другие вопросы по теме