poniedziałek, 30 stycznia 2012

Fantomowe tabelki w JSF

W niniejszym poście przyjrzymy się pewnej anomalii występującej w JSF, której skutki potencjalnie mogą być katastrofalne.
Anomalia została dobrze rozpoznana dosyć dawno, jednak jak pokazuje moje doświadczenie w pracy z zespołami korzystającymi z JSF, nie jest ona uświadomiona.
Dlatego dla tych z czytelników, którzy używają JSF i nie spotkali się ze zjawiskiem "klikania w nieodpowiednie wiersze tabelki" lektura posta jest obowiązkowa:)

Problematyczny scenariusz:
1. Użytkownik A wyświetla na stronie tabelkę z listą rekordów (Encje JPA lub DTO)
Na ekranie dla każdego rekordu mamy możliwość jego edycji/usunięcia poprzez Postback.

2. Podczas gdy użytkownik A delektuje się widokiem zaokrąglonych rogów na naszej stronie, użytkownik B (w tak zwanym międzyczasie) dokonuje w bazie zmiany danych, które są prezentowane na ekranie użytkownika A.
Może być to usunięcie danych lub edycja takich atrybutów, które wpłyną na ilość lub kolejność danych zwracanych przez zapytanie, które używa ekran użytkownika A.

3. Użytkownik A, po zaspokojeniu swych potrzeb estetycznych, klika przykładowo w pierwszy wiersz tabelki w celu usunięcia/modyfikacji rekordu.

4. Ku zdziwieniu użytkownika A, system poddał usunięciu/edycji zupełnie inny rekord niż zamierzony.

Zjawisko to pozwoliłem sobie nazwać Fantomową tabelką - ot jako żarcik techniczny będący paralelą (trudne słowo) do Anomalii Transakcji.

Przykład kodu

Managed Bean:
@ManagedBean()
public class UsersControler{ 
 
 @ManagedProperty("#{userFinder}")
 private UserFinder userFinder;
 
 @ManagedProperty("#{userManagement}")
 private UserManagement userManagement;
 
        //Bean o zasięgu sesji pamiętający nasze kryteria wyszukiwania
 @ManagedProperty("#{usersSearchCriteria}")
 private UsersSearchCriteria usersSearchCriteria;

 private List<User> users;
 
 private User selected;
 
 
 @PostConstruct
 public void search(){  
  users = userFinder.findUsers(usersSearchCriteria.getFirstname(), usersSearchCriteria.getLastname(), null, null);  
 } 

 public void remove(){
  userManagement.deleteUser(selected.getId());
  //search(); - zbędne dla zasięgu Request, konieczne dla View aby odświeżyć
 }
 
 public void remove2(Long id){ 
  userManagement.deleteUser(id);
  //search(); - zbędne dla zasięgu Request, konieczne dla View aby odświeżyć
 }   
}

Powyższy ManagedBean ma domyślny zasięg Request (nie chcemy przecież obciążać stanu sesji listą użytkowników).

Nasz bean ma wstrzyknięte 2 obiekty z warstwy aplikacji: UserFinder (wyszukujący użytkowników), UserManagement (operujący na użytkownikach - w przykładzie usuwamy użytkowników, ale problem jest ogólny - generalnie chodzi o jakąkolwiek modyfikację, która zmieni resultat działania UserFinder.findUsers )

Zwróćcie uwagę, iż kryteria wyszukiwania z formularza nie przechowywane w kontrolerze (który ma zasięg Request) a we wstrzykniętym Modelu o zasięgu Sesji.



    
     
     
      imie:  nazwisko: 
       
       
       
           
       
     
   
    
     ID
     #{_u.id}
    
 
    
    
     
      
      
     
                         
    
         
                
    

Widok jest bardzo prosty: tabelka iteruje po kolekcji użytkowników. Dla każdego z nich wyświetla ID (aby organoleptycznie zaobserwować problem) oraz umożliwia usunięcie (w ogólności modyfikację stanu bazy mającą wpływ na listę) użytkownika. Technicznie mamy tutaj dwa podejścia do przekazywania "klikniętego" użytkownika na Serwer: poprzez f:setPropertyActionListener oraz dzięki wywołaniu metody z parametrem.

To czy przekazujemy ID użytkownika czy obiekt klasy User nie ma znaczenia dla eksperymentu.

Dlaczego tak się dzieje

Załóżmy, że podczas pierwszego renderowania strony do pierwszego wiersza tabelki był "podpięty" pierwszy wiersz z wynikowej listy - użytkownik o id = 1.
Załóżmy, że później - po zmianie stanu bazy - pierwszy użytkownik na liście wynikowej pobranej z bazy ma id = 2.

Silnik JSF podczas postback odtwarza drzewo komponentów graficznych. Następnie "rozsmarowuje" na tym drzewie model. Tak więc podczas postback do pierwszego wiersza tabeli może podpiąć użytkownika o id = 2.

Model zdarzeń w JSF (zgodnie z nazwą Java Server Faces) odbywa się w kontekście serwera, czyli silnik ma informację o numerze klikniętego wiersza w tabelce (nie o id klikniętego usera). Dalej na podstawie klikniętego wiersza, zbiera z tego wiersza model i ten model traktuje jako "kliknięty". Jak widać - zakłada optymistycznie, że tak jest:)

Jeżeli przy postbacku model pobrany z bazy będzie inny - trudno... :P


Dlaczego często nie widać problemu

Programiści JSF często z powodu kłopotów z ogarnięciem złożoności zasięgów życia ManagedBeanów decydują się na rozwiązania "bo działa" i rozszerzają zasięg życia niemal wszystkich ich do Sesji. W przypadku zasięgu sesji nie odtwarzamy modelu danych na podstawie bazy lecz na podstawie ViewState zatem problem z klikaniem w fantomy jest po prostu ukryty - nie występuje. Czyli przy okazji, jako skutego uboczny niechlujstwa, zabezpieczamy się przez błędem fantomowych danych:)

Jeżeli zależy nam na zasobach lub świeżości danych, wówczas musimy wysilić się na rezygnację z Sesji gdzie tylko jest to możliwe i zaczyna się zabawa...

Rozwiązania

1. Zasięg View/Session
Przechowywanie list w Sesji to zwykle słaby pomysł, ale możemy zdecydować się na ich przechowywanie w zasięgu View, który trwa dopóki znajdujemy się na tej samej stronie (GET zrywa ten zasięg).
@ManagedBean()
@ViewScoped
public class UsersControler{

Można pokusić się o wydzielenie samego modelu prezentacji do osobnego ManagedBeana o zasięgu View tak aby nie przechowywać tam całego kontrolera, który jest zbędny i powinien żyć w Request. Wówczas wstrzykujemy model do kontrolera, napełniamy go w kontrolerze, po czym kontroler może już "umrzeć".


2. Ukryte Pole

Jeżeli nie możemy pozwolić sobie na przechowywanie stanu widoku, wówczas pewnym rozwiązaniem, jest rezygnacja z mechanizmów JSF do wskazywania klikniętego wiersza. Nasze buttony powinny przy pomocy JS ustawiać ukryte pole formularza na wartość id modelu wiersza i submitować to pole na managedbeana.


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

Po raz kolejny mamy do czynienia z sytuacją, gdzie próba ukrycia złożoności esencjalnej skutkuje wyciekiem jeszcze większej ilości złożoności przypadkowej.
"...życia nie oszukasz."

wtorek, 3 stycznia 2012

Dopamina Driven Development

Jaki wpływ na tworzenie kodu mogą mieć wytryski dopaminy?
Co wspólnego ma nikotyna z TDD?
Dowiecie się w 22. minucie i 30. sekundzie prezentacji Things I Wish I'd Known od samego twórcy Springa - Roda Johnsona:)


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

Generalnie bardzo polecam obejrzenie całej prezentacji, ale uprzedzam - nie ma w niej ani linijki kodu:)

piątek, 16 grudnia 2011

Skręt kości promieniowej i łokciowej, czyli rzecz o myszkach

Awaria mojej głównej myszki (czyli tej której używam do codziennej pracy, w odróżnieniu od specjalnej do gry ze znajomymi w CoD) skłoniła mnie do napisania kolejnego posta na temat Sprzętu.

Dawno, dawno temu, jako początkujący użytkownik peceta byłem przekonany, że jego głównym elementem, decydującym o ogólnym "ekspiriens" jest procesor i akcelerowana karta graficzna:) "Dziś sam jestem dziadkiem" i pytany przez nietechnicznych znajomych oraz nietechniczną rodzinę doradzam, że najważniejsze elementy zestawu to: krzesło, klawiatura, myszka i matryca (w sensie jej jakości) - reszta zależy już tylko od budżetu:)


Warto wiedzieć, że klasyczne "płaskie" myszki wymuszają nienaturalne (skręcone) ułożenie kończyny górnej i prowadzą do:
  • Skrętu wzajemnego położenia kości promieniowej i łokciowej w naszym przedramieniu,
  • Naprężeniu mięśni przedramienia (wynika z powyższego)
  • Ucisku na naczynia krwionośne (wynika z obu powyższych)

Jeżeli się nad tym chwilę nie zastanowić, to ułożenie dłoni na płaskim blacie tak aby do niego przylegała wydaje się normalne. Ale spróbujcie układać dłoń na zmianę na blacie: w pozycji "do spoliczkowania" i w pozycji "płaskiej", aby powoli zacząć uświadamiać sobie co się dzieje z Waszymi kośćmi i mięśniami.

Wielu z nas skarży się na bóle w okolicach nadgarstka. Ja sam w pewnym momencie miałem problem z utrzymaniem kierownicy w prawej ręce podczas powrotu z pracy do domu.
Jako rozwiązanie polecam tego typu konstrukcje.
Osobiście od ok 5 lat używam modelu Vertical Grip i w tym momencie każde zetknięcie mojej odzwyczajonej od tortur ręki z "płaską" myszką skłania do refleksji: ale po co?

środa, 14 grudnia 2011

DDD na platformie Java EE 6

Dzisiaj coś dla miłośników Enterprise Edition.

Nieustannie rozwijamy projekt ilustrujący implementację zaawansowanych technik Domain Driven Design i architektury Command-query Responsibility Segregation.
Dotychczasowa implementacja oparta na Springu doczekała się swego lustrzanego odbicia na platformie Java EE6
W projekcie znajdziecie między innymi kilka sztuczek w CDI oraz przykłady wykorzystania silnika zdarzeń w modelowaniu biznesowym (dowiecie się również dlaczego budowany Events<> jest nieco ułomny:).

Więcej szczegółów na blogu projektu.

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

W najbliższych planach aplikacja kliencka na Androidzie w architekturze Eventually Connected Client.

środa, 30 listopada 2011

Geneza boskich klas

Zarówno po dzisiejszej konferencji softdevcon jak i zeszłotygodniowej JDD kilka osób prosiło mnie o powtórzenie tekstu o "boskich klasach", którym staram się rozbudzić uczestników prezentacji o DDD. Postanowiłem nieco rozszerzyć opowiastkę...



Narodziny

Każda boska klasa ma swój początek jako niewinny ośmiotysięcznik - klasa zawierająca osiem tysięcy linijek kodu.



Eskalacja

Zmiany w ośmiotysięczniku wymagają specjalnej wyprawy. Śmiałek wspina się przez kilka dni na wysokość ok piątego tysiąca próbując zrozumieć logikę/ironię kodu po czym zakłada tam bazę - rozgrzebuje kod kilkoma enterami aby zrobić sobie nieco miejsca, w którym to następnego dnia (bo aktualnie opada z wycieńczenia i niedoboru podstawowych neuroprzekaźników w mózgu) zacznie drążyć tunelik jako fundament dla nadbudówek.

Poczynania śmiałka powodują oczywiście niejednokrotnie lawiny, które rozrywają na dole i tak już nieszczelne siatki bezpieczeństwa testów.

Kolaps

Z czasem ośmiotysięcznik rozrasta się aby w końcu zapaść się grawitacyjnie pod własnym ciężarem. Powstaje wówczas osobliwość, z której wyłania się Boska Klasa - klasa, która wszystko wie, wszystko potrafi i jest połączona ze wszystkim w każdym możliwym wymiarze.

Boska klasa aby istnieć potrzebuje ofiar. Najlepsze są młode, niewinne praktykantki. W swej naiwności opartej na akademickich wierzeniach we wszechmoc Maszyny Turinga składają się w ofierze na ołtarzu Boskiej Klasy...

Z czasem udaje im się wyrwać z grawitacji osobliwości, ale już nigdy nie będą takie jak wcześniej...

wtorek, 1 listopada 2011

DDD must read

Od dłuższego czasu nosiłem się z zamiarem napisania posta poświęconego modelowaniu agregatów - różnym podejściom i wynikającym z nich konsekwencjom.
Właściwie temat nie jest związany ze specyfiką DDD, a ogólnie z modelowaniem obiektów biznesowych z zachowaniem ich enkapsulacji, które dzięki niej są bardziej odporne na zmiany.

Skoro jednak w sieci istnieją już materiały z dobrą ilustracją graficzną oraz przykładami w kodzie, to oszczędzę sobie pisania i pozwolę polecić ich lekturę każdemu kto zajmuje się modelowanie lub projektowaniem obiektowym (heh kto z czytelników się nie zajmuje "przy okazji" innych spraw?):

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

Tak jak pisałem wcześniej tematyka jest ogólna, nie związana stricte z DDD i lektura powinna być pożyteczna dla każdego.

niedziela, 9 października 2011

Konferencja Warsjawa - kolejny warsztat DDD&CqRS Leaven

Zapraszam na konferencję Warsjawa, która odbędzie się w najbliższą sobotę (15 października) w Warszawie.

Warto dodać, że konferencja jest całkowicie darmowa:)

Podczas konferencji będziecie mogli uczestniczyć między innymi w naszych warsztatach, poświęconych projektowi DDD&CqRS Leaven.

Szczegółowy program warsztatów: https://github.com/warszawajug/warsjawa2011/wiki/Warsztaty-DDD-i-CqRS.