Hello,
I would like to use NSB Saga in DDD project. I’m using MessageSessionAdapter instance injected to BookingService to be able to publish events. That’s ok, but I have troubles with scheduling timeouts since RequestTimeout method is bound to Saga instance and requires context. Why it’s not included in IMessageSession interface and how I can call it from an application layer? It looks like NSB doesn’t support DDD philosophy at all. I consider NSB handlers as Asp.Net Core Controllers, so what I’m trying to do is to move the logic from handlers/saga handlers/api controllers to the application layer.
An example:
// NServiceBus
public class BookingSaga : Saga<BookingSagaData>, IAmStartedByMessages<StartBooking>, IHandleMessages<ConfirmBooking>, IHandleTimeouts<BookingTimeout>
{
private readonly IBookingService bookingService;
public BookingSaga(IBookingService bookingService)
{
bookingService = bookingService;
}
public async Task Handle(StartBooking command, IMessageHandlerContext context)
{
await bookingService.StartBooking(command.Id, Data);
}
public async Task Handle(ConfirmBooking command, IMessageHandlerContext context)
{
await bookingService.ConfirmBooking(this.Data.BookingId);
}
public async Task Timeout(BookingTimeout state, IMessageHandlerContext context)
{
await bookingService.CancelBooking(Data.BookingId, Data.TimeoutIndex);
MarkAsComplete();
}
}
// Infrastructure
public class MessageSessionAdapter : IMessageSessionAdapter
{
private readonly IMessageSession session;
public MessagePublisher(IMessageSession session)
{
session = session;
}
public async Task PublishBookingStartedEvent(Guid bookingId)
{
await session.Publish(new BookingStarted { BookingId = bookingId });
}
public async Task PublishBookingCancelledEvent(Guid bookingId)
{
await session.Publish(new BookingCancelled { BookingId = bookingId });
}
public async Task SetTimeout(int seconds)
{
// not possible: RequestTimeout requires the specific Saga instance and context
Data.TimeoutIndex++;
await session.RequestTimeout(context, TimeSpan.FromSeconds(seconds), new BookingTimeout());
}
}
// Application
public class BookingService : IBookingService
{
private readonly IMessageSession session;
private readonly IBookingEntityFactory factory;
private readonly IBookingRepository repository;
public BookingService(IMessageSession session, IBookingEntityFactory factory, IBookingRepository repository)
{
session = session;
factory = factory;
repository = repository;
}
public async Task StartBooking(Guid bookingId, BookingSagaData data)
{
data.BookingId = bookingId;
var entity = factory.CreateBookingEntity(data);
entity.BookingLogic();
await repository.Add(entity);
if (entity.IsCancellable && entity.NotConfirmed) {
await session.SetTimeout(60);
}
await session.PublishBookingStartedEvent(bookingId);
}
public async Task ConfirmBooking(Guid bookingId)
{
var entity = repository.Get(bookingId);
entity.Confirm();
await repository.SaveChanges();
}
public async Task CancelBooking(Guid bookingId, int timeoutIndex)
{
var entity = repository.GetBookingEntity(bookingId);
entity.Cancel();
await repository.SaveChanges();
await session.PublishBookingCancelledEvent(bookingId);
}
}