CS639 Class 23
REST Services: more examples
paper handout of pg. 180 of RESTful Web
Services, by Richardson and Ruby, showing URI templates for their bookmark
service
orderService Software Architecture, Pull
technology vs. notifications
So far we have been
concentrating on orderServices REST services, since that’s what is important to
clients, and also JAXB, which is more generally useful than just this one app.
Now look at it as a decent
REST service implemention, and get ready for pa4, which expands it to handle
payment and flexible options.
C:\CS\CS639\ORDERSERVICE\SRC
├───functionaltest
│ ...
├───main
│ └───java
│ └───com
│ └───restbucks
│ └───ordering
│ ├───activities
│ ├───domain
│ ├───repositories
│ ├───representations
│ └───resources
└───test
...
unit tests
orderService packages
resources:
where the @GET, @POST, etc., handlers
are for each resource. Minimal REST-related code is here. The methods call into the main app code to do
whatever the app is meant to do.
representations:
where OrderRepresentation is, and any other POJO needed just for external
communication.
domain:
where Order is, also Item, used by both Order and OrderRepresentation, and the
enums for all the options. These are the
POJOs used by the app internals in activities and repositories.
activities:
the heart of the app. Implementations of the actions this app wants to do for
each incoming request. Also known as the
“business layer” or “service layer”
repositories:
code for accessing database tables or similar holders of long-term state for the
app. Also known as the Data Access layer, or DAO layer, for data access
objects.
Web apps (including
orderService) generally have three layers:
·
Presentation
layer: here resources: interacting with a user or external agents
·
Business/Service
layer: here activities: the heart of the app, doing what it knows how to do
·
Data Access
layer, here repositories: arranging long-term storage for app data
What is a layer here?
In a layered system, the code
in a higher layer calls methods in the next layer down, or in its own layer,
but never calls up to the layer above it.
Here resources methods call activities methods, and activities methods
call repositories methods, which just do their thing saving or retrieving data
and return. Then the activities method
returns to the resources method.
That leaves domain as not
being a layer. That is correct: domain
objects circulate in the system, carrying data.
After all, the data needs to flow from the network down into the
long-term storage when an order is created, and back up when order status is
asked for.
Note that the calls go down
the layers even in the case that the data is coming up the layers. Consider a GET for order status.
1.
It first shows up
as a call into resources at the @GET method in OrderResource.
2.
This resources
method calls into activities to get status by id.
3.
This activities
method calls into repositories to get the data from long-term storage
4.
The data is
returned from repositories to activities in an Order object.
5.
Then it is
converted into an OrderRepresentation object and returned from activities to resources
6.
Then in
resources, the GET method of OrderResource sends it off as XML.
Pull Web Interactions
Note that the user has to ask
over and over for status to be sure to find out immediately if their order is
ready. It would be nice if the system
contacted the user when the order became ready.
But HTTP is client-request driven, otherwise known as Pull technology.
Although the server’s web address is known to the client, and the server
accepts new requests any time, the client does not have the same capability.
The server gets to send to the client at the end of each client request, but
once it closes that connection, it has lost contact.
In the case that the server
must get back to the user at some “random” time, email is one option. That’s
because the user has a relationship with another server, and checks it now and
then, or has an email agent that periodically checks and signals new email. It
is strightforward to add email capability to web apps, something like adding
database access (covered in CS636).
Of course HTTP is not the
only protocol on the network. It is possible to have a TCP connection open for
longer times, say hours, and keep a conversation going between two ends. But in
practice, it’s hard to engineer this approach, because the network has
interruptions, and then you have to resynchronize the conversation. It’s better
to live within the restrictions of HTTP.
Having only Pull Requests makes call-down layering
possible
The fact that REST follows
HTTP Pull protocols makes the layering possible and practical. If the system had to notify the client when
the coffee order was ready, that would be an up-call: the change of state would
happen in the repository level, and then the repository would have to call up
to the user-communications level. But
that doesn’t happen.
The orderService package structure is reusable,
“natural” for web services apps, even web apps
So we see that this layered
software organization is not just for this particular app, but can be used for
any REST server. Or any web services
server, for that matter, SOAP or REST.
Or a web app using JDBC.
Even though JDBC can use a
long-term TCP connection, it still does only Pull actions from the client. You
might think you could hook up database triggers to JDBC, but in fact you can’t
do server-to-client notifications via JDBC.
(not
covered in class--)
The
network technology to do notifications is called a publish-subscribe service,
or “pub-sub” for short. It can be done with JMS, Java Messaging Service,
another technology of Java EE, with the help of longer-term TCP connections to
a database. It’s not an easy thing to
work with.
Event Handling the REST way: using feeds
The closest thing in the REST
world to notification is an event “feed”, using the Atom protocol (Chap. 7 in
Webber et al.), or the older RSS protocol.
A feed doesn’t deliver events, but it organizes them to make it easy for
clients to pick them up now and then at their own convenience.
The example in Chap. 7:
Restbucks has occasional price changes, special drinks to put on the menu, etc.
These changes to its basic catalog are “events” that get put in a “feed”. The individual coffee shops “poll” the feed
to pick up these changes and apply them to their local menus. That’s the REST way to handle events.
The APIs between the layers
OK 3 layers. That means 2
APIs between layers:
Call into business
layer: the Service API, tells what the
service can do
Look at Software Layers
handout
In pa4, you will extend these
APIs to cover payment and receipts and options.
We want to go on to Chapter 5
to look at the hypermedia version of orderService, which comes with an Jersey
implementation I’ll post as orderDap, after converting it to tomcat. It is useful for pa4 because it has support
for payments and receipts.
Q: What’s hypermedia? The idea that a response from one URI should
present the all the next-URIs that follow from that state, to guide the user
around the service. We are not returning
URIs, just information relevant to the resource of the URI. In Chapter 5, the responses contain URIs too.
But first, we should look at
other “plain” REST services, i.e., not hypermedia. There’s more we can do with just URI templates
and XML messages.
The Jersey REST Samples.
Jersey comes with a whole
distribution of samples, available in a zip file for example at http://download.java.net/maven/2/com/sun/jersey/samples/jersey-samples/1.0.3/
One of the samples is a
bookmark app that has users with
bookmarks like the example we just looked at, with URI template /users/{userId}/bookmarks/{bookmarkId}.
However it serves JSON and
uses JPA, so it’s not directly in our ballpark.
Another Jersey sample is a
bookstore REST service: items are CDs or books, CDs have tracks. It shows how we can use inheritance in the
representational POJOs.
/ |
Bookstore |
GET |
/count |
Bookstore |
GET |
/time |
Bookstore |
GET |
/items/{itemid} |
Book,
CD |
GET |
/items/{itemid}/tracks/{num} |
Track |
GET |
The
Bookstore resource
returns a list of items, either CDs or books. The resource dynamically
references a Book or CD resource using the getItem method that
is annotated with @Path. Both the Book and CD resources
inherit from the Item class, therefore, the item can be managed as an Item
whether it’s really a Book or CD.
This shows an example where subresources
could more reasonably be used than the simple firstRest2 case, that is, hand
off to a subresource to handle the individual track requests.
Richardson and Ruby’s Bookmark Service: see paper
handout on URIs
Their book on the actual delicious site: http://delicious.com/leonardr/restbook
Look at R&R’s bookmark
service, based on del.icio.us bookmark service.
R&R’s book has lots of
good guidance on designing REST APIs.
See handed out URI templates
to describe the service. Well, not all
of it, but the core part.
See users resource, familiar
setup, like orders in orderService.
See bookmark management
running at second level in URIs: a new feature to us.
Template:
/users/{userId}/bookmarks/{bookmarkId}.
Each user has a set of
bookmarks, each id’d by MD5 hashes of the actual bookmark.
(it’s a problem to use the
URL itself, see the book, and del.icio.us does it this way.)
Java can compute MD5 hashes
using MessageDigest in JDK.
POST to collection URI to add
one, GET, PUT, DELETE on individual bookmarks.
Also here, as in firstRest2:
GET to a collection URI: GET to bookmarks for a user to get all or some of them
using query string. This returns the XML representations of the bookmarks, not
just a list of URIs back into the service, so this is not a hypermedia service.
(You can ask for atom feeds.)
Use of query string: a new
feature to us, for REST services.
--to specify a date or range
of dates on a query
.../?timestamp >= 1280296860145 (Unix
time, seconds since Jan 1 1970)
--Another query: ?limit=100
Could have ?start=50, but not
implemented here.
Another idea: use of
conditional HTTP GET
From HTTP 1.1 spec, now
linked from class web page under HTTP:
The semantics of the GET method change to a "conditional GET" if the request message includes an If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field. A conditional GET method requests that the entity be transferred only under the circumstances described by the conditional header field(s). The conditional GET method is intended to reduce unnecessary network usage by allowing cached entities to be refreshed without requiring multiple requests or transferring data already held by the client.
The
If-Modified-Since is a request header, and the corresponding server response
usually has the Last-Modified header showing the timestamp related to the
response representation. See Webber et
al, pg. 174 for actual header listings, there discussed in terms of caching. If
there are no new-enough ones, the client gets back HTTP 304 “Not
Modified”. Of course this means the
server needs to timestamp the data used for the representations. Webber et al, pg. 83 notes that the time used
here is good to one second.
Looking at the
implementation (R&R, pg. 192), we see that this server uses the
If-Modified-Since header. The client can
use this header to filter out old bookmarks the client doesn’t want to see. If there are bookmarks recent enough, they
are represented in XML and returned to the user. If not, HTTP 304 is
returned. Note this is implemented in Ruby in this book.
Handling query params in
JAX-RS: close to HTML form params, since both are request parameter values.
Recall @FormParam annotation for service method param to provide incoming form
param to Java. Similarly, could have,
for GET /users/{username}/bookmarks?timestamp > 12345678—
@GET
@Path(“/{username}/bookmarks)”)
public
BookmarkList getBookmarks (@PathParm(“username”) String user,
@QueryParam(“timestamp”) long timestamp) …
For the incoming header, use @HeaderParam(“If-Modified-Since”) annotation on a method param.
Handling Hierarchical URIs in Jersey: Using Subresources
Hierarchical URIs like
/users/{userId}/bookmarks/{bookmarkId}.
Jersey implementation of
services for these multilevel URI patterns—isn’t it a mess?
There’s a concept of
subresource that keeps things from blowing up in complexity.
Basic Idea: processing the
URI from left to right--
/users/{userId}/bookmarks/{bookmarkId}
<-main
resource class-> <--subresource class------------->
|
|handoff from main
resource to subresource object
The Bookmarks Project: Sketch of Jersey Implementation
using a Subresource
It’s common to handle two
related URI segments in one class, so for example for bookmarks
/users/{username}/bookmarks/{id}
<--users
resource-><--bookmarks-->
UsersResource:
@Path(/users) on the class
@GET on a method to handle
GET /users
@Path(“{username}”)
@GET on a method to handle
GET /users/username
@Path(“bookmarks”)
return new Bookmarks
subresource object
Bookmarks SubResource: no
@Path on class
@GET on a method to handle
GET /users/username/bookmarks
@Path(“{bookmarkId}”)
@GET on a method to handle
GET /users/username/bookmarks/id
Handling two or more PathParams at once: examples from Burke’s RESTful Java with JAX-RS
@Path (“/customers”)
public class CustomerResource
{
...
@Path(“{first}-{last}”)
@GET
@Produces (“application/xml”)
public Customer
getCustomer(@PathParam(“first”) String firstName,
@PathParam(“last”) String lastName) {
...
}
handles GET
/customers/bill-burke. Assumes no –‘s in
names.
Example returning an image (pg. 66, Burke)
@Path(“/cars/{make}”)
public class CarResource {
@GET
@Path(“/{model}/{year}”)
@Produces(“image/jpeg”)
public Jpeg
getPicture(@PathParam(“make”) String make,
@PathParm(“model”) String model,
@PathParam(“year”) String year)
{
...
}
handles GET /cars/ford/taurus/2003, sends jpeg for
it.
Handling queries: more examples from Burke’s RESTful Java with JAX-RS
@Path
(“/customers”)
public
class CustomerResource {
...
@GET
@Produces
(“application/xml”)
public
Customer getCustomers(@QueryParam(“start”) int start,
@QueryParam(“size”) int size) {
...
}
Handles GET
/customers?start=10&size=20
We can specify default values
too:
@Path
(“/customers”)
public
class CustomerResource {
...
@GET
@Produces
(“application/xml”)
public
Customer getCustomers(@DefaultValue(“0”)
@QueryParam(“start”) int start,
@DefaultValue(“10”) @QueryParam(“size”) int size) {
...
}