Dziś rano, pracując nad komputerem, stanąłem przed problemem, nad którym pracuję. Miałem kilka pomysłów, które doprowadziły do następującego projektu - a komentarze byłyby więcej niż niesamowite. Niestety projekt zaproponowany przez Josha nie jest możliwy, ponieważ muszę pracować ze zdalnym serwerem SQL i nie mogę włączyć usługi Distribute Transaction Coordinator.
Moje rozwiązanie opiera się na kilku, ale prostych zmianach w istniejącym kodzie.
Po pierwsze, mam wszystkie moje repozytoria zaimplementować prosty interfejs markera:
/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }
Po drugie, niech wszyscy moi transakcyjne włączone repozytoria wdrożyć następujący interfejs:
/// <summary>
/// Provides methods to enable transaction support.
/// </summary>
public interface IHasTransactions : IRepository
{
/// <summary>
/// Initiates a transaction scope.
/// </summary>
void BeginTransaction();
/// <summary>
/// Executes the transaction.
/// </summary>
void CommitTransaction();
}
Chodzi o to, że w wszystkie moje repozytoria, zaimplementowałem ten interfejs i dodałem kod, który wprowadza transakcję bezpośrednio w zależności od faktycznego dostawcy (dla fałszywych repozytoriów sporządziłem listę delegatów, która zostanie wykonana przy zatwierdzeniu). Dla LINQ to SQL byłoby łatwe do wdrożenia, takie jak:
#region IHasTransactions Members
public void BeginTransaction()
{
_db.Transaction = _db.Connection.BeginTransaction();
}
public void CommitTransaction()
{
_db.Transaction.Commit();
}
#endregion
Wymaga to oczywiście, że nowa klasa repozytorium jest tworzony dla każdego wątku, ale jest to uzasadnione dla mojego projektu.
Każda metoda używająca repozytorium musi wywoływać BeginTransaction()
i EndTransaction()
, jeśli repozytorium implementuje IHasTransactions
. Aby ułatwić to połączenie, pojawiły się następujące rozszerzenia:
/// <summary>
/// Extensions for spawning and subsequently executing a transaction.
/// </summary>
public static class TransactionExtensions
{
/// <summary>
/// Begins a transaction if the repository implements <see cref="IHasTransactions"/>.
/// </summary>
/// <param name="repository"></param>
public static void BeginTransaction(this IRepository repository)
{
var transactionSupport = repository as IHasTransactions;
if (transactionSupport != null)
{
transactionSupport.BeginTransaction();
}
}
public static void CommitTransaction(this IRepository repository)
{
var transactionSupport = repository as IHasTransactions;
if (transactionSupport != null)
{
transactionSupport.CommitTransaction();
}
}
}
Komentarze są za mile widziane!
Nie sądzę, że jest to rozwiązanie w duchu DDD. Zasadniczo stworzyłeś skrypt transakcji, który wykonuje zadanie Modelu Domeny. Usługa nie powinna na przykład zmieniać statusu klienta. –
Coś w kodzie musi obsłużyć tę regułę biznesową, czy to na tym poziomie, czy na wyższym poziomie, chodziło o to, aby dokonać zmian w ramach jednego TransactionScope, umożliwiając transakcje lokalne lub transakcje rozproszone do obsługi transakcji.Jeśli reguła biznesowa mówi o aktualizacji klienta przy każdym złożeniu zamówienia, jest to dobre miejsce, aby poradzić sobie z tym, ponieważ wszystkie zamówienia przechodzą tutaj. – JoshBerke