wtorek, 21 kwietnia 2009

Richfaces VS Icefaces

Ten artykuł był w sumie jednym z pierwszych które chciałem napisać. Było to spowodowane dylematem który miałem podczas wyboru technologii webowej do następnej aplikacji. W tamtym okresie miałem już doświadczenia z IceFaces i nie byłem do końca przekonany do tej właśnie technologii, natomiast brakowało mi doświadczeń z RichFaces, a był to framework który zapowiadał się bardzo ciekawie. Niestety, nie udało mi się znaleźć na sieci dobrego porównania tych technologii, w związku z tym postanowiłem napisać ten artykuł.

Ogółem

W ogólnym zarysie obie technologie wyglądają podobnie. Posiadają dokładnie to samo zastosowanie, podobny zakres komponentów oraz oparte są na technologii JSF. Można zauważyć pewne drobne różnice jeżeli idzie o dostępne komponenty, ale posiadają podobną ilość. Osobiście odniosłem jednak wrażenie, że IceFaces ma komponenty jednak troche lepiej dobrane i kompletne, podczas gdy w RichFaces są one odrobinę bardziej surowe, ale tak na prawdę jest to raczej subiektywne odczucie. Na podstawie pobieżnego obejrzenia nie byłem w stanie jednak jednoznacznie wybrać bądź odrzucić żadnej z technologii.

Instalacja

Instalacja IceFaces nie należała do najprzyjemniejszych. Co prawda mówię o wersji 1.6, a więc mogło się to zmienić, nie mniej taką właśnie wersję instalowałem. O ile praca odbywała się na serwerze WebLogic problemów nie było. Wszystko działało tak jak trzeba. Niestety na etapie instalacji na JBoss (4.2.2) instalacja nawet przykładowej aplikacji nie była możliwa do wykonania. IceFaces zawiera wbudowaną wersję JSF. Jest to również prawda w przypadku serwera JBoss. Jak można się łatwo domyślić wersje te nie były zgodne i nie chciały ze sobą współpracować. Po wielu próbach rozwiązania problemu, włącznie z postami na forum IceFaces z prośbą o pomoc poddałem się. Dwa miesiące później na forum pojawiły się jakieś pomysły rozwiązań, ale jeżeli mam być szczery, to było to obejście problemu (na poziomie serwera) a nie jego rozwiązanie.

Instalacja RichFaces była stosunkowo prosta. Jedyne co dodatkowo musiałem zrobić, to zainstalować implementacje JSF, ponieważ sam framework był dostarczony bez. Po zainstalowaniu wszystko działało zarówno na serwerze JBoss jak i na WebLogic.

Ajax

Największą zaletą obu frameworków jest obsługa AJAX. Oba podchodzą do problemu trochę inaczej. W przypadku IceFaces Ajax jest wbudowany w komponenty i w zasadzie przezroczysty dla użytkownika. Kontrola nad nim sprowadza sie do kilku ustawień w web.xml oraz parametrach w komponentach. W przypadku RichFaces Ajax jest bardzo widoczny. Jest to rozwiązane za pomocą całej serii dodatkowych komponentów (framework Ajax 4 Java (a4j)). Ten dodatkowy framework jest odpowiedzialny za całą komunikację z serwerem. Niestety oznacza to również, że programista musi napisać dodatkowy kod aby ajax działał w przeciwieństwie do IceFaces, gdzie proces zachodzi automatycznie. Wykorzystanie tego dodatkowego frameworku pozwala jednak na wykorzystanie standardowych komponentów JSF i dodanie do nich obsługi AJAX bez konieczności zastępowania ich. Takie podejście sprawia, że można w łatwy sposób dodać obsługę AJAX do już istniejącej aplikacji. Oba frameworki prezentują również troche inne podejście do odświeżania stanu aplikacji. Oba pozwalają na odpytywanie w celu sprawdzenia czy są nowe dane. Jednak, jeżeli idzie o push, to rozwiązanie zastosowane w obu frameworkach jest nieco inne. W przypadku IceFaces połączenie jest inicjowane ze strony serwera. W przypadku gdy pojawią się jakieś dane, serwer (który utrzymuje cały czas połączenie) wysyła do klienta nowe dane. W przypadku RichFaces klient co jakiś czas odpytuje serwera czy są jakieś dane. Jeżeli serwer przygotował jakąś paczkę danych, to jest ona pobierana. Warto wspomnieć, że RichFaces nie utrzymuje stałego połączenia z serwerem w przeciwieństwie do IceFaces. Osobiście miałem sporo problemów z tym utrzymywanym połączeniem. IceFaces zakładało, że sieć jest idealna, i takie połączenie nie zostanie zerwane. W przypadku zerwania aplikacja nie zachowywała się dobrze. Miało to zostać naprawione w późniejszych wersjach (i jest duża szansa, że obecnie problem już nie występuje).

Problemy

W przypadku obu frameworków natrafiłem na kilka problemów.

IceFaces
Ponieważ sprawa dotyczy starej wersji frameworku myślę, że nie ma sensu opisywać konkretnych problemów z samym frameworkiem, natomiast postaram się oddać zakres problemów z jakim się spotkałem. Po pierwsze były problemy z konkretnymi komponentami, ale były to raczej niewielkie problemy i były raczej uciążliwe, niż uniemożliwiające prace. Rozwiązanie problemów albo było w repozytorium kodu, albo było planowane do wypuszczenia z następną wersją. Niestety problem na który natrafiłem ja był planowany z następną wersją. Mówię niestety, bo o ile po zmianie wersji problem zniknął, o tyle pojawił się inny dużo poważniejszy w komponencie który wcześniej działał prawidłowo. Sama zmiana wersji (z wersji 1.5.x na 1.6) nie przebiegała w sposób prosty i bezproblemowy. Ponieważ zostało zmienione całkowicie nazewnictwo klas w stylach aplikacja straciła cały wygląd. Wymagało to ręcznej zmiany wszystkich styli na docelowe. W wersji 1.6.1 dodane zostało narzędzie do automatycznej migracji styli, ale niestety było to już dłuższy czas po tym, jak style zostały dostosowane. Może to tylko moje odczucia, ale bardzo mi się nie podobało taki załatwienie sprawy.
Dodanie własnego komponentu pracującego razem z AJAX wymagało sporo wysiłku ze strony programisty. Wszelkie niestandardowe rozwiązania, nie przewidziane przez programistów tworzących Framework były dość trudne do zaimplementowania, ponieważ sam framework stanowi dość zamkniętą całość.
Community jest dość rozbudowane i często chętne do pomocy, ale jednak na odpowiedź trzeba trochę poczekać, szczególnie jeżeli problem jest bardziej złożony.
Dokumentacja do projektu jest dość dobra, jest sporo przykładów.

RichFaces
Problemy z samym frameworkiem były podobne do tych z IceFaces. Pojedyńcze elementy nie do końca dobrze funkcjonowały, natomiast znalezienie rozwiązania było znacznie łatwiejsze. Główne problemy natomiast, były nieco innej natury. Do IceFaces można było znaleźć pokaźną ilość przykładów czy HOWTO. Z RichFaces już nie było tak lekko. Ilość przykładów dostarczonych z aplikacją była bardzo skąpa a przykładowy kod demonstrowany w Component Showcase przeklejony żywcem nie zawsze działał. Po analizie kodu źródłowego okazywało się, że źródłowy zawarty na stronie nie posiadał paru elementów koniecznych do działania elementu do którego się odwoływał. Dojście do tego jak dany komponent działał, wymagało przejrzenia kodu źródłowego samej aplikacji demonstracyjnej. Community jest również znacznie mniejsze i trudniej jest uzyskać pomoc w przypadku problemów.

Podsumowanie

Oba frameworki są oprogramowaniem dobrym i stanowią ciekawą ofertę. RichFaces jest napisany tak, że pozwala na zaimplementowanie bardziej specyficznych rozwiązań dużo niższym kosztem, bo kod jest tak pomyślany, że dodanie obsługi Ajax jest proste a same komponenty w większości są standardową implementacją JSF (Sun lub MyFaces). Niestety, to samo rozwiązanie powoduje, że ta dodatkowa kontrola wymaga pisania dodatkowego kodu, a więc dla aplikacji która nie stosuje niestandardowych rozwiązań i liczy się czas, IceFaces jest dużo lepszym rozwiązaniem. Dodatkowo IceFaces jest znacznie lepszy jeżeli idzie o poznanie samego frameworku. Dokumentacja, dobre i duże community sprawia ze ten framework zdobywa coraz większe uznanie. Ja osobiście jednak bardziej polubiłem RichFaces. Miałem z nim znacznie mniej problemów a jednocześnie daje mi dużo większą kontrolę nad tym co się dzieje w aplikacji, co mi osobiście bardzo odpowiada. W przypadku IceFaces czas który został zaoszczędzony na dodatkowym kodzie, został wykorzystany na pisanie łatek do niedziałającego kodu samego frameworku, lub poprawianie nowych problemów po wgraniu kolejnej wersji poprawiającej inne błędy.

Jeżeli miał bym wybierać framework oparty o JSF do następnego projektu to skłaniał bym się bardziej w kierunku RichFaces, jako bardziej dopracowanego i dającego większą swobodę.

środa, 15 kwietnia 2009

GWT oraz Session Beans

Uwaga do tego artykułu.
Kod zawarty w tym wpisie w części nie jest kodem napisanym przezemnie. Sam pomysł został zaczerpnięty z grup googlowych, ja postanowiłem, że pomysł jest na tyle ciekawy, że warto go przedstawić.

Standardowo GWT nie jest zawarte w transakcji. Ten stan raczej nie jest do końca wygodny, szczególnie jeżeli odbywa się jakieś przetwarzanie po stronie serwera. Standardowa struktura aplikacji jest następująca

  • Klient (przeglądarka)
  • Serwer (GWT)
  • Serwer (EJB) (logika biznesowa)
  • DB
Oczywiście ta struktura może być różna, w zależności od aplikacji, ale generalnie wygląda podobnie. Problem polega na tym, że na poziomie Server(GWT) dane nie są objęte transakcjami. Okazuje się jednak, że przy pomocy stosunkowo prostego zabiegu da się tą warstwę całkowicie pominąć.

Standardowo aplikacja GWT wymaga zdefiniowania 2 interfejsów oraz ich implementacji. Interfejsy są zrobione standardowo

Service:

public interface SmapleService extends RemoteService {
// Sample interface method of remote interface
String getMessage(String msg);

/**
* Utility/Convenience class.
* Use SmapleService.App.getInstance () to access static instance of SmapleServiceAsync
*/
public static class App {
private static SmapleServiceAsync app = null;

public static synchronized SmapleServiceAsync getInstance() {
if (app == null) {
app = (SmapleServiceAsync) GWT.create(SmapleService.class);
((ServiceDefTarget) app).setServiceEntryPoint(GWT.getModuleBaseURL() +
"smaple.Smaple/SmapleService");
}
return app;
}
}
}


ServiceAsync:

public interface SmapleServiceAsync {
void getMessage(String msg, AsyncCallback async);
}


Standardowa implementacja wygląda tak:
 
public class SmapleServiceImpl
extends RemoteServiceServlet
implements SmapleService {
// Implementation of sample interface method
public String getMessage(String msg) {
return "Client said: \"" + msg + "\"
Server answered: \"Hi!\"";
}
}


Ten kod niestety nie pozwala na transakcyjność. Wystarczy jednak zrobić kilka niewielkich zmian. Po pierwsze klasa SampleServiceImpl nie powinna implementować interfejsu SampleService. Po drugie, należy przeciążyć metodę processCall odpowiedzialną za obsługę przychodzących wywołąń tak, aby wywoływała metody z SSB.


public class SampleServiceImpl
extends RemoteServiceServlet {
private static final Logger log = Logger.getLogger(SampleServiceImpl.class);

public String processCall(String payload) throws SerializationException {
try {
Object bean = getBean();
synchronized (bean) {
RPCRequest rpcRequest = RPC.decodeRequest(payload, bean.getClass());
log.debug("RPC METHOD CALL: " +
rpcRequest.getMethod() +
" PARAMETERS: " +
Arrays.toString(rpcRequest.getParameters()));
return RPC.invokeAndEncodeResponse(bean, rpcRequest.getMethod(),
rpcRequest.getParameters());
}

} catch (IncompatibleRemoteServiceException ex) {
ex.printStackTrace();
return RPC.encodeResponseForFailure(null, ex);
} catch (NamingException ne) {
ne.printStackTrace();
return RPC.encodeResponseForFailure(null, ne);
}
}

public ISampleBeanService getBean() throws NamingException {
final HttpSession httpSession = getThreadLocalRequest().getSession();
ISampleService bean = (ISampleBeanService) httpSession.getAttribute("SampleBeanService");

if (bean != null) try {
// Call a method to test whether the bean is still live;
// if it's dead, we'll re-create it
synchronized (bean) {
bean.isAlive();
log.debug("Bean alive");
}
} catch (NoSuchEJBException ex) {
log.debug("Bean dead");
bean = null;
httpSession.removeAttribute("SampleBeanService");
}

if (bean == null) {
InitialContext ic = new InitialContext();
Object objRef = ic.lookup("sample/SampleBeanService/local");
bean = (ISampleBeanService) PortableRemoteObject.narrow(objRef,
ISampleBeanService.class);
httpSession.setAttribute("SampleBeanService", bean);
}

return bean;
}


Warto zwrocic uwage, ze nie zgadzaja sie nazwy interfejsow. Bean korzysta z interfejsu ISampleBeanService podczas gdy GWT korzysta z SampleService. To nie jest błąd.


public interface ISampleBeanService extends SampleService {
public boolean isAlive();
}


Ten interfejs jest pomocniczy implementuje tylko metodę isAlive(), wykorzystywaną do sprawdzenia czy SSB zostało stworzone, oraz czy jest nadal dostępne.

Aby całość działała brakuje już tylko samego SSB:


@Stateless(name = "SampleBeanService")
@Local(ISampleBeanService.class)
public class SampleService implements ISampleBeanService {
// Implementation of sample interface method
public String getMessage(String msg) {
return "Client said: \"" + msg + "\"
Server answered: \"Hi!\"";
}
public boolean isAlive() {
return true;
}

}


Bean jak widać jest standardowym beanem. Nie wymaga niczego nadzwyczajnego.

Napotkałem na dwa problemy przy implementacji tego rozwiązania.
  • Problem z sesją. Na dobrą sprawę, jeżeli użyjemy tego rozwiązania pojawia się problem z przechowywaniem danych. Standardowo w GWT dane wrzucane są do sesji HTTP. Niestety w przypadku Statless bean z założenia dane nie są w nim trzymane, a z poziomu SSB nie ma dostępu do Sesji HTTP. Rozwiązaniem jakie się w sumie samo nasuwa są Statefull Session Bean, które w zasadzie właśnie do tego celu były przewidziane.
  • Problem z kompilacją modułów (w Idei). Chodzi o zależności między modułami. W tym przypadku SSB musi być w module WEB, a to nie jest dobre rozwiązanie. Na szczęście znalazłem inne rozwiązanie, które zostało opisane w osobnym wpisie w tym blogu.