订阅动态消息

时间:2014-03-27 15:26:37

标签: nservicebus

以下是我的情景:

我们有一个中央办公室和一些地点(50个并且正在增长)。我们的消息目前是从中心办公室发布的,这些消息是位于这些地点的机器订阅的。目前,每个位置都将收到此类型的所有已发布消息。消息具有locationnumber属性,该属性标识特定消息与哪个位置相关,并且在该位置的客户端上,忽略没有匹配位置数的任何消息。我想要做的是根据订户所在的位置动态创建一个消息类,并让它订阅该类型的消息。然后,发布者将检查数据并生成“相同”的动态消息类(不确定这是否匹配,在此过程中还没有那么远)并发布该消息,以便只有那些实际感兴趣的位置消息已发送给他们。这可能吗?我是否认为NServiceBus完全错误,是否有另一种方法可以进行此类过滤? 我目前只是试图订阅我创建的动态类型,我在尝试时遇到此错误:

No destination could be found for message type DerivedClassOne. Check the <MessageEndpointMappings> section of the configuration of this endpoint for an entry either for this specific message type or for its assembly.

这是我的配置文件:

<configuration>
  <configSections>
    <section name="UnicastBusConfig" type="NServiceBus.Config.UnicastBusConfig, NServiceBus.Core" />
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
  </startup>
  <UnicastBusConfig DistributorControlAddress="" DistributorDataAddress="" ForwardReceivedMessagesTo="">
    <MessageEndpointMappings>
      <!-- publishers don't need to set this for their own message types -->
      <add Messages="NSBDynamicSubscriptionSpike.Messages" Endpoint="basequeue" />
    </MessageEndpointMappings>
  </UnicastBusConfig>

  <appSettings>
    <add key="MessageClassName" value="DerivedClassOne"/>
  </appSettings>
</configuration>

这是我的代码:

namespace NSBDynamicSubscriptionSpike.Messages
{
    public class BaseMessageClass : IMessage
    {
        public string BaseStringProp { get; set; }
        public int BaseIntProp { get; set; }
    }
}

namespace NSBDynamicSubscriptionSpike.Server
{
    public class MessageHandler : AsA_Server,
        IWantCustomInitialization,
        IConfigureThisEndpoint,
        IWantToRunWhenBusStartsAndStops
    {
        private Type myMessageType;

        public IBus Bus { get; set; }

        public void Handle(BaseMessageClass message)
        {
            BaseMessageClass m = (BaseMessageClass) message;
            Console.WriteLine(string.Format("Message type: {0}", m.GetType()));
            Console.WriteLine(string.Format("{0}: {1}", m.BaseIntProp, m.BaseStringProp));
        }

        private bool HandleMessage(object message)
        {
            Handle((BaseMessageClass)message);
            return true;
        }


        public void Init()
        {
            NServiceBus.Configure.With()
                .DefaultBuilder()
                .Log4Net()
                .MsmqTransport()
                .UnicastBus()
                .BinarySerializer()
                .InMemorySubscriptionStorage()
                .UseInMemoryTimeoutPersister()
                .InMemoryFaultManagement()
                .InMemorySagaPersister();
        }


        private Type GenerateDynamicType()
        {
            string dcName = ConfigurationManager.AppSettings["MessageClassName"];
            AssemblyName aName = new AssemblyName(dcName);
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
            ModuleBuilder mb = ab.DefineDynamicModule(dcName);

            TypeBuilder tb = mb.DefineType(dcName,TypeAttributes.Public | TypeAttributes.Class, typeof(BaseMessageClass));

        }

        public void Start()
        {
            myMessageType = GenerateDynamicType();

            //I tried this method first to get the endpoint set up for this type
            MessageEndpointMappingCollection mappings = new MessageEndpointMappingCollection();
            MessageEndpointMapping m;
            m = new MessageEndpointMapping();
            m.AssemblyName = myMessageType.AssemblyQualifiedName;
            m.Messages = myMessageType.FullName;
            m.Endpoint = "basequeue";
            mappings.Add(m);

            IComponentConfig<UnicastBusConfig> busConfig  = Configure.Instance.Configurer.ConfigureComponent<UnicastBusConfig>(ComponentCallModelEnum.None);
            busConfig.ConfigureProperty(u => u.MessageEndpointMappings, mappings);


            //I also tried this from another SO question I found somewhat related, pick one or the other, not both (I think)
            var ucb = Configure.Instance.Configurer.ConfigureComponent<NServiceBus.Unicast.UnicastBus>(ComponentCallModelEnum.Singleton);
            ucb.ConfigureProperty(u => u.MessageOwners, new Dictionary<string, string>()
            {
                {myMessageType.AssemblyQualifiedName, "basequeue"}
            });

            //the line below is what throws the error
            Bus.Subscribe(myMessageType, HandleMessage);
        }

        public void Stop()
        {
            Bus.Unsubscribe(myMessageType);
        }
    }
}

1 个答案:

答案 0 :(得分:1)

我认为没有内置任何可以帮助你的东西。你要做的是content based routing。无论这是不是一个好主意,NSB并不是为了开箱即用而设计的(它是unicast总线)。

我建议采取另一个方向并装饰ISubscriptionStorageStorageDrivenPublisher(实现IPublishMessages)使用此接口来获取要将消息发布到的地址列表。

您的位置编号属性应该成为标题,而不是类属性。

包装器/装饰器可以检查一些参数(例如,传入的位置编号消息头)并稀释由IEnumerable<Address>的修饰实现返回的GetSubscriberAddressesForMessage

以下示例装饰RavenSubscriptionStorage

public class DecoratedSubscriptionStorage : ISubscriptionStorage
{
  public RavenSubscriptionStorage Base { get; set; } //will be injected

  void Subscribe(Address address, IEnumerable<MessageType> messageTypes)
  {
    Base.Subscribe(address, messageTypes);
  }

  void Unsubscribe(Address address, IEnumerable<MessageType> messageTypes)
  {
    Base.Unsubscribe(address, messageTypes);
  }

  IEnumerable<Address> GetSubscriberAddressesForMessage(
                  IEnumerable<MessageType> messageTypes)
  {
    var addresses = Base.GetSubscriberAddressesForMessage(messageTypes);

    //your logic goes here
    return addresses;
  }

  public void Init()
  {
    Base.Init();
  }
}

但是,如果由DI框架/引导程序配置,您自己实现的此接口可以动态修饰NSB正在使用的任何实现。