Ricardo Rocha's Website

Musings on Programming and Programming Languages

Email GitHub Twitter LinkedIn

URL Shortener #4: The Xtend Service Implementation

How do we go about implementing the service’s REST interface and components in Xtend? This series of articles contrasts the Java and Xtend languages around a very simple URL shortening REST service. Xtend is a JVM language that compiles into readable Java and is fully compatible with all Java frameworks, libraries and tools. If you know Java you already know most of Xtend! A presentation is also available

The Java UrlShortener Implementation

Let’s start our comparison revisiting the overallJava UrlShortener implementation:

// The workhorse of our service
public class UrlShortener {
  // Immutable dependency on Shortener
  private final Shortener shortener;
  // Immutable dependency on StorageProvider
  private final StorageProvider storageProvider;

  // Non-property, mutable local variable
  private Map<String, String> storage;
  
  // All-property constructor
  // and associated builder elided...

  // Start the Spark server and set routes
  public void start() {
    // Get fresh storage map each time
    storage = storageProvider.openStorage();
    if (storage == null) {
      throw new NullPointerException("Null storage");
    }
    
    // The first route defined via get,
    // post, put, delete, head or option
    // implicitly starts the Spark server
    // on port 4567

    // Shorten REST endpoint
    post("/api/shorten", (req, res) -> {
      // POST creating shortUrl goes here
    });

    // Redirect REST endpoint
    get("/:hash", (req, res) -> {
      // GET redirecting from short URL
      // goes here
    });

    // Delete REST endpoint
    delete("/:hash", (req, res) -> {
      // DELETE removing short URL
      // goes here
    });
  }

  // Stop the Spark server and
  // clean up after ourselves
  public void stop() {
    // Stop Spark server
    spark.Spark.stop();
    // Persistent stores require closing!
    if (storage instanceof Closeable) {
      try {
        ((Closeable) storage).close();
      } catch (IOException e) {
      }
    }
  }
}

The Xtend UrlShortener Implementation

The Xtend version of the above is:

// The @Buildable active annotation
// generates a full-blown builder
// at compile time!
@Buildable
// Class access defaults to public
class UrlShortener {
  // Field access defaults to private
  
  // Immutable dependency on Shortener
  val Shortener shortener
  // Immutable dependency on Shortener
  val StorageProvider storageProvider

  // Non-property local variable
  Map<String, String> storage
  
  // All-property constructor elided...

  // Start Spark server, config routes
  def start() {
    // Get a fresh storage on each start
    storage = storageProvider.openStorage
    if(storage === null) {
      throw new NullPointerException('Null storage')
    }

    // The first route defined via
    // get/post/put/delete/head/option
    // will implicitly start the Spark
    // server on port 4567
    
    // Shorten REST endpoint
    post('/api/shorten', [req, res |
      // Short URL creation goes here
    ])

    // Redirect REST endpoint
    get('/:hash', [req, res |
      // Short URL redirection goes here
    ])

    // Delete REST endpoint
    delete('/:hash', [req, res |
      // Short URL removal goes here
    ])
  }

  def stop() {
    // Stop Spark server
    Spark.stop
    // Clean up after ourselves
    if(storage instanceof Closeable) {
      try {
        // No need to cast to Closeable!
        storage.close
      } catch(Exception e) {
      }
    }
  }
}

Note the use of the @Buildable active annotation.

An Xtend active annotation is an annotation whose processor is invoked at compile time as part of the translation to Java pipeline. Active annotations can generate additional code (such as constructors and builders in this case) that can be freely referenced as if they’d been written by hand!

Posting a Long URL to Obtain a Short One

The Xtend POST route returning a short URL from a long one looks like:

post('/api/shorten', [req, res |
  // Long URL comes in the request body
  val longUrl = req.body
  try {
    // Try to build URI from long URL
    val uri = new URI(longUrl)
    // We only do HTTP/HTTPS
    if(uri.scheme !== null &&
       uri.scheme.startsWith('http')) {
      // String appears to have
      // a "shorten" method... 
      val hash = longUrl.shorten
      // Map appears to have
      // a "+=" operator... 
      storage += hash -> longUrl
      // Request appears to have
      // a "shortUrlFrom" method
      val shortUrl =
        req.shortUrlFrom(hash)
      // Response appears to have
      // a "respondWith" method
      res.respondWith(SC_CREATED, shortUrl)
    } else {
      res.respondWith(
        SC_BAD_REQUEST,
        '''Not an HTTP URL: «longUrl»''')
    }
  } catch(Exception e) {
    res.respondWith(
      SC_BAD_REQUEST,
      '''Malformed long URL: «longUrl»''')
  }
])

The overall Xtend logic is not that different from its Java counterpart but… what on Earth are those comments stating “… appears to have a method…”?

Xtend has this powerful concept called extension methods where any class can be augmented so as to expose additional methods not present in its original implementation.

Thus, for example, we can have String expose a shorten method that looks intuitive in:

val String longUrl = req.body
val String hash = longUrl.shorten

For this to be valid, an extension method must be in scope such that its first argument is of type String. In the above case, our extension method must look like:

def shorten(String longUrl) {
  shortener.shorten(longUrl)
}

Note above that the return keyword is optional in Xtend. The last expression in the method is the return value.

The += operator attached to Map is an extension method implemented as follows:

def operator_add(
  Map<String, String> storage,
  Pair<String, String> pair) {
  
  storage.put(pair.key, pair.value)
}

The -> operator is syntactic sugar for class Pair. Thus key -> value is equivalent to new Pair<K, V>(key, value)

When we write

val shortUrl = req.shortUrlFrom(hash)

we’re actually using the following extension method:

def shortUrlFrom(Request request,
                 String hash) {
  new URI(request.url).
    resolve(redirectPath + hash).toString
}

Lastly, when we write:

res.respondWith(SC_CREATED, shortUrl)

we’re using the following extension method:

def respondWith(Response response,
                int status,
                String body) {
  response.status(status)
  body
}

where we set the response’s HTTP status and return whatever body was passed to the method.

Requesting a Short URL that Redirects to the Long One

The Xtend implementation of redirecting to the long URL given the short URL is:

get('/:hash', [req, res |
  // Get the short URL's hash
  val hash = req.params(':hash')
  // Do we have it in our key/value store?
  if(storage.containsKey(hash)) {
    // Yes: retrieve the long URL...
    val longUrl = storage.get(hash)
    // ... and redirect the client there
    res.redirect(longUrl)
    // Return no content
    ''
  } else {
    // No such short URL: 404
    res.respondWith(
      SC_NOT_FOUND,
      '''No such short URL: «req.url»''')
  }
])

Deleting a Short URL

The Xtend implementation for the DELETE endpoint is:

delete('/:hash', [req, res |
  // Get the short URL's hash
  val hash = req.params(':hash')
  // Do we have it in our key/value store?
  if(storage.containsKey(hash)) {
    // Yes: remove it
    storage -= hash
    // Signal success w/no content
    res.respondWith(SC_NO_CONTENT)
  } else {
    // No such short URL: 404
    res.respondWith(SC_NOT_FOUND)
  }
])

Note here that we’re using the respondWith method with only an HTTP status but no response body. The underlying extension method is just a shortand:

def respondWith(Response response,
                int status) {
  respondWith(response, status, "")
}

We’re also attributing a -= operator to Map. The underlying extension method is:

def operator_remove(
  Map<String, String> storage,
  String hash) {
  
  storage.remove(hash)
}

Stopping the URL Shortener

In order to stop our server we need to stop the Spark server as such and also close the storage map:

def stop() {
  Spark.stop
  tryAndClose(storage)
}

where tryAndClose is implemented as:

static def tryAndClose(Object obj) {
  if (obj instanceof Closeable) {
    try {
      // No cast to Closeable!
      obj.close
    } catch(Exception e) {
      // Ignore closing errors
    }
  }
}

As an added bonus, when Xtend sees an instanceof condition it generates an implicit cast with the same variable name. Neat!

Implementing the Shortener Interface

We’ve chosen Guava’s Hashing utility class to provide us with hashing algorithms suitable for string shortening due to their very probability of collision.

Two appropriate algorithms are: mumur3 and sipHash. Google’s Guava implements both algorithms in its Hashing utility class.

Implementing our Shortener interfaces based on Guava’s Hashing is a breeze:

// File GuavaHashingShortening.xtend

/**
 * Murmur3-based shortener.
 * This class only supports UTF-8.
 */
class Murmur3Shortener
  implements Shortener {
  
  override shorten(String string) {
    checkNotNull(
      string,
      'String to be shortened cannot be null')
      
    Hashing.murmur3_32().
      hashString(
        string, StandardCharsets.UTF_8).
      toString()
  }
}

/**
 * SipHash-based shortener.
 * This class only supports UTF-8.
 */
class SipHashShortener
  implements Shortener {
  
  override shorten(String string) {
    checkNotNull(
      string,
      'String to be shortened cannot be null')
      
    Hashing.sipHash24().
        hashString(
          string, StandardCharsets.UTF_8).
        toString
  }
}

Unlike Java, Xtend allows multiple type declarations in the same file. Thus, the implementations above reside in the same file (properly baptized as GuavaHashingShortening.xtend.

Note above that an interface-implementing method is not prefixed with def but with override

Implementing the StorageProvider Interface

For key/value persistent stores we’ve selected ChronicleMap. This embeddable key/store store is very fast and easy to use.

The StorageProvider implementation is straight-forward but requires a little bit of configuration:

@Buildable // Give us a builder
class ChronicleMapStorageProvider
  implements StorageProvider {
  
  val String name
  val String filename
  val int entries
  val double averageKeySize
  val double averageValueSize
  
  // Constructors elided...

  override openStorage() {
    ChronicleMapBuilder.
      of(String, String).
      name(this.name).
      entries(this.entries).
      averageKeySize(this.averageKeySize).
      averageValueSize(this.averageValueSize).
      createPersistedTo(new File(filename))
  }
}

In addition to the “real” ChronicalMap-based implementation we also define a volatile, in-memory implementation of StorageProvider useful for testing. This trivial implementation accompanies the interface declaration in the same source file:

// File StorageProvier.xtend

interface StorageProvider {
  def Map<String, String> getStorage()
}

class InMemoryStorageProvider
  implements StorageProvider {
  
  override openStorage() {
    new ConcurrentHashMap<String, String>
  }
}

Notes about Annotations, Constructors and Instantiation

For both UrlShortener and ChronicleMapStorageProvider we’ve used the @Buildable active annotation to give us a named-parameter way of instantiating classes having an all-property constructor.

Thanks to @Buildable, for example, we can instantiate ChronicleMapStorageProvider like:

val provider = 
  ChronicleMapStorageProvider.builder.
    name('test').
    filename(/var/storage/url-shortener.dat).
    entries(1048576).
    averageKeySize(32).
    averageValueSize(64).
    build

or, alternatively,

val builder =
  ChronicleMapStorageProvider.builder => [
    name = 'test'
    filename = '/var/storage/url-shortener.dat'
    entries = 1048576
    averageKeySize = 32
    averageValueSize = 64
  ]
val provider = builder.build

@Buildable goes along especially well the @Data active annotation.

@Data is most appropriate for data-only, immutable classes. All fields are deemed final (val) and an all-field constructor and getters are generated. Thus, if we have a data class like:

@Data
class PersonName {
  String firstName
  String middleName
  String lastName
}

then the augmented Xtend class would be:

class PersonName {
  val String firstName
  val String middleName
  val String lastName
  
  new(String firstName,
      String middleName,
      String lastName) {
    this.firstName = firstName
    this.middleName = middleName
    this.lastName = lastName
  }
  
  String getFirstName() { firstName }
  String getMiddleName() { middleName }
  String getLastNameName() { lastName }
}

Note, incidentally, that Xtend constructors do not replicate the class name. Instead they’re always called new.

The generated Java class would be:

public class PersonName {
  private final String firstName;
  private final String middleName;
  private final String middleName;
  
  public PersonName(String firstName,
                    String middleName,
                    String middleName) {
    this.firstName = firstName;
    this.middleName = middleName;
    this.lastName = lastName
  }
  
  public String getFirstName() {
    return firstName;
  }
  public String middleName() {
    return middleName;
  }
  public String lastName() {
    return lastName;
  }
}

When the data class has too many fields invoking its all-property constructor can become cumbersome and error-prone:

val personName =
  new PersonName('Johnathan',
                 'Christopher',
                 'Aguillard')

@Buildable comes to the rescue by generating a builder where instantiation looks like:

val personName = PersonName.builder.
  firstName('Johnathan').
  middleName('Christopher').
  lastName('Aguillard').
  build

Named-parameter style, much more readable and intuitive!

If someone has no middle name, then instead of passing null for the middleName property we simply omit it in the “with” block:

val personName = PersonName.builder.
  firstName('John').
  lastName('Doe').
  build

Final Notes about Private Fields and Constructors and SnakeYAML

As a rule of thumb, we don’t want our interface-implementing classes to have publicly visible state. We also want them to be immutable. Consequently, we don’t want getter/setter accessors that make the classes mutable and their internals visible.

We achieve this by defining an all-property constructor accompanied by a builder providing named parameter-style instantiation.

This approach, however, is inconsistent with the default approach followed by SnakeYAML which is based on the Java Beans specification. This spec requires an empty constructor and getters and/or setters for each bean property.

Fortunately, SnakeYAML has a BeanAccess.FIELD mode that works with empty private constructors as well as with private fields corresponding to each property.

This style allows us to write Yam files like:

shortener: !!net.xrrocha.urlshortener.shortening.Murmur3Shortener []

storageProvider: !!net.xrrocha.urlshortener.storage.ChronicleMapStorageProvider
  name: url-shortener
  filename: target/url-shortener.dat
  entries: 1048576
  averageKeySize: 32
  averageValueSize: 64

which makes it look as if we were using regular bean properties but, behind the scenes, uses reflection wizardry to instantiate classes through their private constructors and populate their private fields.

Xtend Shortcomings

Xtend currently has a few limitations that make it necessary to implement certain constructs in Java

Enumerations, for one, only allow for declaring their constants. Xtend enumerations cannot implement interfaces or have fields and methods of their own:

enum Color {
  GREEN,
  BLUE,
  RED
}

If you need you enumeration to implement interfaces or have fields or methods you have to resort to good ole’ Java.

Also, inner classes can only be static:

class MyClass {
  static class NestedClass {}
}

This can be alleviated by adding the enclosing this as a field in the inner class but that is grunt work.

Finally, Xtend has no notion of methods references. However, its own lambda syntax is so intuitive and concise that this is not really a problem.

While in java we write:

Runtime.getRuntime().
  addShutdownHook(urlShortener::stop)

in Xtend we write:

Runtime.runtime.addShutdownHook(
  new Thread[urlShortener.stop])

Conclusion

This concludes our tour of Xtend around a simple but meaningful example.

Since Xtend compiles to (readable) Java, it has exactly the same semantics as Java. This means Xtend is fully compatible with all Java frameworks and libraries out there.

If you want to try Xtend in your real-world projects, testing is a good start. Collections literals, for instance, make populating fixtures a breeze and even allow for improvising test data on-the-fly inside test methods.

Java programmers can write working Xtend code after a few hours hours of study. Mastering the finer details of the language comes very quickly because it could be seen, in the end, as Java sàns the cruft: Xtend is all about removing unnecessary verbosity and maximizing clarity of intent.

Also, Xtend is an Eclipse project, very likely to last, evolve and be supported for the times ahead.

Code for the Java and Xtend implementations of our URL shortener reside under a single Bitbucket repo. The implementations in the repo are a bit more elaborate than the somewhat simplified versions shown in this post series. In general, though, the snippets presented here do reflect the actual code in the repo. The repo contains unit and integration tests illustrating how to leverage Xtend syntax and conciseness for testing Java code.

comments powered by Disqus