0
votes

I am trying to setup NServiceBus Sagas using NHibernate persistence, but I think I have something configured incorrectly because I'm getting an error connecting to a service at 127.0.0.1:8080. I am able to get my saga to handle the command message, but after a few seconds the error message below appears in the console window and then the same command is fired again causing the handler in the saga to be invoked again. This happens repeatedly as long as I allow the application to run. I can tell NHibernate is connecting to my database because it creates a table for the saga data, however nothing is ever persisted in that table.

I think there is an error persisting the saga data, and my guess is that it may be trying to use the default RavenDb saga persistence but I'm not sure why this would be.

The error message I receive is the following:

WARN NServiceBus.Unicast.Transport.Transactional.TransactionalTransport [(null)] <(null)> - Failed raising 'transportmessage received' event for message with ID=3753b476-7501-4fd8-90d0-b10aee95a578\22314 System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:8080 at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress) at System.Net.Sockets.Socket.InternalConnect(EndPoint remoteEP) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult,Exception& exception) --- End of inner exception stack trace --- at NServiceBus.Unicast.UnicastBus.HandleTransportMessage(IBuilder childBuilder, TransportMessage msg) at NServiceBus.Unicast.UnicastBus.TransportMessageReceived(Object sender, TransportMessageReceivedEventArgs e) at System.EventHandler`1.Invoke(Object sender, TEventArgs e) at NServiceBus.Unicast.Transport.Transactional.TransactionalTransport.OnTransportMessageReceived(TransportMessage msg)

A sample of the saga I am trying to use is (nothing fancy here, same thing happens whether or not I actually do something in the Handle method):

public class ItemSaga : Saga<ItemData>, IAmStartedByMessages<CreateItemCommand>
{
    public void Handle(CreateItemCommand message)
    {

    }
}

public class ItemData : ISagaEntity
{
    public Guid Id { get; set; }
    public string Originator { get; set; }
    public string OriginalMessageId { get; set; }
}

My endpoint configuration looks like this:

public class EndpointConfig : IConfigureThisEndpoint, AsA_Publisher, IWantCustomInitialization
{


    public void Init()
    {

        var container = new UnityContainer();
        container.AddNewExtension<Domain.UnityExtension>();

        Configure.With()
            .UnityBuilder(container)
            .JsonSerializer()
            .Log4Net()
            .MsmqSubscriptionStorage()
            .MsmqTransport()
                .PurgeOnStartup(true)
            .UnicastBus()
                .ImpersonateSender(false)
            .DisableTimeoutManager()
            .NHibernateSagaPersister()
            .CreateBus()
            .Start(() => Configure.Instance.ForInstallationOn<NServiceBus.Installation.Environments.Windows>().Install()); 
    }
}

And my app.config looks like this:

<MessageForwardingInCaseOfFaultConfig ErrorQueue="error"/>
  <MsmqTransportConfig NumberOfWorkerThreads="1" MaxRetries="5"/>

  <NHibernateSagaPersisterConfig UpdateSchema="true">
    <NHibernateProperties>
      <add Key="connection.provider" Value="NHibernate.Connection.DriverConnectionProvider"/>
      <add Key="connection.driver_class" Value="NHibernate.Driver.Sql2008ClientDriver"/>
      <add Key="connection.connection_string" Value="Data Source=(localdb)\v11.0;Integrated Security=True;AttachDbFileName=|DataDirectory|\App_Data\EventStore.mdf"/>
      <add Key="dialect" Value="NHibernate.Dialect.MsSql2012Dialect"/>
    </NHibernateProperties>
  </NHibernateSagaPersisterConfig>

  <connectionStrings>
    <add name="EventStore" connectionString="Data Source=(localdb)\v11.0;Integrated Security=True;AttachDbFileName=|DataDirectory|\App_Data\EventStore.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <dependentAssembly>
        <assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" />
        <bindingRedirect oldVersion="0.0.0.0-3.3.0.4000" newVersion="3.3.1.4000" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

Just a couple of notes:

This is from a sample app that I am using to test this functionality. It uses a local database file attached to localdb, however my full application using SQL Server 2012 is exhibiting the same behavior. I also had to add a dependenteAssembly entry for NHibernate because the NServiceBus.NHibernate NuGet package currently binds to an older assembly version (as of this posting).

As you can see, I am also using Unity as my IOC, but I have replicated this with a project using Ninject as well. I am using EventStore for my domain storage which is working great. I have command handlers that handle the command and publish events through EventStore to be handled by other processes. However, I have tried disabling all of those leaving me with just my Saga as a command handler and I still get the same error.

Does anyone have any ideas of what I may be doing wrong?

1
Seems like the endpoint is still connecting to RavenDB. Can you try github.com/NServiceBus/NServiceBus/blob/master/src/timeout/… to run the timeoutmanager in memory just to rule out a bug in the TimeoutManager config?Andreas Öhlund
You should also remove the .CreateBus().Start(...) since the host will do this for youAndreas Öhlund
Thanks for the suggestions Andreas. Even with the in memory timeout manager, however, I'm still getting the same errors.Jeremy

1 Answers

0
votes

I have found a workaround to my problem. It seems to be an issue with using the built-in NServiceBus profiles. I was not specifying a profile in the command line arguments of the host, so by default it was loading the NServiceBus.Production profile. The Production profile, by default, uses RavenDB for all persistence.

Looking at the NServiceBus source on GitHub, the Production Profile Handler contains the following in the ProfileActivated method:

Configure.Instance.RavenPersistence();

if (!Configure.Instance.Configurer.HasComponent<ISagaPersister>())
    Configure.Instance.RavenSagaPersister();

if (!Configure.Instance.Configurer.HasComponent<IManageMessageFailures>())
    Configure.Instance.MessageForwardingInCaseOfFault();

if (Config is AsA_Publisher && !Configure.Instance.Configurer.HasComponent<ISubscriptionStorage>())
    Configure.Instance.RavenSubscriptionStorage();

A couple of things to note here:

  • The profile will always call RavenPersistence() on the Configure instance. I have not dug into the inner workings of that method to see if it will actually bypass configuring Raven if other persistence is already defined, but it will always run this method.
  • When I attach to the NServiceBus source and debug through this code, in the second line HasComponent returns false causing the RavenSagaPersister configuration to be run. This happens even if I have NHibernateSagaPerister is defined in the endpoint configuration.

I'm not sure if this behavior is by design, a bug, or misconfiguration on my part. However my workaround was to create my own profile. I had to move the NHibernate configuration calls from my endpoint config to my new profile, but once I did that I was able to use NHibernate persistence without errors.

My custom profile looks like the following (I borrowed the logging handler from the Production profile's logging handler):

public class MyProfile : IProfile
{

}

internal class MyProfileProfileHandler : IHandleProfile<MyProfile>, IWantTheEndpointConfig
{
    void IHandleProfile.ProfileActivated()
    {
        Configure.Instance.NHibernateUnitOfWork();
        Configure.Instance.NHibernateSagaPersister();
        Configure.Instance.DBSubcriptionStorage();
        Configure.Instance.UseNHibernateTimeoutPersister();
    }

    public IConfigureThisEndpoint Config { get; set; }
}

public class MyProfileLoggingHandler : IConfigureLoggingForProfile<MyProfile>
{
    void IConfigureLogging.Configure(IConfigureThisEndpoint specifier)
    {
        SetLoggingLibrary.Log4Net<RollingFileAppender>(null,
            a =>
            {
                a.CountDirection = 1;
                a.DatePattern = "yyyy-MM-dd";
                a.RollingStyle = RollingFileAppender.RollingMode.Composite;
                a.MaxFileSize = 1024 * 1024;
                a.MaxSizeRollBackups = 10;
                a.LockingModel = new FileAppender.MinimalLock();
                a.StaticLogFileName = true;
                a.File = "logfile";
                a.AppendToFile = true;
            });

        if (GetStdHandle(STD_OUTPUT_HANDLE) == IntPtr.Zero)
            return;

        SetLoggingLibrary.Log4Net<ColoredConsoleAppender>(null,
          a =>
          {
              LiteLoggingHandler.PrepareColors(a);
              a.Threshold = Level.Info;
          }
      );
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetStdHandle(int nStdHandle);
    const int STD_OUTPUT_HANDLE = -11;
}

A final note, I also had to set all of the properties of my saga data object as virtual, per standard NHibernate practice. This became very apparent once the system was actually using NHibernate.

public class ItemData : ISagaEntity
{
    public virtual Guid Id { get; set; }
    public virtual string Originator { get; set; }
    public virtual string OriginalMessageId { get; set; }
}

This is a long explanation, but hopefully it'll help someone else out down the road. If anyone has suggestions on a better way to accomplish this, or the correct way to use profiles, please let me know!