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:
- REST first: Based just on my own experience, I’d speculate that remote APIs are typically considered “nice to have” on a product roadmap much more frequently than they are considered “must have.” In other words, I’m guessing they’re most often an afterthought, even in our own products. That’s unfortunate, in my opinion, because I think a well-defined remote API — one that permits programmatic interaction with a substantial portion of a product’s functionality — is the key enabler for an ecosystem of user-built extensions (think “web 2.0 mashups,” which were super hot not too long ago) and consequently more ideas, more buzz and more business.The approach we’re taking here is sort of a riff on “Test First” from Extreme Programming; I’m calling it “REST first,” keeping my fingers crossed that I’m the first person clever enough to call it that. The idea is that if we can build a REST service for our application logic, that can do everything the UI needs with good runtime performance (measured primarily by network response time and total number of requests, given a certain server load), then we have a REST service that’s probably good enough for other people to write code against as well.
- Three tiered architecture: Remember that time when it was the 1990s and it was still cool to talk about your data tier, your business logic and your presentation layer? Well, 20 years later it’s still sort of cool (n.b. “cool” is a relative term). This isn’t exactly the same thing, but we get similar benefits from architecting our plugin to have a clean separation between the user experience and underlying functionality. It keeps our code relatively spaghetti-free, lets us do all kinds of automated integration testing that we’d otherwise need some sort of browser automation for, helps us mentally formalize a complete state machine for our software’s functionality, and perhaps most importantly in the Atlassian development world, it helps us keep big chunks of our code isolated from the effects of the frequent and often undocumented changes that are made in the product APIs between versions. Of course writing a REST API is neither the only way nor the best way to achieve these things, necessarily, but if we’re going to write a REST API anyway, it’s worth keeping the benefits in mind.
- What was all that Velocity stuff for? After I went to the trouble of setting up the Template Renderer in “Episode IV,” you’d figure I was preparing to render search results in Velocity on the server side. Pfft, 1998 called, they want their unnecessary page refreshes back. Velocity templates turn out to be a good, simple solution for some of what we want to do (such as setting an i18n’ed page title, as we saw), and when we come back to the UI side of the picture later in this tutorial, I’ll be doing a bit more work in Velocity. For the most part, however, I’ll want to do most of my work on the client side, using Ajax calls from jQuery, because everybody knows that more jQuery is synonymous with better user experience.
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
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
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
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
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:
- I needed a “self” link in my representation object, so I’ve added one to the regular constructor (and also added the links map in the
- I needed to populate the “self” link somehow, so I’ve used a JAX-RS
UriBuilderimplementation from Jersey (rather than, say, string concatenation).
UriBuilderneeded some base URL, which I can get from another SAL service,
ApplicationPropertiesobject needs to come from somewhere, so I declared another
component-importand injected it into the
SearchResource(along with the
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:
Step 3½: 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
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
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.
The main topic I covered in this post was the Atlassian REST module, including:
- Reasons to build your plugin “REST-first,”
- A little bit about REST API design principles,
- Conventions and idioms when using Jersey and Jackson,
- The RESTClient test utility.
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.