How to design API in Spring MVC? - java

I have a Spring MVC controller but I'm not sure that it is a good or bad design. As far as I know, api versioning is missing but apart from that I implemented Swagger for documentation and added SpringSecurity and tried to follow YARAS(Yet Another RESTful API Standard) to build it but I need another eye on that to comment it.
#Slf4j
#Controller
#RequestMapping
#RequiredArgsConstructor
public class XGameController implements GameController {
private final GameService gameService;
private final ObjectMapper mapper;
#RequestMapping(value = "/", method= RequestMethod.GET)
public String index() {
return "game";
}
#RequestMapping(value = "/login", method= RequestMethod.GET)
public String login() {
return "login";
}
#Secured("ROLE_USER")
#RequestMapping(value = "/games", method= RequestMethod.POST)
public String initializeGame(Model model) {
log.info("New XGame is initializing...");
Game game = new Game();
game = gameService.initializeGame(game.getId());
try {
model.addAttribute("game", mapper.writeValueAsString(game));
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
log.info("New XGame is initialized successfully!");
return "game";
}
#Secured("ROLE_USER")
#RequestMapping(value = "/games/{gameId}", method= RequestMethod.PUT)
public #ResponseBody Game play(#PathVariable("gameId") String gameId,
#RequestParam Integer pitNumber,
#RequestParam String action) {
log.info("Sowing stone is triggered...");
return gameService.executeGameRules(UUID.fromString(gameId), pitNumber);
}
#RequestMapping(value = "/403", method= RequestMethod.GET)
public String error403() {
return "/error/403";
}
}
My swagger snapshot;

I would make some changes.
In /games/{gameId} I would use PATCH instead of PUT. The reason is that PUT is intended to completely replace the resource (in your case, the Game). This does not seem to be what you are doing in this endpoint. PATCH is intended to partially update a resource, which seems much more suited to what you are doing here.
Still in /games/{gameId} I would use the request body to provide the needed data instead of query parameters. It simply doesn't seem right. Query parameters are way more suited to GET requests than to POST, PUT or PATCH.
I would rename /403 to something else that actually gives some context about what 403 is. Having said this, I would go with /error-pages/403. Additionally, I would also consider removing this endpoint from the swagger specification.
Other than this, it seems fine to me.

Some advices :
Use a path that represents the context or the idea of your controller and you can add the version
#RequestMapping("/V1/xgame")
Use specialized annotations such as : #GetMapping, #PostMapping etc...
For /403 use a meaning full name such as /errors
Use custom message that you will return to the users. For that you need a ControllerAdvice.
Google on patterns and best practices Rest API design
Read some books for better undertanding.

Firstly, instead of #RequestMapping use a specific Mapping(Get, Post,etc.) and the use of type of mapping is up to you which you find more particular to the cause of using it
if you are redirecting from a page to homepage try to use return "redirect:"/url"" instead of just returning HTML file directly.
Rename your method for error, RequestMapping value to some more reasonable name.
instead of using return "/error/403"
use return "redirect:/error/403"

Related

How can I override a Spring Data REST method without disabling default implementations

I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.

Spring rest vs standard Rest?

In spring doc, I can get following explanations for the difference of the spring mvc and spring rest.
Spring REST architecture is also based on Spring MVC, slightly making the difference on the View part. Traditional Spring MVC relies on the View technology to render the model data, the Spring REST architecture also does the same, except that the model object is set directly into the HTTP response, which the #ResponseBody converts into JSON/XML automatically. The output of a RESTful web service has to be a JSON or an XML, a standard format that could be easily handled across different consumer application platforms.
But in https://en.wikipedia.org/wiki/Representational_state_transfer.
It has a couple of feactures except for the json response like the rest will use the HTTP PUT/DELETE/POST method to manipulate resource.
I was wondering if below spring controller can be treated as a restful service. I have used #RestController to return json response, but did not use any other rest features.
#RestController
#RequestMapping(value = "/employee")
public class EmployeeController {
#RequestMapping(value = RequestAction.LOADLIST, method = RequestMethod.POST)
public List<Employee> list(#RequestBody Employee bo) {
System.out.println(bo);
return employeeList;
}
#RequestMapping(value = RequestAction.LOAD, method = RequestMethod.POST)
public Employee getEmployee(
#RequestBody Employee input) {
for (Employee employee : employeeList) {
if (employee.getId().equals(input.getId())) {
return employee;
}
}
return input;
}
#RequestMapping(value = RequestAction.ADD, method = RequestMethod.POST)
public Employee addEmployee(#RequestBody Employee bo) {
System.out.println(bo);
return bo;
}
#RequestMapping(value = RequestAction.UPDATE, method = RequestMethod.POST)
public Employee updateEmployee(#RequestBody Employee bo) {
System.out.println(bo);
for (Employee employee : employeeList) {
if (employee.getId().equals(bo.getId())) {
employee.setName(bo.getName());
return employee;
}
}
return bo;
}
}
Your example script is not REST because it change the url for each task, and use always POST verb. Spring REST use different HTTP verbs (GET, POST, DELETE) to differentiate the action. A few times sharing the same url.
Example:
#RestController
#RequestMapping("/users")
public class UsersController {
#GetMapping
public List<User> index() {...}
#GetMapping("{id}")
public User show(...) {...}
#PostMapping
public User create(...) {...}
#PutMapping("{id}")
public User update(...) {...}
#DeleteMapping("{id}")
public void delete(...) {...}
}
Your example is not following the conventionals of a REST API (e.g. GET for retrieval, POST for create, PUT for full update, PATCH for partial update, etc.), but it does not mean, that you can't. As others stated above, you might just got confused with the term. REST is a protocol and it has lots of conventionals for service usages, which if you follow, you can say that your service is REST or RESTful.
This page is the simple best source of tutoring you in this area:
https://restfulapi.net
More importantly this, when we are considering your example: https://restfulapi.net/http-methods/
I also check it sometimes.

How use PUT method in Springboot Restcontroller?

Am developing an application using Spring boot.I tried with all representations verbs like GET, POST , DELETE all are working fine too. By using PUT method, it's not supporting in spring boot. Whether I need to add any new configuration.
Put method works only the request not have any parameters. If i add any query parameter or form data it doesnt work. Kindly any expertize will help me to solve this issue.
#RequestMapping("/student/info")
#RequestMapping(method = RequestMethod.PUT)
public #ResponseBody String updateStudent(#RequestParam(value = "stdName")String stdName){
LOG.info(stdName);
return "ok";
}
Request method 'PUT' not supported
This code will work fine. You must specify request mapping in class level or in function
level.
#RequestMapping(value = "/student/info", method = RequestMethod.PUT)
public #ResponseBody String updateStudent(#RequestBody Student student){
LOG.info(student.toString());
return "ok";
}
Have you tried the following Request Mapping:
#RequestMapping(value = "/student/info", method = RequestMethod.PUT)
There's no need to separate the value and the Request Method for the URI.
Since Spring 4.3 you can use #PutMapping("url") : https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/PutMapping.html
In this case it will be:
#PutMapping("/student/info")
public #ResponseBody String updateStudent(#RequestParam(value = "stdName")String stdName){
LOG.info(stdName);
return "ok";
}
I meet the same issue with spring boot 1.5.*,I fixed it by follow:
#RequestMapping(value = "/nick", method = RequestMethod.PUT)
public Result updateNick(String nick) {
return resultOk();
}
Add this bean
#Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory(){
#Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setParseBodyMethods("POST,PUT,DELETE");
}
};
}
see also
https://stackoverflow.com/a/25383378/4639921
https://stackoverflow.com/a/47300174/4639921
you can add #RestController annotation before your class.
#RestController
#RequestMapping(value = "/v1/range")
public class RangeRestController {
}

Spring MVC - Variables between pages, and Unsetting a SessionAttribute

The question sounds weird, I'm playing around with Spring MVC and am trying to move between two pages and basically I'm creating a JSP page using Spring Form JSTL's so it just uses a POST, and I use a controller to move from one page to the next. But Models are lost from page to page, and I'd like to hide the actual variable so QueryStrings are out of the question(as
far as I know). I know I can use a InternalResourceView, but only allows me to use a model.
I want to transfer a variable that will be exclusive to that page, what's the best way without a model or using QueryStrings?
I was planning on using SessionAttribute to easily define them, but was wondering, how do you remove a SessionAttribute created variable? I tried HttpSession.removeAttribute and it didn't seem to work.
You can also use SessionStatus.setComplete() like this:
#RequestMapping(method = RequestMethod.GET, value="/clear")
public ModelAndView clear(SessionStatus status, ModelMap model, HttpServletRequest request) {
model.clear();
status.setComplete();
return new ModelAndView("somePage");
}
or DefaultSessionAttributeStore.cleanUpAttribute like this:
#RequestMapping(method = RequestMethod.GET, value="/clear")
public ModelAndView clear(DefaultSessionAttributeStore status, WebRequest request, ModelMap model) {
model.remove("mySessionVar");
status.cleanupAttribute(request, "mySessionVar");
return new ModelAndView("somePage");
}
I use it like this on one of my forms that has mulitple sessionAttributes and I want to remove only one of them.
Yes... HttpSession.removeAttribute
You can use the removeAttribute method from the HttpSession class.
you can use WebRequest.removeAttribute(String name, int scope) that works with Spring #SessionAttributes. Quote from #SessionAttributes javadoc - "Alternatively, consider using the attribute management capabilities of the generic {#link org.springframework.web.context.request.WebRequest} interface."
Also look at my example.
#Controller
#SessionAttributes({"sessionAttr"})
public class MyController {
#ModelAttribute("sessionAttr")
public Object defaultSessionAttr() {
return new Object();
}
#RequestMapping(value = "...", method = RequestMethod.GET)
public String removeSessionAttr(WebRequest request, Model model) {
request.removeAttribute("sessionAttr", WebRequest.SCOPE_SESSION);
model.addAttribute("sessionAttr", defaultSessionAttr());
return "myView";
}
}

How to create a default method in SpringMVC using annotations?

I can't find a solution to this, and it's driving me crazy. I have #Controller mapped that responds to several methods using #RequestMapping. I'd like to tag one of those methods as default when nothing more specific is specified. For example:
#Controller
#RequestMapping("/user/*")
public class UserController {
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
So /user/login -> login method, /user/logout -> logout, etc. I'd like to make it so that if someone goes to /user then it routes to one of these methods. However, I don't see anything on #RequestMapping that would allow me to specify one of these methods as a default handler. I also don't see any other annotations that might be used on the class either to do this. I'm beginning to suspect it doesn't exist.
I'm using Spring 2.5.6. Is this solved in 3.0.0? I might just hack Spring to make it work because it's tremendously annoying this isn't more straightforward.
Thanks in Advance.
Take a look at this answer:
Spring MVC and annotated controllers issue
What if you annotate a method with:
#RequestMapping(method = RequestMethod.GET)
You can see an example here:
Spring 3.0 MVC + Hibernate : Simplified with Annotations – Tutorial
The same behavior can be seen here:
Spring Framework 3.0 MVC by Aaron Schram (look at page 21)
Short answer: I do not know how to simply specify one method as default with a simple tag.
But there is this ...
I do not know in which version of Spring this was implemented, but you can provide multiple values to #RequestMapping in 3.1.2. So I do this:
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(value = {"", "/", "/list"}, method = RequestMethod.GET)
public String listUsers(ModelMap model) { }
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView add(HttpServletRequest request, ModelMap model) { }
}
The following URLs then map to listUsers():
http://example.com/user
http://example.com/user/
http://example.com/user/list
I would create one default method without RequestMapping's value in there. Please see method defaultCall() below. You can then simply call them with URL: [yourhostname]/user
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String defaultCall( MapModel model ) {
//Call the default method directly, or use the 'forward' String. Example:
return authenticate( model );
}
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
Ref: Spring Framework Request Mapping
Simply using #RequestMapping("**") on your default method should work. Any more specific mappings should still pick up their requests. I use this method for setting up default methods sometimes. Currently using Spring v4.3.8.RELEASE.

Categories

Resources