I don't mind both styles (after working with Spring MVC 3, Servlet 3, JAX-RS, and Rails)
URL mapping is a framework detail, not a language detail — although the framework can be limited by the language. In Sinatra you'd write:
get 'someroute' do
# do stuff
end
http://www.sinatrarb.com/intro.html#Routeseach style (these and half a dozen others) has its advantages and inconvenients. For instance, separate urls mapping (Rails, Django) give a starting point laying out the structure of the site and make views/controllers easier to reuse (as they're not coupled to URLs)[0], whereas annotating the handler directly gives a better view of where they're involved and how.
[0] They also make it simpler to "graft" sub-sites in
@GET
public Collection<Integer> listStudentIds() {
return STUDENTS.keySet();
}
or @DELETE
@Path("{uid: [0-9]+}")
public boolean deleteStudent(@PathParam("uid") int uid) {
final Student deletedStudent = STUDENTS.remove(uid);
if (deletedStudent == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
return true;
}
}
I'll grant that Java programs often become more verbose than their Python/Ruby counterparts - but I would argue that trait can be held to a minimum with the proper libraries and coding style.I find it amazing that "DRY" has become such a positive pattern when it was touted as one of the driving points of the Ruby on Rails framework. Don't repeat yourself.
For instance, Java folks are always ready to throw exceptions instead of letting stuff resolve itself if it doesn't quite work.
If I returned a fake value - the client would get a 200 response on his attempt to delete a non-existent resource, which is clearly the incorrect behavior. I could return nothing - but then the client would see a 204. So, I choose to throw an exception with a response 'NOT_FOUND'
throw new WebApplicationException(Response.Status.NOT_FOUND);
so that the client gets a 404 response.There are a few things I'd like to point out to add to this example:
1) JAX-RS is still new. In the future, they might improve the exception handling or whatnot (so if you return null, they might decided to return 404 ... who knows)
2) We can always wrap all JAX-RS with a Filter. So you can throw JPA level exception (not found) and let the filter catch it and convert it to WebApplicationException(404). Thus in theory, your JAX-RS implementation won't have any RuntimeException explicitly in the code.
@DELETE
@Path("{uid: [0-9]+}")
public Response deleteStudent(@PathParam("uid") int uid) {
final Student deletedStudent = STUDENTS.remove(uid);
if (deletedStudent == null) {
return Response.status(Response.Status.NOT_FOUND).build();
} else {
return Response.ok().entity(true).build();
}
}The full (buildable) source code - including the CsvParam and DateParam classes - are here: https://github.com/smanek/students
def delete_student(uid)
STUDENTS.remove(uid) || raise WebApplicationException.new(Response.Status.NOT_FOUND)
endAnd that is the reason I still write in Python or Lisp when I don't need something (Hadoop, Lucene, etc) from the JVM ;-)
public void deleteStudent(int uid) {
if (STUDENTS.remove(uid) == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
}Personally I'd rather have a distinct boolean value to test for, the java version is much more explicit as to exactly what its doing.
!!STUDENTS.remove(uid) def delete_student(uid)
STUDENTS.remove(uid) or raise WebApplicationException, Response.Status.NOT_FOUND
end list_student_ids = get(STUDENTS.keys) # [0]
second example, Python: @delete
@path(r'{uid: [0-9]+}')
def delete_student(uid):
if STUDENTS.pop(uid, None): # [0]
return True
raise WebApplicationException(Response.Status.NOT_FOUND)
the last line generally wouldn't be this complex, in Flask you'd just call `flask.abort(404)` (which can be used inline with an `or`, as with waffle_ss's ruby example, but that's not usual/good Python style), in Django it'd be `raise Http404()`.Although in Django you'd really use the `get_object_or_404` shortcut for ORM objects:
def delete_student(uid):
get_object_or_404(StudentModel, pk=uid).delete()
return True
And you wouldn't bother with the return since a 200 result would mean the deletion was correctly executed.[0] Used Python's MutableMapping API here, it works slightly differently than Java's equivalent Map interface: `.keys()` returns a sequence of the mapping's keys and `.pop()` asks for a default value to return in case nothing was found there, otherwise it raises `KeyError` if the key was not found)
r'(?P<uid>[0-9]+)'
(the `r` prefix is for `r`awstring, so the developer does not have to string-escape each backslash used in the regex among other things) get "/list_student_ids" do
STUDENTS.key_set
end
delete "/delete_student/:uid" do
STUDENTS.remove(params[:uid]) or raise WebApplicationException, Response::Status::NOT_FOUND
true
end
1. http://www.sinatrarb.com/Some savings through the removal of types, sure, but that is not what I personally see as most import here.
2. There's also a gain from the framework being able to map things to each other without being told explicitly. `@PathParam("uid")` for instance, it's only there because the framework has no way to match the parameter name `uid` to the URL-extracted parameter `uid`. Likewise with HTTP methods being part of the routing instead of a separate annotation, in part.
3. Java's APIs and type system, such as implicit nulls and not being able to use arbitrary types in boolean contexts