Sunday, January 18, 2009

An example of how not to use Java annotations

Lately I have been spending a lot of time reading and learning about REST. The theory (or "architectural style" as Roy would call it) is quite intriguing. I have spent the better part of the last several years implementing enterprise integration projects using SOAP web services. Learning about REST has been eye opening.

Like any good programmer, I learned a little about the theory and then wanted to get my feet wet by writing some code. I looked around at the different "frameworks" available (im a Java guy):
It was during this investigation that I discovered something terribly wrong with how people are using (or over-using) Java annotations. To be honest, i've seen this problem before (JAX-WS, etc) but ignored it because I thought it was isolated. The fact that I am seeing it again, in several more places, is disconcerting.

Example: JSR-311

To explain the issue, here is an example from the Jersey documentation (JSR-311):

1 // The Java class will be hosted at the URI path "/helloworld"
2 @Path("/helloworld")
3 public class HelloWorldResource {
4
5 // The Java method will process HTTP GET requests
6 @GET
7 // The Java method will produce content identified by the MIME Media
8 // type "text/plain"
9 @Produces("text/plain")
10 public String getClichedMessage() {
11 // Return some cliched textual content
12 return "Hello World";
13 }
14 }
Nice and simple. Without going into the nitty-gritty details of how this works, lets look at the three annotations in this code snippet (from bottom to top).

@Produces("text/plain")
This annotation describes the behavior of the getClichedMessage() method -- it returns plain text. Ok, that seems reasonable to me.

@GET
This annotation describes a mapping between the HTTP GET operation and the getClichedMessage() method. It also describes the behavior of the method. In REST, HTTP GET has very specific semantics which getClichedMessage() must obey. I'll let this slide for now.

@Path("/helloworld")
The final annotation defines a mapping from a URL (or URI) to the HelloWorldResource class. Now I am no expert on Java annotations but this seems like a bad practice to me. This is deployment-specific configuration information being compiled into a java class. Not cool.

I don't mean to pick on JSR-311 specifically. It seems that several other APIs/frameworks are starting to pull this crap.
Example: Streaming a File

Lets look at another example to drive this problem home. Suppose I was going to develop a class which does the following:
  • Takes an 'id' as input
  • Opens a file (based on the 'id')
  • Streams the contents of the file back to the user
This would be pretty trivial to model as a RESTful resource. I won't bore you with the code. Needless to say, this is both a useful and common capability whether you using REST or something else. It is also something that I would want to write once and use in many places (both within a single webapp as well as across multiple webapps). In other words, I would need to make sure that both the logic and annotations are reusable.

How not to use Java annotations:

In basic OO programming classes we are taught about decoupling, separation of concerns, and reuse (among other things). Last I checked, these were still good practices. I believe the examples above demonstrate a violation of these core OO principles.

If you are creating a new annotation or come across a framework which is asking you use an annotation in one of our classes, ask yourself these questions:
  • Does the annotation configure how to use something (as opposed to describing what it is/does)?
  • If you add the annotation to a class, can you reuse that class in different contexts?
If you answered yes to any of these questions, consider yourself on shaky ground.

Remember: Annotations are not the solution to configuration.

Update 2: Removed EJB3/JPA Example. Thanks Nuno.

12 comments:

  1. Hi,

    Your last example is not correct. In fact in your NamedQuerys the word "Magazine" is not related with the table name but with the Magazine java classe. Then you cound have configure in one file called persistence.xml the relations between your table schema names and you classes that will always override the annotations placed in domain classes.

    ReplyDelete
  2. Nuno, I did some more research on that example and you are right. I have removed it. Thanks for the comment.

    ReplyDelete
  3. You are supposed to always be able to replace the behaviour of annotations by xml deploydment descriptors and acutally, the whole idea of annotations is to let developers spent less time in deployment vanities and focus on the interesting part. And after all, when you're developing/testing you don't really want to care about those things.

    ReplyDelete
  4. sorry, my comment was on the attitude of IMHO

    ReplyDelete
  5. David,

    That seems to be a common argument I have seen for the use of annotations. No offense but it has never carried any weight with me. To be clear, I don't think all annotations are bad.

    As someone who is used to developing full-lifecycle projects (conception to production), 'deployment vanities' are very important to me. I guess I never understood why people are so scared of XML. In this day and age, any professional software developer should be comfortable working with XML.

    The spring framework is a great example. If you do it right it makes for some very maintainable code on large long-term projects.

    Jeff

    ReplyDelete
  6. Jeff, I get your point but IMHO:

    * You are talking about a minority of use cases. In a typical DRY-ly application you may have as much as a couple of such cases.
    * Even so, there is no great difference between mapping using XML or using two different web objects that delegate into a service.

    I don't find enough arguments to switch back to the typical XML-to-java coupling scenario.

    ReplyDelete
  7. I disagree with your Jersey example. MVC is still seen as a best practice and in that context Jersey is a View framework.

    Isn't the view meant to be the place where you are allowed to make assumptions about the deployment.

    There probably is code that is abusing annotations just for the sake of having them but I don't think JSR-311 is one of them.

    ReplyDelete
  8. This is an interesting topic... Glad you brought it up.

    I've used both Spring MVC and JAX-RS extensively, and found that there are pretty plenty of methods of combining HTTP/RESTful annotations with flexible deployment strategies.

    Any HTTP-based approach can have external deployment configuration using one of many URL Mapping/Rewriting layers that are out there. Those tools allow you to change the URL deployment strategy pretty easily.

    As far as @GET is concerned... Your application should inherently behave differently based on the HTTP method. GET vs. POST vs. DELETE vs. PUT have specific meanings which will generally require different system behavior, and therefore should be managed differently; a different Java method per HTTP method is a good way to do that, and will not vary based on deployment consideration in a large majority (if not all) cases.

    re: @Produces/@Consumes... I spoke to the Spring folks about Produces/Consumes, and they (rightfully) believe that it is indeed a deployment consideration. Your trivial "text/plain" case doesn't really capture a case of automatically rendering an application POJO to some RESTful REpresentation, which is one of the compelling reasons to use a REST framework in the first place. JAX-RS uses annotation to define which mime-types are renderable. Spring MVC allows you to defer that decision in a View object, thereby allowing a more configurable deployment configuration.

    ReplyDelete
  9. @Gilgamesh: I agree that JAX-RS is an MVC framework... However, The JAX-RS annotations are more in sync with the Controller layer, not the View layer.

    ReplyDelete
  10. @sduskis:

    Regarding URL Mapping/Rewriting layers:
    My issue with @RequestMapping was not as much about changing the URLs for a single application. Rather, I was attempting to hit on the issue of reusing controllers across several applications (i.e. doing usual OO stuff). Im not sure exactly what you are referring to when you are talking about mapping/rewriting layers. I agree that there are certainly ways to do this but at what cost in terms of confusion and complexity? If I have 5 generic controllers that I want to reuse them in 3 web applications, how many mapping/rewrite rules do i have to maintain? Those rules are, of course, in addition to an annotation which now is just a lie.

    My point is you end up with those mapping rules anyway. Why not just map URLs to controller classes in a single easy to understand place (like SimpleUrlHandlerMapping)? The extra layers of indirection add no value and increase the complexity/confusion.

    Regarding @GET:
    I agree that this describes an important behavior. My comment was based on the fact that REST is not exclusively HTTP (see the fielding dissertation). I see the need for something like the @GET. Personally, I think something like @Idempotent would have been cleaner.

    Regarding @Produces/@Consumes: In the original blog post I was not arguing against these.

    ReplyDelete
  11. @Nacho Coloma:

    Regarding your comments (in order):
    * Maybe this is a style thing. I tend to write very thin controllers which delegate most functionality out to other objects. This allows me to inject (using Sping) the functionality that I need without changing much of the controller. As a result, I tend to have fewer controllers that get used in lots of places. For big projects this technique saves major headaches.

    * If you talking old-school Java, I agree. If you are talking about Spring, I wholeheartedly disagree. I assume that by "typical XML-to-java coupling scenario" you are referring to J2EE type deployment descriptors or the like. Yea they suck. Take a look at Spring. It will make you love again. ;)

    ReplyDelete
  12. I think you might qualify as Annotatiomaniac of the year 2015...

    ReplyDelete