Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

6 sierpnia 2009

Ugryźć Spring.NET – (cz.7) Testy jednostkowe

Filed under: Ugryźć Spring.Net — Tagi: , , , , — Beniamin Zaborski @ 21:50

W dzisiejszych czasach nikt już sobie nie wyobraża dobrze zaprojektowanej aplikacji biznesowej bez testów jednostkowych. Istnieją metodologie, które wręcz testy jednostkowe stawiają na pierwszym miejscu. Spring.NET idzie z duchem czasu i posiada wsparcie dla testów jednostkowych przy użyciu NUnit. Bądź co bądź to nadal najpopularniejszy framework testowy dla .NET, ale w zapowiedziach jest już wsparcie Spring.NET dla MbUnit i testów z Visual Studio.
Każdy kto się zetknął z testami jednostkowymi niejednokrotnie narzekał na to, iż spory nakład pracy trzeba włożyć aby przygotować środowisko uruchomieniowe. Mam tu na myśli utworzenie odpowiednich instancji serwisów, wstrzykiwanie do nich zależności obiektów DAO, itp. Często (znam to z autopsji) z tego powodu rezygnowało się z testów szczególnie w mniejszych projektach.
Wyobraźmy sobie najprostszy i najbardziej typowy scenariusz testów – testy serwisów warstwy biznesowej. Na pewno będziemy musieli zacząć od powołania instancji serwisów, a w następnej kolejności pomyśleć o zarządzaniu transakcjami, a także o tym czy wszystko wewnątrz serwisów zostało odpowiednio zainicjowane, tj. np. obiekty DAO.
Na szczęście o tych zmartwieniach pozwala nam zapomnieć mechanizm integracji z testami NUnit w Spring.NET.
Wygląda to dosyć prosto. Na początek informacja, że Spring.NET dostarcza nam klasy o nazwie AbstractDependencyInjectionSpringContextTests. Wystarczy, aby nasza klasa z testami dziedziczyła z tej klasy bazowej i przeciążyła właściwość ConfigLocations typu string[]. Jak można się domyślić ta właściwość zawiera wskazanie na pliki konfiguracyjne Spring.NET. Wygląda świetnie – przecież nikt nie będzie pisał plików dla kontenera IoC ponownie. Wykorzystajmy te z naszej aplikacji wskazując na nie.
Spójrzmy na prosty przykład:


[TestFixture]
public class AbonenciServiceTest : AbstractDependencyInjectionSpringContextTests
{
  private AbonenciService abonenciService;
  public AbonenciService AbonenciService
  {
    set { abonenciService = value; }
  }
  protected override string[] ConfigLocations
  {
    get { return new String[] { „assembly://BeezDev.Turrow.Model.Test/BeezDev.Turrow.Model.Test/Services.xml” }; }
  }

  [Test]
  public void PobierzListeAbonentow()
  {
    IList<AbonentDTO> listaAbonentow = this.abonenciService.PobierzListeAbonentow();
    Assert.AreEqual(0, listaAbonentow.Count);
    AbonentDTO abonentDTO = new AbonentDTO();
    abonentDTO.Id = Guid.NewGuid();
    abonentDTO.Imie = „Jan”;
    abonentDTO.Nazwisko = „Kowalski”;
    this.abonenciService.DodajAbonenta(abonentDTO);
    listaAbonentow = this.abonenciService.PobierzListeAbonentow();
    Assert.AreEqual(1, listaAbonentow.Count);
  }
}

Co tu widać? Dziedziczymy po wspomnianej klasie i uzupełniamy właściwość ConfigLocations wskazując nasz plik konfiguracyjny. W tym wypadku jest to tylko część konfiguracji dotycząca samych serwisów. Dzięki temu Spring.NET korzystając z konfiguracji zapisanej w Services.xml wstrzyknie nam instancję klasy AbonenciService odpowiednio ją inicjując.
Tak wygląda plik Services.xml:


<?xml version=”1.0″ encoding=”utf-8″ ?>
<objects xmlns=”http://www.springframework.net”&gt;
  <object id=”abonenciService” type=”BeezDev.Turrow.Wspolne.Model.AbonenciService, BeezDev.Turrow.Wspolne.Model”>
    <property name=”AbonentDAO” ref=”abonentDAO”/>
  </object>

  <object id=”abonentDAO” type=”BeezDev.Turrow.Wspolne.DataAccess.AbonentDAO, BeezDev.Turrow.Wspolne.DataAccess”/>

</objects>

Widzimy, że do serwisu dodatkowo została wstrzyknięta instancja klasy AbonentDAO.

W zdecydowanej większości systemów biznesowych dane są zapisywane w jakimś repozytorium, którym najczęściej jest baza danych. Istotną kwestią jest to, że takie dane dodane czy zmodyfikowane podczas testu nie powinny mieć wpływu na całość systemu. Oznacza to tyle, że test powinien się wykonywać w transakcji i wszelkie modyfikacje danych nie powinny być widoczne poza tą transakcją. Takie właśnie zachowanie daje nam Spring.NET, tj. w ramach testu tworzy transakcję, a następnie ją wycofuje. Odpowiedzialna za to jest jedna z klas bazowych AbstractTransactionalDbProviderSpringContextTests. Klasa ta bazuje na IPlatformTransactionManager, który jest dostarczony w ramach konfiguracji aplikacji. W pewnych przypadkach takie domyślne zachowanie nie jest wskazane, dlatego też mamy możliwość dziedziczenia po tej klasie i wprowadzenia własnych modyfikacji. Może nas np. w szczególnych przypadkach interesować zatwierdzenie transakcji zamiast domyślnego jej wycofania. Można to osiągnąć poprzez wywołanie metody SetComplete(). Podobnie możemy chcieć wycofać transakcję przed zakończeniem testu – wywołując EndTransaction();

Spring.NET dostarcza nam prosty, ale jakże pomocny mechanizm wsparcia dla testów jednostkowych NUnit. Pozwala nam to zaoszczędzić wiele cennego czasu przy budowaniu testów naszej aplikacji.

29 lipca 2009

Ugryźć Spring.NET – (Cz.6) Serwisy

Filed under: Ugryźć Spring.Net — Tagi: , , , , — Beniamin Zaborski @ 19:58

Współczesne systemy informatyczne to systemy rozproszone, które komunikują się ze sobą na wiele różnych sposobów. U podstaw takiej komunikacji leżą interfejsy, za pomocą których systemy będą się ze sobą komunikować. Interfejsy odgrywają tu rolę fasad i to właśnie je określamy mianem serwisów. Ta część serii będzie traktować właśnie o możliwościach jakie daje nam Spring.NET w kwestii serwisów. O samym wzorcu projektowym Service Layer Martin Fowler pisze tutaj: http://martinfowler.com/eaaCatalog/serviceLayer.html.

W świecie .NET mamy do dyspozycji kilka różnych technologii pozwalających budować i publikować nam serwisy jak: .NET Remoting, WebServices, Enterprise Services czy wreszcie WCF.

Na etapie projektu aplikacji często nie wiemy lub nie chcemy się zajmować takimi konkretami jak technologia, której użyjemy do budowy naszych serwisów. Na tym etapie podchodzimy do serwisów w sposób bardziej abstrakcyjny. Jednak przychodzi moment, gdy musimy się zdecydować na któreś z rozwiązań. Załóżmy, że w pierwszej kolejności wybieramy .NET Remoting, ale po dłuższym zastanowieniu stwierdzamy jednak, że nasze serwisy będą wystawione na świat w postaci WebService i hostowane na serwerze IIS. Taka zmiana wymagać będzie od nas pewnego nakładu pracy i często wprowadza lekkie zamieszanie. A co gdyby tylko na poziomie konfiguracji określać jaką technologią nasze serwisy mają być hostowane? Brzmi świetnie!!!
Tu z pomocą przychodzi Spring.NET, a właściwie cały mechanizm eksporterów serwisów w Spring.NET.
Spring.NET wprowadza kolejną warstwę abstrakcji – eksportera serwisu. Obsługuje wszystkie wymienione wcześniej technologie eksportowania serwisów. Cała konfiguracja, która pozwoli nam wyeksportować serwis jest zupełnie banalna.

Zacznijmy od zdefiniowania dosłownie interfejsu naszego serwisu IAbonenciService.


public interface IAbonenciService
{
  IList<AbonentDTO> PobierzListeAbonentow();
}

Mamy bardzo prosty interfejs serwisu tylko z jedną metodą, teraz czas na jego implementację.


public class AbonenciService : IAbonenciService
{
  #region DAOs

  private AbonentDAO abonentDAO;
  public AbonentDAO AbonentDAO
  {
    get { return abonentDAO; }
    set {abonentDAO = value; }
  }

  #endregion DAOs

  public IList<AbonentDTO> PobierzListeAbonentow()
  {
    IList<AbonentDTO> listaAbonentow = new List<AbonentDTO>();
    foreach(Abonent abonent in AbonentDAO.PobierzListeAbonentow())
      listaAbonentow.Add(AbonentConverter.ToAbonentDTO(abonent));
    return listaAbonentow;
  }
}

Przedstawiłem prostą implementację serwisu, odwołująca się do obiektu DAO jak i obiektu konwertera. Oczywiście instancje obu tych obiektów możemy sobie wstrzyknąć poprzez kontener IoC Springa.

Dodać należy, że dobrym rozwiązaniem będzie umieszczenie interfejsu serwisu w osobnym assembly, np. BeezDev.ExampleSpringApp.Model.Interface, a implementacji w osobnym, np. BeezDev.ExampleSpringApp.Model. Dzięki temu aplikacja klienta będzie posiadała jawną referencję tylko do assembly z interfejsem, a nie implementacją serwisu.

Spróbujmy opublikować nasz serwis poprzez .NET Remoting. Nie będę opisywał szczegółów samej technologii .NET Remoting, bo moim celem jest pokazanie jedynie integracji Spring.NET z .NET Remoting. W sprawie konfiguracji kanałów, portów, aktywacji obiektów (SAO, SAO Singleton, CAO) odsyłam do dokumentacji.

Na pierwszy ogień konfiguracja po stronie serwera. Załóżmy, że nasz serwis hostujemy w zwykłej aplikacji .NET uruchamianej w postaci usługi Windows. Konfiguracja Spring.NET wyglądać będzie następująco:


<object id=”abonentDAO” type=”BeezDev.ExampleSpringApp.Model.AbonentDAO, BeezDev.ExampleSpringApp.Model”>
</object>

<object id=”abonenciService” type=”BeezDev.ExampleSpringApp.Model.AbonenciService, BeezDev.ExampleSpringApp.Model”>
  <property name=”AbonentDAO” value=”abonentDAO” />
</object>

<object name=”saoSingletonAbonenciService ” type=”Spring.Remoting.SaoExporter, Spring.Services”>
  <property name=”TargetName” value=” abonenciService” />
  <property name=”ServiceName” value=”AbonenciService” />
</object>

Co widzimy? Na początek wspomniany wcześniej obiekt typu DAO, dalej definicja serwisu i wstrzyknięcie instancji obiektu DAO. Najciekawsze na koniec – eksport serwisu poprzez .NET Remoting jako SAO Singleton. Właściwość TargetName wskazuje na definicję serwisu, a ServiceName określa nazwę pod jaką będzie widoczny nasz serwis.

To prawie wszystko! Prawie bo reszta to już tylko konfiguracja .NET Remoting, tj.


<system.runtime.remoting>
  <application>
    <channels>
      <channel ref=”tcp” port=”8008″ />
    </channels>
  </application>
</system.runtime.remoting>

Po stronie aplikacji klienta konfiguracja jest równie prosta:


<object id=”abonenciService” type=”Spring.Remoting.SaoFactoryObject, Spring.Services”>
  <property name=”ServiceInterface” value=”BeezDev.ExampleSpringApp.Model.IAbonenciService, BeezDev.ExampleSpringApp.Model.Interface” />
  <property name=”ServiceUrl” value=”tcp://localhost:8008/AbonenciService” />
</object>

Pobranie naszego serwisu po url-u, gdzie protokół (tcp) i port (8008) zdefiniowaliśmy w konfiguracji Remotingu po stronie serwera oczywiście. Nazwa serwisu w url to ta którą podaliśmy w ServiceName.

Konfiguracja .NET Remoting po stronie klienta:


<system.runtime.remoting>
  <application>
    <channels>
      <channel ref=”tcp”/>
    </channels>
  </application>
</system.runtime.remoting>

Oto cała konfiguracja, teraz możemy pobrać po stronie klienta instancję serwisu, przy założeniu że aplikacja serwera hostująca nasz serwis jest uruchomiona.


IApplicationContext ctx = ContextRegistry.GetContext();
IAbonenciService abonenciService = (IAbonenciService)ctx.GetObject(„abonenciService”);
IList<AbonentDTO> listaAbonentow = abonenciService.PobierzListeAbonentow();

To wszystko i działa! Jeśli zdecydujemy opublikować nasz serwis za pomocą innej technologii praktycznie wszystkie zmiany jakich musimy dokonać ograniczą się do zmian exportera w plikach konfiguracyjnych. Po szczegóły związane z serwisami w Spring.NET odsyłam tradycyjnie do dokumentacji projektu.

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.

4 marca 2009

Ugryźć Spring.Net – (cz.4) Dostęp do danych – NHibernate

Filed under: Ugryźć Spring.Net — Tagi: , , , , , , — Beniamin Zaborski @ 07:04

W tej części dalej zajmiemy się tematem dostępu do danych, ale nieco inną metodą. We współczesnych aplikacjach biznesowych stosowanie O/R Mappingu nie jest już niczym nadzwyczajnym, a wręcz stało się normą. Nie wypada zatem, aby Spring.NET nie wspierał takiego rozwiązania. Rzeczywiście Spring.NET posiada obsługę O/R Mappingu, a dokładniej mówiąc jest to NHibernate.
Na początek może dwa zdania co to takiego jest O/R Mapping. Jak podaje wikipedia jest to technika programistyczna do konwersji niekompatybilnych typów systemowych z postaci relacyjnej na obiektową. Jest to prawda, ponieważ relacyjne bazy danych operują na tabelach, obiektowe języki na obiektach. Istnieje wiele różnych komercyjnych jak i niekomercyjnych rozwiązań ORM, niezaprzeczalnie jednym z najbardziej popularnych jest NHibernate. NHibernate wiele zawdzięcza swojemu starszemu bratu z platformy Java o nazwie Hibernate – skądś to znamy ;).
Po takim krótkim wstępie chciałbym jednak oznajmić, iż celem artykułu nie jest opis samego NHibernate-a. Kieruje się tu do osób, które mają już pewne doświadczenie w pracy z NHibernate. Moim zadaniem tutaj będzie pokazanie jak Spring.NET integruje się z NHibernate.
Punktem wyjściowym będzie konfiguracja SessionFactory z użyciem kontenera Springa. Najlepiej przedstawi to poniższy przykład sekcji <objects>:

<db:provider id=”DbProvider”
    provider=”SqlServer-1.1″
    connectionString=
„Data Source=BENIAMINZ\SQLEXPRESS;Initial Catalog=ADMINET;Integrated Security=SSPI;”
/>
<object id=”MySessionFactory” type=”Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate”>
    <property name=”DbProvider” ref=”DbProvider”/>
    <property name=”MappingAssemblies”>
      <list>
        <value>BizDev.SampleSpring.Model</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”/>
      </dictionary>
    </property>
  </object>


Jak widać nasz SessionFactory bazuje na odpowiednim DbProvider o którym była mowa w poprzedniej części. DbProvider dostarcza nam tu informacji potrzebnych do nawiązania połączenia z bazą danych. W następnej kolejności określamy assembly w którym znajdują się pliki mapowania NHibernate-a. Sekcja HibernateProperties zawiera standardowe parametry konfiguracyjne NHibernate-a. Tutaj określiłem dialekt oraz driver bazy danych.

Przejdziemy teraz do wywołania właściwego kodu. NHibernate zawiera co najmniej trzy sposoby na wykonanie operacji na naszych zmapowanych obiektach. Pierwsze podejście opiera się na HibernateTemplate. To klasa narzędziowa pozwalająca na wywołanie standardowych metod CRUD sesji NHibernate. Spójrzmy na przykład: 

public class AbonentDAO : IAbonentDAO
{
  private HibernateTemplate hibernateTemplate;
  ISessionFactory SessionFactory
  {
     set { hibernateTemplate = new HibernateTemplate(value); }
  }
  public void Zapisz(Abonent abonent)
  {
    hibernateTemplate.SaveOrUpdate(abonent);
  }
}    

Widzimy prosty obiekt typu Data Access Object dla uproszczenia zawierający tylko metodę Zapisz. Wewnątrz metody jest wywoływane standardowe SaveOrUpdate z sesji NHibernate.
Jedyne co musimy dostarczyć to SessionFactory – oczywiście możemy to zadanie zlecić kontenerowi IoC. Fragment pliku konfiguracyjnego, który pozwoli wstrzyknąć instancję zdefiniowanego wcześniej SessionFactory do naszego DAO, wygląda tak:

 <object id=”AbonentDAO” type=”BizDev.SimpleSpringApplication.AbonentDAO, BizDev.SimpleSpringApplication„>
  <property name=”SessionFactory” ref=”MySessionFactory”/>
</object>

NHibernateTemplate dostarcza wiele metod z interfejsu Session NHibernate-a, jeśli jednak zechcemy wywołać jakąś metodę Session, której nie ma w NHibernateTemplate, to jest także na to sposób. Taka metoda naszego obiektu mogłaby wyglądać np. tak: 
public Abonent PobierzAbonenta(Guid idAbonenta)
{
  return nhibernateTemplate.Execute(
    delegate(ISession session)
    {
      return session.CreateQuery(”from Abonent a where a.Id = : idAbonenta”)
        .SetGuid(”idAbonenta’, idAbonenta’)
        .UniqueResult<Abonent>();
    }
  );
}      

 

Anonimowy delegat pozwala nam wywołać dowolny kod. To pierwsza z metod pozwalająca operować na obiektach. Alternatywnym sposobem jest dziedziczenie z klasy HibernateDaoSupport. Główną zaletą tego rozwiązania jest to, że zwalnia nas z konieczności używania callback-ów. Każdy przyzna, że nie jest to zbyt czytelny fragment kodu. Spójrzmy na taki przykład:

public class AbonentDAO : HibernateDaoSupport, IAbonentDAO
{
  public void Zapisz(Abonent abonent)
  {
    ISession session = DoGetSession(false);
    session.SaveOrUpdate(abonent);
  }
}  

 

Jednak mój ulubiony sposób operowania obiektami to trzeci sposób zgodnie z dokumentacją nazywany „contextual Sessions”, gdzie NHibernate sam zarządza jedną sesją ISession na transakcję. Taka metoda jest możliwa do zastosowania od wersji 1.2 NHibernate-a.  Zalętą tego rozwiązania jest to, iż nie musimy dziedziczyć po żadnej klasie Spring.NET. Przykład kodu poniżej.

 

public class AbonentDAO : IAbonentDAO
{
  private ISessionFactory sessionFactory;
  public ISessionFactory SessionFactory
  {
    get { return sessionFactory; }
    set { sessionFactory = value; }
  }
  public void Zapisz(Abonent abonent)
  {
    SessionFactory.GetCurrentSession().SaveOrUpdate(abonent);
  }

Jedyne co nam pozostaje to tylko wstrzyknięcie referencji do zdefiniowanego wcześniej SessionFactory do naszego DAO.
Przestawione tu informacje prawie wystarczają do rozpoczęcia pracy z NHibernate w Spring.NET. Dobrze wiemy, że prawie robi dużą różnicę ;). Niektórzy z Was zapewne spostrzegli, że nigdzie nie pojawił się tu temat transakcji. Zupełnie celowo, w tej jak i poprzedniej części, zarządzanie transakcjami zostało przeze mnie pominięte. Tematowi temu planuję poświęcić kolejną część tego cyklu.
Aby nie wyszło na to, iż przedstawiłem tu tylko suchą teorię, która ma się nijak do rzeczywistości, zaprezentuję w pełni działający przykład. Przykład zawiera jeden obiektu typu DataAccesObject o nazwie AbonentDAO, który realizuje proste operacje CRUD za pomocą NHibernate. Pominę definicję encji Abonent jak i treść pliku mapowania. Oto plik konfiguracyjny dla w pełni działającego przykładu:

   <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&#8221;

>
      <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>
     
      <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>
 
      <tx:attribute-driven/>

    </objects>
  </spring>

6 stycznia 2009

Ugryźć Spring.Net – (cz.3) Dostęp do danych – ADO.NET

Filed under: Ugryźć Spring.Net — Tagi: , , , , , , — Beniamin Zaborski @ 07:04

Każda aplikacja biznesowa operuje na danych. Najczęściej są to dane z relacyjnej bazy danych. Jak się do nich dostać?
W zależności od przyjętego modelu architektonicznego naszej aplikacji sposobów jest kilka. Abstrahując od tego Spring.Net ułatwia nam dostęp do danych dostarczając pomocnych narzędzi zarówno dla ADO.NET jak i NHibernate.
Początkowo ten artykuł miał traktować o obu tych metodach dostępu do danych, jednak z kilku powodów zdecydowałem się podzielić to na dwa osobne artykuły. Ze względu na obszerność poruszanych przeze mnie tematów w tej serii artykułów i tak nie zdołam zadowolić wszystkich czytelników. Po szczegóły już tradycyjnie wszystkich zainteresowanych odeślę do dokumentacji projektu, ale to dopiero pod koniec artykułu;).

Spring dostarcza wielu pomocnych klas narzędziowych ułatwiających dostęp do danych przez ADO.NET.

Wymienię kilka najważniejszych z mojego punktu widzenia:
– integracja z mechanizmem zarządzania transakcjami
– zcentralizowane zarządzanie połączeniami, komendami, czy zbiorami danych
– proste mapowanie DataReader to Object
– prostsze dodawanie parametrów do zapytań
– szablony do wykonywania zapytań SQL

Główną zaletą, która wynika (mniej lub bardziej) z powyższej listy jest to, że pracujemy na wyższym poziomie abstrakcji w porównaniu z „czystym” kodem ADO.NET.
Zanim przejdziemy do wykonywania zapytań SQL, kilka słów o połączeniu do bazy danych bo tu Spring także przychodzi nam z pomocą. Mowa tu o interfejsie IDbProvider.

public interface IDbProvider
{
  IDbCommand CreateCommand();
  object CreateCommandBuilder();        
  IDbConnection CreateConnection();
  IDbDataAdapter CreateDataAdapter();
  IDbDataParameter CreateParameter();
  string CreateParameterName(string name);
  string CreateParameterNameForCollection(string name);
  IDbMetadata DbMetadata { get; }               
  string ConnectionString { set; get; }
  string ExtractError(Exception e);
  bool IsDataAccessException(Exception e);    
}

Jak widać jest to dość pomocna fabryka dla połączeń, komend, parametrów, itd. Klasa fabrykująca DbProviderFactory tworzy nam instancję IDbProvider dla wskazanego RDBMS. Lista RDBMS jest zadowalająca i chyba nikomu nie powinno niczego brakować. Wytworzenie naszego IDbProvider-a wygląda tak:

IDbProvider dbProvider = DbProviderFactory.GetDbProvider(„System.Data.SqlClient”);

Tak wygląda to w kodzie, a dla odmiany w pliku konfiguracyjnym (łącznie z użyciem AdoTemplate) tak:

<objects xmlns=’http://www.springframework.net’
         xmlns:db=”http://www.springframework.net/database”&gt;
  <db:provider id=”DbProvider”
      provider=”System.Data.SqlClient”
      connectionString=”Data Source=(local);Database=Bazka;User ID=jimmy;Password=secret;Trusted_Connection=False”/>
 
  <object id=”adoTemplate” type=”Spring.Data.AdoTemplate, Spring.Data”>  
    <property name=”DbProvider” ref=”DbProvider”/>                
  </object>
</objects>

 

 

Klasą która najbardziej nas zainteresuje i wykonuje całą czarną robotę za nas jest AdoTemplate. Opiera się ona na idei Inversion Of Control z główną metodą Execute. Istnieje zarówno tradycyjna jak i generyczna implementacja tej klasy. Kod wykonujemy poprzez przekazanie delegata:

adoTemplate.Execute<int>(delegate(DbCommand command)
{
  command.CommandText =
  „SELECT COUNT(*) FROM Abonenci WHERE CzyAktywny = @CzyAktywny”;
                                                    
  DbParameter p = command.CreateParameter();
  p.ParameterName = „@CzyAktywny”;
  p.Value = czyAktywny;
  command.Parameters.Add(p);
                                                         
  return (int)command.ExecuteScalar();
});

Klasa AdoTemplate obok Execute posiada pokaźną ilość metod, których znaczenia nie trzeba chyba tłumaczyć, np.: ExecuteNonQuery, ExecuteScalar, DataTableCreate, DataTableFill, DataTableUpdate, DataSetCreate, DataSetFill, itp.

Przykład wykonania ExecuteNonQuery i ExecuteScalar:

adoTemplate.ExecuteNonQuery(CommandType.Text, String.Format(„INSERT INTO Przekierowania(We) VALUES ({0})”, 112));int count = (int)adoTemplate.ExecuteScalar(CommandType.Text, „SELECT COUNT(*) FROM Przekierowania”);

Proste, zrozumiałe i o ile mniej kodu niż podczas użycia „czystego” ADO.NET, prawda?
A teraz coś ciekawszego. Wynik zapytania możemy zmapować równie niewielkim nakładem pracy na kolekcję naszych obiektów domeny. Brzmi świetnie, sprawdźmy.
Służą do tego metody z klasy AdoTemplate (których celowo wcześniej nie wymieniłem) z grupy QueryWith. Odpowiedzialne za wykonywania mapowania są trzy interfejsy i delegaty, które musimy obsłużyć:
– IResultSetExtractor / ResultSetExtractorDelegate – umożliwia iterowanie po zwróconym wyniku i zwrócenie odpowiedniego obiektu.
– IRowCallback / RowCallbackDelagete – umożliwia przetwarzanie bieżącego wiersza wyniku zapytania bez zwracania wyniku.
– IRowMapper / RowMapperDelegate – umożliwia przetwarzanie bieżącego wiersza wyniku zapytania ze zwróceniem wyniku.

Interfejsy IResultSetExtractor oraz IRowMapper występują zarówno w wersji klasycznej jak i generycznej. Przyjrzyjmy się teraz każdemu z osobna z wymienionych interfejsów, na przykładach.

adoTemplate.QueryWithResultSetExtractor(CommandType.Text, “SELECT * FROM Abonenci”, new NazwaAbonentaResultSetExtractor<List<string>>());internal class NazwaAbonentaResultSetExtractor <T> : IResultSetExtractor<T> where T : IList<string>, new()
{
        public T ExtractData(IDataReader reader)
        {
            T list = new T();
            while (reader.Read())
            {
                string nazwaAbonenta = reader.GetString(0);
                list.Add(nazwaAbonenta);
            }
            return list;
        }
}

W metodzie naszego extractora iterujemy po wynikach zapytania i pobieramy to co nas interesuje czyli nazwę abonenta zwracając kolekcję typów string. Oczywiście moglibyśmy tu zamiast prostego typu string mapować dane do naszej encji.

Teraz przykład użycia kolejnego dość pomocnego interfejsu czyli IRowCallback. Jak sama nazwa może sugerować, pozwala nam na przetwarzanie danych każdego wiersza wyniku zapytania. Sam w sobie nie zwraca danych, jednak operuje na obiekcie który posiada stan. Prześledźmy jego działanie na przykładzie. Załóżmy, że chcemy z danych abonentów utworzyć słownik w którym znajdą się abonenci pogrupowani po typie (oczywiście można te zrobić o wiele prostszą metodą już w SQL).

TypyAbonentowRowCallback rowCallback = new TypyAbonentowRowCallback ();AdoTemplate.QueryWithRowCallback(CommandType.Text, “SELECT * FROM ABONENCI”, rowCallback);Dictionary<string, IList<Abonent>> result = rowCallback.TypyAbonentow;

A oto definicja naszego TypyAbonentowRowCallback:

internal class TypyAbonentowRowCallback: IRowCallback
{
  private IDictionary<string, IList<string>> typyAbonentow =new Dictionary<string, IList<string>>();  public IDictionary<string, IList<string>> TypyAbonentow
  {
    get { return typyAbonentow; }
  }  public void ProcessRow(IDataReader reader)
  {
    Guid id = reader.GetString(0);
    string nazwa = reader.GetString(1);
    string adres = reader.GetString(2);
    bool czyAktywny = reader.GetString(3);
    string typAbonenta = reader.GetString(4);    Abonent abonent = new Abonent(id, nazwa, adres, czyAktywny, typAbonenta);

    if (!typyAbonentow.ContainsKey(typAbonenta))
      typyAbonentow.Add(typAbonenta, new List<Abonent>());
                
    IList<Abonent> listaAbonentow = typyAbonentow[typAbonenta]; listaAbonentow.Add(abonent);
  }
}

Widzimy, że metoda ProcessRow  klasy TypyAbonentowRowCallback przetwarza każdy wiersz wyniku zapytania, ale nie zwraca go bezpośrednio, a zapisuje w wewnętrznej kolekcji.

Ostatni z przykładów do IRowMapper. Działa podobnie do poprzedniego, jednak jest bezstanowy z reguły ponieważ przetworzone dane bieżącego rekordu są zwracane. Prosty przykład pobrania listy abonentów:

IList<Abonenct> listaAbonentow = AdoTemplate.QueryWithRowMapper<Abonent>(CommandType.Text, “SELECT * FROM ABONENCI”, new AbonentRowMapper<Abonent>());public class AbonentRowMapper <T> : IRowMapper<T> where T : Abonent, new()
{        
  public T MapRow(IDataReader dataReader, int rowNum)
  {
    T abonent = new T();
     abonent.Id = reader.GetString(0);
     abonent.Nazwa = reader.GetString(1);
     abonent.Adres = reader.GetString(2);
     abonent.CzyAktywny = reader.GetString(3);
     abonent.TypAbonenta = reader.GetString(4);
     return abonent;
   }
}

Powyższa implementacja interfesu IRowMapper chyba nie wymaga komentarza.

Jak widzimy Spring.NET daje nam sporo ciekawych i pomocnych narzędzi dostępu do danych poprzez ADO.NET. Użycie czystego ADO.NET wymagało by o wiele więcej linii kodu. Pomocnych narzędzi w AdoTemplate jest dużo więcej, a te tu przedstawione to subiektywnie wybrane przeze mnie. Zachęcam do zapoznania się z tą użyteczną klasą i jak obiecałem na wstępie odsyłam do dokumentacji. W kolejnej części napiszę o dostępie do danych przy pomocy NHibernate.

18 grudnia 2008

Ugryźć Spring.Net – (cz.2) Programowanie aspektowe

Filed under: Ugryźć Spring.Net — Tagi: , , , , , , — Beniamin Zaborski @ 07:03

W tej części zajmiemy się AOP, a raczej tym co ma wspólnego Spring.Net z AOP. Na wstępie odrobina teorii i wyjaśnienia podstawowych terminów. Co to jest AOP?
AOP to skrót od Aspect Oriented Programming czyli po naszemu programowanie aspektowe. Jest to paradygmat programowania, którego główna idea to modularyzacja i enkapsulacja kodu. Autorem całej tej idei jest zespół z firmy Xerox PARC. To oni stworzyli pierwszy i chyba nadal najbardziej popularny aspektowy język programowania – AspectJ.
AOP świetnie uzupełnia podejście OOP czyli Object Oriented Programming. W OOP myślimy o programie jako o hierarchii obiektów, natomiast AOP rozkłada program na tzw. aspekty. Aspekt możemy traktować jako pewne zadanie, które wykonujemy w naszej aplikacji i jest ono rozproszone na wiele obiektów, np. logowanie. Logowanie jest najczęściej przytaczanym przykładem w kontekście programowania aspektowego. Charakter tej operacji idealnie pasuje do paradygmatu aspektowego.

Załóżmy, że mamy w naszej aplikacji sporą grupę obiektów typu Data Access Object i każdy z nich posiada m.in. podstawowe operacje CRUD. Chcemy aby każde wywołanie metody Delete z każdego obiektu DAO było logowane do pliku. Aby się nie napracować zbytnio tu przychodzi nam z pomocą AOP. Zanim przejdziemy do praktycznych przykładów wyjaśnić należy kilka podstawowych terminów związanych z programowaniem aspektowym.

  • Jointpoint (punkt złączenia): dowolne, identyfikowalne miejsce w programie posiadające kontekst, jak: wywołanie metody, wywołanie konstruktora, wyrzucenie wyjątku, itp.
  • Advice (rada): akcja wykonywana przed, po lub zamiast osiągnięcia przez program punktu złączenia. 
  • Pointcut (punkt przekroju): zbiór punktów złączeń dla których powinna się wykonywać wskazana rada.

Odnosząc te terminy do naszego przykładu, to punktem złączenia możemy nazwać metodę Delete, radą – fragment kodu zapisującego informacje do pliku, a punktem przekroju informację np. w postaci wyrażenia regularnego określającego, że rada ma się wykonać we wszystkich obiektach DAO.

Spring.Net posiada wbudowany framework AOP. Oczywiście jeśli nie potrzebujemy go w naszej aplikacji nikt nie wymaga od nas używania go. Trzeba mieć jednak świadomość tego, iż kilka mechanizmów frameworka Spring.Net opiera się na AOP, jak choćby transakcje. Z AOP jest związany jeszcze tzw. proces Weaver czyli tkanie. Polega to na dołączeniu aspektów do oryginalnego kodu. W Spring.Net odbywa się to poprzez generowanie obiektów proxy w trakcie uruchomienia aplikacji (dzięki System.Reflection.Emit jest tworzony odpowiedni kod IL).

Zacznijmy od punktów złączeń odnosząc naszą wiedzę na ten temat do tego co oferuje nam w tym temacie Spring. W Spring-u znajdziemy interfejs Spring.Aop.IPointcut, z którego zainteresuje nas najbardziej property MethodMatcher typu IMethodMatcher.

public interface IMethodMatcher
{
    bool IsRuntime { get; }
     bool Matches(MethodInfo method, Type targetType);
    bool Matches(MethodInfo method, Type targetType, object[] args);
}

 
Metoda Matches(MethodInfo method, Type targetType) jest wywoływana, aby określić czy punkt złączenia będzie osiągnięty/dopasowany na docelowym obiekcie targetType. Takie sprawdzanie będzie wykonane w momencie tworzenia obiektu proxy, aby uniknąć potrzeby weryfikacji przy każdym wywołaniu metody.  Jeśli wystąpi dopasowanie wtedy Matches(MethodInfo method, Type targetType) zwraca true podobnie jak właściwość IsRuntime. Trzy-argumentowa wersja Matches zostanie wywołana na każde wykonanie metody.
Najczęściej stosowaną metodą określania pointcut-ów są wyrażenia regularne. W tym celu Spring dostarcza klasę: Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor. Używając tej klasy możemy dostarczyć listę pattern-ów które określą dopasowanie. Prosty przykład:

<object id=”settersAndAbsquatulatePointcut”
    type=”Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop”>
    <property name=”patterns”>
        <list>
            <value>*Delete</value>
            <value>*Usun</value>
        </list>
    </property>
</object>

Jeśli którekolwiek z tych wyrażeń będzie spełnione wtedy Matches zwróci true – dla metod kończących się na Delete lub Usun. Inne podejście to dopasowanie po atrybucie, np.:

<object id=”cachePointcut” type=”Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop”>
    <property name=”Attribute” value=”Spring.Attributes.CacheAttribute, Spring.Core”/>        
</object>

Takie użycie dopasuje wszystkie metody mające nałożony atrybut Spring.Attributes.CacheAttribute.
Ustaliliśmy już gdzie chcemy wykonać nasz kod, teraz jak określić co chcemy wykonać? Spring.Net dostarcza nam kilka typów rad i odpowiadających im interfejsów :

  • interception around advice – wykonanie w miejsce metody
public interface IMethodInterceptor : IInterceptor
{
    object Invoke(IMethodInvocation invocation);
}

Nasza przykładowa implementacja takiego typu advice-a może wyglądać tak;

public class DebugInterceptor : IMethodInterceptor
{
    public object Invoke(IMethodInvocation invocation)
   {
        Console.WriteLine(„Before: invocation=[{0}]”, invocation);
        object rval = invocation.Proceed();
        Console.WriteLine(„Invocation returned”);
        return rval;
    }
}

Jak widzimy do metody Invoke jest przekazywany parametr invocation, który pozwala nam samemu wykonać właściwą metodę i zwrócić jej wynik.

  • before advice – wykonanie przed wykonaniem metody
public interface IMethodBeforeAdvice : IBeforeAdvice
{
    void Before(MethodInfo method, object[] args, object target);
}

 

  • throws advice – wykonanie przy wyrzucaniu wyjątku
public interface IThrowsAdvice : IAdvice

 

  • after returning advice – wykonanie po wykonaniu metody, przed zwróceniem wyniku
public interface IAfterReturningAdvice : IAdvice
{
  void AfterReturning(object returnValue, MethodBase method, object[] args, object target);
}

Wracając do naszego przykładu najbardziej odpowiedni typ advice to before advice. Zaimplementujmy go zatem:

public class LogujUsuwanieBeforeAdvice : IMethodBeforeAdvice
{
    public void Before(MethodInfo method, object[] args, object target)
    {
        Logger.ToFile(“Usunięto obiekt”, LogLevel.Warning);
       }
}

A tak powinien wyglądać kompletny plik konfiguracyjny do naszego przykładu:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <configSections>
    <sectionGroup name=”spring”>
      <section name=”context” type=”Spring.Context.Support.ContextHandler, Spring.Core”/>
      <section name=”objects” type=”Spring.Context.Support.DefaultSectionHandler, Spring.Core” />
    </sectionGroup>
  </configSections>
  <spring>
    <context>
      <resource uri=”config://spring/objects”/>
    </context>
    <objects xmlns=”http://www.springframework.net”&gt;
      <object id=”abonentDAO” type=”BizDev.SimpleSpringAOP.AbonentDAO, BizDev.SimpleSpringAOP”/>
      <object id=”uzytkownikDAO” type=”BizDev.SimpleSpringAOP.UzytkownikDAO, BizDev.SimpleSpringAOP”/>

      <object id=”logujUsuwanieBeforeAdvice” type=”Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop”>
        <property name=”patterns”>
          <list>
            <value>.*Delete</value>
            <value>.*Usun</value>
          </list>
        </property>
        <property name=”advice”>
          <object type=”BizDev.SimpleSpringAOP.LogujUsuwanieBeforeAdvice, BizDev.SimpleSpringAOP”/>
        </property>
      </object>

      <object id=”daos” type=”Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop”>
        <property name=”ObjectNames”>
          <list>
            <value>*DAO</value>
          </list>
        </property>
        <property name=”interceptorNames”>
          <list>
            <value>logujUsuwanieBeforeAdvice</value>
          </list>
        </property>
      </object>

    </objects>
  </spring>
</configuration>

Kilka słów podsumowania tego co widzimy w konfiguracji. Utworzyliśmy własną radę LogujUsuwanieBeforeAdvice wykonującą nasze zadanie tj. logowanie informacji do pliku. Następnie zdefiniowaliśmy pointcut dla tego advice-a określając, że ma się wykonywać dla metod kończących się ciągiem znaków Delete lub Usun. Na końcu zdefiniowaliśmy obiekt typu ObjectNameAutoProxyCreator wskazując nasz advice oraz określając, że ma być wykonany na wszystkich obiektach, których nazwa kończy się na DAO.
       
Teraz fragment kodu aktywujący wywołanie zaaplikowanych advice-ów:
     

IApplicationContext ctx = ContextRegistry.GetContext();
IDAO adao = (IDAO)ctx.GetObject(„abonentDAO”);
adao.Delete();
adao.SafeDelete();
       
IDAO udao = (IDAO)ctx.GetObject(„uzytkownikDAO”);
udao.Delete();
udao.SafeDelete();

      
A więc zadanie, które sobie założyliśmy na początku zostało zrealizowane bez modyfikacji kodu klas Data Access Object. Warto wspomnieć, iż to wszystko co skonfigurowaliśmy w pliku konfiguracyjnym można zrobić w kodzie bez udziału kontenera IoC, np.:
     

ProxyFactory factory = new ProxyFactory(new AbonentDAO());
factory.AddAdvice(new LogujUsuwanieBeforeAdvice());
IDAO dao = (IDAO)factory.GetProxy();
dao.Delete();

      
Do tego artykułu wybrałem przypadek nie do końca najbardziej trywialny od strony konfiguracji, ale jakże praktyczny. Oczywiście advice-y można aplikować jeszcze na kilka innych sposobów, ale po szczegóły odsyłam wszystkich zainteresowanych  do dokumentacji projektu. W kolejnej części napiszę o mechanizmach dostępu do danych w Spring.Net.

17 grudnia 2008

Ugryźć Spring.Net – (cz.1) Kontener IoC

Filed under: Ugryźć Spring.Net — Tagi: , , , , , , — Beniamin Zaborski @ 07:00

W poprzedniej części wprowadzającej zdradziłem temat, a więc zajmiemy się kontenerem IoC. Napisałem także, że z frameworka Sping.Net można skorzystać kompleksowo lub użyć tylko jego wybranych elementów. Z czego byśmy jednak nie skorzystali to potrzebna nam będzie podstawowa wiedza o skonfigurowaniu Springa i o kontenerze Dependency Injection / IoC.
Zanim zaczniemy musimy wiedzieć co to takiego jest Dependency Injection? Jest to jeden z wzorców projektowych, który jak podaje Wikipedia można zdefiniować tak:

„Wstrzykiwanie zależności (ang. Dependency Injection, DI) – wzorzec projektowy i wzorzec architektury oprogramowania polegający na usuwaniu bezpośrednich zależności pomiędzy komponentami na rzecz architektury typu plug-in. Jest on często utożsamiany z Odwróceniem sterowania (ang. Inversion of Control, IOC), jakkolwiek z technicznego punktu widzenia DI jest jedną ze szczególnych (obecnie najpopularniejszą) realizacji paradygmatu IoC.”

Oryginalna definicję Martina Fowlera można znaleźć tutaj: http://www.martinfowler.com/articles/injection.html#InversionOfControl

Jeśli nie do końca się to jeszcze rozjaśniło to tak własnymi słowami można DI opisać jako czarodziejskie narzędzie, które na podstawie sporządzonej konfiguracji (np. w XML-u) wstrzykuje instancje do naszych klas. To czarodziejskie narzędzie to właśnie kontener Depenedency Injection / IoC. W dalszej części będę tych dwu terminów używał zamiennie choć podkreślić należy subtelne różnice między nimi, które zresztą wyjaśnia Martin Fowler.

Teraz możemy już przejść do samego Spring-a. Interfejsem, który na początku powinien nas najbardziej zainteresować jest IObjectFactory lub też jego bardziej wypasiony brat IApplicationContext. Jak podaje dokumentacja ten drugi ma więcej możliwości i jest bardziej enterprise-centric. IObjectFactory odpowiedzialny jest za powoływanie, konfigurowanie oraz zarządzanie obiektami wewnątrz kontenera IoC. Istnieje kilka implementacji tego interfejsu, jednakże najbardziej zainteresuje nas – przynajmniej na początku – XmlObjectFactory. Ta implementacja bazuje na metadanych dostarczanych do kontenera IoC z XML-a, np. umieszczonego w pliku konfiguracyjnym naszej aplikacji.
Kontener IoC potrafi wstrzykiwać zależności do klas naszej aplikacji na podstawie metadanych. W ten sposób powstaje w pełni skonfigurowana i używalna instancja wymaganej klasy. Metadane mogą pochodzić z różnych źródeł, jednak najbardziej popularne jest umieszczanie ich w XML-u. Takim właśnie przypadkiem zajmiemy się w artykule.

Rozpatrzmy pewien przykład. Daruję sobie przykłady w stylu Hello World i spróbujmy czegoś bardziej praktycznego. Przypadek bardzo często spotykany w aplikacjach biznesowych –  serwis potrzebuje instancji obiektu dającego dostęp do danych.
Załóżmy, że tworzymy serwis AbonenciService, który posiada jedną metodę pobierająca listę abonentów. Nasz serwis pobiera dane z bazy danych przy pomocy metody PobierzAbonentow() obiektu AbonentDAO (Data Access Object).

public class AbonenciService : IAbonenciService
{
    #region Data Access Objects    private AbonentDAO abonentDAO;
    public AbonentDAO AbonentDAO
    {
        get { return abonentDAO; }
        set { abonentDAO = value; }
    }
    
  #endregion    

    public IList<AbonentDTO> PobierzListeAbonentow()
    {
        List<AbonentDTO> listaAbonentow = new List<AbonentDTO>();
        foreach(Abonent abonent in AbonentDAO.PobierzAbonentow())
        {
            listaAbonentow.Add(AbonentConverter.ToAbonentDTO());
        }
        return listaAbonentow;
    }
}

Metoda serwisu PobierzListeAbonentow() wywołuje metodę obiektu AbonentDAO by pobrać listę encji z bazy danych. Nie trudno zauważyć, iż nigdzie jawnie nie tworzymy instancji obiektu  AbonentDAO. Powołaniem instancji tej klasy oraz wstrzyknięciem jej do naszego serwisu zajmie się właśnie kontener IoC. Oczywiście musimy mu w tym pomóc, czyli dokonać odpowiedniej konfiguracji. Najbardziej pożądaną metodą jest użycie sekcji konfiguracyjnej Springa w standardowym pliku konfiguracyjnym (app.config czy web.config).

<configuration>
  <configSections>
    <sectionGroup name=”spring”>
      <section name=”context” type=”Spring.Context.Support.ContextHandler, Spring.Core”/>
      <section name=”objects” type=”Spring.Context.Support.DefaultSectionHandler, Spring.Core” />
    </sectionGroup>
  </configSections>
  <spring>
    <context>
      <resource uri=”config://spring/objects”/>
    </context>
    <objects xmlns=”http://www.springframework.net”&gt;
        …
    </objects>
  </spring>
</configuration>

Tak wygląda najprostszy plik konfiguracyjny zaprzęgający Spring-a do pracy. Na tym etapie jedyne co nas będzie interesować to sekcja <objects>. To w tym miejscu definiujemy obiekty naszej aplikacji. Spróbujmy zatem wstrzyknąć wspomnianą instancję klasy AbonentDAO do serwisu  AbonenciService. Sekcja <objects> powinna wyglądać teraz tak:


<object id=”abonentDAO” type=”BizDev.SpringSample.Model.AbonentDAO, BizDev.SpringSample.Model”></object>

<object id=”abonenciService” type=”BizDev.SpringSample.Model.AbonenciService, BizDev.SpringSample.Model”>
  <property name=”AbonentDAO” ref=”abonentDAO”/>
</object>

W pierwszym tagu <object> definiujemy nasz obiekt typu  AbonentDAO i nadajemy mu identyfikator w kontenerze – abonentDAO. Drugi <object> to nasz serwis typu AbonenciService. Tutaj najbardziej zainteresuje nas linia: <property name=”AbonentDAO” ref=”abonentDAO”/>, która mówi że do property o nazwie  AbonentDAO ma zostać przypisany obiekt, który wcześniej zdefiniowaliśmy pod identyfikatorem abonentDAO. Resztą zajmie się kontener IoC Spring-a. Proste prawda?
Owszem proste, ale został tu przedstawiony najbardziej trywialny przypadek. Możliwości konfiguracji kontenera IoC są znacznie większe. Nie zawsze pożądane jest tworzenie obiektu przez przypisanie do property. Spring daje nam inne możliwości, jak choćby przekazanie obiektu do konstruktora czy utworzenie instancji poprzez metodę fabrykującą.
Wyjaśnienia wymaga jeszcze ref, które znalazło się w pliku konfiguracyjnym. Wskazuje nam na referencję innego obiektu z kontenera. A co gdy chcemy wstrzyknąć bezpośrednio wartość, a nie referencję do innego obiektu? Załóżmy, że nasza klasa serwisu posiada property:
priate int maxCount;
public int MaxCount
{
    get { return maxCount; }
    set { maxCount = value; }
}

Ustawienie wartości MaxCount również możemy zlecić kontenerowi DI, dodając do definicji naszego serwisu w pliku konfiguracyjnym linię: <property name=”MaxCount” value=”1000″/>

Wróćmy teraz do wspomnianych przeze mnie dwóch innych metod wstrzykiwania zależności. Powiedzmy, że klasa serwisu AbonenciService posiada konstruktor:

public AbonenciService(int maxCount)
{
    this.maxCount = maxCount;
}

Ustawiamy wartość MaxCount poprzez konstruktor dodając do definicji klasy AbonenciService  w pliku konfiguracyjnym linię: <constructor-arg index=”0″ value=”2000″/> lub po nazwie parametru konstruktora: <constructor-arg name=”maxCount” value=”2000″/>
A co w sytuacji, gdy powołaniem do życia naszego obiektu zajmuje się jakaś metoda fabrykująca?  Takie podejście również jest dozwolone w Spring-u.
Załóżmy, że nasza klasa AbonentDAO posiada statyczną metodę fabrykującą:

public static AbonentDAO CreateInstance()
{
    return new AbonentDAO();
}

Nasz obiekt AbonentDAO może być wytworzony właśnie za pomocą takiej metody fabrykującej. Konfiguracja AbonentDAO wtedy wymaga dodania do definicji <object>: factory-method=”CreateInstance”. W sytuacji, gdy fabrykowaniem zajmuje się zewnętrzna klasa: factory-method=”CreateInstance” factory-object=”daoFactory”.
Mamy serwis, mamy dao teraz czas na fragment kodu, który coś z tym wszystkim zrobi. Pierwsze i w zasadzie jedyne do czego jesteśmy zobowiązani to zainicjowanie kontenera IoC w naszej aplikacji. Możemy tego dokonać np. takim fragmentem kodu:

IApplicationContext ctx = ContextRegistry.GetContext();

Kontener gotowy do pracy, a więc pobieramy z niego w pełni skonfigurowany i gotowy do działania nasz serwis:

AbonenciService abonenciService = (AbonenciService)ctx.GetObject(”abonenciService”);
IList<AbonentDTO> listaAbonentow = abonenciService.PobierzListeAbonentow();

Zrobione! I nie bolało bardzo ;-). Nie mogę nie zaznaczyć, iż przedstawiłem tu podstawy konfiguracji kontenera IoC w Spring.Net. Po szczegóły odsyłam do wyczerpującej dokumentacji projektu. W kolejnej części opowiem o Spring.AOP.

Ugryźć Spring.Net – (cz.0) Wprowadzenie

Filed under: Ugryźć Spring.Net — Tagi: , , , , , , — Beniamin Zaborski @ 06:59

To pierwszy artykuł z serii na moim blogu poświęcony Spring.NET. Jak na pierwszy przystało należy się kilka słów wstępu. Co to takiego właściwie jest ten Spring.Net?
Spring.Net jest frameworkiem wspomagającym budowanie aplikacji enterprise w środowisku .NET. Dostarcza kompleksowe rozwiązania do budowania aplikacji biznesowych. Sam Spring.NET bazuje na java-owskiej wersji Spring-a, jednakże nie jest jego bezpośrednim portem na platformę .NET, a raczej czerpie z filozofii oraz architektury swojego starszego brata.
Coraz bardziej przyzwyczajamy się, iż wiele świetnych rozwiązań ze świata Javy zostało z sukcesem przeniesionych na platformę .NET. Dobrym przykładem obok Spring.NET może być także NHibernate. Jeszcze kilka ciekawych zostało, czekamy … ;-).
Zadaniem tego artykułu nie jest omawianie architektury aplikacji enterprise, bo z takową raczej każdy kto poważnie interesuje się Spring.NET jest zaznajomiony, ale nie wypada nie wspomnieć o kliku podstawowych sprawach. Dobrze zaprojektowane aplikacje enterprise zbudowane są z fizycznych i logicznych warstw. Praktycznie w każdym modelu powinniśmy być w stanie wyodrębnić logiczne warstwy: dostępu do danych, logiki biznesowej, prezentacji. W zależności od przyjętej architektury tych warstw może być więcej, niektóre z nich podlegają dodatkowemu podziałowi. Spring.Net dostarcza wsparcia praktycznie dla każdej z wymienionych warstw aplikacji. W serii moich artykułów postaram się przedstawić większość funkcjonalności jakie daje nam Spring.
Spring.Net posiada budowę modułową i teraz dwa słowa o każdym z modułów, które w kolejnych częściach serii zostaną przybliżone.

  • Spring.Core: samo serce frameworka, obsługa konfiguracji aplikacji, kontener Dependency Injection, a także walidacja, mechanizm wyrażeń, obsługa wątków, zasobów, itp.
  • Spring.Aop: moduł obsługi tzw. Aspect-Oriented Programming. Dodać należy, iż nawet jeśli nie wykorzystujemy bezpośrednio AOP we własnych aplikacjach, to wiele elementów Spring-a bazuje na AOP, jak choćby transakcje.
  • Spring.Data: zestaw klas ułatwiających nam dostęp do danych poprzez ADO.NET.
  • Spring.Data.NHibernate – zestaw klas ułatwających nam dostęp do danych dzięki integracji z mechanizmem O/R Mappingu Nhibernate.
  • Spring.Services: moduł dający możliwość publikowania serwisów dla aplikacji rozproszonych wykorzystując przy tym dobrze znane technologie jak: .Net Remoting, Enterprise Services, Web Services, czy w najnowszej wersji nawet WCF.
  • Spring.Web, Spring.Web.Extensions: moduły wsparcia dla webowych aplikacji ASP.NET, tj. m.in. bindowanie, walidacja, page/control/module/provider.
  • Spring.Testing.NUnit: integracja testów jednostkowych z Nunit.

Jak widać framework może nam pomóc dość kompleksowo, choć oczywiście możemy skorzystać tylko z wybranych elementów Spring-a.

Oto pierwszy wpis na blogu i lekkie kilka słów wprowadzenia – na początek … wystarczy. W kolejnej części przybliżę ideę kontenera IoC w Spring.Net.

Stwórz darmową stronę albo bloga na WordPress.com.