-
-
Notifications
You must be signed in to change notification settings - Fork 364
Unit of work
If you intend to do something with a database in a message handler, and you want the handling of the incoming message to function as a "unit of work", then you might want to check out this page :)
You can read Fowler's ubiquitous description if you're interested in the details, but it basically boils down to this: You want to do stuff - either fully, or not at all.
Usually, when your "stuff" is "doing work in a database", you will use that database and its ability to
- start a transaction before you start doing stuff
- do stuff as part of the transaction
- commit the transaction if all goes well
- roll back the transaction if something fails
These things can of course be implemented in an ad-hoc fashion whenever you need it, but it's usually better implemented by hooking into Rebus in the right places - this is what we'll talk about here.
The easiest way to hook into the right places is to include the Rebus.UnitOfWork package and use its extension methods to manage your unit of work.
It is encouraged that whatever constitutes your unit of work is modeled in a class explicitly made for this purpose, and then you use that class to manage whichever resources are required for your particular usage scenario.
After you Install-Package Rebus.UnitOfWork -ProjectName <your-project>
you can
Configure.With(...)
.(...)
.Options(o => {
o.EnableUnitOfWork(Create, Commit, Rollback, Cleanup);
})
.Start();
where Create
, Commit
, Rollback
, and Cleanup
are generic methods that are invoked as one would expect for each message handled. The EnableUnitOfWork
method is overloaded, so it comes in a synchronous as well as an asynchronous flavor.
The expected method signatures are such that it is expected that Create
accepts an IMessageContext
and returns a unit of work instance TUnitOfWork
, whose type is then used to infer the signature of Commit
, Rollback
, and Cleanup
, which all receive IMessageContext
and TUnifOfWork
– this way, your unit of work instance need not implement any interfaces or anything in order to be hooked up with Rebus.
This example code is taken from the Unit of work sample from the Rebus samples repository – in this case, the unit of work is configured like this:
Configure.With(new CastleWindsorContainerAdapter(container))
.Transport(t => t.UseMsmq("uow.test"))
.Options(o =>
{
o.EnableUnitOfWork(Create, commitAction: Commit, cleanupAction: Dispose);
})
.Start();
where Create
looks like this:
static UnitOfWork Create(IMessageContext context)
{
var unitOfWork = new UnitOfWork();
// stash current unit of work in the transaction context's items
context.TransactionContext.Items["uow"] = unitOfWork;
return unitOfWork;
}
and the Commit
and Dispose
methods are implemented as these simple expression-bodied members:
static void Commit(IMessageContext context, UnitOfWork uow) => uow.Commit();
static void Dispose(IMessageContext context, UnitOfWork uow) => uow.Dispose();
As you can see, the Create
method stashes the created unit of work instance under the uow
key in the transaction context, because then it can be retrieved in a factory method configured in the IoC container and then used to resolve the necessary SqlConnection
and SqlTransaction
instances in our handlers.
And example on how to do this can be seen in the GetUnitOfWork
which is invoked as part of the container's handler activation:
static UnitOfWork GetUnitOfWork()
{
var transactionContext = AmbientTransactionContext.Current;
if (transactionContext == null)
{
throw new InvalidOperationException("Attempted to get transaction context outside of a message handler!");
}
// get unit of work that was stashed in the transaction context
return transactionContext.GetOrThrow<UnitOfWork>("uow");
}
And that's really all there is to it 😃 take a look at the Unit Of Work sample for running code to play around with.
Basic stuff
- Home
- Introduction
- Getting started
- Different bus modes
- How does rebus compare to other .net service buses?
- 3rd party extensions
- Rebus versions
Configuration
Scenarios
Areas
- Logging
- Routing
- Serialization
- Pub sub messaging
- Process managers
- Message context
- Data bus
- Correlation ids
- Container adapters
- Automatic retries and error handling
- Message dispatch
- Thread safety and instance policies
- Timeouts
- Timeout manager
- Transactions
- Delivery guarantees
- Idempotence
- Unit of work
- Workers and parallelism
- Wire level format of messages
- Handler pipeline
- Polymorphic message dispatch
- Persistence ignorance
- Saga parallelism
- Transport message forwarding
- Testing
- Outbox
- Startup/shutdown
Transports (not a full list)
Customization
- Extensibility
- Auto flowing user context extensibility example
- Back off strategy
- Message compression and encryption
- Fail fast on certain exception types
Pipelines
- Log message pipelines
- Incoming messages pipeline
- Incoming step context
- Outgoing messages pipeline
- Outgoing step context
Prominent application services