2015-05-31 9 views
10

Przeszukując moją głowę, najlepiej jest zaprojektować interfejs API JSON przy użyciu Spring MVC. Jak wszyscy wiemy, IO jest drogi, a więc nie chcę, aby klient wykonywał kilka wywołań API, aby uzyskać to, czego potrzebuje. Jednak w tym samym czasie niekoniecznie chcę zwrócić zlew kuchenny.Sterownik spoczynku sprężyny Zwróć określone pola

Jako przykład pracowałem nad API gry podobnym do IMDB, ale zamiast gier wideo.

Gdybym zwrócił wszystko, co jest podłączone do Gry, wyglądałoby to tak.

/api/gra/1

{ 
    "id": 1, 
    "title": "Call of Duty Advanced Warfare", 
    "release_date": "2014-11-24", 
    "publishers": [ 
     { 
      "id": 1, 
      "name": "Activision" 
     } 
    ], 
    "developers": [ 
     { 
      "id": 1, 
      "name": "Sledge Hammer" 
     } 
    ], 
    "platforms": [ 
     { 
      "id": 1, 
      "name": "Xbox One", 
      "manufactorer": "Microsoft", 
      "release_date": "2013-11-11" 
     }, 
     { 
      "id": 2, 
      "name": "Playstation 4", 
      "manufactorer": "Sony", 
      "release_date": "2013-11-18" 
     }, 
     { 
      "id": 3, 
      "name": "Xbox 360", 
      "manufactorer": "Microsoft", 
      "release_date": "2005-11-12" 
     } 
    ], 
    "esrbRating": { 
     "id": 1, 
     "code": "T", 
     "name": "Teen", 
     "description": "Content is generally suitable for ages 13 and up. May contain violence, suggestive themes, crude humor, minimal blood, simulated gambling and/or infrequent use of strong language." 
    }, 
    "reviews": [ 
     { 
      "id": 1, 
      "user_id": 111, 
      "rating": 4.5, 
      "description": "This game is awesome" 
     } 
    ] 
} 

Jednak nie mogą one potrzebować wszystkich tych informacji, ale potem znowu oni mogą. Robienie wezwań do wszystkiego wydaje się złym pomysłem z I/O i wydajności.

Zastanowiłem się nad zrobieniem tego poprzez podanie parametru include w żądaniach.

Teraz, na przykład, jeśli nie podano żadnych załączników, wszystko, co można uzyskać, jest następujące.

{ 
    "id": 1, 
    "title": "Call of Duty Advanced Warfare", 
    "release_date": "2014-11-24" 
} 

Jednak chcesz, aby wszystkie informacje, które twoja prośba wyglądałaby tak.

/api/game/1?include=publishers,developers,platforms,reviews,esrbRating 

W ten sposób klient ma możliwość określenia, ile informacji chce. Jednak jestem w pewnym sensie beznadziejny najlepszym sposobem na wdrożenie tego przy użyciu Spring MVC.

Myślę, że kontroler wyglądałby tak.

public @ResponseBody Game getGame(@PathVariable("id") long id, 
    @RequestParam(value = "include", required = false) String include)) { 

     // check which include params are present 

     // then someone do the filtering? 
} 

Nie jestem pewien, w jaki sposób opcjonalnie serializuje się obiekt Game. Czy to możliwe? Jaki jest najlepszy sposób podejścia do tego w Spring MVC?

FYI, używam Spring Boot, który obejmuje Jackson do serializacji.

+0

Wydaje się zrobić trochę przedwczesne optymalizacji tutaj. Czy w twojej jednostce jest naprawdę tyle danych, że trzeba je filtrować na żądanie klienta? Na podstawie tego, co pokazałeś, nadmiernie skomplikujesz zarówno klienta, jak i serwer, i przerwiesz RESTfullness twojej usługi, nie oszczędzając przy tym wiele IO. –

+0

W przypadku mojego przykładu, zgadzam się, że to zdecydowanie przesada. Powiedzmy tylko, na przykład, że zwracanie obiektu Game skutkuje ogromnym obiektem JSON, czy uważasz, że lepiej byłoby zrobić/game/1/reviews zamiast/game/1? Include = reviews? – greyfox

+0

jeśli obiekt jest ogromny, to chciałbym poprosić o kolekcje z oddzielnych podprojektów, ponieważ obciążenie związane z wydaniem kilku wniosków byłoby niewielkie w stosunku do całkowitej ilości przesłanych danych. –

Odpowiedz

9

Zamiast zwracać obiekt Game, można go przekształcić do postaci szeregowej jako Map<String, Object>, gdzie klucze map reprezentują nazwy atrybutów. Możesz więc dodać wartości do mapy na podstawie parametru include.

@ResponseBody 
public Map<String, Object> getGame(@PathVariable("id") long id, String include) { 

    Game game = service.loadGame(id); 
    // check the `include` parameter and create a map containing only the required attributes 
    Map<String, Object> gameMap = service.convertGameToMap(game, include); 

    return gameMap; 

} 

Jako przykład, jeśli masz Map<String, Object> takiego:

gameMap.put("id", game.getId()); 
gameMap.put("title", game.getTitle()); 
gameMap.put("publishers", game.getPublishers()); 

byłoby odcinkach tak:

{ 
    "id": 1, 
    "title": "Call of Duty Advanced Warfare", 
    "publishers": [ 
    { 
     "id": 1, 
     "name": "Activision" 
    } 
    ] 
} 
+0

Wow, który jest tak naprawdę prosty i bardzo elastyczny sposób radzenia sobie z tym. – greyfox

+0

Robiąc to z usługą niestandardową, nie brzmi dla mnie bardzo dobrze, czy istnieje sposób "wiosny" na zrobienie tego? – EralpB

+0

@EralpB można również przeszukiwać serializowanie pól na podstawie ich nazwy (używając Jackson) lub użyć czegoś takiego jak GraphQL. Poza tym, nie jestem pewien, czy istnieje "wiosenny" sposób na osiągnięcie tego samego rezultatu. –