Как лучше собрать большую коллекцию обработчиков сообщений - PullRequest
3 голосов
/ 20 мая 2011

Я начинаю работу над системой обработки сообщений, где различные сообщения будут поступать из внешнего источника и должны быть обработаны.Объекты сообщения в основном являются объектами DTO.

Я думаю о том, чтобы классы сообщений находились в собственной сборке, доступной как для системы обработки сообщений, так и для внешней системы, и чтобы обработчики наследовали от общего абстрактного классавдоль следующих грубых линий:

Messages.dll

public abstract class Message {}

public class FooMessage : Message
{
  ...
}

public class BarMessage : Message
{
  ...
}

MessageHandlers.dll

public abstract class MessageHandler<TMessage> where TMessage : Message
{
  public void Handle(TMessage message);
}

public class FooHandler : MessageHandler<FooMessage>
{
  public void Handle(FooMessage message)
  {
    ...
  }
}

А теперь вопрос : Как лучше всего склеить все это?Я, конечно, могу немного подумать и написать вспомогательную функцию для создания экземпляра соответствующего обработчика на основе имени входящего сообщения, но разве это не та штука, с которой контейнеры IoC должны быть хороши?Какой конкретный лучше подходит для работы?Мнения и указатели приветствуются.

Спасибо!

Ответы [ 4 ]

2 голосов
/ 23 мая 2011

Есть несколько способов зарегистрировать ваши обработчики. Я всегда начинаю с настройки всего вручную. В вашем примере это будет выглядеть так:

<!-- language: c# -->
container.Register<IMessageHandler<FooMessage>, FooHandler>();
container.Register<IMessageHandler<BarMessage>, BarHandler>();
// etc

Совет : Вместо использования абстрактного базового класса определите интерфейс IMessageHandler<T>.

Как только приложение растет, это может стать громоздким, поэтому в вашем случае стоит начать регистрацию партии. Все контейнеры DI имеют разные механизмы для этого. Большинство контейнеров поддерживают это из коробки, а другие - нет. Вот как это сделать, используя Simple Injector :

<!-- language: c# -->
container.RegisterManyOpenGeneric(typeof(MessageHandler<>),
    typeof(MessageHandler<>).Assembly);

Это зарегистрирует все конкретные типы, которые реализуют MessageHandler<T> и находятся в той же сборке, что и MessageHandler<T>. Опять же, это зависит от вашего выбора контейнера, как написать это.

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

Например, вы можете определить интерфейс IMessageProcessor, который позволяет обрабатывать любой тип Message:

<!-- language: c# -->
public interface IMessageProcessor
{
    void Process(Message message);
}

Рядом с вашей конфигурацией DI вы можете определить реализацию IMessageProcessor, и она может выглядеть следующим образом:

<!-- language: c# -->
private class DIMessageProcessor : IMessageProcessor
{
    private readonly Container container;

    public DIMessageProcessor(Container container)
    {
        this.container = container;
    }

    public void Process(Message message)
    {
        if (message == null) throw new ArgumentNullException("message");

        Type messageType = message.GetType();

        Type handlerType =
            typeof(IMessageHandler<>).MakeGenericType(messageType);

        var handler = this.container.GetInstance(handlerType);

        handlerType.GetMethod("Handle").Invoke(handler, message);
    }
}

Важно оставить это вне приложения. Мало того, что вы будете смешивать обязанности, но будет сложнее добавить новое поведение. Подумайте, например, о предотвращении повторов сообщений (когда сообщение выполняется несколько раз). Было бы легко реализовать это поведение в качестве декоратора IMessageProcessor. Кроме того, DIMessageProcessor будет легко выполнить модульное тестирование или будет заменено новой, более производительной реализацией (и снова протестировано)

Есть несколько способов зарегистрировать DIMessageProcessor, в зависимости от выбранного вами контейнера. Использование простого инжектора, например:

<!-- language: c# -->
container.RegisterSingle<IMessageProcessor>(new DIMessageProcessor(container));

С этим вы можете ввести IMessageProcessor в типы, которые в этом нуждаются. Вот пример для WCF:

<!-- language: c# -->
[ServiceKnownType("GetKnownMessageTypes")]
public class WCFMessageService
{
    private readonly IMessageProcessor processor;

    public WCFMessageService()
    {
        this.processor = 
            Global.Container.GetInstance<IMessageProcessor>();
    }

    [OperationContract]
    public void Process(Message message)
    {
        this.processor.Process(message);
    }

    public static IEnumerable<Type> GetKnownMessageTypes(
        ICustomAttributeProvider provider)
    {
        var knownMessageTypes =
            from type in typeof(Message).Assembly.GetTypes()
            where typeof(Message).IsAssignableFrom(type)
            select type;

        return knownMessageTypes.ToArray();
    }
}

Надеюсь, это поможет.

0 голосов
/ 20 мая 2011

Предполагая, что все компоненты правильно сконфигурированы в контейнере, вот как вы можете обработать сообщение (где сообщение в этом случае представляет собой сериализованное сообщение, передаваемое по очереди):

var formatter = new BinaryFormatter();
using (var s = new MemoryStream(message.AsBytes))
{
    dynamic consumable = formatter.Deserialize(s);

    var consumerType = typeof(IMessageConsumer<>).MakeGenericType(consumable.GetType());
    dynamic consumer = this.container.Resolve(consumerType);
    consumer.Consume(consumable);
}

Полный пример кода доступен здесь: https://github.com/ploeh/CQRSonAzureDemo/blob/master/BookingWorkerRole/AzureQueueMessageProcessor.cs

0 голосов
/ 21 мая 2011

Поскольку вам необходимо использовать шаблон обмена сообщениями «Запрос / ответ», вы можете использовать проект Agatha (agatha-rrsl) .Это реализация этого паттерна, она зрелая, протестирована под большой нагрузкой и работает в процессе и, конечно, выходит за пределы процесса, используя WCF.

В нее встроено все, что выупомянуть и интегрировать со многими популярными контейнерами IoC (Windsor, StructureMap, Unity, Spring.NET, Ninject), поэтому вам не придется заново изобретать колесо и начинать фокусироваться на своей бизнес-логике.

Вы можете найти оченьполезные статьи, советы по использованию и много информации в блоге Дэви Брайона здесь .

0 голосов
/ 20 мая 2011

Некоторое время назад я делал нечто подобное на основе Виндзора.Вы можете прочитать об этом здесь .

...