Friday 23 June 2017

Web Services in java

Web services are one of the most successful implementations of a Service Oriented Architecture, and they usually are presented as a public Api you can use to expand the capabilities of your applications. There's a web service for everything from obtaining songs lyrics to detect faces in an image, to the point that they can be considered the modern version of libraries.
Every application can count on a (moderately fast) internet connection, so the network bottleneck is not a problem like it was 10 years ago, especially when the interchanged data consist of text. But there are a few issues you should be aware of.
First, web services commonly do not present a standard Api: vendor lock-ins are the norm. What if a service is discontinued, or it changes its API? A Facade-like pattern can help here. Another issues is how to test an application which involves web services? Every test that involves a web service uses the network and when you have plenty of tests the suite becomes slower and slower.

Define what you want

When choosing to use a web service in your application, the first task to accomplish is writing an abstraction over it, in a way similar to wrapping an external library. Like libraries, web services APIs are catch-all so wrapping them is useful for hiding everything you won't need: often an interface composed of one or two methods captures any communication towards the service.
For example, if you want to use Google Translate, an interface with the single method:
String translate(String from, String to, String text)
will often suffice. You want to use it for detection instead?
String detect(String text)
You want to find out the lyrics of a song? I did it in my thesis project:
String getLyrics(String artist, String title)
To see if you're segregating interfaces right, try writing a stub which uses an internal prepopulated Map to satisfy requests, and see if it's too difficult because there are tons of methods.
When the abstraction over the web service is defined, you should build a Facade or a Bridge that implements this interface, so that you can swap in an implementation of a different service that does the same thing at will. Note that swapping in an implementation does not happen only if the web service dies: it happens every day in the testing environment (and ease of testing is the sign of good old loose coupling.)
You will usually write integration tests for the Facade, and isolate internal classes that uses it with unit tests that employ mocks and stubs. JUnit satisfies the requirements for both these types of tests, but you may want to define your base test case or helpers.

Throw away SOAP for REST-like (not RESTful)

SOAP and the plethora of WS-* specification are heavy, semantically poor and difficult to use without supporting libraries. Nearly every public web service offers a REST-like API, which is by far simpler to integrate. You can use the REST-like interface to do exploratory manual testing with Curl or even in your browser. Or you can access it from JavaScript other than from Java if you have a web-based interface.
Why the term REST-like? Because many services proclaim to be RESTful but they do not adhere to all six REST principles. But the simplicity is there at least, so we may call them REST-like like Solr does with itself.
REST-like interfaces return native representations instead of XML envelopes which replicate the HTTP semantics with their own headers (we have alreayd 4 TCP/IP levels; why do you think we need another one on top?)
Web services APIs are also resource-oriented instead of being based on Remote Procedure Call, and this paradigm is something the web has employed for twenty years (a proof that it can work.) You're retrieving information in the form of resources, like a list of tweets, a text, facebook status updates, links... Not calling a method.
Web services talk in XML or JSON; you usually can choose between them.

XML

XML as a format is natively supported in Java, by the SAX and DOM APIs (or even XSLT). If you have a JDK, you already have SAX and DOM available.
But please, do not try to define wrapper classes from the XML Schema (JAXB). The idea is generating a bunch of classes from the schema definition (.xsd files) that let you unmarshal XML documents in objects. Why shouldn't we do it?
  • most web services do not define a schema. Pretty good point.
  • Moreover, even if a schema is available you add 10, 20 or 30 classes to your codebase for nothing. Remember, if we have an abstraction the webservice-specific information cannot escape from the particular driver (Bridge implementation) or implementation (Facade). So either you use all the classes defined by JAXB in your implementation for this particular web service, or the majority of them will sit there rolling their thumbs in a nice gen/ folder.
To parse XML returned from a web service you'll probably just need DOM to extract what you want from the response document and throw away the rest:
public class XMLLyricWikiParser implements LyricWikiParser {

 /* (non-Javadoc)
  * @see it.polimi.chansonnier.driver.lyricwiki.LyricWikiParser#getLyrics(java.lang.String)
  */
 public String getLyrics(String xmlContent) {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            InputSource is = new InputSource();
            is.setCharacterStream(new StringReader(xmlContent.trim()));
            Document doc = db.parse(is);
            doc.getDocumentElement().normalize();
            NodeList lyricsNodes = doc.getElementsByTagName("lyrics");
            Node lyricsNode = lyricsNodes.item(0);
            Element lyricsElement = (Element) lyricsNode;
            return _getTextValue(lyricsElement);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Malformed document.");
        }
    }

    private static String _getTextValue(Element element)
    {
        return ((Node) element.getChildNodes().item(0)).getNodeValue();
    }

}
For the response string, you can use URL and URLConnection to send HTTP requests without further intermiediate layers:
public class HttpLyricWikiBackend implements LyricWikiBackend {
 private static final String LYRICWIKI_REST_API = "http://lyrics.wikia.com/api.php";

 @Override
 public String getSong(String title, String artist) {
  try {
   URL apiEndPoint = new URL(LYRICWIKI_REST_API 
                                  + "?func=getSong&artist="
                                  + _escape(artist)
                                  + "&song=" 
                                  + _escape(title)
                                  + "&fmt=xml");
         URLConnection connection = apiEndPoint.openConnection();
         BufferedReader in = new BufferedReader(
                                 new InputStreamReader(
                                 connection.getInputStream()));
 
         StringBuilder builder = new StringBuilder();
         String inputLine;
         while ((inputLine = in.readLine()) != null) {
          builder.append(inputLine);
          builder.append("\n");
         }
         in.close();
 
   return builder.toString();
  } catch (MalformedURLException e) {
   throw new RuntimeException("Url of the API is not correct.");
  } catch (IOException e) {
   throw new RuntimeException("Cannot communicate with the server.");
  }
 }

 private String _escape(String phrase) {
  return phrase.replaceAll(" ", "%20");
 }
}
Unfortunately, this is very verbose with regard to dynamic languages. Try XPath if you need more concise code.

JSON

json.org has all you need for JSON parsing in Java. Six classes (six!) which do everything. You download them, put the code in a org/json folder in your class path and you can start parsing responses. Native types are welcome in this small library (like String or Boolean), but JSONObject or JSONArray are also included.

Conclusion

So accessing web services from a Java app, being it web-based or not, it's very simple if you isolate the service, hide everything you don't need and avoid slowing down your test suite by testing in isolation (separating integration tests from unit ones).
Formats are standard and well-supported, with quite long code snippets attached (DOM examples are twenty-lines to extract a field value) but all the wiring code is in the abstraction you create. Web services are just adapters for the ports of your application, but there are so many of them that they are worth considering for inclusion. In this article I presented the simplest approach which you can start with, but that can already take you to great results.

No comments:

Post a Comment