sobota, 24 stycznia 2009

Scaling Hibernate: tips, recipes and new perspectives oraz Tune this!

Po godzinnej przerwie rozpoczęły się sesję BOF. Jak się później okazało wszystkie sesje BOF prowadzone były w 2 wąskich, dusznych salkach ze słabym sprzętem audio-video.



Scaling Hibernate: tips, recipes and new perspectives - zgodnie z zapowiedziami Emmanuel Bernard miał przedstawić sposoby optymalizacji aplikacji opartych o Hibernate. Po minutowym wstępie, w którym omówił "klasyczne" metody optymalizacji: batch size czy fetch size ciut więcej można się było dowiedzieć o 2nd level cache:

  • nie ma sensu używać 2nd level cache dla bardzo dużych woluminów danych: jak mamy milion userów w systemie to nie ma sensu cachowac takiej ilości danych: skończy się to Out of memory exception lub sytuacją w której tylko część danych tak naprawdę będzie w cache i to nie koniecznie te, które byśmy chcieli
  • ma być już możliwe używanie JBoss Cache jako transakcyjnego 2nd level cache - miało zostać to ostatnio naprawione

Pojawiła się wzmianka o użyciu Hibernate w systemach masowego przetwarzania/wsadowych i korzystaniu z takich dobrodziejstw jak: batch insert, batch update, ScrollableResults, StatelessSession (pobierane encje są automatycznie odłączane i mogą zostac usunięte przez GC). Ja osobiście dodałbym do tego bezpośrednią możliwość wykonania operacji update/delete z poziomu Hibernate za pomocą session.createQuery("...").executeUpdate();
Następnie przedstawiono różne możliwości radzenia sobie w bardziej specyficznych przypadkach, np. bardzo duża ilość danych, bardzo duże obciązenie baz danych, dane z różnych (politycznych) względów muszą być rozdzielone.
Jeśli chodzi o seperacji danych to wyróżniamy następujące opcje:

  • najprostszą możliwością (choć nie do końca można to nazwać separacją) jest separacja logiczna: wszystkie dane są przechowywanych w tych samych tabelach a separacja odbywa się na poziomie aplikacyjnym(logicznym) za pomocą Hibernate Filter lub jakiś innych tricków. Od razu przypomniał mi się mechanizm przedstawiony przez Alefa Arendsena podczas Spring ONE. Chodziło o to, aby za pomocą Spring AOP wrzucić do sztucznej tabeli identyfikator aktywnej transakcji oraz identyfikator klienta. Encje nie zostały "zbindowane" do tabel a do widoków, których zadaniem jest filtrowanie danych zawartych w tabeli po identyfikatorze klienta, którego aktualna wartość (wartość do "zmatchowania") znajduje się w sztucznej tabeli
  • można rozproszyć dane na wiele schematów i są tutaj 2 podejścia: 1 SessionFactory na 1 schemat (problem skalowalności) lub 1 SessionFactory dla wszystkich schematów (super login, wymaga przepisania zapytań - uwzględnienie nazwy schematów lub implementacji metody onPrepareStatement interfejsu org.hibernate.Interceptor - doklejenie do zapytania nazwy schematu). Ma tu się pojawiać dodatkowo problem z 2nd level cache, ale nie jestem pewien czy dobrze rozumiem dlaczego
  • wykorzystanie mechanizmów bazy danych, szczególnie kuszącym rozwiązaniem jest Oracle VPD (filtr na poziomie bazy danych)

Jeśli dodatkowo danych jest tak dużo i zdecydujemy się je rozpraszać pomiędzy wiele baz danych to Emmanuel Bernard zaprezentował 2 najpowszechniejsze rozwiązania tego problem, oba dotyczące warstwy apikacyjnej:

  • Homogenous nodes - każda instancja serwera aplikacyjnego ma pulę połączeń do każdej z bazy danych. Podejście takie ma mie wiele problemów natury wydajnościowej: duża ilość połączeń do bazy danych, zużycie pamięci, wolny start aplikacji (na każdej instancji startuje tyle SessionFactory ile mamy baz danych)
  • Specialized nodes - poszczególne instancje serwera aplikacyjnego mają dostęp jedynie do określonych baz danych. Ważny jest tutaj odpowiedni mechanizm load-balancing, który musi rozrzucać żądania użytkowników na podstawie żądanej funkcjonalności. Zaletą takiego podejścia jest skalowalność oraz wykorzystanie zasobów, ale mogą pojawić się problemy gdy nie istnieje żadna instancja serwera posiadająca dostęp do wszystkich danych, których będzie wymagać żądanie użytkownika

Rozwiązaniem ma być Hibernate Shards, który jest mechanizmem partycjonowanie poziomego (horizonal partitioning), czyli każda z baz danych posiada ten sam model, a jedynie różne dane. Dla porównania, partycjonowanie pionowe (vertical partitioning) polega na podziale danych ze względu na funkcjonalność i każda baza danych ma inny model : na 1 trzymamy dane użytkowników, na 2 tabele produktów itd. Na poziomie architektury Hibernate Shards rozszerza bibliotekę Hibernate i stanowi warstwę, która enkapsuluje logikę wynikającą z rozproszenia danych pomiędzy wiele baz danych, a z technicznego punktu widzenia opakowuje wiele instancji SessionFactory (podłączonych fizycznie do różnych baz o tym samym modelu) dostarczając użytkownik ujednolicony sposób dostępu do danych. Twórcy biblioteki dokonują wielkich starań i zabiegów, aby do jej obsługi móc korzystać ze znanego i sprawdzonego Hibernate API, istnieją odpowiednie implementacje takich interfejsów jak: SessionFactory, Session, Query, Criteria. Dodatkowo dochodzą do tego bardziej złożone sprawy wynikające ze specyfiki rozproszenia danych, które to zostały wyrażone za pomocą interfejsów:

  • ShardSelectionStrategy - decyduje o tym, na której partycji utworzyć obiekt
  • ShardResolutionStrategy - decyduje o tym na jakiej partycji szukać obiektu o zadanym kluczu głównym
  • ShardAccessStrategy - w jaki sposób wykonywać zapytania, które dotyczą więcej niz 1 partycji

Aktualnie Hibernate Shards udostępnia implementacje (lub kilka implementacji) każdego z tych interfejsów, ale oczywiście bardzo łatwo stworzyć własną.
Jeśli chodzi o zapytania to jak na razie Hibernate w ograniczonym zakresie wspiera Criteria Query (np. avg po poszczególnych partycjach) oraz nie wspiera HQL (problem z parserem). Dodatkowo dany graf obiektów nie może obejmować więcej niż pojedynczej partycji, co osobiście uważam za wielkie ograniczenie. Wyobraźmy sobie przypadek gdy tworzymy rich data model (nasze encje posiadają powiązania do innych encji a nie jedynie ich identyfikatory) i mamy powiązania do generycznych danych, np. kraje (których nie ma sensu rozpraszać). W takim przypadku jak na razie polecane jest mapowanie "nieobiektowe" takiej relacji, tabele krajów przechowywać na jednej z partycji a do szybszego dostępu do niej trzymać ją w cache w pamięci. W przyszłości planuje się zrobić pewnego rodzaju mechanizm replikacji, który to umożliwiałby spójne przechowywanie takiego typu danych w każdej z partycji a dzięki temu możliwe będzie skorzystanie z dobrodziejstw baz danych (ograniczenie foreign key) oraz Hibernate (rich data model).
Przewiduję się dodatkowo wiele problemów z organizacją/reorganizacja danych, aby przygotować je na rozproszenie. Dotyczy to nie tylko inicjalnego rozproszenia, ale co gorsza podziału istniejących juz partycji. Do tego celu stworzony został mechanizm virtual shards, który jest warstwą pośrednią pomiędzy Hibernate Shards a instancjami baz danych czyli partycjami fizycznymi. Podział na partycje wirtualne powinien być dokonany ze względu na wymagania biznesowe i brać pod uwagę "przyszłe potrzeby" systemu. Wiele partycji wirtualnych może początkowo zostać zbindowanych do pojedynczej partycji fizycznej. W razie potrzeby, w dalszej fazie istnienia systemu partycja fizyczna może zostać "przepięta" do dedykowanej dla siebie partycji fizycznej.
Wniosek z tej części prezentacji jest taki, aby próbować wszystkiego, by uniknąć rozpraszania swoich danych. Jednak gdy osiągniemy punkt, w którym nic więcej nie da się zrobić i pozostaje nam jedynie fizyczne rozproszenie danych, a dodatkowo używamy Hibernate to projekt Hibernate Shards na pewno jest w stanie nam pomóc. Następnie Emmanuel Bernard przeszedł do "swojego" tematu, czyli Hibernate Search i właśnie wydawanej ksiązki "Hibernate Search in Action", której jest współautorem.

Tune This! - to było już 2 spotkanie tego dnia z Kirkiem Pepperdine. Po minimalnym wstępie omawiającym miejsca, które mogą mieć negatywny wpływ na wydajność: aplikacja, JVM/OS, hardware oraz paru ogólnikowych wskazówkach związanych z wydajnością, przeszliśmy do konkretnych przykładów. Kirkiem Pepperdine przygotował aplikację, a widownia na podstawie różnych miar zużycia zasobów i wyników z profilera miała ocenić co jest przyczyną słabej wydajności aplikacji. W pierwszym przypadku mieliśmy do czynienia z bardzo dużym obciążeniem procesora. Proces javy nie był głównym konsumentem CPU, a najbardziej obciążał sam system operacyjny. Profiler wskazywał, ze większość wątków było w stanie "wait". Wszystko to oznaczało, że nasza aplikacja ma problemy z lockami. Dopiero po wysunięciu takiej propozycji Kirkiem Pepperdine pokazał nam kod źródłowy aplikacji i faktycznie, ze względu na niewłaściwe użycie synchronized aplikacja działała tak wolno. Po usunięciu problemu, czas działania aplikacji był juz dużo lepszy, jednak tym razem zużycie CPU przez proces java było bardzo wysokie. Powodem tego może być aplikacja sama w sobie lub też działanie GC. Z powodu braku czasu Kirk Pepperdine nie zaprezentował hpjmeter, które to uważa za wyśmienite narzędzie do analizy działania GC, a jedynie pokazał nam w jaki sposób za pomocą HPROF znaleźć, w którym miejscu kodu, aplikacja tworzy na stercie obiekty zajmujące największą ilość pamięci. Jego przykład był dość banalny i błąd polegał na bezsensownym tworzeniu obiektów typu String.

czwartek, 22 stycznia 2009

Visual VM oraz Hibernate Tools

Kolejną sekcją były prezentacje: Tools in Action

VisualVM - new extensible monitoring platform
- tak na prawdę nie ma o czym pisać, po prostu zostało przedstawione narzędzie i jakiś tam mały przykład. Prezentacja bardzo podobna do tej autorstwa Adam Dudczaka http://www.jug.poznan.pl/wp-content/uploads/2008/10/jmx.pdf. Nawet jeśli Kirk Pepperdine przedstawił jakieś tips'n tricks ja tego nie wyłapałem.

Making full use of Hibernate Tools - zaczęło się od przestrogi: "Don't overdo it". Max Rydahl Andersen pokazał działanie Hibernate Tools w JBoss Developer Studio. Podpowiadanie kodu w zapytaniach HQL i plikach .hbm, odpalanie zapytań bezpośrednio z IDE oraz duża ilość wizzardów do generacji różnych artefaktów wzbudziło zainteresowanie. Nie dało się ukryć, że nie wiele osób z widowni na bieżąco śledzi co się dzieje w Hibernate Tools.

Omówiono bardziej szczegółowo mechanizm generowania artefaktów za pomocą Hibernate Tools, a cały schemat jest przedstawiony na rysunku poniżej.


Centralną część stanowi meta model reprezentowany przez klasę org.hibernate.Configuration, i który to może zostać zbudowany na podstawie różnych źródeł: pliki .hbm, klasy java z adnotacjami (Hibernate lub JPA), połączenie JDBC do baza danych . Następnie meta model jest "obrabiany" za pomocą odpowiedniego exportera, który może generować określone artefakty, np. pliki .hbm, klasy JAVA z adnotacjami, schemat bazy danych oraz jego dokumentacje. Osobiście spodobała mi się wbudowana możliwość użycia biblioteki freemarker jako exportera - wystarczy jedynie stworzyć odpowiedni template. Dodatkowo Hibernate Tools daje możliwość dostępu do klas użytkownika z poziomu template, dzięki temu bardziej złożona logika generowania może zostać zawarta w javie a nie bezpośrednio w template.

Reszta prezentacji została poświęcona na różne aspekty związane z konfiguracją odczytywania meta modelu z bazy danych oraz mapowanie go do modelu Hibernate (reverse engineering strategy). Dostępna out-of-box strategia jest bardzo mocno konfigurowalna (nawet per tabela/kolumna), ale jeśli pojawią się specjalne wymagania to zawsze można stworzyć własną implementację.

Wszystko te mechanizmy są dostępne w JBoss Developer Studio lub JBoss Tools(plugin do eclipse) w postaci wizzardów, ale takze z poziomu anta. Max Rydahl Andersen zapewniał, że wszystko to co można "wyklikać" w JBoss Tools jest jednocześnie dostępne jako taski ant.

środa, 21 stycznia 2009

Test Driven Development

Prezentacja Test Driven Development to moja pierwsza prezentacja w stylu randori.
W swojej pracy zawsze staram się stosować TDD i bardzo liczyłem, że zobaczę jak wygląda warsztat TDD profesjonalisty.
Dave Nicolette przygotował zestaw user-stories opisujące wymaganie funkcjonalne działania bankomatu. Cały developement odbywał się zgodnie z Pair programming.

Wszystko to wyglądało bardzo obiecująco, ale ze względu na brak ochotników do pierwszego user-story na scenę została wywołana jedyna dziewczyna z sali. Niestety nie miała pojęcia o TDD, nie znała JUNIT oraz Eclipse (miała zawodowo korzystać z NetBeans) a do tego stres oraz obsługa cudacznej klawiatury Mac, wszystko to spowodowało, że pierwsze user-story zostało zrobione po prawie 45 minutach!.
Dave Nicollette próbował pomagać "ochotniczce" i jednocześnie przekazać trochę informacji pozostałym uczestnikom, którzy albo się nudzili albo dawali nogi na inne trwające równolegle prezentacje.
Po tym falstarcie było już trochę lepiej...

W wielkim skrócie w TDD chodzi o to, aby tworzenie kodu było wymuszane pisanymi testami, a to, że jednocześnie walidujemy swój kod jest pewnym "pozytywnym skutkiem ubocznym".
TDD sprawdza się szczególnie dobrze jak nasze wymagania są opisane za pomocą use-case i user stories.
Oczywiście po stworzeniu kodu, nie usuwamy testów, które posłużą nam do testowania regresyjnego.
Jeśli pomimo tego na produkcji zostanie znaleziony błąd to są (powinny być) 2 możliwości:
  • jeśli testy nie przechodzą, to taki kod nie powinien pojawic się na produkcji
  • jeśli testy przechodzą, to oznacza, że nasze repozytorium nie zawiera testu testujący taki przypadek

Oprócz podstaw opisujących pojedynczą iteracje w TDD (napisz test, upewnij się, że test nie przechodzi, napisz minimalną ilość kodu aby test przeszedł, zrób refactor) przedstawiono dodatkowe wskazówki i wnioski:

  • stosując TDD nie powinno się tworzyć, żadnego kodu jeśli test tego "nie wymusza"
  • powinien zostac stworzony test dla każdej reprezentatywnej ścieżki
  • powinno się testować zarówno HAPPY PATH jak i EXCEPTION PATH
  • nie można wpadać w obsesje związana z miarą pokrycia kodu testami
  • nie jest niczym niezwykłym jeśli rozmiar kodu testów jest 10 razy większy niz kodu aplikacji
  • z technicznego punktu widzenia kody testów powinny być traktowane jak kod produkcyjny

W pewnym momencie sam miałem przyjemność "pokodować" na scenie, co było niezapomnianym przeżyciem.

poniedziałek, 19 stycznia 2009

REST In Peace

Pierwsza prezentacja w ramach University to:
REST (in peace) with Java.

Pomimo tego, że już uprzednio spotkałem się z terminem "REST", kilkanaście razy pojawił się przy okazji innych tematów oraz wysłuchałem 2 prezentacji na ten temat, stwierdziłem, że zawsze warto posłuchać.

Cała prezentacja została podzielona na 2 części: opis czym jest REST oraz opis stworzonego przez Marca Portiera i Steven Noelsa frameworku Kauri. Opis teoretyczny, pomimo że prowadzony w niezbyt typowej formie: uczeń dopytuje nauczyciela czym jest REST, bardzo mi się spodobał, a szczególnie przypadło mi do gustu trafne porównanie REST do stylu architektonicznego(np. gotyk) a nie do konkretnej architektury. Zaczęło się od paru słów na temat definiowania czym jest REST i czemu termin ten jest tak mało zrozumiały:

  • REST jako twór opisany przez Fieldinga jest zbyt mało konkretny i pragmatyczny, nie zawiera praktyk, na których można by się wzorować
  • większość dokumentów opisujących REST mówi tylko o tym czego nie powinno się robić, czego lepiej unikać - ale z 2 strony nie opisują co trzeba zrobić aby stworzyć poprawny system oparty o REST
  • REST jako abstrakcja, której nie można kupić
Przy okazji polecono po raz kolejny pozycję:
RESTful Web Services, którą zachwalają chyba wszyscy specjaliści od REST.

Jeśli chodzi o Web Services to sprawa wygląda z teoretycznego punktu widzenia bardzo prosto:

  1. Należy określić co chcemy z danym "bytem" zrobić. Intencja ta może zostać wyrażona za pomocą: metody HTTP (GET, POST, PUT, DELETE) , URI np. GET /path?method=removeThat lub treści przesyłanego komunikatu np. informacja zawarta w komunikacie SOAP
  2. Należy określić na jakim "bycie" wykonujemy daną operację. W tym przypadku informacja ta jest określona albo w URL lub w treści komunikatu, np. w komunikacie SOAP

Jest to analogiczne do świata programowania obiektowego - musimy wiedzieć na jakim obiekcie wywołać jaką metodę.
W ten sposób dochodzimy do 2 a praktycznie do 3 typów Web Services:

  • RPC - w których to sam komunikat zawiera informację na jakim bycie oraz jaką operację wykonujemy - typowe podejście SOAP z pojedynczym URI jako endpointem "rozrzucającym" do dalszych warstw a wszystkie komunikaty przychodzą metodą POST
  • RESTful/Resource Oriented - operacja do wykonania jest wyrażona za pomocą metody HTTP a "byt", na którym wykonujemy działanie jest określony w URI
  • Hybryda - różne połączenia cech typów przedstawionych powyżej

Następnie przedstawione zostały 4 główne koncepcje, na których opiera się REST:

  • RESOURCE - czyli czym jest zasób: byt, który możemy wyróżnić w systemie, nazwać go, na którym możemy wykonać operację i dla którego istnieje reprezentacja) oraz rodzaje zasobów
  • URI - jako adres oraz jednocześnie nazwa zasobu, URI powinny być: naturalne, przewidywalne, unikalne, zgodne z przyjętą strukturą (hackable!). Dany zasób może mieć więcej niż 1 URI, ale trzeba pamiętać o canonical variant
  • REPRESENTATIONS - czyli reprezentacja zasobu: dane/bajty, które możemy zobaczyć jak spytamy się o dany zasób. Reprezentacja może też być związana z danymi przesyłanymi od klienta do serwera, np. przy operacji PUT lub POST. Reprezentacje pojedynczego zasobu mogą się różnić ze względu na format i język, a negocjacja odbywa się przy pomocy nagłówków przesyłanych przez klienta: Accept oraz Accept-language. W odpowiedzi serwer może określić czym różnią się poszczególne reprezentacje za pomocą nagłówka: Vary. Ma to bardzo duże znaczenia dla cachowania odpowiedzi, a odpowiedź z nagłówkiem: Vary: * nie jest cachowana. Z praktycznego puktu widzenia negocjacja zawartości (Content Negotiation) może oprócz przekazywania nagłówków: Accept oraz Accept-language opierac się na przekazaniu pewnych wartości w parametrze żądania HTTP, np. ?hl=nl (@google) lub bezpośrednio w URI, np. /path/resource-name.nl.html. Prowadzi to do relacji 1URI na 1 reprezentacje co nie jest stricte zgodne z REST, ale ma być bardzo praktyczne.
  • LINKS - czyli prezentacja URI w reprezenatacji, linki w swojej naturze są miękkimi referencjami (soft-references)

oraz 4 właściwości na których opiera sie REST Oriented Architecture:

  • ADDRESSABILITY - czyli użycie URI do dostępu do dowolnych zasobów. Jako przykład braku tej właściwości został przedstawiony "tradycyjny" sposób uzyskania dostępu do określonego elementu na liście wyszukiwania: wejdź na stronę domową, uzupełnij kryteria wyszukiwania w taki i taki sposób, element szukany to 3 od góry na 2 stronie. Do zalet związanych z ADDRESSABILITY zaliczamy: łatwość dystrybucji adresów zasobów - URI mogą być publikowane wszędzie (email, strona HTML, a nawet przekaz ustny); "link love"; wykorzystanie URI w usługach związanych z bookmarking i tagging; zasoby (lub w szczególności ich reprezentacje) mogą być cachowalne; mogą być składowymi systemów typu Mash-up, byc "obrabiane" przez usługi tłumaczeniowe i walidacyjne (Translation & Validation Services). Dodatkowo pojawiły się takie hasła jak : Chaining, Aggregation, Scraping, które nie do końca rozumiem w tym kontekście.
  • STATELESSNESS - bezstanowość, która mówi, że każde żądanie musi zawierać w sobie wystarczającą ilość danych aby mogło byc w pełni obsłużone bez korzystania z kontekstu przechowywanego na serwerze(HTTP request isolation). Podejście takie ma wiele zalet, a oprócz tych wymienionych przy Addressability dochodzi jeszcze większa skalowalność oraz ułatwiony load-balancing. W jaki sposób radzić sobie ze stanem w aplikacjach opartych o REST jest przedstawione poniżej.
  • CONNECTEDNESS - cecha związana z koncepcją LINKS, która określa, że reprezentacja może zawierać link/linki (soft-references). Linki te doprowadzą nas do zasobów, których reprezentacje mogą zawierać linki do kolejnych zasobów itd. Idea jest prosta: publikujemy pojedynczy URL a klient nawiguję do kolejnych linków zawartych w reprezentacjach poszczególnych zasobów i w ten sposób "uczy"się naszej aplikacji.
  • UNIFORM INTERFACE - określa sposób interakcji klienta z serwerem. Objemuję nie tylko metody HTTP żądania, ale także nagłówki żądania/odpowiedzi oraz kody tych ostatnich. Jeśli chodzi o odpowiedzi serwera powinno się wykorzystywać nagłówki oraz kody odpowiedzi zdefiniowane w HTTP, ale jednocześnie unikać własnych nagłówków X-Headers i Cookies aby wszystko było w myśl zasady: "Principle of least surprise"

Poszczególne metody HTTP wraz z ich semantyką to:

  • GET - pobranie reprezentacji zasobu. GET powinno być operacją bezpieczną oraz idempotentą, czyli jej wykonanie nie powinno zmieniac stanu na serwerze i do tego niezaleznie od tego ile razy wykonano tą metodę. Dopuszcza się pewne "delikatne skutki uboczne" wynikające z wykonania metody: podbicie liczników, odłożenie się logów. Bardzo podobała mi się anegdota o narzędziu (nie pamiętam nazwy), które w tle pobierało strony, do których istniały linki na aktualnie oglądanej stronce. Sama idea wygląda pięknie: w momencie kiedy czytam zawartość danej strony, w tle automatycznie ściągają się stronki, do których aktualnie czytana strona posiada linki. Problem polegał na tym, że bezwiedne podążanie za zawartymi na stronie odnośnikami potrafi nieświadomie zmienić stan na serwerze. Wyobraźmy sobie co się stanie jesli na czytanej przeze mnie stronie znajduję się link a jego wywołanie: GET /entry?id=12&method=delete usuwa zasób z serwera ...
  • HEAD - to samo co GET, ale bez contentu
  • OPTIONS - określa jakie metody są dostępne dla danego zasobu, powinna być bezpieczna i idempotenta
  • DELETE - usuwa zasób, idempotentna. Pomimo, że odpowiedz serwera na ponowne wykonanie operacji DELETE na uprzednio usuniętym zasobie może róznić się od odpowiedzi na pierwsze(faktyczne) usunięcie, z punktu widzenia serwera stan się nie zmienił.
  • PUT - uaktualnia, choć bardziej poprawne/dokładne jest stwierdzenie, że PUT nadpisuje stan istniejącego zasobu ignorując jego stan aktualny. PUT może być używany do tworzenia nowych zasobów w sytuacji kiedy to klient decyduję pod jakim URI dany zasób będzie "rezydował". Metoda idempotentna.
  • POST - pomimo tego, że wiele dokumentów opisuję POST jako metodę służącą stricte do tworzenia zasobów nie jest to do końca prawda, ponieważ w tym przypadku "Sky is the Limit". Metoda nie jest (nie musi być) ani bezpieczna ani idempotentna

Następnie przedstawione zostały podstawowe kroki przy analizie oraz projektowaniu systemów opartych na REST. Obawiałem się, że zostanie przedstawiony przykład w stylu biblioteki, ale Marc Portier oraz Steven Noels pokazali kilka nieznanych mi chwytów:

  • Tworzenie zasobów może zostać zrealizowane za pomocą PUT w przypadku gdy klient decyduję pod jakim URI ma dany zasób istnieć - serwer odpowiada kodem 200, lub za pomocą POST - serwer odpowiada kodem 201 oraz nagłówkiem location header, którego wartością jest adres nowo utworzonego zasobu
  • POST moze być wykorzystywany do updatowania stanu zasobu. Takie podejście pozwala uniknąć przepisywania aktualnego stanu zasobu oraz umożliwia inkrementalne uaktualnianie stanu zasobu (PUT nadpisuję stan)
  • W pewnych przypadkach POST może zostać użyte do wywołania metod o innej semantyce, np. POST uri?method=put/delete , ale podejście z 1 endpointem obsługującym wszystkie żądania jest w świecie REST niepoprawne

Jedną z właściwości REST jest bezstanowość, która to często jest "nie do przełknięcia" dla wielu osób. Znaczna ilość jeśli nie większość aktualnie działających systemów korzysta z mechanizmu stanowości, przy czym stan ten zazwyczaj jest przechowywany po stronie serwera.
Statelessness wcale nie oznacza, że stanowości nie ma, a jedynie to, że klient jest odpowiedzialny za utrzymywanie stanu a nie serwer. Dodatkowo wyróżniono 2 typy stanów "Conversational State" jako ten "bad" oraz "Application state" jako ten "good", ale jak na razie nie potrafię dobrze złapac różnicy czym jeden różni się od drugiego. W końcu pokazano w jaki sposób pragmatycznie radzić sobie z przechowywaniem stanu po stronie serwera aby pozostać w świecie REST. Tym rozwiązaniem są tymczasowe zasoby (Temporary Resources). Scenariusz wygląda zazwyczaj w taki sposób:

  • Klient wysyła żądanie utworzenia tymczasowego zasobu (z jego punktu widzenia nie jest to zasób tymczasowy), np.

    POST /temp/upload HTTP/1.1

    Serwer odpowiada adresem pod którym znajduję się nowo utworzony zasób, np.

    HTTP 1.1 201 Created
    Location: /temp/upload/666
  • Teraz juz możemy ładować do utworzonego przed chwilą kontenera kolejne zasoby, np.
    PUT /temp/upload/666/cv-jb.pdf HTTP/1.1
    Host: example.com

    <DATA>

    i 2 wywołanie:

    PUT /temp/upload/666/cl-jb.pdf HTTP/1.1
    Host: example.com

    <DATA>
  • Jednak to klient musi trzymać "referencje" do swoich zasobów:

    PUT /application/20090120/jb HTTP/1.1
    Host: example.com

    {name:”James Bond”,
    mugshot: “/temp/upload/666/cl-jb.pdf”,
    resume: “/temp/upload/666/cv-jb.pdf” }

W bardzo podobny sposób można zrealizować (zasymulować) atomowość pomiędzy wywołaniami. W swoich założeniach HTTP nijak ma się do XA. Rozwiązaniem jest potraktowanie transakcji jako zasobu, a całe przetwarzanie może wyglądać w następujący sposób:

  • Najpierw tworzymy sobie transakcje jako zasób:

    POST /tx/transfer HTTP/1.1
    Host: example.com

    Serwer odpowiada adresem pod którym znajduję się nowo utworzony zasób/transakcja:

    201 Created
    Location: /tx/transfer/933

  • Następnie ładujemy poszczególne operację:

    PUT /tx/transfer/933/account/912-883-32 HTTP/1.1
    Host: example.com

    balance=300

    oraz

    PUT /tx/transfer/933/account/555-283-12 HTTP/1.1
    Host: example.com

    balance=300
  • W końcu finalizujemy transakcję:

    PUT /tx/transfer/933 HTTP/1.1
    Host: example.com

    committed=true

Wykorzystanie mechanizmu zasobów tymczasowych wiąże się z też z pewnymi konsekwencjami: musimy utworzyć kontener (zasób tymczasowy), przechowywać w jakiś sposób jego składowe (na dysku/w pamięci/w BD ?) oraz zaimplementować mechanizm usuwania kontenerów i ich zawartości. Jak to zazwyczaj bywa: No free launch.

Ostatnią sprawą jaką poruszono jest kolejkowanie (Queueing) żądań.
Tradycyjne cachowanie sprawdza się jeśli żądanie jest typu read. W innym przypadku można posłużyć się "Write Accept" Proxy, które na dane żądanie odpowiada jedynie kodem HTTP 202 Accepted. Żądanie klienta nie jest obsługiwane w "real-time", ale jest kolejkowane do dalszego przetwarzania. Dodatkowo może zostać zwrócony nagłówek Location, zawierający adres do zasobu w celu późniejszego sprawdzenia statusu przetwarzania żądania. Podejście takie sprawdza się szczególnie w przypadku systemów "głosuj teraz" ("vote now") i pozwala sobie poradzić z krótkotrwałą falą żądań.

Później został przedstawiony framework Kauri, a prezentacja nabrała bardziej marketingowego charakteru.

sobota, 17 stycznia 2009

Devoxx 08

W dniach 08-12 grudnia 2008 miałem przyjemność wzięcia udziału w największej konferencji poświęconej Java w Europie DEVOXX.
Na blogu będę zamieszczać opisy/relacje z każdej prezentacji na której byłem aby wiedza mi za szybko z makówki nie uciekała...

Ale najpierw 3 słowa wstępu.
Dla mniej zainteresowaych, Devoxx jest to kontynuacja odbywającej się już od paru lat konferencji JavaPolis. Ze względu na problemy z prawami do używania zastrzeżonego przez Suna słowa "Java" najpierw JavaPolis zostało przechrzczone na Javoxx, ale to znowu zakwestionowali prawnicy Suna i w końcu staneło na Devoxx.
Konferencja organizowana jest przez belgjski JUG, na którego czele stoi charyzmatyczny lider Stephan Janssen, który jest na liście speakerów konferencji GeeCON.

Od jutra zamierzam opisywać dzień po dniu wszystkie prezentacje, na których byłem...

Aaaa...najważniejsza sprawa: możliwość wzięcia udziału w Devoxx zawdzięczam Poznan JUG

Myśl przewodnia

Blog ten stworzony został w dużej mierze na moją właśną potrzebę...
Piszę bo zapominam...