niedziela, 29 listopada 2009

Seam == JSF + EJB ?

WSTĘP

Framework Seam opiera się na bardzo silnym mechanizmie - bijekcji.

Bijekcja to nowa jakość w technice Dependency Injection, która pozwala na budowanie bardziej naturalnych konstrukcji Inversion on Control.

/**
 * Na marginesie dodam, że DI i IoC to nie jest to samo.
 * Wyjaśnienie różnic w tym przydługim poście:
 * Wprowadzenie do wstrzykiwania zależności i Springa zarazem
 */

Bijekcja w skrócie przedstawia się następująco:
- podczas wywołania metody
- następuje wstrzyknięcie zależności do obiektu, którego metoda jest wołana (pola adnotowane @In)
- metoda jest wykonana
- następuje wystrzyknięcie obiektów do kontekstu Seam (pola adnotowane @Out)
- następuje czyszczenie (nulllowanie) wstrzykniętych zależności

Od strony technicznej odbywa się to w Seam dzięki specjalnym interceptorom. Przechwytują one wywołania metod wszystkich komponentów i wokół tych wywołań dodają całą opisaną powyżej magię.

Koncepcja jest jak już napisałem piękna, ponieważ pozwala na budowanie dużo bardziej naturalnych konstrukcji. Wstrzyknięcia następują per wywołanie metody a nie jedynie raz, po stworzeniu komponentu - zaspawane na wieki wieków (albo przynajmniej do restartu servera z powodu zwisu aplikacji;)
Dodatkowo niemal bez ograniczeń możemy wstrzykiwać w siebie komponenty o różnych zasięgach - nie ma ograniczenia szerszy zasięg w węższy. Dlatego, że wstrzyknięcie następuje jedynie na czas wywołania metody i nie będziemy mieli nigdy problemu z nieświeżą instancją komponentu.


PROBLEM

Twórcy frameworka poszli jeszcze dalej...
Umożliwiają używanie Sesyjnych EJB wprost w JSF - bez potrzeby pośredniej warstwy Managed Beanów (lub ich specyficznej odmiany Backing Beanów). Czyli komponenty Seam, które są jednocześnie komponentami EJB będą widoczne w kontekście JSF.
Managed Beany są podobno zbędą warstwą, która tylko przeszkadza. Możliwość wołania EJB z JSF jest rzekomo tryumfem rozumu na platformą korporacyjną.

Hmmm w bardzo prostych systemach klasy "Przeglądarka Bazy Danych" rzeczywiście można by się obyć bez MB, ale w niniejszym poście przedstawię do jakich kuriozalnych konstruktów dochodzi gdy w nietrywialnych przypadkach wiążemy JSF wprost z EJB.

Na wstępie zaznaczam, że na potrzeby niniejszych rozważań pomijamy aspekty warstwowej architektury, elementarnych zasad projektowania mówiących o kohezji klasy i temu podobnych staromodnych ograniczeniach. Skupiamy się na "produktywnym" kodowaniu na wyścigi rodem z najlepszych tutoriali i książek.

Zaczynamy.
Przykład prosty, klasyczny ekran prezentujący listę czegoś.
Wymagania:
Po ordynarnym wejściu na stronę przez GET chcemy aby lista wyświetlała wszystkie dane.
Widok ma pozwalać również na wyszukanie czegoś po jakimś atrybucie - czyli wpisanie szukanego słowa w pole tekstowe i naciśnięcie buttonu z zaokrąglonymi rogami "Szukaj" (POST gwoli ścisłości).




Na początek implementujemy pierwsze wymaganie: po wejściu na stronę widzimy listę wszystkiego...

Strzępek kodu widoku:

<h:dataTable value="#{listaCzegos}" var="_cos">
<h:column>#{_cos.nazwa}</h:column>
...
</h:dataTable>


Stateless Session Bean, który dostarcza danych dla widoku:


@Stateless
@Name("cosProwajder")
public class CosProwajderBean implements CosProwajderLocal{
@Out
private List listaCzegos;

@Factory("listaCzegos")
public void initListaCzegos(){
listaCzegos = //pobranie danych
}
}


Co się tutaj dzieje: JSF rząda komponentu listaCzegos. Seam widzi, że nie istnieje on w kontekście, ale na szczęście znalazł ochotnika, który go sfabrykuje - metodę otagowaną adnotacją @Factory("listaCzegos"). Metoda zostaje wywołana, metoda ustawia pole prywatne, a ponieważ pole jest adnotowane @Out to po chwili jest wystrzykiwane do kontekstu. Dzięki temu JSF "widzi" listę i może ją już teraz spokojnie renderować w tabelce.

Kod Session Beana mógłby równie dobrze wyglądać tak:

@Stateless
@Name("cosProwajder")
public class CosProwajderBean implements CosProwajderLocal{

@Factory("listaCzegos")
public List initListaCzegos(){
listaCzegos = //pobranie danych
return listaCzegos;
}
}


Ale już śpieszę wyjaśnić skąd poprzednia konstrukcja. Mam już w zamyśle spełnienie drugie wymagania - funkcjonalności wyszukiwania. Zatem na widoku pojawi się pole tekstowe i button:


<h:form>
<h:inputText value="#{searchFilter.name}" />
<h:commandButton action="#{cosProwajder.search}" />
<h:form>


Nasz Session Bean dostanie metodę search, która wyszuka dane na podstawie wstrzykniętych kryteriów, następnie wynik ustawi w prywatnym polu, z którego to po chwili wartość zostanie wystrzyknięta do kontekstu Seam, skąd JSF będzie ją widział.


@Stateless
@Name("cosProwajder")
public class CosProwajderBean implements CosProwajderLocal{
@Out
private List listaCzegos;

@In
private SearchCriteria searchCriteria;

@Factory("listaCzegos")
public void initListaCzegos(){
listaCzegos = //pobranie danych
}

public void search(){
listaCzegos = //pobranie danych na podstawie searchCriteria
}
}


Jeszcze tylko dla ścisłości komponent przechowujący kryteria wyszukiwania. Zasięg PAGE aby kryteria były widoczne po przeładowaniu strony:

@Name("searchCriteria")
@Scope(PAGE)
public class SearchCriteria implements Serializable{
private String name;
//getter i setter
}



Pytanie: co jest nie tak z poniższym kodem?

@Stateless
@Name("cosProwajder")
public class CosProwajderBean implements CosProwajderLocal{
@Out
private List listaCzegos;

@In
private SearchCriteria searchCriteria;

@Factory("listaCzegos")
public void initListaCzegos(){
listaCzegos = //pobranie danych
}

public void search(){
listaCzegos = //pobranie danych na podstawie searchCriteria
}
}


Dla ułatwienia wyrzucę linijki odwracające uwagę i zaznaczę kluczowy element:
(To przecież nie jest egzamin na certyfikat - chcemy się tu dowiedzieć czegoś pożytecznego)


@Stateless //<<-------------
public class CosProwajderBean implements CosProwajderLocal{
private List listaCzegos;

private SearchCriteria searchCriteria;

public void search(){
listaCzegos = //pobranie danych na podstawie searchCriteria
}
}


Właśnie!
Niby mamy bezstanowy komponent, ale korzystamy z niego w stanowy sposób!

Wyobraźmy sobie, że nasz wspaniały komponent biznesowy jest tak genialny, że chcemy go wykorzystać jeszcze gdzieś poza JSF, np wywołać zdalnie. Musimy wówczas:
1. ustawić kryteria wyszukiwania (zakładając, że mamy setter)
2. odpalić metodę search(), która zmieni stan - tu jest ta nieszczęsna stanowość
3. odebrać wynik przez getter

Czyli JSF wymusza na nas styl "strzelania z muszkietu": załaduj i wypal.

Czy dałoby się wykorzystać mimo wszystko ten super-kod poza Seam, np przykrywając go fasadą:


@Stateless
public class CosProwajderFacadeBean implements CosProwajderFacadeRemote{
@Ejb
private CosProwajderLocal cosProwajder;
public List search(SearchCriteria searchCriteria){
cosProwajder.setSearchCriteria(searchCriteria);
cosProwajder.search();
return cosProwajder.getListaCzegos();
}
}

Niestety NIE, ponieważ nie mamy gwarancji, że kontener JEE przy każdym z 3 wywołań bezstanowego komponentu zaserwuje nam tą samą instancję!


Dlaczego ten bezsensowny kod działa w ogóle w Seam? Tak jak napisałem na wstępie - interceptory Seam. Jeden z nich przechwytuje wołanie metody search na komponencie, wstrzykuje kryteria, wykonuje metodę, wystrzykuje wynik. Ponieważ wstrzykiwanie i wystrzykiwanie nie są wywołaniem metod biznesowych bezstanowego komponentu to interceptor cały czas operuje na tej samej instancji.


ROZWIĄZANIE

Łatwo można rozwiązać problem sensowności kodu zmieniając bean bezstanowy na stanowy. Jednak wciąż mamy problem z bezsensownością logiczną. Dlaczego jakiś komponent będący de facto wrapperem dla procedury ma być stanowy?

Owszem w pewnych sytuacjach stanowość może mieć sens, np: z przyczyn wydajnościowych komponent stanowy trzyma wynik jako jakiś kursor po stronie bazy. Klient Stanowego Komponentu Sesyjnego przegląda listę wynikową po kawałku. Tę argumentację zaliczam.

Innym usprawiedliwieniem może być chęć wybrania (kliknięcia) wiersza - z technicznych powodów musi wówczas przechować listę. Innym jeszcze usprawiedliwieniem może być naiwna paginacja, która w naiwnych paginatorach działa na danych sesyjnych pobranych w całości z bazy.
Czy jednak sensowne jest dopasowywanie API komponentów biznesowych do takich szczegółów technicznych jakiś frameworków prezentacji?
Poza tym wciąż będziemy mieli kuriozalne korzystanie z niego w fasadzie - w stylu "strzelania z muszkietu": załaduj i wypal.


PRAWDZIWE ROZWIĄZANIE

Aby nasze komponenty biznesowe miały sensowny interfejs w nietrywialnych przypadkach musimy ponieść ten niesamowity trud wprowadzenia warstwy jakiś Managed Beanów - w Seam zwanych Akcjami.

Są to zwykłe POJOs, które mają API w stylu "strzelania z muszkietu" a logikę biznesową delegują do EJB:


@Name("cosControler")
public class CosControler{
@In //EJB
private CosProwajderLocal cosProwajder;

@Out
private List listaCzegos;

@In
private SearchCriteria searchCriteria;

@Factory("listaCzegos")
public void initListaCzegos(){
//używamy pustych kryteriów (w celu optymalizacji można by wynieść je do singeltona)
listaCzegos = cosProwajder.search(new SearchCriteria());
}

public void search(){
//Wywolanie EJB
listaCzegos = cosProwajder.search(searchCriteria);
}
}


Jest to również doskonałe miejsce do wstrzyknięcia np kontekstu FacesMessages, kontekstów Seam, parametrów Request, bindowanie UIComponent i innych zależności typowych dla technikaliów frameworków. W tej warstwie możemy sobie na to śmiało pozwolić i dzięki niej nie musimy brudzić EJB zależnościami od Seam i JSF.

//==========================

Sama możliwość wołania Sesyjnych EJB z JSF jest oczywiście bardzo wygodnym ficzerem i warto czasem z niej korzystać. Ale tylko wówczas gdy ma to sens i jest racjonalnie uzasadnione.

Tworzenie komponentu biznesowego, który jest "zbrukany" stylem i zależnościami pewnych technologii powoduje, że nie jest to już ani komponent ani biznesowy. Komponent - czyli pewna reużywalna część; biznesowy - czyli zajmujący się jedynie logiką biznesową.

środa, 25 listopada 2009

Geneza i ograniczenia OO

Dziś jedynie dwa linki, które uważam za godne polecenia.

Przeczytałem niedawno tekst, który lekko zachwiał moim światopoglądem: Aristotle's Error or Agile Smagile. Tekst traktuje ogólnie o historii - znajdziecie tam również rzekomą genezę Object Oriented.

Rzekomo pierwotnie wcale nie chodziło o modelowanie świata, lecz o zabawy ze stertą - co będzie gdy zamiast chwilowo przechowywać zmienne na stosie wrzucimy je na dłużej do sterty.

Teorię OO Analysis i OO Design rzekomo "dorobiono" później. Tak, tak, wiem - sam jestem w szoku podobnym do tego gdy wydedukowałem, że nie ma żadnego św Mikołaja;P

Nie wiem na ile historia o pierwocinach OO jest prawdziwa, ale tym razem wielki szacunek dla Uncle Boba za zdystansowane podejście.



Kolejny link to ciekawa prezentacja człowieka, któremu doświadczenie również pozwala zdystansować się od mainstreamu Are We There Yet?. Prezentacja pozwala uświadomić sobie ograniczenia OO oraz wyjaśnia (tym, którzy jeszcze tego nie widzą) dlaczego niektóre wydawałoby się proste języki wprowadzają wysoką złożoność (przypadkową).

Zobaczycie też jak bardzo podobne są do siebie języki, o które często spieramy się "który lepszy". Nie brakuje też aluzji do takich "przełomowych usprawnień" jak usunięcie średników:) Dowiecie się również z czego wg autora prezentacji wynika potrzeba posiadania testów:)

//=======================

Od razu ostrzegam, że treści kryjące się pod linkami są mocno filozoficzne, ale może warto oderwać się na chwilę od kodu aby później móc spojrzeć na niego inaczej...

poniedziałek, 9 listopada 2009

Czysty kod to przetestowany kod?

Clean Code to skądinąd szczytna idea, na fali której kilka osób chce się wylansować ostatnimi czasy. Przyjrzymy się jej dzisiaj nieco bliżej aby sprawdzić czy nie kryje się za nią przypadkiem merytoryczna próżnia...

Ewangelizatorem tej idei jest nasz ulubiony Wujek Dobra Rada - Uncle Bob. Polecam wszystkim jego prezentację pt Craftsmanship and Ethics.

Uncle B z pasją opowiada o profesji i etyce w naszej branży. Właściwie to przekonuje, że już najwyższy czas aby wykrystalizowały się one bo póki co jeszcze się ich nie dorobiliśmy. Tutaj zgadzam się z nim bez 2 zdań. Metodyka byle-jak-byle-działało-ale-miało-zaokrąglone-rogi-na-gui to aktualnie standard.

Jako że są to idee bardzo mi bliskie to pierwsza część prezentacji jest na prawdę budująca. Dalej Uncje B twierdzi, że drogą do profesjonalizacji i nieskazitelnego kodu jest Test Driven Development. "Nie wolno Ci napisać ani linijki produkcyjnego kodu bez napisania uprzednio testu!" grzmi wujaszek! Kropka.

Postępuj tak a w nagrodzę po śmierci będą czekać na Ciebie w niebie zastępy dziewic - do tego nimfomanek rozumiejących maszynowy kod w reprezentacji binarnej. Marzenia...



...ale chyba nie będzie tak łatwo.

TDD owszem jest techniką, która ma swoje przeznaczenie i zastosowania ale nie dajmy się zwieść, że rozwiązuje wszystkie problemy. Co najwyżej niektóre.

Sensowna krytyka tego typu rozumowania znajduje się w typ poście: It's OK Not to Write Unit Tests. Wujkowi Dobra Rada chyba tylko wydaje się, że rozprawił się z adwersarzem przy pomocy racjonalnych argumentów: Excuse me sir, What Planet is this?

O ile w pewnych sytuacjach testy mogą "sterować" developmentem to nie zapominajmy o technikach, które rzeczywiście prowadzą do clean code.

Co nam po nawet 100% pokryciu kodu testami jeżeli kod będzie jedną wielką kupą...
Działającą nawet perfekcyjnie, ale wciąż jedną wielką kupą... [wstawić_swoją_ulubioną_metaforę]

Refaktoryzacja to podstawa? Co nam po wsparciu dla refaktoryzacji dzięki pokryciu kodu testami, które zwalniają nas z napięcia towarzyszącemu zmianom skoro... nie mamy pojęcia jak refaktoryzować ani w ogóle, w którą stronę refaktoryzować?

Do tego właśnie zmierzam... TDD nie daje żadnych wskazówek jakich technik developerskich użyć aby osiągnąć upragniony Clean Code. To oczywiste, że nie daje wskazówek, bo nie leży to w "domenie" TDD. Ale trzeba to sobie uświadomić i nie dajmy sobie wcisnąć kitu, że testy jednostkowe rozwiązują problem - są po prostu niewystarczające. Pomagają rozwiązać - ale rozwiązanie właściwe leży chyba gdzie indziej.

Czysty kod, aby powstał musi być wypracowany poprzez pewien wysiłek umysłowy. Nie wystarczy dorwać się do klawiatury i radośnie stukać (o tym na koniec). Na ten proces musi składać się rzetelna analiza problemu, projekt i dopiero implementacja. Nie mam tu oczywiście na myśli metodyki Waterfall, ale po prostu pewnej pracy, którą trzeba wykonać - nawet w umyśle jednej osoby.

Dla każdego z tych etapów mamy zestaw sprawdzonych technik oraz narzędzi, które oczywiście nie gwarantują sukcesu, ale zwiększają prawdopodobieństwo powstania upragnionego Clean Code.

Na poziomie analizy możemy posłużyć się Archetypami Modeli Biznesowych. Są to skatalogowane gotowe rozwiązania standardowych problemów biznesowych. Modele te zostały wypracowane przez wytrawnych analityków dzięki czemu można je dostosowywać do swych potrzeb. Są również zaprojektowane z myślą o rozbudowie modelu. Można by dużo o nich pisać, ale nic nie zastąpi przykładu. A przykład w jednym z najbliższych postów. Póki co zastanówmy się co nam po doskonale obłożonym testami modelu, który jest kupą...

Gdzieś na styku analizy i projektu mamy na przykład Domain Driven Design.

Zanim przejdziemy do poziomu projektu zatrzymajmy się przez chwilę na architekturze, będącej stylem, na podstawie którego będziemy projektować. Zastanówmy się co nam po doskonale obłożonym testami kod jeżeli architektura jest kupą...

Na poziomie projektu mamy zestaw technik np Object Oriented. GRASP i SOLID - pomagające w poprawnym interpretowaniu OO i uniknięciu proceduralnego kodu dziwacznie upchanego w klasy.
Nie zapominajmy o Wzorcach Projektowych. Kiedyś było o nich głośno, ale aktualnie młodzież woli sobie wyklikać/wygenerować trochę kodu. Ci bardziej Agile obłożą go paroma testami i jest git;)
Wzorce mogą pomagać u utrzymaniu czystej struktury kodu, ale przy ich "pomocy" można również bardzo skutecznie niepotrzebnie zagmatwać powinny pozostać prostsze - cała sztuka w wyważeniu. Ale tego oczywiście nie dowiemy się z marketingu TDD;)

Co do wzorców to planuję kiedyś stworzyć serię postów z realistycznymi (nieksiążkowymi) przykładami - ale jak zwykle czasu brak. Co do GRASP i SOLID to można znaleźć trochę dobrych przykładów - np ten.

Kiedy już wiemy co i jak to na poziomie implementacji możemy podeprzeć się techniką testów jednostkowych. Ale to czy kod będzie Clean zależy od pracy umysłowej włożonej również (albo przede wszystkim) wcześniej.

Konkludując: TDD ew. jako dodatek na drodze do Clen Code wujaszku;P


Aby uzupełnić całość warto wspomnieć o podejściu Specify First - dobry opis w poście TDD is not test-first. TDD is specify-first and test-last. Niektórzy twierdzą, że dopiero przy takim podejściu można mówić o prawdziwym TDD.
Chodzi generalnie o to, że pisząc najpierw test - nawet nie interfejs implementacji, a test - stawiamy się w roli kodu "klineta" danej klasy. Na pewno wpływa to pozytywnie na nasz design - bardzo pozytywnie.

Ale to znowu półśrodek. Technika owszem dobra, ale dla tych z nas, którzy są wciąż jeszcze na poziomie myślenia kodem.

Myślenie kodem to zupełnie normalna faza w rozwoju programisty i każdy przez nią przechodzi. Jeżeli jesteś początkujący to zapewne zauważyłeś, że gdy mówisz do kogoś ifami i pętlami to on krzywi się i zaczyna rysować jakieś prostokąty połączone strzałkami aby cokolwiek zrozumieć. Jeżeli bawisz się w to już kilka lat to zapewne irytują cię juniorzy, którzy mówią do Ciebie kodem. Normalna sprawa, abstrahowanie przychodzi z czasem ...

Zatem "Specify first" jest dobre na początek gdy świerzbią nas paluszki i chcemy jak najszybciej dorwać się do klawiatury i nastukać coś co można będzie za chwilę radośnie debugować. Analizowanie, modelowanie, projektowanie... to dobre dla zgredów.

Przy podejściu Top-Down rzeczywiście klasy "niższe" mogą być pokraczne bez wyobrażenie sobie jak będą używane. Ale top-down powinien być style analizowania problemu, natomiast projektowanie klas można przeprowadzić bottom-up dzięki czemu powstaną "komponenty" - niczym niezależne części "ruchomej machiny". Z czasem przychodzi umiejętność wyobrażania sobie i manipulowania obiektami "w pamięci" (własnej) bez potrzeby pisania czegokolwiek. Pomocy może być ew. ołówek, kartka i UML. Ktoś pamięta w ogóle co to jest UML? Nie to nie jest kolejna biblioteka do mockowania;P

Taaak.... kiedyś pewnie powstanie Unified Mocking Library - w sumie już nic mnie nie zdziwi w naszej branży...

//===============

Od razu uprzedzę ew. komentarze aby uniknąć niepotrzebnego niezrozumienia.
W tekście nie zajmuję stanowiska ZA albo PRZECIW testom jednostkowym. Moim celem było zwrócenie uwagi na fakt, że same testy niewiele wnoszą do koncepcji Clean Code. TDD jest techniką implementacyjną oraz od niedawna jest sprzedawany jako technika specyfikowania dla niecierpliwych/początkujących.

Moja teza jest następująca: aby osiągnąć Clean Code potrzeba dodatkowo kilku innych technik developerskich. Dopiero synteza całości może dać zadowalający rezultat.

I niestety trzeba się wysilić znacznie bardziej niż tylko dopisać więcej kodu - testującego. A podstawowe zagadnienia z zakresu inżynierii oprogramowania (Low Coupling, High Cohesion, IoC,...) przydają się również choćby do pisania kodu podatnego na testy - o wysokim poziomie testability.