March 25, 2009

Wstrzykiwanie EJB do akcji Struts2 z @EJB

Chciałbym dzisiaj pokazać niekoniecznie odkrywczy, ale dość ciekawy przykład integracji frameworku Struts2 z EJB3 - a wszystko sklejone za pomocą Springa. Tytuł posta jest bowiem tendencyjny: EJB można wstrzykiwać do dowolnych beanów Springowych, ale skupimy się na akcjach Struts2.

Przyjmijmy, że w naszej aplikacji istnieje EJB z lokalnym interfejsem DateServiceLocal:
@Local
public interface DateServiceLocal {
String getCurrentDate(Locale locale, TimeZone timeZone);
}

Usługa prosta, zwraca bieżący czas w podanej strefie czasowej, uwzględniając lokalizację (np. czas w Australii po rosyjsku). Z drugiej strony chcemy skorzystać z tej usługi w akcji Struts2. Jednak brzydzi nas (anty)wzorzec Service Locator, a tym bardziej szastanie na prawo i lewo klasą InitialContext. Mamy XXI wiek, "don't call me, I'll call you" głosi hasło reklamowe wzorca Dependency Injection. Nasza akcja powinna wyglądać tak:

public class DateAction extends ActionSupport {

@EJB
private DateServiceLocal dateService;
private Locale language;
private TimeZone timeZone;

public String execute() throws Exception {
addActionMessage(dateService.getCurrentDate(language, timeZone));
return SUCCESS;
}

/*...*/
}


Mamy pole oznaczone standardową adnotacją @EJB (uwaga: nie potrzebuje settera) oraz pola language i timeZone pochodzące z formularza. Potem w execute() najzwyczajniej w świecie wołamy metodę lokalnego interfejsu naszego EJB. To wszystko! Jak przygotować środowisko do tak wygodnej integracji?

Przede wszystkim przygotowujemy aplikację Struts2 zintegrowaną ze Springiem (cyklem życia akcji ma zarządzać kontener IoC). Jak to zrobić, już pokazywałem, chociażby w artykule "Elegancki CRUD w jednej akcji Struts2". Następnie tworzymy springowe proxy odwołujące się do wskazanego EJB. Jeśli nigdy nie korzystaliście z tej funkcji: Spring tworzy specjalne proxy, które z jednej strony zachowuje się jak zwykły bean springowy, jednak w rzeczywistości deleguje wszystkie wywołania do wskazanego EJB. Dzięki zastosowani przestrzeni nazw jee wystarczy wpisać:


<jee:local-slsb
id="dateService"
jndi-name="dateserver-ear-${project.version}/DateService/local"
business-interface="com.blogspot.nurkiewicz.dateserver.DateServiceLocal"/>


Przykład testowałem na JBossie 5.0.1, który poprzedza nazwy JNDI nazwą EARa zawierającego EJB-JAR. A ponieważ maven domyślnie dokleja wersję do nazwy artefaktu ear (dateserver-ear-0.0.1-SNAPSHOT.ear, i radzę tego nie zmieniać), należy przefiltrować plik applicationContext.xml i wkleić doń aktualną wersję. Naturalnie zahardkodowanie 0.0.1-SNAPSHOT to kiepski pomysł :-).

Od tej chwili mamy już bean springowy o id dateService, a w rzeczywistości proxy do EJB. Jeśli spodziewacie się teraz deklaratywnego wstrzykiwania tego beanu do odpowiednich akcji to muszę Was zawieźć... Nic więcej robić nie trzeba! No, prawie. Poniższy wpis sprawi, że Spring przeszuka wszystkie właściwości nowotworzonych beanów i będą oznaczone adnotacją @EJB, automatycznie wstrzyknie pasujący bean/EJB:


<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>


Nawiasem mówiąc ten post procesor (zachęcam do pisania własnych post procesorów, to naprawdę potężny mechanizm!) rozpoznaje również adnotacje javax.annotation.PostConstruct i PreDestroy - również używam ich w tej aplikacji.

Ot, cała filozofia. Od tej pory możemy się cieszyć bardzo wygodną integracją frameworku webowego Struts2 z back-endem w EJB3. A wszystko to spaja - o zgrozo - największy rywal EJB, czyli Spring. Może zatem, zamiast się gryźć, próbujmy połączyć te dwa żywioły?

Tutaj dostępny kod źródłowy przykładowej aplikacji (maven friendly). Podzieliłem ją na cztery artefakty: ejb-api (interfejs EJB), ejb, web i ear. Jest to dodatkowy zysk z EJB: bardzo wyraźny podział widoku i logiki biznesowej, punktem styku są jedynie interfejsy komponentów sesyjnych. Do potestowania wystarczy mvn package i wdrożenie na JBossie. A na koniec obiecany czas w Australii po rosyjsku - jak na aplikację testową całkiem przydatna funkcjonalność ;-). U nich już jutrzejszy poranek, a w Polsce pora iść spać. Dobrej nocy!

March 8, 2009

Wzorzec Visitor - realny przykład

Wizytator (odwiedzający) jest bardzo ciekawym wzorcem projektowym. Ponieważ wytłumaczenie w dwóch zdaniach do czego ten wzorzec służy jest dość trudne, zacznijmy od przykładu.

Załóżmy, że w naszym systemie przechowujemy informacje o klientach różnego typu. Każdemu typowi odpowiada jedna klasa dziedzicząca po klasie Customer (z punktu widzenia wzorca może to też być klasa abstrakcyjna czy nawet interfejs): NormalCustomer, VipCustomer, GroupCustomer. Jedną z funkcji systemu ma być generowanie listu powitalnego dla każdego klienta, przy czym list ten ma się znacząco różnić w zależności od typu klienta. Zadanie wydaje się trywialne: 


public class Customer {
 //...
 public abstract Letter generateInvitationLetter();
 //...
 }


...i wystarczy zaimplementować odpowiednią metodę w każdej z klas reprezentujących typ klienta. Polimorfizm, głupcze! - chciałoby się sparafrazować Billa Clintona. Ale czy na pewno? Za chwilę zauważymy, że każda z tych metod wymaga do pracy komponentu LetterService, dostępu do bazy danych poprzez CustomerDao i komunikacji ze starym systemem księgowym za pomocą AccountantSystemFacade... I rodzi się problem. Wstrzykiwać te usługi do obiektu dziedziny? Ani Spring, ani EJB3 tak nie pracuje. Przekazywać je jako argument generateInvitationLetter()? Kiepsko - jeśli jedna z klas dziedziczących potrzebuje dodatkowo jeszcze komponentu, trzeba go przekazać wszystkim i zmienić interfejs wszystkich klas...

Ale, pomysłowy programista szybko znajdzie rozwiązanie. Gdzieś, pewnie w komponencie LetterService, doda metodę generateInvitationLetter(Customer customer). Ponieważ LetterService jest usługą, można do niego wstrzyknąć inne potrzebne komponenty. I co programista w tej metodzie napisze?


public Letter generateInvitationLetter(Customer customer){
 if(customer instanceof NormalCustomer)
  return generateForNormalCustomer((NormalCustomer)customer);
 if(customer instanceof VipCustomer)
  return generateForVipCustomer((VipCustomer)customer);
 if(customer instanceof GroupCustomer)
  return generateForGroupCustomer((GroupCustomer)customer);
 throw new IllegalArgumentException("Unknown Customer type: " + customer.getClass().getName());
}


Gdybym miewał koszmary z najgorzej napisanymi fragmentami kodu w Javie, ten gościłby w moich snach co najmniej raz w tygodniu. Oszczędzę moim Czytelnikom wyjaśniania, co w tej konstrukcji jest nie tak*. Nawiasem mówiąc i tak nie jest tragicznie, bo programista mógłby zwracać null "bo coś trzeba" i potem tropić NPE zupełnie w innym miejscu w kodzie. A zatem co pozostaje? Oczywiście wzorzec, do którego zmierzam.

Najpierw musimy stworzyć specjalną metodę accept() w klasie bazowej. W oryginale nic ona nie zwraca, wprowadzenie generycznego typu T jest moim wkładem w rozwój wzorców projektowych. Cóż, w ósmej klasie ówczesnej podstawówki wymyśliłem sortowanie bąbelkowe, może tym razem będę jednak pierwszy :-).


public class Customer {
 //...
 public abstract <T> T accept(CustomerVisitor<T> visitor);
 //...
}


Czyli abstrakcyjna metoda przyjmująca nieznany nam jeszcze obiekt CustomerVisitor, parametryzowany typem jednocześnie zwracanym przez metodę accept(). Chyba troszkę odlatujemy, dlatego szybciutko przedstawiam implementację tej metody w:


public <T> T accept(CustomerVisitor<T> visitor){
 return visitor.visit(this);
}


Dodam, że w KAŻDEJ klasie dziedziczącej po Customer implementacja jest taka sama! Co?!? Nie do końca, zauważcie, że w każdej klasie referencja this ma typ właściwy dla tej klasy, a nie typ klasy bazowej Customer. Pora odkryć wszystkie karty:


public interface CustomerVisitor<T> {
 T visit(NormalCustomer customer);
 T visit(VipCustomer customer);
 T visit(GroupCustomer customer);
}


Jak wspomniałem referencja this ma zawsze odpowiedni typ: NormalCustomer , VipCustomer lub GroupCustomer . Już na etapie kompilacji można zatem określić, którą z przeciążonych wersji metody visit() należy wywołać w accept(). Ot, cała magia! To troszkę tak, jakbyśmy przekazali komponent LetterService abstrakcyjnej metodzie generateInvitationLetter() w klasie Customer, a następnie pozwolili wywołaniu polimorficznemu w odpowiedniej klasie dziedziczącej samemu wybrać, którą metodę tego komponentu wywołać.

A zatem jak Visitor pomaga nam w rozwiązaniu naszego oryginalnego problemu?


public class InvitationLetterGeneratorVisitor implements CustomerVisitor<Letter> {
 Letter visit(NormalCustomer customer) {/*...*/}
 Letter visit(VipCustomer customer) {/*...*/}
 Letter visit(GroupCustomer customer) {/*...*/}
 }


i wywołanie:


Customer customer = //...
Letter letter = customer.accept(invitationLetterGeneratorVisitor );


Tak naprawdę nie potrzebujemy osobnej klasy InvitationLetterGeneratorVisitor, przecież komponent LetterService może dodatkowo implementować CustomerVisitor<Letter>! I wtedy dla wygody dodajemy metodę w LetterService:


public Letter generateInvitationLetter(Customer customer){
 return customer.accept(this);
}


Zwróćcie uwagę, że nie przekazujemy explicite komponentu LetterService, a jedynie obiekt implementujący CustomerVisitor<Letter>.

Dlaczego takie podejście jest wygodne? Załóżmy, że nasz system ma teraz generować również listy z wyciągami z konta, przydzielać klientom promocje w zależności od historii ich zakupów oraz oceniać ich zdolność kredytową. Naturalnie wszystko w zależności od typu klienta. Oceńcie, ile czasu zajmie (i jak eleganckie będzie):
- dorobienie odpowiednich metod przyjmujących wszystkie potrzebne usługi jako argumenty w klasie Customer i klasach dziedziczących, 
- dopisanie metod w odpowiednich komponentach z powtarzającymi się kaskadami if-instanceof,
- napisanie klas implementujących CustomerVisitor dla każdego z przypadków

A jak kosztowne jest dodanie nowego typu klienta? W obu przypadkach to spory problem. Jednak co wolicie - szukać w kodzie wszystkich wystąpień wodospadów if-instanceof (lub czekać, aż otrzymacie odpowiedni wyjątek w działającej aplikacji) - czy grzecznie dopisać do wszystkich napisanych wizytatorów jedną metodę (bez tego kompilator nie będzie zadowolony).

Mam nadzieję, że zachęciłem Was do skorzystania z tego wzorca. Pisałem ostatnio aplikację gdzie hierarchie dziedziczenia były długie i szerokie i wzorzec Visitor znakomicie ułatwił programowanie.

* Zwracanie się do rozmówcy per "każdy wie, że to rozwiązanie jest złe, nie trzeba tego tłumaczyć" z reguły oznacza, że wypowiadający nie ma żadnych argumentów, ale nauczył się bardzo brzydkiego chwytu erystycznego. Zrobiłem to nieumyślnie, wybaczcie, zatem tłumaczę się na potrzeby polemiki:
- konstrukcje switch, wielokrotne warunki na tej samej zmiennej, etc. prawie zawsze sugerują użycie polimorfizmu. My też spróbowaliśmy...
- instanceof i rzutowanie w dół (zwłaszcza na taką skalę) trudno nazwać eleganckim rozwiązaniem w duchu OOP
- łatwo pominąć jakiś typ w serii warunków, zwłaszcza, gdy lista typów zwiększa się po dłuższym czasie. Poza tym jeśli nasza hierarchia jest dłuższa, sprawdzenie najpierw typu nadrzędnego a potem podrzędnego będzie trudnym do wykrycia błędem.
- po prostu źle wygląda ;-)