Skip to content

Instantly share code, notes, and snippets.

@erikfried
Created February 24, 2012 10:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikfried/1899925 to your computer and use it in GitHub Desktop.
Save erikfried/1899925 to your computer and use it in GitHub Desktop.
Comparison Java + JAX-RS vs. Java + Playframework 2 vs. Scala + Playframework 2
package se;
import com.sun.jersey.api.NotFoundException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Response;
import java.util.Collection;
@Path(value = "/recept")
@Produces("application/json")
public class ReceptResource {
@GET
public Collection<ReceptRepository.Recept> all() {
return ReceptRepository.all();
}
@GET
@Path("/{id}")
public Response get(@PathParam("id") int id){
ReceptRepository.Recept recept = ReceptRepository.get(id);
if(recept == null){
throw new NotFoundException("Recept id " +id + " finns inte");
}
CacheControl cacheControl = new CacheControl();
cacheControl.setMaxAge(3600);
EntityTag eTag = new EntityTag("" + recept.hashCode());
return Response
.ok(recept)
.tag(eTag )
.cacheControl(cacheControl)
.build();
}
}
package controllers;
import play.mvc.*;
import test.ReceptRepository;
import static play.libs.Json.toJson;
import static test.ReceptRepository.*;
public class PlayJavaReceptResource extends Controller {
public static Result all() {
return ok( toJson( ReceptRepository.all() ) );
}
public static Result get(int id) {
Recept recept = ReceptRepository.get(id);
if(recept == null){
return notFound("Recept " +id +" finns inte");
}
response().setHeader(CACHE_CONTROL, "max-age=3600");
response().setHeader(ETAG, "" +recept.hashCode());
return ok( toJson(recept) );
}
}
package controllers
import play.api.mvc._
import com.codahale.jerkson.Json._
import test.ReceptRepository
import play.api.http.ContentTypes
object ReceptResource extends Controller {
def get(id: Int) = Action { request =>
ReceptRepository.get(id) match {
case Some(recept) =>
Ok( generate(recept))
.withHeaders(
CACHE_CONTROL -> "max-age=3600",
ETAG -> ("" + recept.hashCode()))
.as(ContentTypes.JSON)
case none => NotFound
}
}
def all = Action {
Ok( generate( ReceptRepository.all)).as(ContentTypes.JSON)
}
}
package test;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.*;
public class ReceptRepository {
private static Map<Integer, Recept> data = new HashMap<Integer, Recept>();
static {
List<ReceptIngrediens> sjomansbiffIngredienser =new ArrayList<ReceptIngrediens>();
sjomansbiffIngredienser.add(new ReceptIngrediens(1, "Sjöman"));
sjomansbiffIngredienser.add(new ReceptIngrediens(1, "Öl"));
Recept sjomansbiff = new Recept(1, "Sjömansbiff", "Koka den hackade sjömannen i länge i öl", sjomansbiffIngredienser);
List<ReceptIngrediens> fiskMedSasIngredienser =new ArrayList<ReceptIngrediens>();
fiskMedSasIngredienser.add(new ReceptIngrediens(1, "Fisk"));
fiskMedSasIngredienser.add(new ReceptIngrediens(1, "Sås"));
Recept fiskMedSas = new Recept(2, "Fisk med Sås", "Blanda fisken med såsen", fiskMedSasIngredienser);
data.put(sjomansbiff.id, sjomansbiff);
data.put(fiskMedSas.id, fiskMedSas);
}
public static Collection<Recept> all() {
return data.values();
}
public static Recept get(int id) {
return data.get(id);
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
static public class Recept {
public final int id;
public final String namn;
public final String beskrivning;
public final List<ReceptIngrediens> ingrediensLista;
/* Jaxb need this default constructor */
private Recept(){
id = 0;
namn = null;
beskrivning = null;
ingrediensLista = null;
}
public Recept(int id, String namn, String beskrivning, List<ReceptIngrediens> ingredienser){
this.id = id;
this.namn = namn;
this.beskrivning = beskrivning;
this.ingrediensLista = ingredienser;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Recept)) return false;
Recept recept = (Recept) o;
if (id != recept.id) return false;
if (beskrivning != null ? !beskrivning.equals(recept.beskrivning) : recept.beskrivning != null)
return false;
if (ingrediensLista != null ? !ingrediensLista.equals(recept.ingrediensLista) : recept.ingrediensLista != null)
return false;
if (namn != null ? !namn.equals(recept.namn) : recept.namn != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (namn != null ? namn.hashCode() : 0);
result = 31 * result + (beskrivning != null ? beskrivning.hashCode() : 0);
result = 31 * result + (ingrediensLista != null ? ingrediensLista.hashCode() : 0);
return result;
}
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
static public class ReceptIngrediens {
public final int mangd;
public final String beskrivning;
/* Jaxb need this default constructor */
private ReceptIngrediens(){
mangd = 0;
beskrivning = null;
}
public ReceptIngrediens(int mangd, String beskrivning){
this.mangd = mangd;
this.beskrivning = beskrivning;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ReceptIngrediens)) return false;
ReceptIngrediens that = (ReceptIngrediens) o;
if (mangd != that.mangd) return false;
if (beskrivning != null ? !beskrivning.equals(that.beskrivning) : that.beskrivning != null) return false;
return true;
}
@Override
public int hashCode() {
int result = mangd;
result = 31 * result + (beskrivning != null ? beskrivning.hashCode() : 0);
return result;
}
}
}
package test
case class Recept(id: Int, namn: String, beskrivning: String, ingredienser: List[ReceptIngrediens])
case class ReceptIngrediens(mangd: Int, namn: String)
object ReceptRepository {
val sjomansBiff = Recept(1,
"Sjömansbiff",
"Hacka en sjöman. Lägg i en gryta. Koka i öl. Länge.",
List( ReceptIngrediens(1, "Sjöman"),
ReceptIngrediens(1, "Öl")))
val fiskMedSas = Recept(2,
"Fisk med sås",
"Ta en fisk. Blanda med sås",
List( ReceptIngrediens(1, "Fisk"),
ReceptIngrediens(1, "Sås")))
val data: Map[Int, Recept] = Map(sjomansBiff.id -> sjomansBiff, fiskMedSas.id -> fiskMedSas)
def get(id: Int): Option[Recept] = data.get(id)
def all: Iterable[Recept] = data.values
}
GET /recept controllers.ReceptResource.all
GET /recept/:id controllers.ReceptResource.get(id: Int)
@erikfried
Copy link
Author

Three roughly equivalent implementations of a very simple (read only) REST interface.

This is not an example of pure or correct "REST-ness", i know it´s not.
This is not an example of how to set appropriate response headers

This is just a quick comparison to diplay how on could go about to:

  1. Generate json from some domain classes
  2. Set some HTTP response properties (headers, content type status code etc)
  3. Map urls to Classes and metods and parameters

All implementations have

  • A resource
  • A repository
  • Some "domain" classes (implemented as inner classes in the repos, just to make it fewer files in this gist)

The "routes" file is needed by the play implementations
Both Java implementations use the same domain classes, but the play implementation could remove the JAXB annotations.

@gobijan
Copy link

gobijan commented Feb 24, 2012

Be fair and also use the java stack of playframework as comparison ;)

@erikfried
Copy link
Author

@tschundee: It was never my point to be fair ;-) But you are right. I added a "PlayJavaReceptResource"
The Repositry and "domain classes" in that case are identical with the JAX-RS ones, only that I removed the JAXB annotations.

@gobijan
Copy link

gobijan commented Feb 24, 2012

Nice...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment