wtorek, 15 marca 2011

Testowanie wywołania JMSTemplate

Jakiś czas temu zostałem zapytany w jaki sposób przetestować jednostkowo coś takiego:

public class SenderService {
private JmsOperations jmsTemplate;

public void doSend(final MyMessage myMessage){
jmsTemplate.send(new MessageCreator() {

public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(myMessage);
}
});
}
}

Pytanie jest dość podchwytliwe, gdyż uważam, że testy jednostkowe bardzo dobrze się sprawdzają do testowania logiki systemu. Kod powyżej nie ma jakiejś extra rozbudowanej logiki, ale jest ona dość dobrze zakopana. Poza tym wychodząc od TDD (jak przykazane) jak mogło dojść, że mamy kod a teraz zastanawiamy się jak go przetestować ?
Tworzenie testów na tym etapie wydaję się sensowne jedynie w celach regresji.

Z drugiej strony można się upierać, czy jest sens aby ten kod testować jednostkowo, czy nie testować go jedynie integracyjnie. Takie podejście wydaje się zgodne z tym co można wyczytać w http://www.growing-object-oriented-software.com/ a powyższy kod leżałby w warstwie adaptera zgodnie z Ports and Adapters pattern. Jednakże, z pragmatycznego punktu widzenia (albo wtedy gdy w ogóle nie mamy automatycznych testów integracyjnych/systemowych) przetestowanie jednostkowo powyższego kodu jest jak najbardziej możliwe.

Najpierw zastanowiłbym się co chcemy przetestować, bo w metodzie send dzieją się 2 rzeczy: wywołanie JMSTemplate.send oraz pośrednio budowa JMS Message. Najsensowniejsze wydaje się rozdzielenie obu funkcjonalności i przetestowanie ich osobno. Zacznijmy od testowania budowania JMS Message za pomocą jednego z dwóch podejść:
  1. tworzymy top level class implementującą MessageCreator

    public class MyMessageCreator implements MessageCreator {
    private final MyMessage message;

    public MyMessageCreator(MyMessage message) {
    this.message = message;
    }
    @Override
    public Message createMessage(Session session) throws JMSException {
    return session.createObjectMessage(message);
    }
    }


  2. tworzymy factory

    MessageCreator getMessageCreator(final MyMessage message) {
    return new MessageCreator() {
    @Override
    public Message createMessage(Session session) throws JMSException {
    return session.createObjectMessage(message);
    }
    };
    }
    }


Dzięki temu możemy w stosunkowo łatwy sposób przetestować, że nasz kod poprawnie tworzyJMS message (na potrzebę testów użyjemy mocków/stubów)

Następnie, możemy sprawdzić czy nasza metoda send faktycznie użyje jmsTemplate.send z odpowiednie parametrem. Taki test będzie zależał od tego w jaki sposób zaimplementowaliśmy tworzenie JMS Message. Jeśli stworzyliśmy własną klasę MessageCreator to możemy sprawdzić czy parametr metody send jest właśnie jej typu. Ewentualnie można użyć (gruba rura!) PowerMock. W drugim przypadku będziemy mogli zamockować factory. Jeśli rolę fabryki pełni osobna klasa (potencjalnie ukryta pod interfejsem) to nie wydaję się to najgorsze (nie licząc tego że powstały nam kolejne artefakty), a w przypadku gdy fabryką będzie lokalna metoda to już wchodzimy w tematy partial-mocking.

Gdybyśmy jednak chcieli podany kod przetestować bez jego uprzedniej refaktoryzacji, czyli bez wprowadzania podziału na: budowania JMS message i wywołanie jmsTemplate, to też jest to jak najbardziej możliwe. Sprawa wydaje się stosunkowo prosta: należy sprawdzić czy metoda jmsTemplate.send jest wywołana z odpowiednim parametrem. Odpowiedni parametr to taki, który jak wywołamy na nim metodę send
to na przekazanej JMS session zostaną wywołane odpowiednie metody. Poniżej przykłady jak to zostało zrobione za pomocą EasyMock i Mockito

public class SenderServiceEasyMockTest {
private SenderService sut = new SenderService();
private JmsOperations jmsOperations;
private MyMessage myMessage = new MyMessage("neverMind");

@Before
public void setUp() {
jmsOperations = EasyMock.createMock(JmsOperations.class);
sut.setJmsTemplate(jmsOperations);
}

@Test
public void testDoSendByArgumentMatcher() {
final Session session = EasyMock.createMock(Session.class);
IArgumentMatcher argumentMatcher = new IArgumentMatcher() {

public boolean matches(Object argument) {

MessageCreator messageCreator = (MessageCreator) argument;
try {
EasyMock.expect(session.createObjectMessage(myMessage))
.andReturn(null);
EasyMock.replay(session);
messageCreator.createMessage(session);
} catch (JMSException e) {
throw new RuntimeException(e);
}
return true;
}

public void appendTo(StringBuffer buffer) {
}
};

EasyMock.reportMatcher(argumentMatcher);
jmsOperations.send((MessageCreator) null);
EasyMock.replay(jmsOperations);

sut.doSend(myMessage);

EasyMock.verify(jmsOperations, session);
}

@Test
public void testDoSendByCapture() throws JMSException {
Capture<Messagecreator> tocapture = new Capture();
jmsOperations.send((MessageCreator) EasyMock.and(EasyMock.anyObject(),
EasyMock.capture(tocapture)));
EasyMock.replay(jmsOperations);

sut.doSend(myMessage);

MessageCreator value = tocapture.getValue();
Session session = EasyMock.createMock(Session.class);
EasyMock.expect(session.createObjectMessage(myMessage)).andReturn(null);
EasyMock.replay(session);
value.createMessage(session);
EasyMock.verify(jmsOperations, session);
}
}



public class SenderServiceMockitoTest {
private SenderService sut = new SenderService();
private JmsOperations jmsOperations;
private MyMessage myMessage = new MyMessage("SSSS");

@Before
public void setUp() {
jmsOperations = Mockito.mock(JmsOperations.class);
sut.setJmsTemplate(jmsOperations);
}

@Test
public void testDoSendByMatcher() {
sut.doSend(myMessage);

Mockito.verify(jmsOperations).send(Mockito.argThat(new ArgumentMatcher<Messagecreator>() {

@Override
public boolean matches(Object argument) {
MessageCreator messageCreator = (MessageCreator) argument;
Session session = Mockito.mock(Session.class);
try {
messageCreator.createMessage(session);

Mockito.verify(session).createObjectMessage(myMessage);
} catch (JMSException e) {
throw new RuntimeException(e);
}
return true;
}
}));
}


@Test
public void testDoSendByCaptor() throws JMSException {
sut.doSend(myMessage);

ArgumentCaptor<Messagecreator> messaArgumentCaptor = ArgumentCaptor.forClass(MessageCreator.class);
Mockito.verify(jmsOperations).send(messaArgumentCaptor.capture());
Session session = Mockito.mock(Session.class);
MessageCreator value = messaArgumentCaptor.getValue();
value.createMessage(session);
Mockito.verify(session).createObjectMessage(myMessage);
}
}

Pomimo braku jakiegokolwiek refaktoringu testy w EasyMock wyglądają o wiele mniej czytelniej od tych napisanych w Mockito. Wynika to głównie z potrzeby wołania w odpowiednich miejscach EasyMock.replay oraz (co gorsze) braku mechanizmów do weryfikacji zachowań, które zaszły w ramach wywołania metody podległej testowi. W przypadku EasyMock jest potrzeba wyspecyfikowania a priori wszystkich interakcji, a w Mockito możemy wygodnie użyć Mockito.verify. Szczególnie w tym przypadku bardzo wpływa to na czytelność kodu, gdy sekcje given/when/then sa zaburzone. Dodatkowo użycie mechanizmu capture w obu przypadkach wydaję się być zdecydowanie wygodniejsze od użycia argumentMatcher

czwartek, 10 marca 2011

AspectJ a 2.2250738585072012e-308

Ostatnio Dawid Weiss podczas spotkania w ramach Poznan JUG pokazał w jaki sposób obejść problem związany z Double.parseDouble(). Do czasu wydania (a co ważniejsze) zainstalowania patcha można się posiłkować wykorzystaniem AspectJ - nie pokryję to jednak wszystkich możliwych przypadków w których ten bug może się objawić. Aspekt oryginalnie przedstawiony przez Dawida na prezentacji był - jak słusznie myślałem - niekompletny, bo m.in. nie pokrywał przypadku kiedy Double.parseDouble byłoby wołane po refleksji.

package jug.demos.aspectj.aspects;
public aspect ParseDoubleHotFix{
double around(String s):
!within(jug.demos.aspectj.aspects..*) &&
call(double java.lang.Double.parseDouble(String)) &&
args(s){
if (s.indexOf("2250738585072012") >= 0) {
throw new IllegalArgumentException(
"We apologize for inconvenience, but this number is"
" temporarily not parseable by Oracle: " + s);
} else {
return Double.parseDouble(s);
}
}
}


Pełny kod aspektu został przysłany dzień po prezentacji na listę Poznan JUG. Pierwsze co rzuciło się w oczy to fakt, że zdefiniowane pointcuty są typu call zamiast execute. Czyli zamiast "opakować" wykonanie Double.parseDouble za pomocą execute, "opakowujemy" wywołania. Jak wyjaśnił Dawid jest to związane z tym "że java.lang.Double jest klasą systemową i ładuje się bardzo wcześnie. Load time weaver w AspectJ nie byłby w stanie jej pewnie stosownie owinąć, nawet gdyby go zmusić. Domyślnie AspectJ nie przetwarza klas systemowych w ogóle (java.* i javax.*). To, co można ew. zrobić, to przetworzyć rt.jar w trybie offline (z wymuszeniem przetwarzania pakietów systemowych), ale nie próbowałem."

Próba obejścia tego błędu poprzez sprawdzenie wyłącznie własnego kodu jest zdecydowanie niewystarczające, ponieważ feralna liczba może być parsowana przez kod, który wykorzystujemy, a do którego nie mamy źródeł. Najgorzej jednak wygląda sprawa w przypadku gdy wywołanie byłoby z poziomu biblioteki standardowej java - wtedy rozwiązania z wykorzystaniem AspectJ nie za wiele by się zdało (chyba że offlinowe przetworzenie rt.jar o czym wspomniał Dawid). Jednak gdy odrzucimy ten skrajny przypadek to i tak zakres kodu który jest uruchamiany w ramach naszej aplikacji, a który bezpośrednio nie kontrolujemy daje duże pole do popisu wszelkiej maści vulnerabilities. Jako przykład posłużył Dawidowi Tomcat 6.0.24.
Wydaję mi się, że eksperyment najłatwiej przeprowadzić przy wykorzystaniu AJDT wraz z podpiętym pod Eclipse Tomcatem (najłatwiej zrobić to w STS):
  • tworzymy sobie projekt typu AspectJ Project.
  • tworzymy w nowo otwartym projekcie nowy Aspect,
  • uaktywniamy go poprzez utworzenie pliku z odpowiednimi wpisami w META-INF/aop-ajc.xml w projekcie utworzonym powyżej (dodatkowo dodamy sobie <weaver options=" -XnoInline -verbose -showWeaveInfo"/>)
  • z poziomu IDE przechodzimy do konfiguracji Tomcata i otwieramy ustawienia związane z uruchomieniem serwera
  • w zakładce Arguments do VM Arguments dodajemy -javaagent:<ścieżka do aspectjweaver-1.6.10.jar>
  • w zakładce Classpath do User Entries dodajemy utworzony powyżej projekt typu AspectJ
  • uruchamiamy Tomcata
W logach zobaczymy m.in. wpis w stylu:

weaveinfo Join point 'method-call(double java.lang.Double.parseDouble(java.lang.String))' in Type 'org.apache.catalina.connector.Request' (Request.java:2591) advised by around advice from

Oznacza to że w linii 2591 klasy org.apache.catalina.connector.Request znajduję się wywołanie Double.parseDouble (czyli w tej linii znajduje się jointpoint spełniający określony pointcut ). Przeglądając źródła klasy org.apache.catalina.connector.Request można zobaczyć, że wywołanie Double.parseDouble odbywa się przy w metodzie getLocale, a dokładniej przy parsowaniu wartości quality factor. Oznacza to, że w przypadku gdy na serwerze zostanie wykonana metoda request.getLocale w odpowiedzi na żądanie HTTP o nagłówku Accept-Language i jego dowolną wartością, ale z q=2-2250738585072012e-308, obsługujący wątek "trafi" w busy-loop.

Dawid w swojej prezentacji skupił się głównie na load-time weaving - w odróżnieniu od compile/binary time weaving takie podejście nie wymaga ingerencji w proces budowania kodu. Ma to znaczną zaletę, którą wykorzystałem podczas pracy u klienta, kiedy okazało się, że aplikacja działa zdecydowanie wolniej od tego co od niej oczekiwano. Zamiast próbować podłączać ją pod profiler czy też optymalizować na ślepo, chciałem zmierzyć czas obsługi żadania z podziałem na warstwy/komponenty. Dodanie takiej logiki w kodzie byłoby nie tylko bardzo upierdliwe, ale także wymagałoby przebudowania i skompilowania aplikacji (klient wersjonował otrzymywane binaria). Zamiast tego napisałem aspekt, zapakowałem go w jara i jedyne co pozostało to zmuszenie administratora aby zmodyfikował parametry uruchamiania JVM (dodajemy javagent oraz jar z aspektem do classpath). Nie potrzeba było w takim przypadku w żaden sposób przetwarzać dostarczonej poprzednio aplikacji.
Modyfikacja parametrów startowych JVM często trafia na opór ze strony administratorów, ale w tym przypadku szczęśliwie się udało.

Z punktu widzenie developmentu/deploymentu LTW jest strasznie wygodne - wystarczy stworzyć nowy AspectJ project, napisać w nim aspekty, dodać ten projekt do classpath (w IDE bajecznie łatwe) projektu, dla którego chcielibyśmy aby zadziałały aspekty, zmodyfikować parametry startowe projektu (javaagent ) i uruchomić....

W SpringFramework AspectJ jest w pełni wykorzystywany do implementacji @Configurable oraz do zarządzania transakcjami w trybie aspect. Uruchamianie testów za pomocą Spring TestContextFramework, które tworzą context korzystający z Load Time Weaving dość znacząco zwiększa czas uruchomienia testów. Wynika to (zgodnie z tym co powiedział Dawid) z tego że sam weaver jest napisany w javie (co implikuje problemy z opakowywaniem klas języka/biblioteki standardowej). Dodatkowo w kontekście wydajności:
  • unikanie dynamic pointcut (typu cflow, cflowbelow)
  • unikanie generycznych typów parametrów/wartości zwracanych
  • tworzenie pointcut minimalizujących ilość jointpointów
  • sprawdzanie weaving logs

Na końcu Dawid pokazał 2 dodatkowe zastosowania użycia AspectJ
  • mierzenie wydajności/śledzenie ścieżek wywołania
  • szukanie błędów związanych z concurrency
To drugie wygląda bardzo ciekawie, szczególnie, że błędy związane z concurrency mogą się pojawiać losowo i są bardzo ciężkie do wykrycia za pomocą standardowych technik: unit testy/debugging. Dawid zaproponował aby zdefiniować wymagania co do współbieżności wybranej klasy za pomocą aspektu - przy wejściu do metody podnosimy flagę (target + wątek), a przy wyjściu opuszczamy flagę (target + wątek) . Dzięki temu możemy sprawdzić czy przy wejściu do metody flaga jest opuszczona. Dawid zaproponował aby zrealizować to w taki sposób aby zdeployować aspekt wraz z aplikacją na serwerze uruchomionym w trybie debug. Dalej, podpiąć się do serwera z IDE, ustawić breakpoint wraz z Suspend Policy ustawioną na Suspend VM w linii, która nie powinna zostać wywołana jeśli nasz kod który poddajemy sprawdzeniu jest poprawny. Maciej Biłas w mailu następnego dnia zaproponował aby zamiast bawienie się w tryb debug i breakpointy programowowo wykonać heapdump i wczytać go do Eclipse Memory Analyzer. HeapDump wygenerowane przez jave w wersji od 6u14 mają zawierać extra informacje, która pozwolą Eclipse Memory Analyzer na dogłębną analizę zawartości pamięci wraz z stanem wątków. Myślę że warto to sprawdzić...
. W kontekście debuggingu warto sprawdzić projekt http://youdebug.kenai.com/

sobota, 22 maja 2010

GeeCon dzień 1

The Future of Java - keynote w wykonaniu Thorbiörn Fritzona zdecydowanie nie rzucał na kolana. Chyba trochę zabrakło pomysłu na prezentacje. Thorbiörn rozpoczął od wspominania czasów gdy Sun był firmą zatrudniającą geeków (" We are geeks, we have no friends. We have peers and treat them as friends"), którzy robili "cool stuff", ale nie mieli zbytnio głowy do robienia pieniędzy. Takie podejście (jak i sami ludzie, z których część odeszła z firmy) uległo zmianie po przejęciu firmy przez Oracle. Jednak samo przejęcie nie ma w zdecydowany sposób wpłynąć na los samej Javy. Większość produktów oferowanych przez Oracle (nie mówimy tu o bazie danych) jest oparta na Javie i dlatego powinno vendorowi zależeć na tym aby język i sama platforma dalej się rozwijała. Zresztą skoro sama Java jest "open-source" to nawet gdyby Oracle miał upaść (co podobno mu w niedalekiej przyszłości nie grozi) to i tak sam język wraz z JDK pozostanie. Ogółem podobno nie ma się co martwić, a nawet można wyglądać w przyszłość z nadzieją: Oracle już teraz się angażuje (poprzez m.in. przekazywanie kodu) w działania na rzecz OS oraz wyszedł z inicjatywą wprowadzenia zmian w JCP, ale w tej ostatniej sprawie żadnych dalsze szczegóły nie zostały podane...
Następnie Thorbiörn przedstawił parę cech nadchodzących w Java 7, napomknął o planowanym połączeniu grup tworzących JRockit i HotSpot, dalszym rozwoju NetBeans platform oraz GlassFish (wersja org i com).
Wspomniał dodatkowo o JRockit Mission Control oraz wchodzącym w jego skład JRockit Flight Recorder jako narzędziach do monitorowania i diagnozowania działania JVM. To ostanie narzędzie ma mieć możliwość nagrywanie co się dzieje w JVM (coś w stylu czarnych skrzynek w samolotach nagrywających parametry lotu) a następnie można wykonać dump nagranych parametrów w zadanej przez nas chwili/interwale.

Object Teams: The Next Dimension of Modularity - bardzo ciekawa prezentacja poruszająca temat związany z modułowością/modularnością. Temat ten w obecnej chwili kojarzy mi się od razu z zapowiadanymi zmianami w Java 7 lub też OSGI, jednak prezentacja dotykała trochę innego tematu. Zaczęło się od przypomnienia/objaśnienia czemu powinno zależeć nam na modularności: write/read/understand/change one piece at a time, clean reusability. W tradycyjnym/książkowym podejściu OO system składa się z obiektów - bytów posiadających dane oraz zachowanie. Zachowanie systemu jest definiowane jako zbiór interakcja pomiędzy obiektami (każdy z obiektów dodaje swoją cegiełkę). Podejście to ma mieć kilka zasadniczych wad:
  • no boundaries - struktura taka nie pozwala nam w łatwy sposób określić, który obiekt z którym może się komunikować.
  • bloated classes - implementacja danego zachowania ma być realizowana przez wiele klas, a dodawanie kolejnych zachowań powoduję dodawanie kolejnych elementów do istniejących klas co powoduję, że nasze klasy są bardzo rozbudowane i mają wiele odpowiedzialności
  • scattered behaviour - skoro realizacja danego zachowania jest rozrzucona pomiędzy wieloma klasami można spodziewać się problemów przy maintaince: tudności w zmienianiu/dodawaniu/usuwaniu danego zachowania
Nie do końca zgadzam się z tymi argumentami. Wydaję mi się, że system napisany zgodnie z zasadami OO nie musi mieć tych (wszystkich) wad.
Drugie podejście opiera się na strukturze, w której to mamy do czynienia z operacjami oraz encjami. Operacje są bezstanowe i implementują logikę systemu a jednocześnie manipulują encjami (strukturami), które nie posiadają metod, a jedynie przechowują dane. Takie podejście też ma swoje wady
  • no encapsulation for data - encje trzymają tylko dane a nie posiadają metod, które działałyby na tych danych
  • no “natural” structure within operation module - zdecydowanie takie podejście jest bardzo mało obiektowe i wygląda mało naturalnie
  • no re-use between operations - każda z operacji to osobny "świat"
Podejście takie od razu przypomina mi Anaemic Domain Model oraz Transaction Script opisywane przez Martina Fowlera.
Wady obu podejść: Data Centric i Behavoiur Centric ma być pozbawiony ObjectTeams. Jest to projekt, w którym to dla naszych obiektów (tych obiektów trzymających dane oraz posiadających zachowania) nazywanymi od teraz "base" są tworzone struktury "role", a ich zarządzaniem zajmują się struktury "team". Dla danego "Base" może być zdefiniowane jedno lub więcej role. Przypomina to trochę mechanizm dziedziczenia, ale jest to coś innego: dany base może występować nie tylko w wielu role ( a nawet w wielu role tego samego typu), a poza tym role są dynamiczne - w czasie działania dana instancja może występować w wielu role. Team implementują dane zachowanie (use-case) poprzez enkapsulacje interakcji pomiędzy zbiorem obiektów role. Role wzbogacają/zmieniają zachowanie odpowiadających im instancji base poprzez mechanizm call interception. W ten sposób core behviour znajduje się w base, zachowania base specyficzne dla danej funkcjonalności są zawarte w role, które to są zdefiniowane w team. Do tego dochodzi jeszcze mechanizm Team Activation, ktory pozwala na drobno ziarniste włączanie/wyłączanie mechanizmu call interception.
Całość wygląda całkiem, całkiem. Pomimo tego, że role są dynamiczne, eclipse w postaci Object Teams Development Tooling (OTDT) ma zapewniać bardzo dobre wsparcie podczas developmentu, a jako dodatkowy plus ObjectTeams ma też działać pod Equinox.

Easy to Use Highly Available Java Database Access- niestety nie byłem na całej prezentacji i przyszedłem po około 20 minut po rozpoczęciu. Craig przedstawiał MySql Cluster jako transakcyjną baza danych zaprojektowaną z myślą o pracy w silnie obciążonym środowisku wymagającym szybkiego, wydajnego i wysoce-dostępnego dostępu do danych. Jako przykład infrastruktury wskazano MySQL Cluster Carrier Grade Edition, które to ma pracować z dostępnością "5 9's" wykonując miliony operacji na minute. Razem z długą listą firm, które to korzystają z takiego rozwiązanie oraz wybranymi scenariuszami użycia zrobiło to naprawdę duże wrażenie. Oczywiście osiągnięcie takich wyników niesie ze sobą pewne (a dla osób przyzwyczajonych do pracy z typowymi bazami SQL dość znaczne) ograniczenia co do mechanizmów przechowywania danych (na prezentacji Craig wspomniał jedynie o typach danych i braku ograniczenia foreign-key). W dokumentacji projektu jasno jest napisane, że przejście z MyIsam czy InnoDB na NDB (specyficzny dla MySQL Cluster storage engine ) może wymagać zmiany schematu, zapytań albo nawet zmian w samej aplikacji.
Dostęp do MySql Cluster można uzyskać za pomocą: C++ API, SQL ( za pomocą MySQL Server) oraz MySQL Cluster Connector for Java. Ten ostatni interfejs udostępnia 2 możliwości: , ClusterJ lub ClusterJPA jako plug-in dla OpenJPA. ClusterJ przypomina na pierwszy rzut oka JPA/JDO, też mamy do czynienia z mapowanie, ale trochę innym i zdecydowanie uboższym od tego, do którego jesteśmy przyzwyczajeni. Jest to związane z tym, że MyClusterJ nie jest out-of-box bazą z interfejsem SQL - czyli mapowania nie przekładają się na SQL a na wewnętrzne API MySQL Cluster. Interfejs ClusterJ udostępnia nam dobrze znane obiekty: SessionFactory, Session, Transaction, Query, ale mapowania robimy na interfejsie a nie na klasie. Dodatkowo nie są wspierane takie mechanizmy jak: relacje, dziedziczenia, tworzenie tabel/indexów na podstawie mapowania a zapytania zawsze dotyczą pojedynczej tabeli. ClusterJPA stanowi pewne rozszerzenie i obejście w/w ograniczeń (jestem ciekaw jaki podzbiór JPA jest tak naprawdę wspierany). Jest to plugin dla OpenJPA, który pewne operacje: primary key reads, inserts, updates, deletes wykonuje przy pomocy ClusterJ API, a pozostałe realizuje za pomocą JDBC (czyli SQL). W przypadku innych providerów JPA wszystkie operacje będą wykonywane za pomocą JDBC.

The High Availability Non-Stop, Fault-Tolerant Services Tutorial - całkiem ciekawa prezentacja dotyczącą tworzenia skalowalnych i "niezatapialnych" systemów.
Eugene rozpoczął od samych podstaw. Na początku omówił czym jest skalowalność (własność systemu umożliwiająca obsługę rosnącej ilości pracy lub też łatwość rozbudowy istniejącego systemu w przypadku zwiększenia zapotrzebowania na pewne zasoby: sieć, moc obliczeniowa, dostęp do BD), typy skalowalności: horizonal-out ( dodanie nowych węzłów mających taką samą funkcjonalność jak te dotychczas), vertical-up (rozbudowa istniejących węzłów poprzez dodanie zasobów, np. pamięci, procesorów, storage). Dalej zdefiniował high availability (w zadanym okresie czasu zapewnienie całkowitej ciągłość działania systemu pod kątem funkcjonalnym ) i podał sposób obliczania dostępności jako 100 - (100*D/U) gdzie D to unplanned downtime a U to uptime. Szczególnie podkreślił fakt, że uptime != available (system może być uruchomiony, ale ze względu na np. problemy z siecią jest niedostępny). W ten sposób przeszedł do tabelki definiującej poziom dostępności jako ilość 9 - w zależności od ilości 9 system nie będzie up przez określony czas w roku. Określanie dostępności w taki sposób ma wiele niedoskonałości i ma służyć głównie w materiałach marketingowych. Problemem samy w sobie jest uptime (system jest up, ale ze względu na awarie sieci jest niedostępny dla klientów), poza tym z powodów problemów wydajnościowych system może być uznany za up, ale praktycznie nieużyteczny dla użytkowników, gruboziarniste uśrednianie poziomu dostępności nie odzwierciedla wymagań wielu systemów (brak dostępności systemu w okresie peak load a niedostępność w okresie "wakacji" ). Definicja HA oraz sposobu mierzenia uptime powinny zostać jasno sprecyzowane w SLA (Service level agreement). Po tym teoretycznym wstępie (choć nie wiem czy go do końca poprawnie zrozumiałem) przeszedł do bardziej praktycznych zagadnień:
  • load balancer - "rozrzuca" żądania pomiędzy 2 lub więcej zasobami, ukrywając je jednocześnie przed klientami, którzy widzą jedynie pojedynczy zasób w postaci load balancera. Mogą być bezstanowe (typowe dla żądań webservices) lub stanowe (np. dla systemów wymagających współdzielenia sesji HTTP). Istnieje wiele możliwych algorytmów "rozrzucania" żądań do zasobów, np. na podstawie contentu, priorytetów, mocy obliczeniowej zasobów, itd.
  • cache - przechowywanie danych które są "ciężkie" do pobrania lub obliczenia. Cache sprawdza się szczególnie dobrze dla danych read-only. Problemem niestety są wszelkie bezpośrednie zapisy do danych trzymanych w cache. Standardowo można to rozwiązać przez write-through - synchronicznie uaktualniamy cache oraz storage, write-behind - wartość w cache oznaczamy jako dirty i zapisujemy do storage asynchronicznie, no-write allocation - zakładamy, że z cache tylko czytamy i nie ma zapisów. Podane strategie chyba mają dotyczyć tylko przypadków w których to cache pełni role "proxy" dostępu do faktycznego data source. Osobiście nasunął mi się przypadek w którym do data source jest modyfikowany "z boku" przez inna aplikacje która jest nieświadoma cache. Odwołania do cache mogą być realizowane w sposób nie jawny (,np. Terracota, która też posiada API do cachowania ) lub jako bezpośrednie (,np. Coherence, Memcache). Wspomniał także o web caching i wyróżnił 2 podejścia: web accelerators ( użycie CDN jak S3, Akami , które to będą serwować zasoby) lub proxy cache (Squid)
  • cluster - grupa 2 lub więcej maszyn połączonych szybką siecią. Klaster jest widoczny dla użytkownika jako pojedyncza maszyna i jest budowany w celu zwiększania dostępności/wydajności systemu. Przy założeniu tej samej wydajności budowa klastra jest bardziej opłacalna niż skalowania pojedynczej maszyny (scale up). Wyróżniamy konfiguracje A/A (active/active) w której to wszystkie maszyny jednocześnie pracują oraz A/P (active/passive) w której to część maszyn rozpoczyna prace tylko gdy inne ulegną awarii - wymagane są co najmniej 2 węzły (główny i zapasowy), mechanizm wykrywania awarii oraz możliwość przełączenia ruchu do węzłów zapasowych w momencie awarii.
  • grid - system, realizujący dane zadanie jako zbiór niezależnych od siebie tasków (Map-Reduce)
Następnie Eugene poruszył tematy związane z redundancy i fault-tolerance. W konfiguracji A/A w przypadku gdy nasza aplikacja jest bezstanowa sytuacja wydaje się bardzo prosta - jeden węzeł wylatuje i można działać dalej. W przypadku konfiguracji A/P (na której mają być uruchomione aplikacje "stanowe" które mają skalować się jedynie "w górę"- scale up) obługa fault-tolerance jest trudniejsza i droższa: wymagane jest przełączenia na zapasowe węzły i może wymagać ingerencji ze strony administratora. W dalszej części przedstawione zostały przykładowe konfiguracje i schematy architektur które mają zapewniać scalability i HA. Eugene chyba nie do końca wstrzelił się z czasem bo konfiguracje były dość rozbudowane i przybliżenie jakichkolwiek szczegółów trwałoby zdecydowanie za długo. Wspomniany został case w którym to pewna firma miała coraz to większe ilość danych do przetwarzania, ładowanie ich do bazy Oracle stawało się coraz bardziej bezsensowne: rosły koszty rozbudowy bazy a czas przetwarzanie rósł. Rozwiązaniem okazał się Hadoop. W innym przypadku dzięki zastosowaniu odpowiedniej architektury (w szczególności load-balancerów na wielu poziomach) bardzo łatwo można było przeprowadzać inkrementalne uaktualnienie systemu.
Ogółem prezentacja bardzo ciekawa, ale ze względu na czas przedstawiona trochę po wariacku.

Java in high-performance computing - chyba najciekawsza prezentacja dnia. Dawid po chwili mniej-czy-bardziej rozrywkowej przeszedł do konkretów: czy Java jest szybsza od C++?
Odpowiedz jest godna konsultanta: to zależy ... Dawid rozpoczął od zdefiniowania HPC (High-performance computing) na potrzeby swojej prezentacji: działamy w środowisku o ograniczonych zasobach (np. CPU, pamięć) i mamy ograniczony czas na wykonanie przetwarzania. System napisany w duchu HPC nie powinien mieć żadnych oczywistych wad. Dawid przedstawił kolejne przykłady, które ukazywały znaczne różnice w czasie wykonania w zależności od pewnych warunków/ustawień:
  • funkcja dodająca 2 argumenty ma zdefiniowane odpowiednio argumenty: 2 inty (prymitywy), 2 Integery, Integer vararg. Różnice w czasie wykonania w zależności od maszyny wirtualnej potrafią wynieść nawet kilkanaście sekund.
  • uruchomienie identycznej aplikacji na windows oraz ubuntu kończy się zupełnie inaczej pomimo tego, że są to te same JVM. Na ubuntu JVM uruchamiał aplikacje automatycznie w trybie "server VM"
Ze względu na to że mamy wielu dostawców JVM, istnieje wiele różnych wersji tych maszyn, mogą mieć włączone różne opcje, różne kompilatory (server/client) nie ma uniwersalnej metody tworzenia wydajnych aplikacji. Dodatkowo, to co faktycznie zostanie uruchomione nie jest do końca określone w bytecode. Jedyną drogą osiągnięcia wydajności wydaje się być cykl: zmierz-popraw-zmierz.
Jeśli chodzi o benchmarking to Dawid znów pokazał parę solidnych przykładów:
  • czas wykonania martwego kodu może się różnić w zależności od JVM. HotSpot bardzo ładnie sobie radzi z usuwaniem martwego kodu, zostało to szczególnie ciekawie pokazane za pomocą odpowiedniej flagi dla JVM, dzięki której mogliśmy zobaczyć kod w assemblerze.
  • Hostpot bardzo dobrze sobie radzi z wywołaniami metod wirtualnych: potrafi przeprowadzić optymalizację (aggressive inlining), ale w odpowiednim momencie może się z tego wycofać i powrócić do standardowego wywołania wirtualnego.
  • wywołanie metody ze standardowego API javy (Integer.bitCount()) zostało zamienione przez JIT na odpowiednią instrukcje procesora, która to była dostępna na maszynie na której to aplikacja była uruchomiona.
Mierzenie wydajności wcale nie jest trywialne a wartości, które otrzymamy mogą zależeć od wielu czynników. Sam proces mierzenia powinien odbywać się w warunkach stabilnych: po fazie "warm-up", odpalamy odpowiednią ilość przebiegów, wyciągamy średnie, wariancje, min, max itd. Ważne aby nasze scenariusze testów odpowiadały faktycznym scenariuszom użycia systemu i aby mierzyć na środowisku docelowym, ponieważ jak się okazało ma to bardzo duże znaczenie...
Więcej na ten temat można znaleźć w artykule.
Pewne flagi wraz z objaśnieniami których używał Dawid można znaleźć tu

An Introduction to EclipseRT, Equinox and OSGi - nigdy nie byłem fanem prezentacji o eclipse. Uważam że jest to (bardzo) dobre narzędzie i tak na prawdę tylko narzędzie. Zdarzało mi się widzieć prezentacje o Eclipse RCP, Eclipse RAP czy Eclipse Platform, ale nie do końca byłem przekonany co do użyteczności tych narzędzi w tworzeniu rozwiązań biznesowych. Byłem w stanie wyobrazić sobie ich wykorzystanie do tworzenia różnego typu narzędzi lub "rozwiązań naukowych". Jak się później okazało, większa część środowiska myśli bardzo podobnie do mnie.
Chris rozpoczął od krótkiego objaśnienia OSGI: moduły jako bundle, import/export package, dynamiczne services, BundleContext, BundleActivator . Nie obyło się bez wspomnienia o korzeniach OSGI, OSGI Alliance, OSGI Specifications oraz implementacjach OSGI (np. Equinox, Fexlix, Knopflerfish, Concierge). Następnie Chris przedstawił EclipseRT jako zbiór narzędzi, frameworków oraz środowisk uruchomieniowych (runtimes) do wykorzystania przy budowie własnych rozwiązań, niezależnie od tego czy są to systemy typu desktop, web, SOA, enterprise, embedded. Na liście projektów wchodzących w skład EclipseRT można znaleźć wielu "starych znajomych" m.in: EclipseLink, Jetty, Birt, Swordfish, RAP, Virgo (Spring DM). Pomimo, że każdy z nich dotyczy zupełnie innej dziedziny to łączy je fakt uruchamiania na platformie Equinox (też wchodzi w skład EclipseRT). Dzięki temu, że pod spodem mamy OSGI, projekt oraz nasze własne aplikacje/moduły mogą korzystać z wszystkich dobrodziejstw związanych z tym środowiskiem. Jako przykład wykorzystanie EclipseRT został przedstawiony Toast. Jest to system do zarządzania flotą pojazdów, oparty na EclipseRT, który ma służyć jako działający przykład rozwiązania wykorzystującego mechanizmy i możliwości EclipseRT.
W dalszej części Chris wspomniał o stackless stack oraz o CODA - architekturze budowania systemów opartej o komponenty. Pomysł sam w sobie nie jest niczym nowym (już JavaBeans opisywało tworzenie aplikacji z komponentów), ale tym razem dzięki OSGI wszystko zapowiada się trochę inaczej.