• 沒有找到結果。

Creating scalable web services with REST

Chapter 9

Creating scalable web

services with REST

What's in this chapter?

In this chapter you'll learn how to create web services that can handle a huge number of requests by adopting the REST architecture.

Scalability difficulty with SOAP

Suppose that you'd like to create a web service like what's offered by Amazon to allow clients to query information of books. Definitely this can be done with SOAP over HTTP:

However, if there are a huge number of such clients or a huge number of such requests, a single server may be unable to handle it. So, you may add more servers (see below). But where do they get the book information from? They have to get it from a single database. This incurs two problems: First, even if you could set up a database cluster, the scalability is limited. Second, the web service servers probably need to stay close to the database and thus can't be located near the clients:

POST /books HTTP/1.1

<Envelope>

<Body>

<bookQuery isbn="1234-5678-9"/>

</Body>

</Envelope>

HTTP/1.1 200 OK

<Envelope>

<Body>

<book>

<isbn>1234-5678-9</isbn>

<title>Java programming</title>

...

</book>

</Body>

</Envelope>

Server

This is not a very scalable solution. Instead, note that this query operation is a read-only operation, then why hit the database at all? It is possible to set up cache proxies as shown below. Then, if a client in Europe queries for the book 1234, the proxy in Europe only needs to contact the main server only once to get the data. For subsequent queries on the same book from other European clients, it can return the result to the clients without contacting the main server at all and therefore the proxies will have shifted the load away from the main server. In addition, as the proxy and the clients are all in Europe, the response will be very fast:

To scale it even further, you could even, say, set up a child proxy for each country in Europe, forming a hierarchy of proxies:

Server 1

Server 2

Server N ...

The database cluster is hardly scalable.

The servers are co-located in the data center.

Client 1

Client 2

Client 3

Client 10,000 ...

Huge number of requests

Proxy (For US)

Server 1: Query for

book 1234.

Client (Europe)

Proxy (For Asia) Proxy

(For Europe)

2: For the first time only, query for book 1234.

Client (Europe) 3: Query for

book 1234. 4: Here you go.

This is a highly scalable architecture. Of course, a proxy shouldn't cache the information forever as sometimes the book's information is updated. But let's handle this issue later.

For now, consider how to set up such a proxy? The proxy needs to check if the HTTP request is a SOAP message and then check if it is the <bookQuery>

operation. If so, extract the isbn attribute from the <bookQuery> element and use it as the key to look up its cache. It means that this proxy is completely tailor-made for this particular web service. If you had 100 operations, you would need to develop 100 proxy programs. This is a serious problem. You need a generic proxy that works for all operations.

Using a generic proxy

In order to use a generic proxy, all web service operations must state if they are read-only or not in a standard way (see below). In addition, they must indicate their target objects (the book in the example) being operated on in a standard way so that the proxy can look up the result with the target as the key:

Proxy (For US)

Server

Client (France)

Proxy (For Asia) Proxy

(For Europe)

Client (France) Proxy

(UK)

Proxy (France)

If you look carefully at the requests, they look exactly like standard HTTP requests:

Therefore, as long as you represent web service operations as standard HTTP requests, standard HTTP proxies can be used to cache the results of read-only operations (GET requests) and thus achieving extreme scalability. This is not a co-incident: HTTP was designed to be highly scalable in day one.

What about the response? As you aren't using SOAP, you don't need the

<Envelope>, but you still would like it to remain language neutral, so you may probably continue to use XML (see below). Of course, XML is just one of the possible representations of the book, not the book itself. For example, you could represent it as HTML or even image showing the cover of the book.

This style/architecture of network computing system is called REST foo.com/books/1234 as the key.

3: Perform a WRITE

on this target. 4: Don't bother with the cache. Go straight to the main server.

GET /books/1234

HTTP/1.1 200 OK

<book>

<isbn>1234-5678-9</isbn>

<title>Java programming</title>

...

</book>

(REpresentative State Transfer): the representation state (e.g., an XML document) of a target, or rather, a resource in the official terms (e.g., the book) is transferred from one host to another (e.g., server to proxy to client). A web service using this style is called a RESTful web service.

Creating a RESTful web service

Let's implement this book query operation in a RESTful web service. Copy the SimpleService project and paste it as BookService. Delete the WSDL file as it is not required for a RESTful web service. Instead, create an XSD file named BookService.xsd (the name is unimportant) in its place:

<?xml version="1.0" encoding="UTF-8"?>

<schema xmlns="http://www.w3.org/2001/XMLSchema"

targetNamespace="http://ttdev.com/bs"

xmlns:tns="http://ttdev.com/bs" elementFormDefault="qualified">

<element name="book">

<complexType>

<sequence>

<element name="isbn" type="string"></element>

<element name="title" type="string"></element>

</sequence>

</complexType>

</element>

</schema>

You can create it visually or just input the text by hand. This XSD file defines the schema for the <book> element used as the response. Next, modify the CodeGenerator class:

Delete the com.ttdev.ss package. Run the CodeGenerator class. You should see messages like below in the console:

parsing a schema...

compiling a schema...

com/ttdev/bs/Book.java

com/ttdev/bs/ObjectFactory.java com/ttdev/bs/package-info.java

Refresh the project and you should see those new files in the com.ttdev.bs package. Next, proceed to create a class named BookResource in that package:

package com.ttdev;

import com.sun.tools.xjc.XJCFacade;

public class CodeGenerator {

public static void main(String[] args) throws Throwable { XJCFacade.main(new String[] {

"-d", "src/main/java",

"src/main/resources/BookService.xsd" });

} }

XJC stands for XML-Java compiler. It generates Java classes corresponding to XML elements or types.

Put the generated .java files into this folder.

The path to the XSD file to be read by the compiler.

Note that the annotations used such as @Path and @GET are all defined in the javax.ws.rs package. They are standardized in the JAX-RS (Java API for XML for RESTful web services) specification.

Create a BookServer class in the com.ttdev.bs package to publish the book resource:

package com.ttdev.bs;

import javax.ws.rs.GET;

import javax.ws.rs.Path;

import javax.ws.rs.PathParam;

@Path("books/{isbn}") public class BookResource {

@GET

public Book getDetails(@PathParam("isbn") String isbn) { if (isbn.equals("1234")) {

Book book = new Book();

book.setIsbn("1234");

book.setTitle("Java Programming");

return book;

}

return null;

} } GET /books/1234 Host: foo.com

1: CXF will try to match the path against template and find them matched.

{isbn} is called a "path parameter". It will obtain the value of "1234".

2: CXF will use the HTTP method to find the method to call. In order to call the method, it will create a new instance of BookResource before calling the method.

3: Inject the value of the isbn path parameter ("1234").

4: The Book object returned belongs to the Book class generated from the XML schema.