RESTful w Play! struktura

117

Planujemy projekt dostarczający treści głównie do aplikacji mobilnych, ale potrzebujemy mieć stronę internetową.

Moje pytanie brzmi, czy warto używać Jersey lub Restlet do tworzenia interfejsów API REST dla naszych aplikacji mobilnych, a następnie używać Play! do obsługi witryny.

Czy też bardziej sensowne jest używanie Play! zrobić to wszystko? Jeśli tak, jak zrobić REST z Play! struktura?

Gary
źródło

Odpowiedzi:

112

Zgodnie z żądaniem proste podejście podobne do REST. Działa prawie tak samo, jak rozwiązanie Codemwncis, ale używa nagłówka Accept do negocjacji zawartości. Najpierw plik tras:

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

Nie określasz tutaj żadnego typu treści. Takie postępowanie jest konieczne tylko wtedy, gdy chcesz mieć „specjalne” identyfikatory URI dla określonych zasobów. Podobnie jak deklarowanie trasy do, /users/feed/aby zawsze powracała w Atom / RSS.

Kontroler aplikacji wygląda następująco:

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);
}

Jak widać, usunąłem tylko metodę getUserJSON i zmieniłem nazwę metody getUser. Aby różne typy treści działały, musisz teraz utworzyć kilka szablonów. Po jednym dla każdego typu treści. Na przykład:

user.xml:

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

user.json:

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

user.html:

<html>...</html>

Takie podejście zapewnia przeglądarkom zawsze widok HTML, ponieważ wszystkie przeglądarki wysyłają tekst / html typu treści w nagłówku Accept. Wszyscy inni klienci (prawdopodobnie niektóre żądania AJAX oparte na JavaScript) mogą definiować własne pożądane typy zawartości. Używając metody jQuerys ajax (), możesz wykonać następujące czynności:

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

Który powinien dostarczyć Ci szczegółowych informacji o użytkowniku o identyfikatorze 1 w formacie JSON. Play obsługuje obecnie natywnie HTML, JSON i XML, ale możesz łatwo użyć innego typu, postępując zgodnie z oficjalną dokumentacją lub korzystając z modułu negocjacji treści .

Jeśli używasz Eclipse do programowania, sugeruję użycie wtyczki klienta REST, która pozwala przetestować trasy i odpowiadający im typ zawartości.

seb
źródło
2
Dzięki za opublikowanie tego. Sztuka teatralna! dokumenty są jednymi z najlepszych, jakie widziałem, wyjaśniających podstawową strukturę rzeczy, ale czasami brakuje im szczegółowych przykładów. Przedstawienie dwóch podejść na tym samym przykładzie naprawdę wyjaśnia sprawę.
Brad Mace
Wypróbowuję Twój przykład, jestem ciekawy, gdzie przesłane dane JSON są konwertowane na klasę użytkownika. na przykład w funkcji createUser stwierdzam, że newUser ma wartość null.
Gary,
2
@Gary: Może użyłeś „user” zamiast „newUser”? Nazwa kontrolera i parametr formularza muszą być zgodne. Stworzyłem prosty projekt, który pokazuje powyższą metodę, w tym dane wyjściowe HTML / XML / JSON dla wszystkich użytkowników na github.com/sebhoss/play-user-sample
seb
Dzięki, przetestowałem to za pomocą curl do wysyłania ciągu JSON i wygląda na to, że platforma odtwarzania nie rozpoznała typu zawartości aplikacji / json: groups.google.com/group/play-framework/browse_thread/thread/ ...
Gary
@Gary: Dzięki za podpowiedź! Wygląda na to, że jest to naprawione w gałęzi master, możesz spróbować zbudować go samodzielnie, a następnie przetestować ponownie ...
seb
68

To wciąż popularne pytanie, ale najwyżej ocenione odpowiedzi nie są aktualne w aktualnej wersji gry. Oto działający przykład REST z grą 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
źródło
Chciałbym również zobaczyć zaktualizowaną wersję odpowiedzi seba, ale niestety Twoja odpowiedź usunęła całą magię .xml i .html. :-(
flaschenpost
26

Użyj Play! zrobić to wszystko. Pisanie usług REST w Play jest bardzo łatwe.

Po pierwsze, plik tras ułatwia pisanie tras zgodnych z podejściem REST.

Następnie piszesz swoje akcje w kontrolerze dla każdej metody API, którą chcesz utworzyć.

W zależności od tego, jak chcesz zwrócić wynik (XML, JSON itp.), Istnieje kilka metod, których możesz użyć. Na przykład użycie metody renderJSON umożliwia bardzo łatwe renderowanie wyników. Jeśli chcesz renderować XML, możesz to zrobić w taki sam sposób, jak budowałbyś dokument HTML w swoim Widoku.

Oto zgrabny przykład.

plik tras

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

Plik aplikacji

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);
}

plik getUser.xml

<user>
   <name>${user.name}</name>
   <dob>${user.dob}</dob>
   .... etc etc
</user>
Codemwnci
źródło
Czy można wybrać właściwą metodę getUser na podstawie nagłówka Accept?
Timo Westkämper,
jest, ale nie do końca wiarygodne. Jeśli play wie, że nagłówek jest żądaniem JSON, spróbuje wyrenderować plik getuser.json. Jeśli nagłówek to XML, to spróbuje getuser.xml. Jest to jednak znacznie łatwiejsze do zrozumienia i bardziej podobne do REST dla user / User / {id} / type
Codemwnci
29
Nie sądzę, aby bardziej przypominało REST jawne określenie typu reprezentacji w identyfikatorze URI. Lepiej jest użyć bezpośrednio nagłówka Accept i nie zmieniać identyfikatora URI, ponieważ zasób, który chcesz zobaczyć, pozostaje taki sam. Powyższy przykład można przepisać tak, aby zawierał tylko jedną metodę getUser (Long id), która działa dokładnie tak samo, jak jej obecna implementacja, ale zamiast definiować getUserJSON, getUserXML itp., Należy raczej zdefiniować szablon getUser.json i getUser.xml. Chociaż zmieniłbym to również na user.json / user.xml
seb
Dzięki, to jest bardzo pomocne. Doceniam to!
Gary,
1
@seb - czy możesz przekształcić swój komentarz w odpowiedź? Chciałbym zobaczyć przykład techniki, którą opisujesz
Brad Mace,
5

Integracja z implementacją JAX-RS jest możliwym alternatywnym podejściem do korzystania z wbudowanego routingu HTTP Play. Aby zapoznać się z przykładem RESTEasy, zobacz RESTEasy Play! moduł .

Takie podejście ma sens, jeśli już zainwestowałeś w JAX-RS lub jeśli potrzebujesz niektórych zaawansowanych funkcji REST, które zapewnia JAX-RS, takich jak negocjowanie treści. Jeśli nie, byłoby łatwiej po prostu użyć Play bezpośrednio do obsługi formatu JSON lub XML w odpowiedzi na żądania HTTP.

Peter Hilton
źródło
2

Wygląda na to, że to podejście jest zepsute w Play w wersji 1.2.3. Jeśli pobierzesz źródło wykonane przez @seb i wspomniane wcześniej https://github.com/sebhoss/play-user-sample , utworzenie nowego obiektu użytkownika za pomocą POST z obiektem JSON nie jest już możliwe.

Musisz mieć określone metody tworzenia wykonane przy użyciu POST json i xml. Przedstawiono tutaj: https://groups.google.com/forum/#!topic/play-framework/huwtC3YZDlU

tchristensen
źródło