Problem
Currently both Inbound and Outbound implementation assumes there is:
IDbConnection
- either explicit
IDbTransaction
or ambient TransactionScope
With such constraints there is no way to introduce other backing storage with NoSQL databases such as MongoDB, Neo4j, Redis etc..
Possible solution
Assuming we want to keep the flow of current Resonance.Outbox
API, there should still be a way to:
using (var anyConnection = OpenConnection())
{
using (var anyTransaction = BeginTransaction())
{
anyConnection.DoUserCode();
outobox.Send(myEvent);
anyTransaction.Commit();
}
}
There should be:
- generic type param for transaction, ie.
ITransactionalOutbox<TTransaction>
where TTransaction would be any implementation of database transaction, such as:
IDbTransaction
for SQL Databases
AsyncTransaction
for Neo4j
IClientSession
for MongoDb
ITransaction
for Redis
- etc.
- common contract to start connection and transaction and to commit/rollback transaction in
IMessageRepository
Such change would change current implementation of these APIs:
ITransactionalOutbox:
public interface ITransactionalOutbox<TTransactionSource>
{
Task Send<TMessage>(TMessage message, TTransactionSource transaction = null, DateTime? sendTime = null);
}
TransactionalOutbox:
public class SqlTransactionalOutbox : ITransactionalOutbox<IDbTransaction>
IMessageRepository:
public interface IMessageRepository<TTransactionSource>
{
Task SaveMessage(SerializedMessage serializedMessage, TTransactionSource transaction = null);
Task<ICollection<SerializedMessage>> GetMessagesAsMarkedSent(TTransactionSource transaction, uint? howManyMessages = null);
Task<ICollection<SerializedMessage>> GetMessagesAsRemoved(TTransactionSource transaction, uint? howManyMessages = null);
}
SqlServerMessageRepository:
public class SqlServerMessageRepository : IMessageRepository<IDbTransaction>
{
//(...)
}
and would also introduce extension of contract for IMessageRepository
for managing transaction for Outbound part:
public interface IReadMessageRepository<TTransactionSource> : IMessageRepository<TTransactionSource>
{
Task<TTransactionSource> BeginTransaction();
Task CommitTransaction();
Task RollbackTransaction();
}
Definition of done
- Implementation of example NoSQL integration; example: MongoDB, Neo4j.
- Previous SQL solution still works.
Points of further improvement
Perhaps there also should be a way to introduce transaction options as second generic type param, ie:
public interface IReadMessageRepository<TTransactionSource, TTransactionOptions> : IMessageRepository<TTransactionSource>
{
Task<TTransactionSource> BeginTransaction(TTransactionOptions transactionOptions);
//(...)
}