Wzorzec Unit Of Work cz.1

Siema,
po zmaganiach z Entity Framework w końcu możemy ruszyć dalej z architekturą 🙂 Dziś omówimy sobie wzorzec Unit Of Work i dowiemy się po co w ogóle go stosować. Wpis z racji swojego rozmiaru podzielę na dwie części: teoretyczną oraz samą implementację.

 


Jeszcze mały update do wpisu o setupie projektu. Pomiędzy warstwą Web oraz Domain pojawiła się jeszcze jedna warstwa o nazwie Proxy. Co prawda nie jest to proxy w rozumieniu wzorca, a bardziej zajmuje się zamianą DTO na Domain Objects i vice versa. Ponadto ma swój udział w dziś omawianym wzorcu, dlatego chciałem o tym wspomnieć. A teraz przejdźmy do tematu.


 

Sama idea wzorca Unit Of Work jest czysto optymalizacyjna. W klasycznej architekturze aplikacji webowej, która używa wzorca repozytorium, jest tak że każdemu repo wstrzykujemy jeden kontekst bazy danych (w EF np. DbContext). Co w przypadku kiedy nasza metoda domenowa, musi dokonać zmiany stanu np. dwóch tabel? Wówczas mogłoby wyglądać to tak:

 

public void DoSomeDatabaseMagic(UserEntity user)
        {
            _userRepository.Update(user);
            _userActivityRepository.Add(user, DateTime.UtcNow);
 
            _userRepository.Save();
            _userActivityRepository.Save();
        }

 

Wygląda na to, że aby zapisać zmiany musimy wykonać dwie operacje Save, które najprawdopodobniej wywołują metodę SaveChanges, na kontekście. Oznacza to, że jedna metoda wykonała dwa round tripy do bazy danych w dwóch różnych transakcjach. Oczywiście, ktoś może powiedzieć „nie panikujmy, to potrwa ułamki sekund”. Tak potrwa, ale zazwyczaj (choć mogę się mylić) aplikacje webowe tworzy się dla jak największej liczby użytkowników, i gdyby 10 000 użytkowników miałoby wykonać naszą metodę, otrzymalibyśmy 20 000 otwartych transakcji bazodanowych. Pamiętajmy także, że baza nie Pudzian i po prostu może się poddać. Z pomocą przychodzi nam własnie Unit Of Work, który rozwiązuje nasz problem. Dodam, że implementacji tego wzorca widziałem już z 10 i każda diametralnie się od siebie różniła, choć finalnie prowadziły do tego samego. Wobec czego to co tu pokażę jest „tworem” wypracowanym z moim byłym teamem i uważam, że jest ok. Ok, jak to działa?

W największym uproszczeniu polega to na tym, aby jeden kontekst bazodanowy rozpropagować po wszystkich  naszych repozytoriach. Wówczas wszystkie operacje na różnych tabelach będą operować na jednym kontekście. Kiedy uznamy, że wszystkie zmiany są gotowe do zapisu wykonamy jeden raz SaveChanges po czym wyślemy nasze zmiany w jednej transakcji. Dzięki temu nie narażamy naszej bazy na pewną śmierć, a i skracamy czas oczekiwania na zakończenie wszystkich operacji, gdyż wszystko dziej się w jednym round tripie. Brzmi fajnie? Działa też nieźle 😉 Sama implementacja będzie obejmowała stworzenie:

  • Klasy UnitOfWork oraz jej interfejsu. W konstruktorze będzie ona przyjmować DbContext. Będzie odpowiedzialna za tworzenie jednaj transakcji bazodanowej, zapis stanu bazy danych, ewentualny rollback transakcji. (Metody Save zostaną usunięte z repozytorium).
  • Klasy UnitOfWorkFactory oraz jej interfejsu. Nasza fabryka będzie produkowała jeden UnitOfWork dla jednej metody w warstwie Proxy.
  • Klasy DomainServiceFactory<TService> oraz jej interfejsu. Fabryka serwisów domenowych będzie zwracała implementację wybranego serwisu domenowego dla metody w warstwie Proxy. Metoda Get, będzie przyjmowała UnitOfWork i wstrzyknie go, do serwisu domenowego.
  •  Klasy RepositoryFactory<TRepository> oraz jej interfejsu. Fabryka repozytoriów będzie zwracała implementację wybranego repozytorium dla metody w warstwie Domain. Metoda Get, będzie przyjmowała UnitOfWork i wstrzyknie zawarty w nim DbContext, do repozytorium.
  • Klasy CustomResolver oraz jej interfejsu. Będzie to wrapper na standardowy IContainer Autofaca, który będzie udostępniał metody podmieniające wstrzykiwane zależności.

Jeszcze (uproszczony) diagram sekwencji, który przedstawia kolejność wywołań:

schemat

Wygląda może strasznie, ale uwierzcie – nie będzie źle 🙂 Jutro wszystko sobie ładnie zaimplementujemy i będzie git! Jeżeli ktoś nie może jednak wytrzymać to zapraszam na githuba projektu, gdzie wszystko zostało już napisane.

Do jutra !

You may also like...