rest api design and construction with java ee - pages from my work diary
DESCRIPTION
When developers use JPA, Bean Validation and other features with JAX-RS, they start off with only a faint indication of the problems they would run into. There is more than one way to use these in conjunction with each other. Some are good, some are okay, and some are downright awful. This talk will explore practical solutions and patterns for REST APIs built with Java EE. Attendees will learn of contextual rules so they're more effective at constructing and designing JAX-RS based REST APIs.TRANSCRIPT
REST API DESIGN AND CONSTRUCTION WITHJAVA EE
PAGES FROM MY WORK DIARYBy / Vineet Reynolds L P @VineetReynolds
Senior Software EngineerJBoss Developer Experience, Red Hat
ABOUT THE SPEAKEREngineer in the JBoss Developer Experience teamCurrently, contributor to :JBoss Forge 1.x and 2.0AngularJS scaffolding for JBoss ForgeJDF quickstartsTicketMonster
Others:Arquillian (GlassFish, WebLogic container adapters)
AGENDAA 10 minute guide to RESTJPA and JAXRSWriting a datadriven REST API with JPA and JAXRSComposite keysCyclic and bidirectional relationshipsLazyfetchingClass hierarchies (java.sql.Date)Bean Validation and error reporting
A 10 MINUTE GUIDE TO RESTNote this is really a 10 minute guide. This is not a deepdive
into .Roy Fielding's doctoral dissertation
WHAT IS REST ?REST = REpresentational State TransferIt is an architectural style.It describes the design of a hypermedia system (the web).It intends to conceptualize how a well designed Webapplication should behave.
EXAMPLESGET /events returns all Events.GET /events/1 returns the Event with ID 1.POST /events creates a new Event.PUT /events/1 updates the Event.DELETE /events/1 deletes the Event.
EXAMPLESAssuming GET /events/1 returns
What kind of updates can be made in a PUT ?Session names could be updated.New Sessions could be added.Existing sessions could be deleted.
"name" : "JUDCON", "sessions" : [ "name" : "Aerogear" , "name" : "Infinispan" , "name" : "OpenShift" , "name" : "WildFly" ]
JAX-RS + JPA + EJB : THE BASICSLet's expose a JPA entity as a REST resource.
@Entitypublic class Event
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id = null;
@Version private int version = 0;
@Column private String name;
// Getters, Setters, equals() and hashCode() ...
JAX-RS + JPA + EJB : THE BASICSCREATING NEW EVENTS
POST /simpleevent/rest/events HTTP/1.1Host: localhost:8080ContentType: application/json;charset=UTF8...
"name":"JUDCON"
HTTP/1.1 201 CreatedLocation: http://localhost:8080/simpleevent/rest/events/1...
JAX-RS + JPA + EJB : THE BASICSFETCHING ALL EVENTS
GET /simpleevent/rest/events HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*...
HTTP/1.1 200 OKContentType: application/json...["id":1,"version":0,"name":"JUDCON"]
JAX-RS + JPA + EJB : THE BASICSFETCHING ONE SPECIFIC EVENT
GET /simpleevent/rest/events/1 HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*...
HTTP/1.1 200 OKContentType: application/json...
"id":1,"version":0,"name":"JUDCON"
JAX-RS + JPA + EJB : THE BASICSUPDATING AN EVENT
PUT /simpleevent/rest/events/1 HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*..."id":1,"version":0,"name":"JUDCON India"
HTTP/1.1 204 No Content...
JAX-RS + JPA + EJB : THE BASICSDELETING AN EVENT
DELETE /simpleevent/rest/events/1 HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*...
HTTP/1.1 204 No Content...
COMPOSITE KEYS ARE NOT FUNAddressability suffers. Matrix parameters will help with
composite keys.
// Key for event = name, locationGET /events;name=JUDCON;location=India
But ...They're not a standard. Just a W3C design note.Lack of client support (but improving). Among popular JSlibraries, only Dojo, AngularJS, Backbone.js has support.If cheap, establish a mapping scheme for composite keys.
RELATIONSHIPSHandling modifications to relationships in JPA entities can get
difficult.
CREATE A NEW EVENT
POST /bidievent/rest/events HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*...
"name":"JUDCON","location":"Bangalore"
HTTP/1.1 201 CreatedLocation: http://localhost:8080/bidievent/rest/events/1...
FETCH AN EXISTING EVENT
GET /bidievent/rest/events/1 HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*...
HTTP/1.1 200 OKContentType: application/json..."id":1,"version":0,"name":"JUDCON","location":"Bangalore","sessions":[]
CREATE A NEW SESSION
POST /bidievent/rest/sessions HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*
"sessionName":"Aerogear", "speaker":"Sebastian", "event":"id":1, "version":0, "name":"JUDCON", "location":"Bangalore", "sessions":[]
HTTP/1.1 201 CreatedLocation: http://localhost:8080/bidievent/rest/sessions/2...
FETCH THE NEWLY CREATED SESSION
GET /bidievent/rest/sessions/2 HTTP/1.1Host: localhost:8080Accept: application/json, text/plain, */*...
HTTP/1.1 500 Internal Server ErrorContentType: text/html;charset=utf8
JBWEB000065: HTTP Status 500 org.codehaus.jackson.map.JsonMappingExceptionfailed to lazily initialize a collection of role: com.example.bidievent.modelno Session (through reference chain: com.example.bidievent.model.Session["event"
WHAT JUST HAPPENED?Event.sessions is lazily fetched.During serialization, the proxy does not find an activesession.Let's try and solve this crudely, using eager fetching ...
"id":2,"version":0,"sessionName":"Aerogear","speaker":"Sebastian","event":["id":2,"version":0,"sessionName":"Aerogear","speaker":"Sebastian","event":["id":2,"version":0,"sessionName":"Aerogear","speaker":"Sebastian","event":...// This never terminates
HOW DO YOU SOLVE THIS ?If you can, do not expose collection members of JPA entitiesdirectly.Use subresources for dependent collections (1:M).Use a mapping layer. Create Representation classes to mapJPA object graphs.The mappings translate RESTful actions into operations inthe domain model.Or, use different representations of the same JPA entity fordifferent operations (not a good idea).One to create an entity with collections, another to updateonly the entity.Model collections as a separate resource.Makes sense for M:N relationships.
JAVA.SQL.* HATES YOUConsider the following
During serialization, startTime's type is java.sql.Time.Given a value of 2:00 PM IST, the field will be serialized as"startTime":"14:00:00".When deserializing back, "startTime":"19700101T14:00:00.000+05:30" should be supplied.
@Temporal(TemporalType.TIME)java.util.Date startTime;
JAVA.SQL.* HATES YOUBefore, we discuss solutions, let's talk about:
ISO8601 vs Unix timestamps. Which one to use ?The former is almost always better.Date, time and timestamp formats. Omit unnecessary info inISO8601 formats (like time values for Dates).
JAVA.SQL.* HATES YOULessons learnt Be aware of the actual types of the objects duringserialization and deserialization.Constrain the actual types and hence theserializers/deserializers used.When writing custom serializers, design them for classhierarchies.Think of the equals() contract, but for serialized forms.And of course, avoid java.util.Date/Calendar. Use specifictypes in JodaTime and eventually JSR310 (Java 8).Using a specific type binds a serializer + deserializer to thefield.
BEAN VALIDATION AND VIOLATION REPORTINGResponses for Constraint violation errors on JPA entities arenot standardized.By default, HTML responses will be provided if noExceptionMappers exist forConstraintViolationExceptions.Bean Validation for request parameters is available outofthebox, but requires activation.RESTEasy has one format, Jersey has another.
QUESTIONS ?