C # net ядро, упрощающее лямбда-выражение умно - обновлено - PullRequest
0 голосов
/ 31 октября 2018

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

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

imageservices.AddMassTransit(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq()">

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

Ответы [ 3 ]

0 голосов
/ 31 октября 2018

Предположение: В моем ответе предполагается, что существует перегрузка для .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);
        });
    }

}));
0 голосов
/ 31 октября 2018

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

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

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

0 голосов
/ 31 октября 2018

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