2009. december 27., vasárnap

RESTful web szolgáltatások Jersey-vel

Az általam is már többször részletezett, nehézsúlyú, bonyolult SOAP alapú web szolgáltatások és API-k összetettségét ellensúlyozva jelent meg Roy Fielding PhD dolgozata alapján a REST fogalom.

Most egy egyszerű kliens-szerver alkalmazást kellett írnom, ahol különböző eseményeket kellett küldenem a szerver oldalra. Az RMI-t elvetettem, hiszen bonyolultabban vezethető át a protokollja a tűzfalon, és csak Java-ból használható, a SOAP-ot ágyúnak éreztem, képbe került még az XML-RPC, mely nem szabványos, így választásom a JAX-RS-re esett, mellyel Java-ban lehet RESTful web szolgáltatásokat építeni.

A JAX-RS egy specifikáció (JSR 311), melynek jelenlegi legfrissebb verziója az 1.1-es, és több implementációja is létezik, köztük a referencia implementáció, a Jersey, melynek legutolsó verziója a 1.1.4.1.

A REST egy szoftver architektúra, mely létező, bevált protokollokra, szabványokra építkezik, ahelyett, hogy újat találjon ki. Úgynevezett erőforrásokból (resource) építkezik, és mindegyik ilyen erőforrásnak van egyedi azonosítója, és ezeket össze is lehet linkelni. A kliens ezen erőforrásokat kéri le, azok azonosítója alapján, de lehetőség van új erőforrás hozzáadására, módosítására, törlésére is. A kérések egymástól függetlenek, nem létezik munkamenet (session) fogalom, a kliens mindig az adott erőforrás egy adott állapotát kapja vissza. (Emiatt egyszerűbb az architektúra, könnyen megoldható a gyorsítótárazás - cache, valamint a terhelés elosztás.) Az erőforrásokat különböző módon lehet megjeleníteni, pl. egy számsort magával a számok sorozatával, de akár egy grafikonnal. Ezen tulajdonságok alapján jött a rövidítés: representational state transfer.

A felhasznált létező szabványok az URI, mellyel az erőforrások azonosítója adható meg. Az erőforrások különböző megjelenítési módjait MIME type-pal lehet megadni, ami lehet egyszerű szöveg, html, xml, vagy manapság az egyre divatosabb JSON is, vagy speciálisabb esetekben pl. kép is. A leggyakrabban használt protokoll a HTTP, hiszen ez biztosítja a kliens-szerver architektúrát, állapotmentességet, cache-elhetőséget, különböző rétegek kialakítását (akár transzarens módon a titkosítást, lásd https), valamint ez van átengedve a legtöbb hálózati eszközön, tűzfalon. A HTTP metódusaival a CRUD műveleteket is megvalósíthatóak, a legtöbbet használt GET, POST mellett létezik a PUT és DELETE is. Ugyanúgy, ahogy a klasszikus web szolgáltatások esetében, más protokoll is választható.

A JAX-RS a JAX-WS-hez hasonlóan egyszerű POJO-kkal dolgozik, melyekre annotációkat kell használni.

A konkrét példára visszatérve, ami HTTP protokollon lett megvalósítva, a következő URL-eket definiáltam:

  • /events: GET esetén XML formátumban adja vissza az eddig elküldött eseményeket, POST esetén az eseményt várja XML formátumban, melyet elment
  • /events/14: XML formátumban adja vissza a 14-es azonosítójú eseményt

Első esetben a visszaadott XML:

<events>
 <event>
   <id>1</id>
   <date>2009-12-26T17:58:26.571Z</date>
   <message>Első esemény</message>
 </event>
</events>

Amennyiben egy eseményt szeretnénk lekérdezni, vagy felküldeni, csak az event tag tartalma használandó. Amennyiben nem XML-lel akarunk dolgozni, használható az "application/xml" helyett pl. az "application/json" MIME type is.

A RESTful web szolgáltatásomat egy Spring-es alkalmazásba próbáltam beépíteni, mely egy egyszerű NetBeans-es projekt, melynek a build folyamatát Ant vezérli. És itt jött az első meglepetés, hogy a leírások, és a teljes folyamat Maven-re van optimalizálva. A dokumentáció azt részletezi, mit kell a pom.xml-be beírni. Én jobban szeretem az olyan library-ket, amely készítői a nem Maven-t használókra is gondoltak, és legalább összeállítanak egy olyan csomagot (zip, tgz), amiben megtalálhatóak a jar-ok, azok függőségei, teszt esetek, API JavaDoc formátumban, dokumentáció és példa alkalmazások. Ezt most nekem kellett összevadásznom mindenféle kaotikus Maven repository-kből, persze nem sikerült elsőre.

Az alkalmazásba a következő JAR-okat kellett tennem: jsr311-api-1.1.jar, jersey-core-1.1.4.1.jar, jersey-server-1.1.4.1.jar (az utóbbi kettő helyettesíthető a jersey-bundle-1.1.4.1.jar állománnyal), valamint az asm-3.1.jar állományt. Mivel Spring-et használok, kellett a jersey-spring-1.1.4.1.jar is.

Először készítsük el az eseményt reprezentáló osztályt:

public class Event {
private Long id;

private Date date;

private String message;

// konstruktorok, getter/setter metódusok
}

Utána készítsük el az erőforrást reprezentáló osztályt:

@Path("/events")
@Component
public class EventResource {

@GET
@Produces({"application/xml", "application/json"})
public List listEvents() {
// DAO hívás, események betöltése
}

@GET
@Path("/{id}")
@Produces({"application/xml", "application/json"})
public Event findEvent(@PathParam("id") long id) {
  // DAO hívás, esemény betöltése
}

@POST
@Consumes({"application/xml", "application/json"}) {
public void createEvent(Event event)
// DAO hívás, esemény mentése
}
}

Látható, hogy az erőforrásban három metódust definiáltunk, rendre a következő funkciókkal: események betöltése, esemény betöltése, esemény mentése. Mindhárom a /events címen érhető el, mint az osztályon lévő @Path annotáció mutatja. Az első két funkció GET metódussal érhető el, és XML és JSON kimenetet is képes gyártani (@Produces annotáció), a harmadik POST metódussal, és képes XML és JSON bemenetet is fogadni (@Consumes annotáció). A második metódusnál figyeljük meg, hogy az URL-ben megadhatók változók is, melyre később a @PathParam annotációval hivatkozunk.

Ahhoz, hogy a kérés ki is legyen szolgálva, a web.xml-be betesszük a következő részt:

<servlet>
 <servlet-name>Jersey Spring Web Application</servlet-name>
 <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
</servlet>
<servlet-mapping>
 <servlet-name>Jersey Spring Web Application</servlet-name>
 <url-pattern>/events/*</url-pattern>
</servlet-mapping>

Valamint a Spring applicationContext.xml állományába:

<context:annotation-config/>

<context:component-scan base-package="jtechlog.restful" />

Ez eredményezi, hogy a @Component annotáció hatására a Spring beolvassa az EventResource osztályt.

Amikor böngészőből meg akartam hívni a /events URL-t a következő hibaüzenetet kaptam:

SEVERE: Internal server error
javax.ws.rs.WebApplicationException
at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:253)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:814)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:740)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:731)
at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:372)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:452)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:633)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
...

Ebből látszik, hogy a hibaüzenet nem informatív, nem mondja meg, hogy konkrétan mi is a hiba. Némi gondolkodás után rájöttem, hogy mivel az XML-t JAXB-vel próbálja legyártani, érdemes lenne tenni egy @XmlRootElement annotációt az Event osztályra. Ezután tökéletesen működött.

És most nézzük a kliens kódot, mondjuk az esemény mentésére (úgy tűnik, itt már nem a standard javax.ws.rs csomagokat kell importálni, hanem a com.sun.jersey.api.client csomagokat):

ClientConfig cc = new DefaultClientConfig();
Client c = Client.create(cc);
WebResource wr = c.resource("http://localhost:8080/restful/events");
Event event = new Event(new Date(), "Első esemény");
wr.path("events").type(MediaType.APPLICATION_XML_TYPE).post(event);

Látható, hogy ez a RESTful web szolgáltatások használata nem csak az API ismeretét igényli, hanem egy másfajta gondolkodásmódot is.

Matt Weisfeld: The object-oriented thrught process

Egy oktatás miatt olvastam el az Addison Wesley kiadásában megjelent Matt Weisfeld: The object-oriented thrught process könyvet. A harmadik kiadásnál tartó könyv megcélzott olvasóközönsége azon vezetők, tervezők, fejlesztők, akik el szeretnék sajátítani az objektumorientált (oo.) gondolkodásmód alapjait, mindezt programozási nyelv és technológia függetlenül.

Az első kilenc fejezet az oo. alapfogalmait mutatja be: objektumok, osztályok, egységbezárás (encapsulation), információrejtés elve (data hiding), újrafelhasználhatóság módjai (öröklődés és aggregáció), kiterheszthetőség, karbantarthatóság, többalakúság (polymorphism), absztrakt osztályok és interfészek, konstruktorok, metódus túlterhelés (overloading). A könyv több oldalról közelíti meg a black box technikát, mely szerint elegendő ismerni az interfészt (melyet javasolt mindig az azt felhasználó szemszögéből megtervezni, és minél szűkebbre venni, és iteratív módon bővíteni) és az ahhoz tartozó szemantikát (ő szerződésnek - contract nevezi, gyakorlatilag az API-ról és annak dokumentációjáról van szó), az implementáció maradjon rejtve. Így az osztályaink között laza kötés lesz, az implementációt változtathatjuk anélkül, hogy azt az interfészen keresztül használó osztályainkat módosítani kéne, növelve ezzel az átláthatóságot, karbantarthatóságot, robosztusságot, tesztelhetőséget. Előjönnek az oo. világban unalomig használt példák, mint a síkidom, és leszármazottjai, a kutyafajták, az autó és annak részei, és az általam is oly gyakran emlegetett alkalmazott osztály.

Ezen blokknak kicsit kilógó eleme a 6. fejezet (Designing with Objects), mely a témát egy fejlesztési folyamaton keresztül kívánja szemléltetni, a követelményelemzéstől a tervezésig (CRC kártyák használatával, UML osztálydiagrammok előállításáig). Kicsit elnagyolt rész ez, ahol kevés kitekintés van a különböző módszertanokra, valamint azok többi részére (pl. az agilis módszertanok megemlítésre sem kerülnek).

A 10. fejezet (Creating Object Models with UML) az UML osztálydiagrammjának pár elemét mutatja be, de nagyon alap szinten. Nagyon jó ötletnek tartottam a 11. (Objects and Portable Data: XML) és 12. fejezet (Persistent Objects: Serialization and Relational Databases) témáját, amivel oo-val foglalkozó könyvek keveset írnak. Sajnos amennyire jó a témaválasztás, annyira rossz a kivitelezése. Az író az eddigi Java példákról átváltott a .NET-es példákra, feltehetőleg nem ismerte a JAXB technológiát, melynek C#-os változatát ismerhetjük meg ebben a fejezetben, valamint a prezisztenciánál kizárólag a szerializáció lett megemlítve, és tévesen az object-relational mapping (ORM) néven a JDBC. Valódi ORM-ről szó sem esik, mint pl. a JPA, mely tényleg objektumorientált megközelítés, szemben a JDBC-vel, ami inkább procedúrális megközelítés.

A következő két fejezet (Objects and the Internet, Objects and Client/Server Applications) az elosztott alkalmazásoknál alkalmazott oo. technológiákat ismerteti, először a JavaScript oo. lehetőségeit, majd részletesebben a CORBA-t, és felületesen a web szolgáltatásokat. Két egyszerű kliens/szerver alkalmazás is bemutatásra kerül, az első szerializálva viszi át az adatokat, a második XML-ben, TCP socket-en keresztül.

Az utolsó fejezet (Design Patterns) a tervezési mintákról szól, a tervezési minták három csoportjából (creational, structural és behavioral) mutat egyet-egyet (singleton, adapter és MVC), áttekintő szinten.

A könyvet így azoknak tudom ajánlani, akiknek az oo. fogalomrendszer nem ismert. Így kiváló lehet pl. azoknak, akik még csak most tanulják ezeket (pl. főiskolai vagy egyetemi tárgy ajánlott olvasmányának), vagy akik most váltanak struktúrális programozásról oo. programozásra (pl. COBOL-ról, PL/SQL-ről, C-ről). A könyv egy hétvége alatt elolvasható. Azoknak azonban, akiknek az ebben a bejegyzésben leírt fogalmak ismertek, azaz már legalább egy fél éve programoznak valamilyen oo. programozási nyelven, a könyv nem fog sok újdonságot mutatni. Demonstráióként főleg UML diagrammok és Java programok szerepelnek, de olyan egyszerűek, hogy programozási ismeretek nélkül is megérthetőek (és a legtöbb példa átirata is szerepel a fejezet végén C# és VB nyelven). Sajnos a Java kód minősége nem éppen megfelelő, gyakran a legalapvetőbb konvenciók sincsenek betartva (pl. váltózó vagy metódus nevek kisbetűvel).

Nagy várakozással vettem kezembe a könyvet, mert ugyanezen sorozatban (Developer's Library) jelent meg egyik kedvenc könyvem (Agile Java Development with Spring, Hibernate and Eclipse), és valami hasonlót vártam itt is. Összességében elmondható, hogy a könyv első kilenc fejezete kiváló, szájbarágós, bevezető anyag az oo. gondolkodásba, sok ismétléssel, így gyakorlatilag beleveri az emberbe a tudást. A többi fejezet témája is nagyon jó ötlet, de nem jól megvalósított. Az UML-ből több is belefért volna (ugyanennyi oldalon az UML Distilled sokkal több információt ad át), az egyéb fejlettebb oo. technológiákból elegendő lett volna a szerializáció, XML-binding, az ORM és a CORBA (sőt, inkább az RMI) elméleti alapjait kifejteni, mintsem a valóságtól távol álló tutorial szagú kódrészleteket mutatni. Ezekre inkább ezért más könyveket javaslok.