wtorek, 7 sierpnia 2012

SOAP Webservice Client w CQ 5.4





Ostatnio zajmowałem się uruchomieniem części klienckiej stosu SOAP WebServices na platformie CQ 5.4. Sama platforma oparta jest na Apache Felix, Apache Sling, oraz dodatkowych bundle stworzonych przez Day/Adobe. Pierwotnie próbowano uruchomić na CQ 5.4 stos Apache CXF, który to jest dostępny w wersji OSGI. Jednak duża ilość wymaganych zależności, oraz zależności do tych zależności itd spowodowała, że graf zależności rozrastał się niczym rak. Dodatkowo szukanie w internecie różnego typu bundli eksportujący dany pakiet nie jest najprzyjemniejszym zadaniem. Z racji tego, że nasze serwery instancje CQ są uruchomiane na SUN JRE spróbowałem uruchomić wbudowany stos JAX-WS - czyli tak naprawdę jakąś starszą wersję projektu Metro.

Na potrzeby SOAP WebService korzystałem głównie z Spring WebServices i po raz pierwszy uruchamiałem JAX-WS. Na potrzeby projektu potrzebowałem sparsować WSDL (przesłany nam emailem), wygenerować obiekty JAXB oraz SOAP client stubs, wysłać komunikat, odebrać odpowiedz i wypisać ją na konsole.
  1. Generowanie artefaktów JAX-WS
     
       src/main/generated
    
    
       
     
        org.codehaus.mojo
        build-helper-maven-plugin
        1.7
      
         
       add-source
       generate-sources
       
       add-source
       
       
              ${generatedSourcesFolder} 
     
    
     
     
      
      
      maven-clean-plugin
      
      
       
           ${generatedSourcesFolder}
           false
           
            **
           
          
         
        
       
       
        org.jvnet.jax-ws-commons
        jaxws-maven-plugin
        2.2
        
         
          generate-sources
          
           wsimport
          
         
        
        
         ${basedir}/src/main/resources
         true
         true
         ${generatedSourcesFolder}
         ${basedir}/src/main/resources
         2.0
        
       
          
    
    
    w/w konfiguracja w pom.xml pozwala nam na wygenerowanie obiektów JAXB oraz client stubs w katalogu src/main/generated , który to będzie potraktowany przez Mavena jako dodatkowy katalog ze źródłami aplikacji (zawartość katalogu będzie czyszczona przy w fazie clean ).
    Do katalogu src/main/resource wrzuciłem dwa pliki:
    • myWsdl.wsdl - plik wsdl od klienta
    • binding.xml - który zawiera instrukcję na potrzeby JAXB w kontekście generowanie klasy java na podstawie XML Schema.
      
       
       
              
                  
      
      
      W przypadku gdy JAXB ma obrabiać elementy XML Schema znajdujące się wewnątrz pliku WSDL musimy wyspecyfikować lokalizację tych elementów za pomocą odpowiedniej składni: <położenie-pliku wsdl względem pliku xjb>#types?schema1. W naszym przypadku pliki wsdl oraz xjb leżą obok siebie czyli specyfikujemy myWsdl.wsdl#types?schema1
    Po odpaleniu mvn clean compile - powinniśmy mieć wygenerowane wszystkie artefakty na potrzeby łączenia się z naszym WebServices.
  2. Wywołanie WebServices
    Po wygenerowaniu wszystkie potrzebnych artefaktów możemy próbować wywołać nasze WebServices. Powinna zostać stworzona klasa z anotacją @WebServiceClient o nazwie takiej jak wartość atrybutu name elementu service z pliku wsdl. Klasa ta posłuży nam jako entry point do wołania usług. Standardowo kod kliencki JAX-WS wygląda mniej więcej tak:
    • utwórz za pomocą new instancje klasy która jest oznaczona anotacją @WebServiceClient
    • wywołaj na tej instancji metodę get*, która to zwróci proxy implementujące interfejs oznaczony annotacją @WebService - nazwa interfejsu odpowiada wartości atrybutu name elementu port który jest zawarty w elemencie service
    • przygotowania komunikatu wejściowego za pomocą wygenerowanej klasy ObjectFactory (z anotacją @XmlRegistry), która służy jako fabryka obiektów JAXB
    • wywołanie odpowiedniej metody na proxy przekazując odpowiedni obiekt-komunikat wejściowy
    W moim przypadku niestety musiałe wprowadzić kilka zmian:
    1. zmiana adresu URL endpointa
      BindingProvider bindingProvider = (BindingProvider) <nasze proxy>;
      bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);
      
    2. W przypadku tworzenia instancji klasy z anotacją @WebServiceClient za pomocą bezparametrowego konstruktora, plik wsdl jest ładowany z lokalnego dysku - patrz na blok static w klasie z anotacją @WebServiceClient. Niestety jest z tym duży problem gdy uruchomimy nasz kod na innej maszynie od tej, na której to został on zbudowany. Można to rozwiązać na 2 sposoby (przynajmniej mi znane):
      1. za pomocą JAX-WS catalog tak jak zostało to opisane tu . Takie podejście wymaga dodania pliku jax-ws-catalog.xml oraz dodania w naszym pom.xml elementu wsdlLocation. Rozwiązanie wydaję się jak najbardziej odpowiednie, ale jak się później przekonałem niestety nie działa w naszym systemie... Plik katalogu jest zaczytywany przez thread's context classloader, który to niestety go nie widzi. Pewnym obejście jest podmiana thread's context classloadera na czas wywołania WebServices, ale to rozwiązanie wydaje mi się zbyt inwazyjne
      2. można skorzystać z alternatywnego konstruktora dla klasy z anotacją @WebServiceClient i samemu podać URL do pliku wsdl oraz serviceName. Ten drugi można skopiować ze stałych z klasy z anotacją @WebServiceClient. Jeśli chodzi o URL do pliku wsdl, to z racji tego że nasz wsdl znajduje się w src/main/resources to przy kompilacji zostanie przeniesiony do katalogu target i będzie dostępny na classpath - wystarczy wywołać
      3. private URL getWsdlURL() {
          ClassLoader classLoaderToLoadWsdlBy = getClassLoaderToLoadWsdlVia();
          return classLoaderToLoadWsdlBy.getResource("myWsdl.wsdl");
         }
        
        private ClassLoader getClassLoaderToLoadWsdlVia() {
          return <klasa z anotacja @WebServiceClient>.class.getClassLoader();
         }
        
  3. Wypisanie komunikatu wyjściowego na konsole
    JAX-WS w dużej mierze ukrywa przez nami "dokumentową naturę SOAP WebServices". Dostarcza nam za to abstrakcje w postaci proxy, z którym to pracujemy w trybie RPC - wywołujemy metody przekazując parametr/parametry wejściowe i dostajemy wynik. W celach diagnostycznych możemy mieć potrzebę wypisać komunikat wyjściowy za pomocą JAXB Marshaller. Niestety obiekt, który dostaniemy w wyniku wywołania metody na proxy nie koniecznie może być zserializowany do XML przez JAXB - ze względu na brak anotacji @XmlRootElement. Rozwiązanie jest w miarę proste - wystarczy stworzyć odpowiedni obiekt-wrapper:
    
    private void unmarshallToSysout(Object response) throws JAXBException {
      Marshaller m = buildMarshaller(response);
      m.marshal(wrapWithRootElem(response), System.out);
     }
    
     private JAXBElement<Object> wrapWithRootElem(Object response) {
      return new JAXBElement<Object>(new QName("http://www.namespace.com/schema/My",
        "My"), (Class<Object>) response.getClass(), response);
     }
    
     private Marshaller buildMarshaller(Object response) throws JAXBException,
       PropertyException {
      JAXBContext context = JAXBContext.newInstance(response.getClass()
        .getPackage().getName());
    
      Marshaller m = context.createMarshaller();
      m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
      m.setProperty(javax.xml.bind.Marshaller.JAXB_ENCODING, "UTF-8");
      return m;
     }
    
    Alternatywnie, można dodać system property com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true
  4. Wywołanie WebServices z Cq 5.4
    Wywołania WebServices powinny działać bez problemu w aplikacji standalone (np z JUnit), jednak nie działają na CQ 5.4. Aktualnie mam 3 rozwiązania tego problemu:
    1. Dopisanie do sling.properties dodatkowego wpisu: sling.bootdelegation.com.sun=com.sun.*. Jest to metoda opisana dla CQ 5.5,ale działa także dla CQ 5.4
    2. przygotowanie specjalnego fragment bundle, który to eksportuje wszystkie pakiety z pakietu com.sun - jest ich prawie 400. W przypadku gdy chcemy wołać WebServices z poziomu javy a nie z poziomu jsp musimy ustawić w bundle, który woła WebServices odpowiednie import-package - albo ustawić mu wprost, że wymaga system bundle. To drugie rozwiązanie wydaję się mniej eleganckie, ale w tym przypadku łatwiejsze - wystarczy że w naszym pom.xml dodamy
      
      org.apache.felix
      maven-bundle-plugin
      true
      
       
         system.bundle
       
      
      
      
    3. przygotowałem alternatywny stos JAX-WS, bazują na webservices-rt-1.3.1.jar (nowa wersja jax-ws-ri jest dostępna jako bundle OSGI, ale ma tyle zależności, że ciężko je wszystkie pościągać i poinstalować). Wykorzystanie tak przygotowanego stosu jest niestety dość niewygodne:
      • bundle, który wywołuje WebServices musi mieć ustawiony Require- Bundle na bundle stosu,
      • przy wywołaniu WebServices ustawienie thread's context classloadera na ten, który ładuje/ma dostęp do klas stosu, czyli na przykład ten co załadował klasę: com.sun.xml.ws.spi.ProviderImpl

poniedziałek, 7 maja 2012

DependencyInjectionJunitRulesTestExecutionListener

Ostatnio strasznie spodobały mi się rozszerzenia do @Rule do junit. Pomimo tego że widzę pewne problemy ze stabilnością całego mechanizmu (jak np, zamiana kolejności uruchamiania @Rule a metod @Before i @After) i kilku braków ( m.in. nie działają @Rule zdefiniowane na poziomie suite - dla klas z @RunWith (? extend Suite) ) )  to rozwiązanie jest na prawdę super.
Dzięki wykorzystaniu @Rule możemy bardzo ładnie enkapsulować logikę związaną z wykonaniem testów ,np. współbieżne wykonanie metody testowej , modyfikowanie/zawieszanie metod testowych. Mechanizm jest na tyle uniwersalny, że w przyszłości annotacje @Before/@After maja zostać uznane za deprecated na rzecz wykorzystania @Rule. Aktualnie junit w wersji 4.10 wymaga aby anotacja @Rule była umieszczona na publicznym nie statycznym property typu TestRule lub MethodRule (deprecated). Co ciekawe wygląda na to, że @Rule działają bardzo dobrze z Spring TestContext Framework (przynajmniej dla junit-4.10 oraz SpringJUnit4ClassRunner z wersji springa 3.1.1).  Integracja może wyglądać w dwojaki sposób:
  • DI dla implementacji interfejsu TestRule
  • TestRule zdefiniowane bezpośrednio w spring
W pierwszym przypadku stworzyłem bardzo prosty listener który  injectuje beany do @Rule  i @ClassRule:
public class DependencyInjectionJunitRulesTestExecutionListener extends
        AbstractTestExecutionListener {
   @Override
    public void prepareTestInstance(final TestContext testContext)
            throws Exception {
        Object testInstance = testContext.getTestInstance();
        List<TestRule> ruleFields = getRuleFields(testInstance);
        doInjection(testContext, ruleFields);
    }

    private void doInjection(final TestContext testContext,
            List<TestRule> ruleFields) {
        AutowireCapableBeanFactory beanFactory = testContext
                .getApplicationContext().getAutowireCapableBeanFactory();
        for (TestRule testRule : ruleFields) {
            beanFactory.autowireBeanProperties(testRule,
                    AutowireCapableBeanFactory.AUTOWIRE_NO, false);
            beanFactory.initializeBean(testRule, testContext.getTestClass()
                    .getName());
        }
    }

    private List<TestRule> getRuleFields(Object testInstance) {
        TestClass testClass = new TestClass(testInstance.getClass());
        return testClass.getAnnotatedFieldValues(testInstance, Rule.class,
                TestRule.class);
    }

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        List<TestRule> classRuleFields = getClassRuleFields(testContext);
        doInjection(testContext, classRuleFields);
    }

    private List<TestRule> getClassRuleFields(TestContext testContext) {
        TestClass testClass = new TestClass(testContext.getTestClass());
        return testClass.getAnnotatedFieldValues(null, ClassRule.class,
                TestRule.class);
    }
   


}
Implementacja bazuję na dostępnym out-of-box DependencyInjectionTestExecutionListener oraz TestClass. Wszystko wygląda nieźle, ale niestety nie do końca działa :(. Brakuję na pewno reinjectowania beanów dla @ClassRule  w przypadku gdy nasza metoda/klasa używa @DirtiesContext. Implementacja nie powinna być trudna gdyż DirtiesContextTestExecutionListener ustawia odpowiednią flagę na TestContext (niestety ta flaga jest usuwana przez DependencyInjectionTestExecutionListener, czyli trzeba albo nasz listener ustawić przed DependencyInjectionTestExecutionListener albo rozszerzyć ten ostatni).
Większym problem jest jednak to, że aktualnie dla Spring 3.1.1.RELEASE odpowiednie metody klasy TestExecutionListener, a dokładnie metoda beforeTestClass jest wywoływana po @ClassRule. "Winna" temu jest metoda classBlock w klasie ParentRunner, po której to dziedziczy SpringJUnit4ClassRunner. Zgodnie z tym co można znaleźć w javadoc @ClassRule opakowują uruchomienie @BeforeClass/@AfterClass, a te z kolei SpringJUnit4ClassRunner opakowuje w wywołanie TestExecutionListener#beforeTestClass. Reasumując: najpierw zostaną uruchomiene @ClassRule, następnie TestExecutionListener#beforeTestClass a na końcu metody @BeforeClass. Metody definiujące zadaną kolejność oraz wywołania są protected, czyli wygląda na to, że zmiana jest jak najbardziej do zrobienia. W innym przypadku, tzn gdy nie potrzebujemy injectowania dla @ClassRule z przedstawionego powyżej kodu można spokojnie wyrzucić metodę beforeTestClass.
Jeśli chodzi o drugie podejście to sprawa wygląda całkiem podobnie. Tzn. w fazie TestExecutionListener#prepareTestInstance jest wykonywane DI, które to może injectować do property z anotacją @Rule. Jeśli chodzi o @ClassRule to DI dla pól statycznych nie działa by design. Są pewne obejścia, ale chyba bez jakiegoś większego plumbingu się nie obejdzie.
EDIT: aktualnie pojawiła się potrzeba stworzenia odpowiedniego test fixture, który to byłby dostępny dla kilku metod testowych. Można to łatwo zaimplementować jako TestRule albo metodę @Before, jednak ze względu na to, że tworzenie tego fixure może być czasochłonne lepiej zrobić to tylko raz. W szczególności chodzi o stworzenie i "ustawienie" stanu odpowiedniego bytu w systemie przy wykorzystaniu Selenium (Webdriver) aby testy w danej klasie mogły bazować na tak przygotowanym bycie. W przypadku JUnit najsensowniejsze wydaje się wykorzystanie @BeforeClass. W moim przypadku instancja WebDrivera jest injectowana przez Spring TestContext Framework, a skoro injectowanie do pól statycznych (tak aby były do użycia przez @BeforeClass) nie działa out-of box zdecydowałem się na to aby tworzyć test fixture za pomocą ClassRule - DI dla ClassRule zostało zaimplementowane w DependencyInjectionJunitRulesTestExecutionListener#beforeTestClass. Problemem jest tylko to, że w aktualnej implementacji Spring TestContext Framework bazuje na Junit 4.5, czyli SpringJUnit4ClassRunner uruchamia TestExecutionListener#beforeTestClass przed @BeforeClass, ale po odpaleniu ClassRule. Zmiana tej kolejności okazała się stosunkowo prosta, ale potrzebowałem stworzyć nowy Runner:
public class RunSpringAroundClassRulesJUnit4ClassRunner extends
        SpringJUnit4ClassRunner {

    public static class AroundTestClassCallbacks extends Statement {
        private final Statement statement;

        public AroundTestClassCallbacks(Statement next,
                TestContextManager testContextManager) {
            this.statement = new RunAfterTestClassCallbacks(
                    new RunBeforeTestClassCallbacks(next, testContextManager),
                    testContextManager);
        }
        @Override
        public void evaluate() throws Throwable {
            statement.evaluate();
        }
    }

    public RunSpringAroundClassRulesJUnit4ClassRunner(Class<?> clazz)
            throws InitializationError {
        super(clazz);
    }

    @Override
    protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);
        statement = withBeforeClasses(statement);
        statement = withAfterClasses(statement);
        statement = withClassRules(statement);
        statement = withAroundTestClass(statement);
        return statement;
    }

    private Statement withAroundTestClass(Statement statement) {
        return new AroundTestClassCallbacks(statement, getTestContextManager());
    }

    @Override
    protected Statement withBeforeClasses(Statement statement) {
        List<FrameworkMethod< befores = getTestClass().getAnnotatedMethods(
                BeforeClass.class);
        return befores.isEmpty() ? statement : new RunBefores(statement,
                befores, null);
    }

    @Override
    protected Statement withAfterClasses(Statement statement) {
        List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods(
                AfterClass.class);
        return afters.isEmpty() ? statement : new RunAfters(statement, afters,
                null);
    }

    private Statement withClassRules(Statement statement) {
        List<TestRule> classRules = classRules();
        return classRules.isEmpty() ? statement : new RunRules(statement,
                classRules, getDescription());
    }
}

poniedziałek, 16 maja 2011

GeeCon 2011 UniversityDay

Podczas pierwszego slotu przemieszczałem się pomiędzy "Spring into the Next Decade" a "Paradoxes of API Design". Oryginalnie chciałem iść na Spring, ale jak się okazało Josh Long zdecydowanie nie miał pomysłu na aż 3 godzinną prezentacje. W efekcie przeprowadził ją jako kompilacje z kilku krótszych opuszczając przy tym kilka szczegółów. Przy omawianiu podstaw konfiguracji Spring Framework przeniosłem się obok posłuchać o API. Prezentacja była dość ciekawa, ale trochę zbyt teoretyczno-filozoficzna (podobno w dalszej części powiało trochę praktyką). Jaroslav opowiadał o tym, że wiedzę o tym jak tworzyć API czerpiemy głównie ze szkoły i środowiska, w kŧórym pracujemy. Dokonał podziału potencjalnych użytkowników naszego API na: rationalist (ci którzy zanim zaczną działać najpierw pogłębiają swoją wiedzę o API przez czytanie dokumentacji, książek, artykułów), empirist (ci którzy uczą się poprzez prototypownie robione "rozpoznanie ogniem") oraz clueless (ci co nie mają zbytnio pojęcia i nie mają czasu/nie chcą się uczyć). Zdecydowaną większość mają stanowić ci ostatni. Szczególnie dla nich przygotowywana jest dokumentacja w formie gotowych use-cases, które mają poprowadzić ich jak po sznurku do realizacji najczęstszych scenariuszy. Przy tworzeniu API bardzo ważne jest zachowanie spójności (np. sposób konfiguracji związanej z zaczytaniem konfiguracji z pliku czy z BD ma być identyczny) oraz projektowanie w taki sposób aby ewentualnie umożliwić późniejsze zmiany zachowując kompatybilność. Jaroslav następnie omówił różne poziomy kompatybilności (np. na poziomie kodu źródłowego, na poziomie binarnym) oraz potencjalne gotchas z nimi związane. W tym przydługawym momencie wróciłem na prezentacje o Spring i tu powoli zaczęło się coś dziać. Josh omawiał dostępne scope komponentów (conversation/flow/view web scopes, oraz process scope wykorzystywany przez ACTIVITI BPMN 2). Następnie omówił kilka infrastrukturalnych mechanizmów Springa: BeanPostProcessor (użyteczne do modyfikowania/opakowywania beanów), BeanFactoryPostProcessor (modyfikowanie definicji beanów, dodawanie beanów, zaczytywanie konfiguracji beanów z alternatywnych źródeł, np. BD), FactoryBean (bardziej złożone tworzenie beanów, enkapsulacja potencjalnie złożonego kodu tworzącego beana). Oprócz teorii pokazał kilka przykładów wykorzystania w/w struktur: CorporationCodeStandardsAwareBeanFactoryPostProcessor, AutoToStringBeanPostProcessor, StartupAwareBeanPostProcessor, AutoToStringFactoryBean, ConditionalDelegatingPlatformTransactionManagerFactoryBean. Wszystkie przykłady kodu mają być dostępne w git.springsource.org/spring-samples/spring-samples.git. W końcu pojawiło się trochę kodu i nowości ze Spring 3.0/3.1: environmental beans, Spring EL (dostęp do SystemProperties, wywołanie statycznej metody ), użycie AnnotationConfigApplicationContext. Ważną zmianą w wersji 3.0 jest przeniesienie mechanizmu OXM (Object/XML Mapping) z projektu Spring Web Services do Spring Framework. Dzięki temu mamy spójny mechanizm do wykorzystania w isntniejących i nowych projektach/modułach: Spring Batch, Spring Integration, Spring MVC, Spring REST. Spring 3.0 dostarcza także prosty mechanizm związany z cachowaniem. Całość jest zrealizowana za pomocą interceptorów i nie wygląda na specjalnie skomplikowaną. Spring dostarcza out-of-box implementacje, które jako cache wykorzystują Oracle Coherence, GemFire lub Ehcache. Ciekawy jest mechanizm wskazywania w annotacji w jaki sposób ma być tworzony klucz do cache (@Cacheable(key = "#p0")) - kluczem będzie pierwszy paramter wywołania metody). Josh następnie w kilku słowach przedstawił projekt Spring Data, który wprowadza rozszerzenia dla istniejących mechanizmów DAO (np, Spring Data JPA dla JPA), ale przede wszystkim ma być "parasolem" technologii dostępu do NoSQL datastore. Projekt jest jak najbardziej rozwojowy i nie wspiera jeszcze wielu istniejących datastore, ale mam nadzieję że jest to tylko kwestia czasu. Josh pokazał przykład integracji z Redis (używany przez http://stackoverflow.com/) za pomocą StringRedisTemplate. Nie wiem czy ze względu na to że było to jedynie demo, ale sam kod był trochę pokręcony, bo przy zapisywaniu/odczytywaniu obiektu Customer (id, firstName, LastName) wykonywaliśmy operację na dwóch kluczach: customer:fn: oraz customer:ln:. Wydaję mi się, że w Redis można jako value przechowywać list/set wartości. Dodatkowo na potrzeby generowania kolejnych identyfikatorów trzymaliśmy dodatkowe entry (z kluczem customerId), które było inkrementowane przy zapisywaniu nowej instancji Customer. Kolejne na liście "what's new in Spring 3.0" zostały omówione mechanizmy związane z lifecycle/scheduling/ thread pool/ asynchronous support. Oczywiście nie obyło się bez pokazu abstraction of services i użycia @Transactional i spójnego zarządzania transakcjami niezależnie od użytego TransactionManager. Następnie Josh zaczał omawiać kilka projektów należących do Spring Portfolio koncentrując się na najlepiej znanych sobie: Spring Batch i Spring Integration. Szczególnie ciekawie wygląda ten drugi, który ma być implementacją wzorców z książki Enterprise Integration Patterns. Największe wrażenie zrobiła na mnie lista adapterów, których było chyba z 20 (np, umożliwiające dostęp za pomocą POP3, IMAP, Twitter, XMPP, File, FTP, HTTP, JMS, AMQP). Josh zaczął się trochę rozwodzić na temat AMQP i RabbitMQ, w którego to implementacje ma być mocno zaangażowana ekipa ze SpringSource. Podobno RabbitMQ ze względu na szybkość i niezawodność jest częściej wybierany na depolymentach na EC2 od dedykowanego Amazon SQS. Nie obyło się, też bez kilku przykładów związanych z wczytywaniem "dużego" pliku csv do bazy danych za pomocą Spring Batch. Przykład był dość trywialny, ale całkiem sensownie wygląda możliwość śledzenia stanu procesu oraz ewentualnych problemów w dedykowanych tabelach utworzonych przez Spring Batch. Mnie najbardziej przekonało rozwiązanie combo w którym to Spring Integration monitoruje dany katalog na dysku, a w przypadku pojawienia się nowego pliku, odpala jego przetwarzanie za pomocą Spring Batch. Pomimo kilku prób nie udało się odpalić na emulatorze projektu wykorzystującego Spring Android, lepiej poszło z Spring Blazde DS, ale ze względu na brak czasu nie udało się pokazać Spring Hadoop oraz Spring Social. Osobiście żałowałem, że nie udało się przedstawić i omówić działań SpringSource w obszarze cloud computing. Jedyna informacja jaką wyłapałem to ta, że CloudFoundry z produktu związanego z Amazon AWS stało się platformą, która ma być niezależna od cloud provider i (w przyszłości?) umożliwiać uruchamianie (dla celów testowych/demonstracyjnych) cloud applications na maszynie deweloperskiej. W tym celu powstało CloudFoundry.com, CloudFoundry.org a w przyszłości Cloud Foundry Micro Cloud. Więcej na temat tych projektów można znaleźć tu. SpringSource uruchomił swój kanał na YouTube, na którym mają być wrzucane ich materiały. Dodatkowo warto przejrzeć Green Beans.

"Patterns and Anti-Patterns in Hibernate" by Patrycja Węgrzynowicz - no prezentacja była dość przeciętna. Tematy, które zostały poruszone były całkiem ciekawe, ale było ich zdecydowanie za mało na 3 godziny. Na sam początek się trochę spóźniłem i trafiłem na omówienie rozwiązania problemu współbieżnego dostępu do danych w aplikacji CaveatEmptor z "Hibernate In Action". Problemem ma być implementacja funkcjonalności dodawania nowego bida. Algorytm jest taki, że przy dodawaniu nowego bida, sprawdzamy czy jest on większy od aktualnie najwyższej oferty. Implementacja jest następująca:
public someMethod(){
Bid currentMinBid = itemDao.getMinBid(itemId);
Bid currentMaxBid = itemDao.getMaxBid(itemId);
//lock for Upgrade
Item item = itemDao.getItemById(itemId, true);

Bid newBid = item.placeBid(userDao.findById(userId), newAmount,currentMaxBid, currentMinBid);
}


Patrycja pokazała, że może dojść do sytuacji, w której to będą składane jednocześnie 2 bidy (oba większe od aktualnie najwyższego bida): 1000 i 1002. W specyficznym scenariuszu może dojść do tego, że bid o wartości 1002 zostanie "wypchnięty" (każdy z wątków pobierze maxBid o wartośći 700, wątek bid-1002 zablokuję krotke, zmieni wartość na 1002 i zrobi commit, w tym czasie wątek bid-1000 będzie porównywać wartość 1000 z wartością 700, bo dla niego aktualnie najwyższy bid został wyliczony wcześniej, bid 1000 zostanie przyjęty i zostanie wykonany commit). W ten sposób bid o wartości 1000 zostanie dodany po bid wartości 1002. Ma to szczególnie znaczenie gdy mapujemy bidy jako listę a nie zbiór.
Proponowane rozwiązania to: użycie wersjonowania (rozumiem że chodzi o optimistic locking), ustawienie poziomu izolacji na REPEATABLE READ, ustawienie poziomu izolacji na SERIALIZABLE, zmiana kolejności instrukcji w kodzie. Pierwsze dwa rozwiązania mają być nieskuteczne. Jeśli chodzi o poziomy izolacji transakcji to sprawa jest tym bardziej ciekawa, gdyż niektóre bazy danych,np ORACLE nie implementują wszystkich poziomów, a nawet jeśli implementują, to implementacje mogą się od siebie różnić. Przykład rozwiązania, które nie działa, był oparty na MySql i pokazywał, że w momencie wydawania zapytania o item MySQL ma robić snapshot danych i reużywać go przy kolejnych zapytaniach. W wyniku takiego działania, końcowy rezultatem ma być dodanie 2 krotek do tabeli bidów, ale z tymi samymi wartościami w kolumnie definiującej pozycje na liście - przy następnym odczycie bidów Hibernate ma mieć problem z ich poprawnym załadowaniem. Chyba nie do końca trafia do mnie te wytłumaczenie. Szczególnie, że spodziewam się, że MySQL zrobi snapshot dopiero po uzyskaniu locka. W takim przypadku snapshot zrobiony jako drugi powinien widzieć zmiany zrobione w właśnie zakończonej transakcji. Nawet jeśli moje rozumowanie jest poprawne to chyba i tak nie da się w ten sposób rozwiązać oryginalnego problemu. Jeśli chodzi o SERIALIZABLE to rozwiązanie ma działać, ale chyba nie podejmę się dokładnej analizy. SERIALIZABLE zawsze automatycznie kojarzy mi się z problemem braku skalowalności i jest przyjmowane "z założenia" jako zbyt ciężkie.
Jeśli chodzi o wersjonowanie to dodanie mechanizmu versioning bez zmiany kodu nic się nie zmieni. Wynika to z tego, że druga transakcja uzyska lock po wykonaniu commit pierwszej, czyli już będzie miała zupdatowany licznik wersji. Użycie samego optimistic locking bez lockowania, też nie pomoże (wątek bid-1000 odczyta "stare" min/max bid , w tym momencie wykonany będzie commit transakcji wątku bid-1002, wątek bid-1000 odczyta item z wersją ustawioną przez wątek bid-1002, ale wykona porównanie ze "starymi" min/max bid). Rozwiązaniem jest zmiana kodu poprzez zmiane kolejności wywołań na:

public someMethod(){
//lock for Upgrade
Item item = itemDao.getItemById(itemId, true);
Bid currentMinBid = itemDao.getMinBid(itemId);
Bid currentMaxBid = itemDao.getMaxBid(itemId);

Bid newBid = item.placeBid(userDao.findById(userId), newAmount,currentMaxBid, currentMinBid);
}

W tym przypadku najpierw lockujemy a dopiero później wykonujemy kolejne operacje.Wydaję mi się, że taki kod byłby poprawny gdybyśmy zrezygnowali z lockowania krotki item, użyli jedynie optimistic locking, przy jednoczesnym updatowaniu item przy dodawaniu bida. Ten ostatni krok, ma jak najbardziej wartość biznesową - możemy trzymać tam datę ostatniego bidowania. W przypadku składania dwóch jednoczesnych bidów jedna z operacji zakończy się wyjątkiem OptimisticLockException. Wyjątek ten można (najlepiej za pomocą AOP) złapać a całą operację powtórzyć.

Patrycja wskazała, też alternatywne rozwiązania: obliczanie najwyższego bid programowo (wymaga załadowania całej listy bidów), redundentne przechowywanie najwyższego bid w item (denormalizacja BD, ale szybki dostęp przy ponoszeniu kosztów utrzymywania kopii informacji), wykorzystanie zaawansowanych możliwości związanych z mapowaniem. Ten ostatni pomysł wykorzystuję dość rzadko używane feature Hibernate i polega na tym, że do item dodajemy dodatkowe property wraz z mappingiem, dla którego jest wyspecyfikowane zapytanie(Embedded Query) definiujące to property. Zapytanie to ma mieć możliwość korzystania z order by, where, groupBy. Dodatkowo, dostęp do property możemy określić jako lazy/eager (w tym przypadku chyba najsensowniejsze jest określenie tego property jako set z fetch =FetchType.Lazy). Następnie zostały przedstawione 2 dość proste błędy w kodzie CaveatEmptor, aby w końcu przejść do większych problemów związanych z aplikacjami używającymi Hibernate a w ogólności dowolnego ORM. Chodzi tu o problem Anaemic Domain Model. Rozpoczęło się od próby zdefiniowania OOP i przedstawieniu podstawowych ( encapsulation/inheritance/polimorphism ) oraz zaawansowanych (SOLID) zasad świata OOP. W CaveatEmptor brak enkapsulacji jest bardzo widoczny co może spowodować wzrost ilości bugów (szczególnie w stylu czemu coś się dodało/usunęło z bazy) oraz problemy z maintainance. Rozwiązaniem ma być używania odpowiednich modyfikatorów dostępu, najlepiej mappowanie fields zmiast properties, defensive copying (w przypadku kolekcji skutkuje to pobraniem lazy mapped collection z BD), unmodifiableCollections (możliwy problem przedstawiony w przykładzie poniżej). Ważne jest dodatkowo zrozumienie w jaki sposób działa mechanizm dirty checking w Hibernate, szczególnie w stosunku do kolekcji entity i embedded objects - należy re-używać kolekcje które zostały stworzone/wypełnione przez Hibernate zamiast tworzyć nowe kolekcje (dirty checking ma wykorzystywać identity of collections w celu stwierdzenia czy kolekcja uległa zmianie). Patrycja pokazała dość podchwytliwy przykład:
public List getBids(){ return Collections.unmodifiableList(bids);}
public void setBids(List newBids){
bids.clear();
if(newBids != null){
bids.addAll(newBids);
}
}

W momencie gdy klient wywołałby:
bids = getBids();
setBids(bids);

to z bazy mogą zostać usunięte określone bidy. Problem jest w tym, że Collections.unmodifiableList() stworzy nam wrapper opakowujący podaną mu w parametrze listę, ale wszystkie zmiany na źródłowej liście są odzwierciedlane we wrapperze. W rezultacie do bids zostanie dodana pusta lista. Następne na tapetę trafiły problemy wydajnościowe Hibernate. Byłem bardzo zdziwiony, że nie zostały zbytnio poruszone problemy: n+1i cartesian product. Patrycja pokazała 2 przypadki:
  1. nadpisywanie listy:
    A a = (A)session.get(A.class,2);
    a.setBs(a.getBs());

    W przypadku standardowego (a'la JavaBean) mapowania uruchomienie poniższego kodu skutkuję następującą interakcją z BD: pobraniem A z id=2, pobraniem odpowiadającej kolekcji B, usunięcie wszystkich B przypisanych do danego A, wstawienie kolejno poprzednio usuniętych B.

  2. oraz wykorzystanie immutable list przy mapowaniu:
     public class A{
    @OneToMany
    public List getBs(){
    Collections.unmodifiableList(bs);
    }
    public void setBs(List b){
    this.bs = bs;
    }
    }

    Operacja załadowanie A po identyfikatorze ma skutkować następującą interakcją z BD: pobranie A, pobranie odpowiadających B, usunięcie odpowiadających B,dodanie kolejno usuniętych przed chwilą B.

Dalej przedstawiono porównanie dostępnych strategii mapowania hierarchii. Szło to dość sprawnie i w końcu Patrycja przeszła do omówienia swojego projektu Yonita, który ma przeprowadzać analize kodu pod kątem potencjalnych defektów, anti-patterns, bad practices, vulnerabilities i innych takich. Zgodnie z tym co można wyczytać na stronie projektu Yonita dokonuję analizy statycznej jak i "dynamic web testing". Przy czym to drugie ma polegać na generowaniu testów automatycznych. Chyba jestem zbyt sceptycznie do tego nastawiony, ale parę lat temu rozmawiałem z goścmi z Parasoft i chyba przestałem wierzyć w cudowne narzędzia, szczególnie jeśli chodzi o automatyczne generowanie testów.

"Code Generation on the JVM" by Hamlet D'arcy. Kilka razy miałem przyjemność a raczej nie-przyjemność bycia na prezentacji, która omawiała różne narzędzia i metody związane z generacją kodu. Wszystkie one jednak bazowały na narzędziach do generowaniu kodu źródłowego(CORBA stub generation, WSDL2Java, Java Beans generation). Hamlet skoncentrował się na zupełnie innych narzędziach, a w szczególności na: Lombok, Spring Roo oraz paru innych wynalazkach do Groovy.
W jednym z projektów korzystam z Lombok i bardzo sobię to narzędzie chwalę. Jedyna rzecz która mnie bardzo irytuję to fakt, że pod eclipse (może pod innym IDE jest podobny problem) w przypadku dodawania przez eclipse (CTRL+1) nowej metody do klasy z annotacją Lombok dostajemy w edytorze błąd. Wynika to z tego, że eclipse wstawił nam sygnaturę nowo utworzonej metody w miejsce, w którym są wygenerowane, "niewidzialne" metody. Nie licząc tej niedogodności, samo narzędzie jest na prawdę super. Nie obyło się bez nieśmiertelnego przykładu z generowaniem getterów/setterów, aby pokazać bardziej zaawansowane możliwości: @Cleanup (w Java 7 będzie specjalna instrukcja związana z automatic resource management), val ( final local variables oraz detekcja typu), @Synchronized (enkapsulacja locków wraz z generowaniem ich jako puste Object[] by były serializowalne), @Log. W celu sprawdzania jakie zmiany zostały dokonane przez Lomboka można użyć javap lub delombok. Hamlet wspomniał o samym mechanizmie działania Lomboka, który opiera się na annotacji, oraz Eclipse Handler i Javac Handler. Podobno stworzenie własnych rozszerzeń nie jest specjalnie trudne a jako first-step warto zobaczyć tu.
Następne na tapetę poszło Spring roo, ale nie było już tak dokładnie omawiane jako Lombok. Chyba Hamlet nie do końca zna ten projekt i skończyło się na odpaleniu jakiegoś bardzo prościutkiego przykładu oraz wspomnieniu o tym, że roo działa w czasie kompilacji, korzysta z inter-type declarations oraz jest dostępny mechanizm push-in refactoring. Po przejściu projektów javowych, Hamlet przeszedł do swojego świata Groovy. Zaczęło się od Groovy transformations a już sama liczba dostępnych transformacji jest naprawdę imponująca. Dotyczą one: generowania kodu (np. @ToString, @Lazy (wraz z użyciem volatile tworzy inteligentny lock aby uniknąć dwukrotnego inicjalizowania), @TupleConstructor ), zarządzania współbieżnością (np. @Synchronized, @WithReadLock, @WithWriteLock), logowanie(np. @Log, @Slf4j) , ograniczaniem dostępu (np. @PackageScope), implementacji patternów (np. @Singleton, @Immutable, @Delegate) oraz wielu innych przypadków. Wszystkie z tych transformacji mają się opierać na implementacji interfejsu GroovyCodeVisitor, którego to metody są wykonywane przy przechodzeniu przez AST. Następnie Hamlet pokazał projekt CodeNarc do statycznej analizy kodu w groovy, który też opiera się na wzorcu visitor. Kolejny narzędziem był GContracts, który ma realizować idee desing by contract dla groovy. Pomimo tego, że projekt jest nowy, wygląda całkiem, całkiem. Ostatni był Spock, który jest frameworkiem do testowania napisanym w groovy. Na pierwszy rzut oka sama idea przypomina użycie Parameterized runner,w którym to dane podawane są w formacie wiki. Spock ma też podobno bardzo dobre wsparcie do mocków.

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.

piątek, 26 marca 2010

4developers

Dziś miałem okazje wziąć udział w konferencji 4developers. Udział w konferencji zawdzięczam mojemu ukochanemu Poznan JUG.

Walka o życie – kuzyni i następcy Javy opanowują świat Michael Hunger prezentacja była tak nudna,że po 10 minutach wyszedłem i przeniosłem się do sali ze ścieżki PHP. Pomimo tego że nigdy nie napisałem linijki w PHP(z czego tak naprawdę jestem dumny) to temat cloud computingu przemawiał do mnie jak najbardziej.

PHP w chmurze Ivo Jansch prezentacja okazała się bardzo ciekawa i nie miała za wiele wspólnego z PHP. Na początku omówione zostały podstawowe pojęcia związane z cloud computing, a w szczególności: IaaS, PaaS, SaaS. Jeśli chodzi o IaaS to jest to podstawowy poziom, który z punktu widzenia klienta opiera się na tym że uzyskujemy dostęp do hardware (sami definiujemy wymagania co do tego hardware )na którym możemy zainstalować system operacyjny, a następnie samą aplikacje. To gdzie fizycznie zostanie uruchomiona aplikacja najczęściej nas nie interesuje...
Instalacja systemu operacyjnego jest zazwyczaj pomijana ze względu na to, że najczęściej wybiera się obraz maszyny wirtualnej z zainstalowanych już systemem operacyjnym. Jako developer dostajemy dostęp do takiego "obrazu" i możemy sobie instalować naszą aplikacje. Ivo szczególnie polecał zaznajomienie się z rackspacecloud, który ma dostarczać bardzo wygodne API, dzięki któremu z poziomu PHP można odpalić nową maszyną wirtualną i zdeployować aplikacje. Kolejny poziom to PaaS, czyli w tym przypadku przechodzimy o poziom wyżej - z poziomu systemu operacyjnego na którym możemy sobie odpalić dowolną aplikacje, otrzymujemy platformę (w pewnym sensie możemy mówić o serwerze-usłudze) na którym odpalamy naszą aplikacje. Najlepszym przykładem jest tutaj Google App Engine który pozwoli nam zdalnie deployować aplikacje napisane w Java i Python. Jak na razie nie ma dostępnej darmowej infrastruktury PaaS dla PHP (istnieje płatne rackspacecloud sites). Ostatni poziom to SaaS czyli gdy tak na prawdę sama aplikacja jest usługą, z której to możemy korzystać. Najlepszym przykładem to Google Apps (docs, spreadsheet) lub też SalesForce CRM. Następnie Ivo omówił typowe problemy (choć lepiej nazwać to specyfiką środowiska) związane z tworzeniem aplikacji uruchamianych w chmurach: bezpieczeństwo, obsługa stanu, regulacje prawne. Na końcu wspomniał jeszcze o private developer clouds(jako VMware vSphere oraz tych udostępnianych przez terremark).
Automatyczne generowanie kodu Marek Berkan prezentacja sponsora, która niestety okazała się bardzo słaba. Mimo, że nie spodziewałem się cudów, to jednak liczyłem na coś więcej ... Przedstawione zostało parę scenariuszy, w których to generowanie kodu ma być bardzo użyteczne: generowanie kodu encji oraz DAO na podstawie schematu bazy danych, generowanie obiektów Javy z XML. Wszystko to było mało odkrywcze: użycie Hibernate, JAXB. Patrząc na Hibernate z punktu widzenia "generacji kodu" to wyniki nie są oszałamiające: głowną bolączką ma być brak automatycznego generowania stałych definiujących nazwy kolumn. W czasie prezentacji pokazał 2 pomysły, które wydają się bardzo sensowne: generowanie stałych dla propertiesów Strutsowych form beanów oraz dla kluczy bundli. Dzięki temu wszelkie zmiany w źródle (definicji form beana czy plikach bundli) są wyłapywane przez kompilator.
TopLink Grid – skalowanie aplikacji korzystających z Java Persistence API Waldekmar Kot - jedna z najlepszych (przynajmniej dla mnie ) prezentacji. Waldek zaczął trochę ospale od przedstawienia standardu JPA i podstawowych pojęć z nim związanych: EntityManager, EntityManagerFactory, PesistenceUnit i czegoś tam jeszcze. Ze względu na to że używam Hibernate nie do końca rozumiem te wszystkie pojęcia...
Następnie omówił standarodowe mechanizmy cachowania: cache transakcyjny(patrząc z perspektywy Hibernate cache związany z Session), oraz ten drugi (nie pamiętam dokładnie jakiego Waldek użył sformułowanie, ale chodziło o cache na poziomie SessionFactory czyli, second-level cache). Ten ostatni jest współdzielony przez cache transakcyjne w ramach pojedynczego JVM, ale może być także współdzielony w ramach wielu JVM. W ten to sposób doszedł do sedna, czyli pojęcia Data Grid: jako osobnej warstwy (najczęściej osadzonej na innym JVM/innej maszynie fizycznej niż nasza aplikacja ) która świadczy usługi przechowywania ale także może przetwarzać obiekty. Zazwyczaj taki Data Grid składa się z większej liczby węzłów, które to tworzą ze sobą sieć Peer-to-Peer i dostarczają nam możliwość bezpiecznego/skalowalnego/wydajnego przechowywania w pamięci dużej ilości danych. Bardzo podobała mi się mechanizm związany z możliwością wykonywania operacji bezpośrednio na Data Grid: zamiast ściągać dane do aplikacji, modyfikować je i odsyłać, można "wysłać" operacje bezpośrednio w Data Grid. Połączenie ze sobą Data Grid (w przypadku Oracle mówimy tu o konkretnym produkcie Oracle Coherence) wraz z implementacją JPA (Oracle Toplink) daję nam Oracle TopLink Grid. Takie połączenie pozwala na zwiększenie wydajności i skalowanie naszej warstwy DAO poprzez sam mechanizm cachowania, ale jednocześnie dostarcza kilku nowych możliwości. Chodzi o to że Oracle Coherence pracujący tuż za plecami naszej warstwy DAO (ale przed bazą danych) może pracować w jednym z 3 trybów:
  • Cache: standardowy second-level cache (cachowanie na podstawie id)
  • Read: w stosunku do trybu powyżej, nasze zapytania SQL są "przechywytywane" przez Data Grid, interpretowane oraz pobierane bezpośrednio z cache. Zapytania mają być rozpraszane na poszczególne instancje składające się na Data Grid (osobiście jestem ciekaw jak wygląda to w przypadku zapytań agregujących które przechodzą wzdłuż przez dane fizycznie przechowywane na innych instancjach Data Grid)
  • Entity: dodatkowo w przypadku modyfikacji najpierw modyfikowane są obiekty w Data Grid a później dane w bazie danych. Jednocześnie jest możliwe takie ustawienie że dane do modyfikacji są buforowane (ze względu na ilość lub czas) i wysyłane w 1 paczce do bazy danych.
Temat na pewno był bardzo ciekawy, ale ze względu na ograniczenia czasowe (dobrze, ze po tej prezentacji był obiad- a tylko szczęśliwcy dostali voucher :( , można było jeszcze trochę Waldka dopytać) nie udało się pokazać więcej.


Zbuduj pierwszą aplikację typu RIA z wykorzystaniem Flex 4 Piotr Walczyszyn
- bardzo fajna prezentacja, której głównym punktem była prezentacja Desktop Keeley :) Prezentacja składała się z 2 części: teoretycznej, w której omówione zostały podstawowe informacje na temat platformy FLEX, oraz praktycznej, w której to Piotr przedstawił w jaki sposób tworzyć w niej aplikacje. W części pierwszej można było usłyszeć o Flex SDK, sposobach komunikacji pomiędzy modułami SWF a częścią serwerową (SOAP, HTTP/HTTPS, AMF/AMFS, RTMP/RTMPS). Nie mogło się obyć bez prezentacji przykładowych aplikacji wykorzystujących FLEX: http://www.windowshop.com/, http://www.taaz.com/makeover.html, oraz "popisowej" http://www.adobe.com/devnet/flex/tourdeflex/. Następnie pojawiło trochę informacji na temat Adobe AIR, czyli runtime umożliwiający uruchamianie aplikacji Flex na desktop. Dzięki temu że można wyjść poza sandbox przeglądarki można skorzystać z dodatkowej funkcjonalności: instalacja i uruchamianie jako natywna aplikacja, dostęp do systemu plików, dostęp do wbudowanej bazy danych SQLite, możliwość uruchamianie aplikacji na wielu platformach: Windows, Mac, Linux a także Nokia S60 i Android...
"Lokalnym" przykładem aplikacji Adobe AIR to e-deklaracje, a inne ciekawe można zobaczyć na stronach Adobe. W 2 części Piotr przedstawił w jaki sposób stworzyć prostą aplikacje we Flex 4: aplikacja stworzona we FLEX łączy się z serwerem - Apache Tomcat, BlazeDS, MySQL. Musze przyznać, że robiło to duże wrażenie, szczególnie jeśli chodzi o skrócenie czas developmentu - choć nie do końca wiem czy to zasługa FLEX4 czy Flex Builer. Szczególnie spodobał mi się w części klienckiej mechanizm automatycznego generowania proxy oraz dto dla wystawionych na serwerze usług. Co ciekawe, zaimplementowany został (bez pokazania szczegółów) mechanizm asynchronicznego powiadamiania klientów. Miał to działać na takiej zasadzie, że usługa dodająca element do bazy wysyłała wiadomość do JMS topic. Na tym topicu "słucha" BlazeDS i powiadamia klientów za pomocą HTTP.
Możliwość asynchronicznego powiadamiania klientów (server push) bez potrzeby korzystania z warswty pośredniej - JMS, ma być zaimplementowana w komercyjnym Flash Media Server lub open-source red5.

Co nowego w Java SE 7? Marcin Kalas hmm nie do końca chyba słuchałem, a przynajmniej nic nie zanotowałem... chyba przychodzą na tą prezentacje miałem złe nastawienie: no bo przecież mój ukochany Websphere 6.1 działa na Java 5... Z tego co pamiętam to zostało wspomniane o paru udogodnieniach:
nowe File I/O, udogodnienia związane z przetwarzaniem równoległym JSR 166, nowy garbage collector, skompresowany wskaźnik 64 bitowy. Myślę że wszystko co było powiedziane można znaleźć tu

Craftsmanship i wzorce projektowe Sławek Sobótka - prezentacja zdecydowanie mniej techniczna od innych, ale całkiem ciekawa. Sławek skupił się na przedstawieniu wzorców projektowych jako ważnego ogniwa dojścia do profesjonalizmu. Wzorce nie tylko wypada znać, rozumieć, ale też wiedzieć kiedy stosować. Ważne jest aby pamiętać, że zastosowanie każdego z wzorców niesie ze sobą określone korzyści ale też i koszty. Mi osobiście spodobało się użycie chain of responsibility do wybierania strategii oraz wzbogacanie strategii poprzez decorator.

Modele komponentowe SCA, OSGi, Distributed OSGi i OSGi Enterprise a Java EE Jacek Laskowski - dziwna prezentacja, chyba nie do końca wiem do czego Jacek dążył ... Rozpoczęło się od próby zdefiniowania podstaw: interfejs, usługa, komponent poprzez bardziej abstrakcyjne byty: "rusztowanie" i "szkielet", ale później poszło już z górki. Jacek przedstawił historie powstawania "architektur komponentowych" na platformie Java. Zaczęło się od JavaBeans, przez EJB, Spring, JEE, OSGi, Spring DM, OSGi Blueprint Container, Distributed OSGi, Enterprise OSGI, SCA. Ja wyłączyłem się przy SCA i tego przejścia nie zaczaiłem...
Prezentacja miała omawiać aspekty programowania deklaratywnego, ale tak na prawdę przemieniła się (przynajmniej ja to tak odebrałem) jako refleksje na temat architektury komponentowej na platformie. Jacek (w mojej ocenie) popełnił 2 błędy/nieścisłości:

  • przyrównanie JavaBean do POJO - JavaBean to jest POJO, ale POJO to niekoniecznie JavaBean.
  • Spring to nie tylko DI, ale też AOP i Abstraction Of Services. Jest to znacząca różnica, szczególnie w momencie gdy historycznie porównujemy Spring do J2EE.