RESTful no Play! estrutura

117

Estamos planejando um projeto basicamente servindo conteúdo para aplicativos móveis, mas precisamos ter um site.

Minha dúvida é se faz sentido usar Jersey ou Restlet para desenvolver APIs REST para nossos aplicativos móveis e, em seguida, usar o Play! para servir o site.

Ou faz mais sentido apenas usar o Play! fazer tudo? Em caso afirmativo, como fazer REST com o Play! estrutura?

Gary
fonte

Respostas:

112

Conforme solicitação, uma abordagem simples semelhante a REST. Funciona quase da mesma forma que a solução da Codemwncis, mas usa o cabeçalho Aceitar para negociação de conteúdo. Primeiro, o arquivo de rotas:

GET     /user/{id}            Application.user
POST    /user/                Application.createUser
PUT     /user/{id}            Application.updateUser
DELETE  /user/{id}            Application.deleteUser

Você não especifica nenhum tipo de conteúdo aqui. Fazer isso é IMHO apenas necessário quando você deseja ter URIs "especiais" para determinados recursos. Como declarar uma rota para /users/feed/sempre retornar em Atom / RSS.

O controlador de aplicativo tem a seguinte aparência:

public static void createUser(User newUser) {
    newUser.save();
    user(newUser.id);
}

public static void updateUser(Long id, User user) {
    User dbUser = User.findById(id);
    dbUser.updateDetails(user); // some model logic you would write to do a safe merge
    dbUser.save();
    user(id);
}

public static void deleteUser(Long id) {
    User.findById(id).delete();
    renderText("success");
}

public static void user(Long id)  {
    User user = User.findById(id)
    render(user);
}

Como você pode ver, apenas removi o método getUserJSON e renomeei o método getUser. Para que diferentes tipos de conteúdo funcionem, agora você precisa criar vários modelos. Um para cada tipo de conteúdo desejado. Por exemplo:

user.xml:

<users>
  <user>
    <name>${user.name}</name>
    . . .
  </user>
</users>

user.json:

{
  "name": "${user.name}",
  "id": "${user.id}",
  . . . 
}

user.html:

<html>...</html>

Essa abordagem dá aos navegadores sempre a visualização HTML, já que todos os navegadores enviam um tipo de conteúdo text / html em seu cabeçalho Aceitar. Todos os outros clientes (possivelmente algumas solicitações AJAX baseadas em JavaScript) podem definir seu próprio tipo de conteúdo desejado. Usando o método jQuerys ajax (), você pode fazer o seguinte:

$.ajax({
  url: @{Application.user(1)},
  dataType: json,
  success: function(data) {
    . . . 
  }
});

Isso deve fornecer a você os detalhes sobre o usuário com o ID 1 no formato JSON. Atualmente, o Play oferece suporte nativo a HTML, JSON e XML, mas você pode usar facilmente um tipo diferente seguindo a documentação oficial ou usando o módulo de negociação de conteúdo .

Se você estiver usando o Eclipse para desenvolvimento, sugiro usar o plugin do cliente REST, que permite testar suas rotas e o tipo de conteúdo correspondente.

seb
fonte
2
Obrigado por postar isso. O jogo! documentos são alguns dos melhores que já vi para explicar a estrutura básica das coisas, mas às vezes faltam exemplos detalhados. Ter as duas abordagens demonstradas no mesmo exemplo realmente esclarece as coisas.
Brad Mace
Estou testando seu exemplo, estou curioso para saber onde os dados JSON postados são convertidos para a classe de usuário. por exemplo, dentro da função createUser, descubro que newUser é nulo.
Gary
2
@ Gary: Talvez você tenha usado "user" em vez de "newUser"? O nome do controlador e o parâmetro do formulário devem corresponder. Eu criei um projeto simples que mostra o método acima, incluindo HTML / XML / saída JSON para todos os usuários em github.com/sebhoss/play-user-sample
seb
Obrigado, eu testei usando curl para enviar string JSON e parece que o framework do play não reconheceu o tipo de conteúdo application / json: groups.google.com/group/play-framework/browse_thread/thread/…
Gary
@Gary: Obrigado pela dica! Parece que está corrigido no branch master, você pode tentar construí-lo você mesmo e depois testar novamente ..
set
68

Esta ainda é uma pergunta popular, mas as respostas com maior votação não estão atualizadas com a versão atual do jogo. Aqui está um exemplo funcional de REST com play 2.2.1:

conf / routes:

GET     /users                 controllers.UserController.getUsers
GET     /users/:id             controllers.UserController.getUser(id: Long)
POST    /users                 controllers.UserController.createUser
PUT     /users/:id             controllers.UserController.updateUser(id: Long)
DELETE  /users/:id             controllers.UserController.deleteUser(id: Long)

app / controllers / UserController.java:

public static Result getUsers()
{
    List<User> users = Database.getUsers();
    return ok(Json.toJson(users));
}

public static Result getUser(Long id)
{
    User user = Database.getUser(id);
    return user == null ? notFound() : ok(Json.toJson(user));
}

public static Result createUser()
{
    User newUser = Json.fromJson(request().body().asJson(), User.class);
    User inserted = Database.addUser(newUser);
    return created(Json.toJson(inserted));
}

public static Result updateUser(Long id)
{
    User user = Json.fromJson(request().body().asJson(), User.class);
    User updated = Database.updateUser(id, user);
    return ok(Json.toJson(updated));
}

public static Result deleteUser(Long id)
{
    Database.deleteUser(id);
    return noContent(); // http://stackoverflow.com/a/2342589/1415732
}
Alden
fonte
Também gostaria de ver uma versão atualizada da Resposta de seb, mas infelizmente sua resposta removeu toda a magia .xml e .html. :-(
flaschenpost
26

Use o Play! para fazer tudo. Escrever serviços REST no Play é muito fácil.

Em primeiro lugar, o arquivo de rotas torna simples escrever rotas que estejam em conformidade com a abordagem REST.

Em seguida, você escreve suas ações, no controlador, para cada método de API que deseja criar.

Dependendo de como você deseja retornar o resultado (XML, JSON etc), existem alguns métodos que você pode usar. Por exemplo, o uso do método renderJSON permite que os resultados sejam renderizados com muita facilidade. Se você deseja renderizar XML, pode fazê-lo da mesma maneira como faria para construir um documento HTML em sua Visualização.

Aqui está um bom exemplo.

arquivo de rotas

GET     /user/{id}            Application.getUser(format:'xml')
GET     /user/{id}/json       Application.getUserJSON
POST    /user/                Application.createUser
PUT     /user/{id}            Application.updateUser
DELETE  /user/{id}            Application.deleteUser

Arquivo de aplicativo

public static void createUser(User newUser) {
    newUser.save();
    renderText("success");
}

public static void updateUser(Long id, User user) {
    User dbUser = User.findById(id);
    dbUser.updateDetails(user); // some model logic you would write to do a safe merge
    dbUser.save();
    renderText("success");
}

public static void deleteUser(Long id) {
    // first check authority
    User.findById(id).delete();
    renderText("success");
}

public static void getUser(Long id)  {
    User user = User.findById(id)
    renderJSON(user);
}

public static void getUserJSON(Long id) {
    User user = User.findById(id)
    renderJSON(user);
}

arquivo getUser.xml

<user>
   <name>${user.name}</name>
   <dob>${user.dob}</dob>
   .... etc etc
</user>
Codemwnci
fonte
É possível escolher o método getUser correto com base no cabeçalho Aceitar?
Timo Westkämper
é, mas não totalmente confiável. Se o play souber que o cabeçalho é uma solicitação JSON, ele tentará renderizar um arquivo getuser.json. Se o cabeçalho for um xml, ele tentará getuser.xml. No entanto, é muito mais fácil de entender, e mais semelhante ao REST, para user / User / {id} / type
Codemwnci
29
Não acho que seja mais semelhante a REST especificar o tipo de representação explicitamente no URI. É melhor usar o cabeçalho Aceitar diretamente e não alterar o URI, pois o recurso que você deseja ver permanece o mesmo. O exemplo acima poderia ser reescrito para ter apenas um único método getUser (Long id) que faz exatamente o mesmo que sua implementação atual, mas em vez de definir um getUserJSON, getUserXML, etc., você deve definir um modelo getUser.json e getUser.xml. Embora eu mudar o nome que a user.json / user.xml também
seb
Obrigado, isso é muito útil. Agradeço!
Gary
1
@seb - você pode expandir seu comentário para uma resposta? Adoraria ver um exemplo da técnica que você descreve
Brad Mace
5

A integração com uma implementação JAX-RS é uma possível abordagem alternativa ao uso do roteamento HTTP integrado do Play. Para um exemplo do RESTEasy, consulte o RESTEasy Play! módulo .

Essa abordagem faz sentido se você já investiu em JAX-RS ou se precisa de alguns dos recursos avançados REST que o JAX-RS fornece, como negociação de conteúdo. Caso contrário, seria mais simples usar o Play diretamente para servir JSON ou XML em resposta a solicitações HTTP.

Peter Hilton
fonte
2

Parece que essa abordagem foi quebrada na versão 1.2.3 do Play. Se você baixar o código-fonte feito por @seb e mencionado anteriormente https://github.com/sebhoss/play-user-sample , a criação de um novo objeto de usuário usando POST com um objeto JSON não será mais possível.

Você precisa ter métodos específicos para a criação usando POSTs json e xml. Descrito aqui: https://groups.google.com/forum/#!topic/play-framework/huwtC3YZDlU

tchristensen
fonte