Сообщение Publi sh generi c (на основе интерфейса) и сообщение «Использовать конкретный (конкретный класс)» в MassTransit - PullRequest
0 голосов
/ 10 марта 2020

У меня такая проблема выбора дизайна и как-то неопрятно, но напрасно. Это работает только в определенном c сценарии.

Я пытаюсь опубликовать sh и использовать сообщение в MassTransit. Пример: (Publisher - простое консольное приложение)

IShape message = GetShape(/**Business Logic will return some concrete object (Circle or square) based on some business inputs**/);  
bus.Publish(message);

(Потребители - CircleConsumer и SquareConsumer)

 class CircleConsumer : IConsumer<IShape>
    {
        public Task Consume(ConsumeContext<IShape> context)
        {
            var circle = context.Message as Circle;
            return Task.CompletedTask;
        }
    }

    class SquareConsumer : IConsumer<IShape>
    {
        public Task Consume(ConsumeContext<IShape> context)
        {
            var square = context.Message as Square;
            return Task.CompletedTask;
        }
    }

(Конфигурация потребителей в. Net Базовый проект размещенной службы)

 public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>()
                   .AddScoped<SquareConsumer>()
                    .AddScoped<CircleConsumer>()
                    .AddMassTransit(cfg =>
                     {
                         cfg.AddBus(ConfigureBus);
                         cfg.AddConsumer<SquareConsumer>();
                         cfg.AddConsumer<CircleConsumer>();
                     })
                    .AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>())
                    .AddSingleton<IHostedService, TestMTConsumerHostedService>();

                    IBusControl ConfigureBus(IServiceProvider provider)
                    {
                        return Bus.Factory.CreateUsingRabbitMq(cfg =>
                        {
                            var host = cfg.Host(hostContext.Configuration["RabbmitMQ:Server:Host"], hostContext.Configuration["RabbmitMQ:Server:VirtualHost"], h =>
                            {
                                h.Username(hostContext.Configuration["RabbmitMQ:Auth:Username"]);
                                h.Password(hostContext.Configuration["RabbmitMQ:Auth:Password"]);
                            });

                            cfg.ReceiveEndpoint("CircleQueue", ep =>
                            {
                                ep.PrefetchCount = 16;
                                ep.UseMessageRetry(r => r.Interval(2, 100));
                                ep.Consumer<CircleConsumer>(provider);
                            });

                            cfg.ReceiveEndpoint("SquareQueue", ep =>
                            {
                                ep.PrefetchCount = 16;
                                ep.UseMessageRetry(r => r.Interval(2, 100));
                                ep.Consumer<SquareConsumer>(provider);
                            });
                        });
                    }

                });

Мое требование - опубликовать Publisher sh сообщение без знания конкретных классов. И только один из потребителей получает сообщение в зависимости от типа сообщения.

Но похоже, что оба получателя получают сообщение, а также приведение не работает. Желаемый: Предположим, что когда издатель отправляет объект Square, вызов должен принимать только пользователь Square. Но в моем случае и SquareConsumer, и CircleConsumer получают сообщение.

В качестве обходного пути это работает:

  1. Всегда публикуется sh конкретные объекты.

            bus.Publish(new Square());
    
  2. Объявите потребителей с конкретными типами.

           class CircleConsumer : IConsumer<Circle>
            {
                public Task Consume(ConsumeContext<Circle> context)
                {
                    var circle = context.Message;
                    return Task.CompletedTask;
                }
            }
    
            class SquareConsumer : IConsumer<Square>
            {
                public Task Consume(ConsumeContext<Square> context)
                {
                    var square = context.Message;
                    return Task.CompletedTask;
                }
            }
    

Но было бы здорово, если бы я мог сделать это в общем.

Есть предложения?

Ответы [ 2 ]

2 голосов
/ 10 марта 2020

Если вы измените свой код следующим образом:

object message = GetShape(/**Business Logic will return some concrete object (Circle or square) based on some business inputs**/);  
bus.Publish(message);

и потребители

class CircleConsumer : IConsumer<Circle>
{
    public Task Consume(ConsumeContext<Circle> context)
    {
        // do circle stuff
    }
}

class SquareConsumer : IConsumer<Square>
{
    public Task Consume(ConsumeContext<Square> context)
    {
        // do square stuff
    }
}

, он будет работать как положено.

Здесь я подробно остановлюсь на изменениях:

  1. Использование Publish с экземпляром указанного типа c означает использование перегрузки Publish<T>(T message), которая использует T в качестве типа сообщения. При явной установке типа сообщения на object мы вызываем перегрузку Publish(object message). В этом случае MassTransit будет искать все типы, которые реализует сообщение.
  2. Вам не нужно использовать сообщение типа общего интерфейса, если вы хотите использовать сообщения конкретного типа. Вам просто нужно создать потребителей для указанных типов c. Пока вы используете publi sh, как я описал в предыдущем пункте, сообщение будет go на IShape и Circle обменах (например).
0 голосов
/ 10 марта 2020

Обновление: Наконец-то я подошел с подходом ниже. Однако у меня sh MassTransit была возможность маршрутизировать сообщения чисто полиморфные c по своей природе. Это просто обходной путь, а не верное решение. Все еще добро пожаловать в новые подходы.

Используя небольшую помощь от рефлексии и своего рода методов друзей в конкретных классах, я пришел к этому.

Издатель:

            IShape message = GetShape(text);
            var castedMessage = ReflectionHelper.CastToConcreteType(message);
            bus.Publish(castedMessage);

        public static class ReflectionHelper
        {
            public static object CastToConcreteType(object obj)
            {
                MethodInfo castMethod = obj.GetType().GetMethod("Cast").MakeGenericMethod(obj.GetType());
                return castMethod.Invoke(null, new object[] { obj });
            }
        }

Интерфейс и Конкретные классы для типов сообщений:

    public interface IShape
        {
            public string Color { get;  }
            public string Name { get; }
        }

        public class Circle : IShape, ITypeCastable
    {
            public string Color => "Red";
            public string Name => $"{Color}Circle";
            T ITypeCastable.Cast<T>(object obj) => Cast<T>(obj);
            public static T Cast<T>(object o) => (T)o;
        }
        public class Square : IShape, ITypeCastable
        {
            public string Color => "Green";
            public string Name => $"{Color}Square";
            T ITypeCastable.Cast<T>(object obj) => Cast<T>(obj);
            public static T Cast<T>(object o) => (T)o;
        }

        public interface ITypeCastable
        {
            T Cast<T>(object obj);
        }

Потребители: очень незначительное изменение путем замены интерфейсов именами конкретных классов в типовой поставке generi c.

    class CircleConsumer : IConsumer<Circle>
    {
        public Task Consume(ConsumeContext<Circle> context)
        {
            var circle = context.Message;
            return Task.CompletedTask;
        }
    }

    class SquareConsumer : IConsumer<Square>
    {
        public Task Consume(ConsumeContext<Square> context)
        {
            var square = context.Message;
            return Task.CompletedTask;
        }
    }
...