Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

21 Maj 2009

Ugryźć Spring.Net – (cz.5) Zarządzanie transakcjami

Filed under: Ugryźć Spring.Net — Beniamin Zaborski @ 21:30

Czas zająć się wreszcie kwestią istotną, ale często traktowaną przez wielu z nas po macoszemu, mianowicie transakcjami, a w zasadzie to zarządzaniem transakcjami. Każdy zdaję sobie sprawę z tego, że jest to bardzo ważny temat, ale nie do końca poświęcamy mu odpowiednią ilość czasu. W tym artykule chciałbym przybliżyć cały mechanizm zarządzania transakcjami jaki daje nam Spring.NET.

To co powinno nas na początku zainteresować to interfejs IPlatformTransactionManager, który wygląda tak:


public interface IPlatformTransactionManager
{
  ITransactionStatus GetTransaction( ITransactionDefinition definition );
  void Commit( ITransactionStatus transactionStatus );
  void Rollback( ITransactionStatus transactionStatus );
}

Spring.Net dostarcza kilka różnych implementacji tego interfejsu, tj.:

  • AdoPlatformTransactionManager – obsługa lokalnych transakcji ADO.NET
  • ServiceDomainPlatformTransactionManager – obsługa transakcji rozproszonych bazujących na menadżerze Enterprise Services
  • TxScopePlatformTransactionManager – obsługa transakcji lokalnych i zdalnych bazująca na System.Transactions
  • HibernatePlatformTransactionManager – obsługa transakcji lokalnych dla NHibernate i ADO.NET

Jak widać metoda GetTransaction zwraca odpowiedni obiekt implementujący interfejs ITransactionStatus w zależności od przekazanych parametrów (ITransactionDefinition). Dzięki temu obiekt ITransactionStatus może reprezentować nową lub już istniejącą transakcję.

Co zatem określa obiekt ITransactionDefinition? Przyjrzyjmy się temu bliżej. Oto co definiuje:

  • Isolation – poziom izolacji transakcji
  • Propagation – pozwala określić czy ma zostać utworzona nowa transakcja czy kod ma być wykonany w istniejącej transakcji
  • Timeout – czas działania transakcji po jakim zostanie zgłoszony błąd przekroczenia czasu oczekiwania
  • Read-only – ważna właściwość określająca, że dane w transakcji nie będą modyfikowane. Ma to szczególne znaczenie w przypadku NHibernate, bo podnosi wydajność takiej transakcji.

ITransactionStatus zwracany z metody GetTransaction określa status transakcji i niejako jej kontekst wykonania. Zauważmy, że parametr tego typu przyjmują metody Commit i Rollback interfejsu IPlatformTransactionManager.

Najprostszym sposobem powołania do życia menadżera transakcji jest zlecenie tego kontenerowi IoC. W wypadku prostego menadżera dla lokalnych transakcji ADO.NET plik konfiguracyjny wygląda tak:


<objects xmlns=’http://www.springframework.net’ xmlns:db=”http://www.springframework.net/database”&gt;
  <db:provider id=”DbProvider”
  provider=”SqlServer-2.0″
  connectionString=”Data Source=BENIAMINZ\SQLEXPRESS;Initial Catalog=ADMINET;Integrated Security=SSPI;”/>
  <object id=”TransactionManager” type=”Spring.Data.AdoPlatformTransactionManager, Spring.Data”>
    <property name=”DbProvider” ref=”DbProvider”/>
  </object>
</objects>

Jedyne co wymaga wyjaśnienia to DbProvider, ale jego już używaliśmy niejednokrotnie. Menadżerowi transakcji wystarczy przekazać referencję do odpowiedniego DbProvider, który jak widać na przykładzie jest zdefiniowany wyżej.

W wypadku menadżera transakcji bazującego na System.Transactions plik konfiguracyjny wyglądałby dużo prościej:


<object id=”TransactionManager” type=”Spring.Data.TxScopeTransactionManager, Spring.Data”>
</object>

Tu już nie przekazujemy DbProvidera, ponieważ mechanizm typu System.Transactions to rozproszony system zarządzania transakcjami.

Jeszcze inaczej się przedstawia konfiguracja menadżera transakcji dla NHibernate:


<object id=”HibernateTransactionManager” type=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate”>
  <property name=”DbProvider” ref=”DbProvider”/>
  <property name=”SessionFactory” ref=”MySessionFactory”/>
</object>

Podobnie jak w wypadku tego pierwszego przekazujemy DbProvidera, ale jeszcze dodatkowo musimy wskazać na odpowiedni SessionFactory. SessionFactory definiowaliśmy w części poświęconej dostępowi do danych poprzez NHibernate, więc nie będę tego ponownie przedstawiał.

Zarządzanie transakcjami w Spring.Net jest możliwe na dwa sposoby: deklaratywnie i imperatywnie. Jak podaje dokumentacja większość użytkowników wybiera sposób deklaratywny. Hmm … ja też! Podstawowy powód mojego wyboru jest taki, iż ta metoda wymaga naprawdę niewiele wysiłku, a także i może przede wszystkim, nie „wplątuje” się w kod logiki biznesowej aplikacji.
Dodatkowe cechy deklaratywnej obsługi transakcji w Spring.NET to możliwość definiowania własnych reguł wycofywania transakcji, czy możliwość kontrolowania procesu działania transakcji, tj. np. wykonania dodatkowego kodu podczas rollback. Możliwe jest to dzięki temu, iż deklaratywna obsługa transakcji opiera się na AOP.

W typowych przypadkach transakcje powinniśmy stosować na poziomie komponentów biznesowych czy serwisów pełniących rolę fasad. Nie stosujemy transakcji na niższym poziomie abstrakcji, tj, bezpośrednio w obiektach dostępu do danych. Przedstawię prosty przykład serwisu:


public class AbonenciService : IAbonenciService
{
  private AbonentDAO abonentDAO;
  public AbonentDAO AbonentDAO
  {
    get { return abonentDAO; }
    set { abonentDAO = value; }
  }

  [Transaction()]
  public void DodajAbonenta(AbonentDTO abonentDTO)
  {
    AbonentDAO.Zapisz(AbonentConverter.ToAbonent(abonentDTO));
  }
}            

Jak widać jedyne co musimy zrobić to opatrzyć metodę serwisu atrybutem [Transaction()]. Tutaj został użyty najprostszy przypadek, ale można również na tym poziomie określić dodatkowe cechy transakcji jak: poziom izolacji, to czy transakcja ma być read-only, itp.

Przyjrzyjmy się teraz przykładowi kodu z poprzedniej części serii poszerzonemu o definicję naszego serwisu:


<spring>
  <parsers>
    <parser type=”Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services” />
    <parser type=”Spring.Data.Config.DatabaseNamespaceParser, Spring.Data” />
    <parser type=”Spring.Transaction.Config.TxNamespaceParser, Spring.Data” />
  </parsers>
  <context>
    <resource uri=”config://spring/objects” />
  </context>
  <objects xmlns=”http://www.springframework.net&#8221;
  xmlns:db=”http://www.springframework.net/database&#8221;
  xmlns:tx=”http://www.springframework.net/tx”&gt;
    <db:provider id=”DbProvider”
    provider=”SqlServer-2.0″
    connectionString=”Data Source=BENIAMINZ\SQLEXPRESS;Initial Catalog=ADMINET;Integrated Security=SSPI;”/>
    <object id=”NHSessionFactory” type=”Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12″>
    <property name=”DbProvider” ref=”DbProvider”/>
    <property name=”MappingAssemblies”>
      <list>
        <value>BizDev.SimpleSpringApplication</value>
      </list>
    </property>
    <property name=”HibernateProperties”>
      <dictionary>
        <entry key=”hibernate.connection.provider”
        value=”NHibernate.Connection.DriverConnectionProvider”/>
        <entry key=”hibernate.dialect”
        value=”NHibernate.Dialect.MsSql2005Dialect”/>
        <entry key=”hibernate.connection.driver_class”
        value=”NHibernate.Driver.SqlClientDriver”/>
        <entry key=”hibernate.current_session_context_class”
        value=”Spring.Data.NHibernate.SpringSessionContext, Spring.Data.NHibernate12″/>
      </dictionary>
    </property>
  </object&gt

  <object id=”transactionManager” type=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12″>
    <property name=”DbProvider” ref=”DbProvider”/>
    <property name=”SessionFactory” ref=”NHSessionFactory”/>
  </object>

  <object id=”AbonentDao” type=”BizDev.SimpleSpringApplication.AbonentDAO, BizDev.SimpleSpringApplication”>
    <property name=”SessionFactory” ref=”NHSessionFactory”/>
  </object>
 
  <object id=”AbonenciService” type=”BizDev.SimpleSpringApplication.AbonenciService, BizDev.SimpleSpringApplication”>
    <property name=”AbonentDao” ref=”AbonentDao”/>
  </object>

  <tx:attribute-driven/>
  </objects>
</spring>

To jest kompletny w pełni działający przykład na którym można dostrzec: obiekt dostępu do danych, serwis, provider bazy danych, NHibernate-owe SessionFactory oraz to co nas interesuje najbardziej czyli menadżer transakcji dla NHibernate. Wyjaśnienia wymaga jeszcze wpis <tx:attribute-driven/>, który jest niezbędny dla deklaratywnego mechanizmu zarządzania transakcjami. Tutaj został zdefiniowany z domyślnymi parametrami, tj. wskazuje na menadżera transakcji o domyślnej nazwie „transactionManager”. Jeżeli ta nazwa byłaby inna wtedy musielibyśmy to określić tak:


<object id=”nhTransactionManager” type=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12″>
  <property name=”DbProvider” ref=”DbProvider”/>
  <property name=”SessionFactory” ref=”NHSessionFactory”/>
</object>
<tx:attribute-driven transaction-manager=”nhTransactionManager”/>

Inna metoda obsługi transakcji w Spring.NET to metoda imperatywna, czyli programowa. To ta mniej preferowana przeze mnie. Do zarządzania transakcjami w ten sposób możemy skorzystać z jednego z dwóch rozwiązań:

  • TransactionTemplate
  • IPlatformTransactionManager

Pierwsza metoda polega na stosowaniu anonimowych delegatów. Prawda, że ten kod nie wygląda przyjemnie?


tt.Execute(delegate(ITransactionStatus status)
{
  try
  {
    AbonentDAO.Zapisz(abonent);
  } catch (Exception ex)
  {
    status.RollbackOnly = true;
  }
  return null;
});

Drugie podejście to użycie klasy implementującej IPlatformTransactionManager. Interfejs IPlatformTransactionManager już znamy. Spójrzmy na samo opisujący się przykład, gdzie transactionManager implementuje wspomniany interfejs:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.PropagationBehavior = TransactionPropagation.Required;
ITransactionStatus status = transactionManager.GetTransaction(def);
try
{
  AbonentDAO.Zapisz(abonent);
}
catch (Exception e)
{
  transactionManager.Rollback(status);
  throw;
}
transactionManager.Commit(status);   

Wygląda prosto i tak też jest w rzeczywistości. Pobieramy instancję ItransactionStatus, a następnie wykonujemy commit na sukces lub rollback w wypadku niepowodzenia przekazując ją jako parametr.

Spring.NET daje nam świetny mechanizm zarządzania transakcjami i zwalania nas od implementowania własnych rozwiązań pozwalając skupić się na właściwym biznesie aplikacji.
Dokumentacja Spring.Net omawia jeszcze szczegółowo wiele aspektów zarządzania transakcjami. Zainteresowanych samym działaniem mechanizmu od kuchni oraz tym jaką rolę w tym wszystkim odgrywa AOP odsyłam do dokumentacji.
W kolejnej części omówię stosowanie serwisów w aplikacjach rozproszonych Spring.NET.

Reklamy

13 Maj 2009

DO or not DO?

Filed under: Codzienne dylematy modelarza — Tagi: , , , , — Beniamin Zaborski @ 07:06

DO or not DO? DO jak Data Object, zwane także DTO – Data Transfer Object, VO – Value Object czy nawet Presentation Entity. Używać czy nie używać?
Oczywiście nie spodziewajcie się jednoznacznej odpowiedzi, a jeśli już taka padnie to pewnie będzie dość subiektywna. Problem ten przewija się na wielu forach i stosowanie obiektów DTO ma tyle samo przeciwników co zwolenników. Dużo zależy jednak od aplikacji jaką piszemy, od jej typu, rozmiaru, itp. Ja jednak obecnie zaliczam się do grupy zwolenników stosowania obiektów DTO (choć może mnie ktoś przekona i zmienię zdanie). Czemu? To postaram się właśnie tutaj przedstawić. Zanim jednak zacznę należy się krótkie wprowadzenie teoretyczne.
Czasy gdzie dominującym modelem architektonicznym w aplikacjach biznesowych był klient-serwer bezpowrotnie (mam nadzieję) minęły. Dziś przystępując do projektu z góry zakładamy model n-warstwowy (szczególnie 3-warstwowy). Oznacza to, że w naszej aplikacji będzie można wyróżnić co najmniej następujące warstwy logiczne:
– warstwa dostępu do danych
– warstwa logiki biznesowej
– warstwa prezentacji.
Jest to model bardzo ogólny i odnosi się do wszystkich typów aplikacji czy to web-owych czy desktopowych. W kontekście poruszanego tematu nas najbardziej będzie interesować styk warstwy logiki biznesowej i warstwy prezentacji.
Nasuwa się pytanie: czy do warstwy prezentacji przesyłać bezpośrednio encje biznesowe? Ja jednak postawię to pytanie inaczej: dlaczego nie przesyłać do warstwy prezentacji encji biznesowych?
Jak już wspomniałem wcześniej jestem zwolennikiem stosowania obiektów DTO. Zdaję sobie sprawę, że wymaga to dodatkowej pracy (co często wytykają przeciwnicy), ale to procentuje później.
Warto zerknąć tutaj i zobaczyć jak Martin Fowler przedstawia Data Transfer Object.

Oto co przemawia za tym rozwiązaniem:

1) Luźnie powiązania.

Podstawową zaletą takiego rozwiązania jest wprowadzenie kolejnego luźniejszego powiązania pomiędzy warstwą logiki biznesowej, a prezentacji. Ma to szczególne znaczenie, kiedy nagle okaże się, iż trzeba dokonać pewnych zmian w warstwie prezentacji. W ten sposób często unikamy modyfikacji w warstwie logiki biznesowej, co wydaje się być dobre.

Przeciwnicy powiedzą: W większości typowych przypadków i tak dokonanie zmian w warstwie logiki biznesowej będzie niezbędne.

2) Różne wymagania.

Encja reprezentująca jakiś fragment naszego biznesu z jednej strony musi komunikować się z klientem (warstwa prezentacji), a z drugiej z repozytorium w którym jest przechowywana (warstwa dostępu do danych). Oba te wymagania nakładają na encje pewne zadania. Patrząc od strony warstwy dostępu do danych to powinno być możliwe wykonanie operacji CRUD na encji. Czy to poprzez procedury składowane, czy mechanizm O/R Mapping-u. Z drugiej strony encja powinna współpracować z warstwą prezentacji, tj. bindować się z kontrolkami, obsługiwać mechanizmy powiadamiania o niespełnionych regułach walidacyjnych itp. To znowu nakłada na każdą encje zupełnie inne zadania. Stąd warto rozdzielić te dwa jakże odmiene od siebie grupy zadań, powierzając komunikację z warstwą prezentacji właśnie specjalnie do tego wydzielonym obiektom DTO.

Przeciwnicy powiedzą: Zbyt duży nakład pracy, a korzyści nie tak widoczne jak by się można było tego spodziewać.

3) Większa wydajność.

W czasach systemów rozproszonych często warstwy aplikacji znajdują się na różnych maszynach stąd komunikują się poprzez sieć. Komunikacja ta może się odbywać z wykorzystaniem różnych mechanizmów jak WebService (SOAP), .NET Remoting, COM+, itp. Często do pojedynczych widoków w warstwie prezentacji przesyłamy tylko część danych z encji. Nie zawsze encje pokrywają się 1 do 1 z widokami. Zamiast przesyłać poprzez sieć duże obiekty encji biznesowych warto przepakować do obiektów DTO tylko te dane, które faktycznie będą nam potrzebne w widoku.

Przeciwnicy powiedzą: W dzisiejszych czasach wydajność systemów to nie problem, zresztą kto na to zwraca uwagę.

Jak napisałem na początku odpowiedzi jednoznacznej nie ma. Bez dwóch zdań koszty pracy związane ze stosowaniem obiektów DTO są wyższe, no  przynajmniej na początku. Warto rozważyć, przed przystąpieniem do nowego projektu, stosowanie obiektów DTO, co nie oznacza, że w każdym przypadku będzie to trafione rozwiązanie. A więc pytanie „DO or not DO” pozostaje otwarte …

Blog na WordPress.com.