简体   繁体   中英

Injecting objects into a MassTransit Saga using Autofac

I'm trying to get autofac to inject an factory into a saga on creation, and I can't get it working. I've had no problem injecting the factory into a consumer so I know it's registered correctly, so I'm assuming I'm not registering the sagas correctly and autofac isn't building them up.

Here's my registration code:

var mapTypes = assembly.GetTypes()
   .Where(type => type.Implements(typeof(SagaClassMapping<>)));

builder.Register(context => GetSessionFactory(mapTypes)).AsSelf().SingleInstance();

// register all sagas and consumers
builder.RegisterAssemblyTypes(assembly)
   .Where(type => type.IsAssignableTo<ISaga>() || type.IsAssignableTo<IConsumer>())
   .AsSelf();

builder
    .RegisterGeneric(typeof(NHibernateSagaRepository<>))
    .As(typeof(ISagaRepository<>))
    .SingleInstance();

builder
    .Register(context => ServiceBusFactory.New(sbc =>
    {
        sbc.UseLog4Net();
        var queueUri = new Uri(ConfigurationManager.AppSettings["ReceiveQueue"]);
        var scope = context.Resolve<ILifetimeScope>();
        sbc.UseRabbitMq(transportConfig => 
            transportConfig.ConfigureHost(queueUri, hostConfig =>
            {
                hostConfig.SetUsername(ConfigurationManager.AppSettings["busUser"]);
                hostConfig.SetPassword(ConfigurationManager.AppSettings["busPassword"]);
            }));
        sbc.ReceiveFrom(queueUri);
        sbc.Subscribe(cfg => cfg.LoadFrom(scope));
    }))
    .SingleInstance();

The saga itself is pretty standard

public class MySaga : SagaStateMachine<MySaga>, ISaga
{
    public Guid CorrelationId { get; private set; }
    public Func<MyObject> ObjectBuilder { get; set; }
    public MySaga() { }

    public MySaga(Guid correlationId)
    {
        CorrelationId = correlationId;
    }

    Static MySaga()
    {
        Define(() =>
        { .... }
    }

I've tried adding the Func<MyObject> to a constructor, but it's not hit, it does work in a consumer so I know Autofac can build a Func<MyObject> . I also tried using property injection with no luck:

builder.RegisterAssemblyTypes(assembly)
   .Where(type => type.IsAssignableTo<ISaga>() || type.IsAssignableTo<IConsumer>())
   .PropertiesAutowired()
   .AsSelf();

and

builder.RegisterType<MySaga>()
    .OnActivated(arg => arg.Instance.MyBuilder =
        arg.Context.Resolve<Func<MyObject>>())
    .AsSelf();

Any help on what I'm doing wrong would be appreciated.


I got a reply from Chris Patterson on the masstransit-discuss group that pointed out I was probably doing it wrong.

Automatonymous is a better choice if you have dependencies, since the state machine and the state itself are separate classes.

Injecting dependencies into a class hydrated via NHibernate is never going to end well. There are a couple of helper classes that can be used to perform property-injection into the saga after it is loaded from NHibernate, the decorating saga repository I think has been posted here.

Here is the example of the injecting repository for Magnum: https://github.com/MassTransit/MassTransit/blob/master/src/MassTransit.Tests/Saga/Injecting_Specs.cs

Given it's NHibernate that's hydrating the object I should be looking there for the hook. I've got a workaround for my current issue, but I'll post an answer here if/when I find one.

old question but it took a while for us to figure out a suitable way to wiring up sagas and autofac and then adding nhibernate on top of that.

First create a wiring class (property injection)

public class MySagaRepository<TSaga> : ISagaRepository<TSaga> where TSaga : class, ISaga
    {
    private ISagaRepository<TSaga> _inner;
    private readonly IComponentContext _context;

    public MySagaRepository(ISagaRepository<TSaga> inner, IComponentContext context)
    {
        _inner = inner;
        _context = context;
    }

    public IEnumerable<Action<IConsumeContext<TMessage>>> GetSaga<TMessage>(IConsumeContext<TMessage> context, Guid sagaId, InstanceHandlerSelector<TSaga, TMessage> selector, ISagaPolicy<TSaga, TMessage> policy) where TMessage : class
    {
        return _inner.GetSaga(context, sagaId, (saga, message) =>
        {
            _context.InjectProperties(saga);
            return selector(saga, message);
        }, policy);
    }

    public IEnumerable<Guid> Find(ISagaFilter<TSaga> filter)
    {
        return _inner.Find(filter);
    }

    public IEnumerable<TSaga> Where(ISagaFilter<TSaga> filter)
    {
        return _inner.Where(filter).Select(x =>
        {
            _context.InjectProperties<TSaga>(x);
            return x;
        });
    }

    public IEnumerable<TResult> Where<TResult>(ISagaFilter<TSaga> filter, Func<TSaga, TResult> transformer)
    {
        return _inner.Where(filter, x =>
        {
            _context.InjectProperties(x);
            return transformer(x);
        });
    }

    public IEnumerable<TResult> Select<TResult>(Func<TSaga, TResult> transformer)
    {
        return _inner.Select(x =>
        {
            _context.InjectProperties(x);
            return transformer(x);
        });
    }
}

Then wire it

builder.RegisterGeneric(typeof(NHibernateSagaRepository<>)).Named("innerRepo", typeof(ISagaRepository<>));
builder.RegisterGenericDecorator(typeof(MySagaRepository<>), typeof(ISagaRepository<>), fromKey: "innerRepo");

For the persistance it was just a matter of

public class SagaPersistenceHandler
{
    public static ISessionFactory CreateSessionFactory()
    {
        var provider = new SqlServerSessionFactoryProvider(ConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString, new[]
        {
            typeof (MySagaMap)
        });

        return provider.GetSessionFactory();
    }
}

Now wire that

builder.Register(c => SagaPersistenceHandler.CreateSessionFactory()).As<ISessionFactory>();

and the saga mapping to the saga (not included)

public MySagaMap()
    {
        SchemaAction(NHibernate.Mapping.ByCode.SchemaAction.None);
        Table("dbo.[tTable]");
        Property(x => x.Id);
        Property(x => x.CloseDate);
    }
}

All thats left is to register your saga

builder.RegisterType<MySaga>().AsSelf(); 

worked well (credits goes to @erikkallen)

I made an adapter for Autofac to register and configure MT state machine sagas.

The repo is here .

It is available on nuget.org.

You can register your consumers and state machines by calling:

builder.RegisterSagaStateMachines(sagasAssembly);   // register all state machines
builder.RegisterConsumers(consumersAssembly);       // register consumers (standard MassTransit Autofac integration)

Note that RegisterConsumers is the standard MassTransit.Autofac extension.

In your endpoint configuration you need to call the configuration like this:

x.ReceiveEndpoint(queueName, c =>
{
    c.LoadConsumers(container);
    c.LoadStateMachineSagas(container);
});

Pre-condition is that you have to register ISagaRepository implementations as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM