Prerequisites
The sample relies on the availability of a SQL Server named .
and the existence of a database named Samples.
.
Code walk-through
This sample shows a simple client/server scenario.
Client
sends aStartOrder
message toServer
.Server
starts anOrderSaga
.OrderSaga
requests a timeout withCompleteOrder
data.- When the
CompleteOrder
timeout fires, theOrderSaga
publishes anOrderCompleted
event. Server
then publishes a message that the client has subscribed to.Client
handles theOrderCompleted
event.
NHibernate config
NHibernate is configured with the right driver, dialect, and connection string. Then, since NHibernate needs a way to map the class to the database table, the configuration code does this using the ModelMapper API. Finally, the configuration is used to run the endpoint.
var endpointConfiguration = new EndpointConfiguration("Samples.NHibernate.Server");
var persistence = endpointConfiguration.UsePersistence<NHibernatePersistence>();
// for SqlExpress use Data Source=.\SqlExpress;Initial Catalog=Samples.NHibernate;Integrated Security=True;Max Pool Size=100;Encrypt=false
var connectionString = @"Server=localhost,1433;Initial Catalog=Samples.NHibernate;User Id=SA;Password=yourStrong(!)Password;Max Pool Size=100;Encrypt=false";
var hibernateConfig = new Configuration();
hibernateConfig.DataBaseIntegration(x =>
{
x.ConnectionString = connectionString;
x.Dialect<MsSql2012Dialect>();
x.Driver<MicrosoftDataSqlClientDriver>();
});
AddMappings(hibernateConfig);
persistence.UseConfiguration(hibernateConfig);
Order saga data
Note that to use NHibernate's lazy-loading feature, all properties on the saga data class must be virtual
.
public class OrderSagaData :
ContainSagaData
{
public virtual Guid OrderId { get; set; }
public virtual string OrderDescription { get; set; }
}
Order saga
public class OrderSaga :
Saga<OrderSagaData>,
IAmStartedByMessages<StartOrder>,
IHandleTimeouts<CompleteOrder>
{
static ILog log = LogManager.GetLogger<OrderSaga>();
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<OrderSagaData> mapper)
{
mapper.ConfigureMapping<StartOrder>(message => message.OrderId)
.ToSaga(sagaData => sagaData.OrderId);
}
public Task Handle(StartOrder message, IMessageHandlerContext context)
{
Data.OrderId = message.OrderId;
var orderDescription = $"The saga for order {message.OrderId}";
Data.OrderDescription = orderDescription;
log.Info($"Received StartOrder message {Data.OrderId}. Starting Saga");
var shipOrder = new ShipOrder
{
OrderId = message.OrderId
};
log.Info("Order will complete in 5 seconds");
var timeoutData = new CompleteOrder
{
OrderDescription = orderDescription
};
return Task.WhenAll(
context.SendLocal(shipOrder),
RequestTimeout(context, TimeSpan.FromSeconds(5), timeoutData)
);
}
public Task Timeout(CompleteOrder state, IMessageHandlerContext context)
{
log.Info($"Saga with OrderId {Data.OrderId} completed");
var orderCompleted = new OrderCompleted
{
OrderId = Data.OrderId
};
MarkAsComplete();
return context.Publish(orderCompleted);
}
}
Handler using ISession
The handler uses the ISession
instance to store business data.
public class ShipOrderHandler :
IHandleMessages<ShipOrder>
{
public Task Handle(ShipOrder message, IMessageHandlerContext context)
{
var session = context.SynchronizedStorageSession.Session();
var orderShipped = new OrderShipped
{
Id = message.OrderId,
ShippingDate = DateTime.UtcNow,
};
session.Save(orderShipped);
return Task.CompletedTask;
}
}
The database
Data in the database is stored in three different tables.
The saga data
IContainSagaData.
maps to the OrderSagaData primary key and unique identifier columnId Id
.IContainSagaData.
andOriginator IContainSagaData.
map to columns of the same name with typeOriginalMessageId varchar(255)
.- Custom properties on SagaData, in this case
OrderDescription
andOrderId
, are also mapped to columns with the same name and the respecting types.