Plugin Architecture, Episode V ("The 3-Tiered Architecture Strikes Back")

In my last post, I walked through the initial steps of creating a well-architected, cross-product search plugin, including a look at the Atlassian plugin descriptor, various useful plugin module types and a few different reusable components provided by the plugin development platform.

However, I find myself feeling underwhelmed: our cross-product search plugin is not capable of search. It is a servlet that, despite its beautifully integrated user experience, contains no actual functionality. So let’s get back to work.

Step 1: REST basics

This is really where things start to get fun, and architecturally interesting. The approach we’ll take, and which we consider one of our most beneficial technical best practices, is to write a REST interface that will provide all of the functionality that our user interface will eventually need. There are a few points I want to address about this before getting started:

Okay, with all that out of the way, let’s get started. The plugin development platform provides a REST module type that provides a pre-configured Jersey (JAX-RS implementation) and Jackson (JSON processor). As usual, we just need to declare a dependency in the Maven POM and Atlassian plugin descriptor:


The important attributes for the <rest> element are path and version: collectively with the host application’s context path they determine the base URI for all the REST resources provided by the plugin. I’ve specified a path of /search-tutorial and version of 1.0, and the Plugin SDK is running my Confluence instance at http://localhost:1990/confluence, which means any resources we create will be addressable from a base path of http://localhost:1990/confluence/rest/search-tutorial/1.0.

I’ve also created a SearchResource class, using JAX-RS annotations, which of course still doesn’t do any searching whatsoever. It returns our traditional favorite, “hoho,” as a JSON string. One thing worth noting is that, for now, I’m using an Atlassian-specific annotation, @AnonymousAllowed, to let me access the resource without authentication, just for the sake of convenience. We can see this in action by requesting the resource we’ve defined. You can use any tool you want for this (curl or wget should work fine, for example) but I happen to use a little Java app, appropriately named RESTClient, since it knows how to format and syntax-highlight JSON. Behold, the fruits of our labor:


Hoho. What’s next? Well, it’s not great to be constructing a JSON string manually; better would be to let Jackson do it for us, using its reflection magic to serialize an object into JSON syntax. While we’re at it, let’s not hardcode “hoho” into the object. Instead, we’ll set our resource up to accept a path parameter, and then have the response echo the parameter.


There are a lot of annotations floating around now, some of which are JAX-RS standard and some of which are Jackson-specific. At this point, I’m going to be completely hand-wavey for the sake of brevity and just say “they’re pretty much self explanatory!” One thing worth pointing out is that I added the @JsonCreator annotation to my SearchResultRepresentation constructor, which lets Jackson reconstitute SearchResultRepresentation instances from JSON. We’re not using that functionality right now, and I don’t imagine we’ll ever have a reason to use it in production (since the JSON responses will only be consumed by the front end). The reason I’ve included it, and the reason we always include @JsonCreator constructors by convention, is for the sake of integration tests. We write our integration tests in Java primarily, so we want Jackson to be able to parse our JSON responses into Java objects for us. Similarly, the getter for the echo field isn’t strictly necessary (and my IDE complains to me that getEcho() is unused), but I’ve included it by convention for testing.

Annotations and convention aside, the code should be pretty easy to follow. Our get() method takes a parameter, which is used in the representation object’s constructor. The representation is returned in the ok() response, serialized to JSON automatically. Using RESTClient, the request and response now look something like…

Step 2: Search (really, finally)

Okay, we’ve finally got enough of an architectural framework built, on top of which we could probably implement any plugin we wanted to, more or less. I’m actually really eager right now to start illustrating how similar the “plumbing” we’ve built so far is to the architecture of other real-world plugins… But I’ll keep it to myself for now, and focus on getting this search functionality actually working, finally.

One of the most important components in the Atlassian plugin development platform is a cross-product API called SAL, the Shared Access Layer. It contains many useful services that are (for the most part) implemented in each Atlassian product. We’ll be using several services from SAL in this tutorial, starting with SearchProvider.

Just like in “Episode IV,” we need to declare a dependency in the Maven POM, declare a component-import in the plugin descriptor, and inject the SearchProvider into an appropriate object’s constructor:


This pattern of code reuse, consisting of injecting an imported component, is one that pops up frequently in Atlassian plugin development. More sophisticated plugins, in addition to using components provided by the platform, also provide their own components (and even new plugin module types) to other plugins.

One thing I should probably point out here is that I chose to inject the SearchProvider object into the SearchResource constructor, rather than into SearchResultsRepresentation. Ignoring the fact that it’s good design to keep the REST representation objects separate from the application logic, it also wouldn’t work. You can only inject dependencies into objects constructed by the plugin system, not into objects you construct yourself using new. SearchResource is constructed by the plugin system (by way of the REST module), but SearchResultsRepresentation is not.

One last thing before moving on: SearchResultsRepresentation now has two constructors: one that’s a @JsonCreator, like we had before, and a new one that takes a SAL SearchResults instance. This is a very common pattern: all REST representation objects typically have a @JsonCreator-annotated constructor, with parameters identical to the class’s fields, and a regular constructor that takes an instance of the domain object(s) that the representation is based on.

Okay, let’s smoketest this:

Step 3: More RESTful, More SAL

Nothing crashed, good enough. Before we finish up, and add representation objects corresponding to the interesting bits of SearchResults, there’s a small but important bit of technical debt we need to address. We’re writing a REST service, but thus far we’ve neglected an important design principle in REST API design: HATEOAS. Yeah, it’s an awkward name for a design principle, and I personally have no idea how to pronounce it, but it’s important nonetheless: we want clients of our API to have only one URL baked in, from which all other resources in our REST API are transitively reachable. This principle simultaneously reduces unnecessary coupling between client and server, and makes it much easier for the client to access the resources it’s likely to need.
The convention we use at Atlassian is to include a map of links, including a “self” link, in the representations returned from all REST resources. Let’s get this set up:


Okay, I did a few things at once there:

In a bigger codebase, I would be more inclined to keep this stuff separate from my REST resource objects; I’d probably factor it out into some sort of LinksBuilder just to keep the resources clean. But for the purposes of this tutorial, considering we have only one resource, I was lazy.

Another quick smoketest:


Decent. Of course, if there were more REST resources in the plugin, we’d want to link those as well, but this is good for now.

Step 3&frac12;: Losing my Battle with OCD

One final bit of cleanup, before we finish fleshing out the results: although we’ve been really good so far about maintaining a clean macro-level architecture, we’ve been a bit lazy about micro-level code quality — things like null checks and mutable collections — the sorts of things that a static analysis tool might warn about. For that, we like to use Google’s core Java libraries:


Making preconditions explicit results in safer code, and making our data immutable wherever possible results in both safer and faster code, particularly when concurrency comes into play. The Google libraries do a nice job of providing these, while contributing to readability (e.g. ImmutableMap.of(...) is much better than Collections.unmodifiableMap(new HashMap(...))).

Step 4: The Coup de Grâce

We like to say that we spend 90% of our time, as Java web developers, transforming collections. SAL’s SearchResults contains a collection of search matches and a collection of error messages that we need to transform into some useful REST representation. Let’s do this.


First off, I’ve added representation classes for search matches and errors (empty for now), and shuffled the package structure around a bit, just to keep things clean. I’ve also finally removed the @AnonymousAllowed annotation on the get() method, since to get any useful search results, you do actually need to be an authenticated user. To find out the username for the current logged in user, I’m using yet another SAL service, UserManager, which I’ve imported and injected the usual way. Nothing too fancy yet…

But now, it’s time to…


TRANSFORM COLLECTIONS LIKE A BOSS. Okay, if you haven’t seen this style of code before, you’ll probably want to take a couple minutes to stare at it. Here’s the deal: we’re nerds, we like functional programming, and we hate for loops. So, rather than iterating through the list of matches in the SearchResults, constructing a new SearchMatchRepresentation for each one, and adding it to a list, I’m using the Google common libraries to do the transformation all in one shot. I actually would have been much happier to write this whole thing in Scala, because I think transforming collections (and pretty much everything else) is just much more pleasant in it, but I don’t think that would have gone over well here.

I’ll just say: you totally don’t have to write your code this way, but I’ll be proud of you if you do.

One last thing I should mention about the SearchMatchRepresentation and SearchErrorRepresentation classes: they aren’t really for standalone use. In other words, they don’t correspond to individual, addressable REST resources. They’re just aggregate bits of the SearchResultsRepresentation, and as such, they don’t contain their own individual links map.
Enough delayed gratification, let’s see what we’ve ended up with:


I’m satisfied. We’ve got a search term in the resource URI, we’re authenticated, and we’ve got an actual search result in our RESTClient. Still no user interface to speak of, and still not cross-platform, but our search plugin finally has some actual search functionality. Huge success.

Summary

The main topic I covered in this post was the Atlassian REST module, including:

We also covered a few components from SAL that are useful in many different plugins, and some functional-style techniques for making your code faster and more bulletproof. In “Episode VI,” we’ll finally write a user interface for the search plugin, tying together the work we’ve done so far.

Again, I’ve made the code for this iteration available for download. And as before, if you like this tutorial and you want to see more, please let us know!

Exit mobile version