I try to build a rest api with spring and face some issues. My original api is built with express on node and saw that some stuff I am pretty used to seem more complicated in spring.
For example, I have the following case, I could borrow the "controller" for /tasks even from the UserController.
/users
/users/:id
/users/:id/tasks
/tasks
Or I can easily inherit routes, my delegating them down. Spring doesn't seem to have something like that, where I could reference an already existing controller. It even seems to me that the RequestMapping value becomes long.
Is there something similar in Spring like what express can? Because I couldn't find any large spring mvc rest projects to illustrate that
You could add multiple values for #RequestMapping and also use Path Variables that fits your needs. For example,
#RequestMapping(value = "/users/{id}/tasks", method = GET)
public String getUserTaksFromIdPathVariable(#PathVariable("id") long id) {
return "Get all tasks from user with id=" + id;
}
And as I said, you can have multiple values:
#RequestMapping({"/tasks", "/users/{id}/tasks"}, method = GET)
public String getTasks(#PathVariable("id") Optional<long> id) {
return "whatever";
}
Related
Old, one and only method in controller.
// Cancel all shipments for specific item (beacuse from today selling companyX product is forbidden)
#PostMapping("/items/shipments/stop")
public List<String> stopShipmentsForItem(#RequestBody List<UUID> itemsUuids) {
return itemShippingService.stopShipment(itemsUuids);
}
Propose endpoint which stops specific shipping for specific item
// Extending existing one with query string
#PostMapping("/items/shipments/stop")
public List<String> stopShipmentsForItem(#RequestBody List<UUID> itemsUuids, #RequestParam(required = false) UUID shipmentUuid) {
return itemShippingService.stopShipment(itemsUuids, shipmentUuid);
}
OR
// Creating new one with path variables
#PostMapping("/items/{itemUuid}/shipments/{shipmentUuid}/stop")
public String stopShipmentForItem(#PathVariable UUID itemsUuid, #PathVariable UUID shipmentUuid) {
return itemShippingService.stopShipment(itemsUuids, shipmentUuid);
}
One opinion - First option equals less code, it supports Open for extensions from SOLID, query string should be use for filtering and UUID shipmentUuid underneath in service is used for filtering purposes.
Second opinion - Second option is more reasonable as those are different actions and should be separated and be responsible for one type of action and as with i.e. getting resources from DB you probably will use different endpoints for one resource and all resources
Which approach do you think is better? - keep in mind that old endpoint should still works as previously after potential modification.
Do you have any sources to prove that your opinion is correct?
I've been trying to start a REST api with Spring Boot and I'm a bit strugling with the separation of my resources and which endpoint should be in which file.
Let's say we have an api enpoint to deal with a user and achievements from this user:
/user/{id} GET - to fetch user by id
/achievement/{id} GET - to fetch by achievement
Which are both in their separates resources file:
UserResource
#RestController
public class UserResource {
public UserResource() {...}
#GetMapping("/users/{id}")
public UserDTO getUser(String id) {
log.debug("REST request to get User : {}", login);
return userService.getUserWithAuthoritiesById(id).map(AdminUserDTO::new));
}
And AchievementResource
#RestController
public class AchievementResource {
public AchievementResource(...) {...}
#GetMapping("/achievements/{id}")
public ResponseEntity<Achievement> getAchievement(#PathVariable Long id) {
return achievementRepository.findById(id);
}
}
So far so good, pretty simple. My problem comes when I must get all achievements from a User. Naming covention says I should have an endpoint such as:
/user/{id}/achievements GET
But where should this endpoint be? I feel like both Resources could be good since for the UserResource, the root of the endpoint is the user, but the AchievementResource could be logical too since we are returning achievements.
Easy answer: you have the wrong problem
But where should this endpoint be?
The definition of the resource should be in your machine readable api definition. You produce the class files you need by feeding your definition into a code generator for your choice of language. The generator will put the classes it creates in files somewhere, and you leave them in this default arrangement until some point in the future when you have a compelling reason to arrange them differently (at which point, you fork the code generator and make your preferred design the default).
That said, when designing by hand there's nothing particularly special about "REST endpoints". The guidelines for where resource classes belong is no different from any other classes in Java....
That said, I find that the literature around file layout heuristics rather disappointing. There doesn't seem to be a lot of material discussing the trade offs of different designs, or contexts in which one choice might be more compelling than another.
For your specific situation, I would advise putting the new resource into a file of its own. The argument here being that your UserResource has User dependencies, and your AchievementsResource has achievements dependencies, but your new thing has both, and as a matter of (hand waves) principle, we should avoid bringing unneeded achievements dependencies into the namespace of the UserResource (and vice versa).
In other words, if we find ourselves adding imports to an existing file to implement a new thing, that's a hint that the new thing may be better placed somewhere else.
Using separate files also has nice mechanical advantages - it reduces merge collisions, each file will have its own source control history (meaning that the history of Users isn't cluttered with a bunch of commits that are exclusively about new thing). See Adam Tornhill's work over at CodeScene, for example.
As you separated the controllers, it is not wrong, you should classify the methods by their general entity, "if I need to recover the user's achievements", it is related to both, however, where does she get this data from? of the Achievements knowing that each achievement must have a relationship in the database with the user, you can very well look it up in the achievement controller with a List returnAchievementsByUser (Integer Id) method.
It depends on your point of view and the business behind the scene. You can use just one endpoint in many cases; if "users" are the main resources who have achievements, then "/users/{user-id}" and {users/{user-id}/achievements/{achievement-id} get the user by Id and special achievement of the user
#RestController
#RequestMapping("users")
public class UsersRestController{
#GetMapping("/{user-id}")
public UserDTO getUser(#PathVariable("user-id") String id) {
code...
}
#GetMapping("/{user-id}/achievements/{achievement-id}")
public AchievementDTO getAchievement(#PathVariable("user-id") String userId,
#PathVariable("achievement-id") String achievementId) {
code...
}
}
And if locating "achievements" on top of "users" in their entity hierarchy has meaning to you and your business, then /achievements/{achievement-id}/users/{user-id} can be a rest presentation:
#RestController
#RequestMapping("achievements")
public class AchievementsRestController{
#GetMapping("/{achievement-id}")
public UserDTO getAchievement(#PathVariable("achievements-id") String id) {
code
}
#GetMapping("/{achievements-id}/users/{user-id}")
public AchievementDTO getAchievement(#PathVariable("user-id") String userId,
#PathVariable("achievement-id") String achievementId) {
code
}
}
finally ,whenever they are not in an entity hierarchy, you can pass userId to
"/achievements/{achievements-id}" (or achievement-id to "/users/{user-id}") as a RequestParam.
I'm trying to set up my website to allow location additions to the urls.
EG: mysite.com/us/ca/sanfrancisco/home
While also still allowing mysite.com/home and everything in between.
Spring boot parent so you know what version of spring I'm using:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
If there's another piece of versioning you need, let me know.
I get that I can add in regex variables to the request mapping, but how would I go about persisting those urls across more requests?
So right now for an example, the testing error page I have:
#RequestMapping({"/error/501", "/{state:[a-z]{2}}/error/501", "/{state:[a-z]{2}}/{city:[a-z]+}/error/501"})
public ModelAndView testingLocations(ModelMap model, #PathVariable(value="state", required = false) String state,
#PathVariable(value="city", required=false) String city){
getLogger().info("State: {}", state);
model.addAttribute("stateTest",state+":"+city);
model.addAttribute("view", "error");
return new ModelAndView("error/501", model);
}
But when I'm on my testing page, and I click the home button, it takes me back to mysite.com/home
So My Questions
Is there a way for me to persist it so that if they're currently on a location based url, it will apply that base to the future navigations? (unless they manually enter the url to not include them)
Then as a follow-up, is there a way for me to globally apply these request variables without requiring me to add the #PathVariable to every request mapping method? I get that I can just add the request mapping variable strings themselves to the controller class, so that I don't need those on every method. But is there a way for me to utilize those without needing the #PathVariable annotations?
Finally, is there a way for me to make this not as hardcoded, like a way for me to say /{*location}/error to cover as deep as the locations will allow? While still having the verification on the location formatting, so verifying that 1 we support the locations given, 2 the format is correct (/ca/sanfrancisco vs /anything/anything
The last one I can live with, if I need to have the /state/city/municipality/actualtarget
As far as verifying that we support the locations given, I understand that's on my end, which I'll probably just have a small database to keep track of where we do and do not support for the given variables.
Is there a best practice for building this system? I tried to find something on this, but googling "spring boot location url" is not the best at giving me what I need, since "location" can apply to a pretty wide range of topics. I've gotten to where I am from searching, but I can't seem to pin down these last few steps.
Any help/advice/suggestions is appreciated. If upgrading versions is required, I'm not sure how viable that is at the moment, I'd have to look into it. Preferably I'd like the solution to be able to be done on the current spring version I'm running.
The best way here is:
#RequestMapping("/some/{foo}/{baz}")
public String hi(CompositeObject compositeObject) {
return "hi";
}
#Data
public class CompositeObject {
private String foo;
private String baz;
}
Spring provides functionality for request path and request parameters to collect it into a composite object. It doesn' work either with body or headers.
If you have something optional like state, then just keep it null at the controller and handle later
I would like to know a simple solution for receiving images and simple data in a single post using Spring. I am a beginner in Java so I would like to know the easy way. I've used several backend frameworks and I've encountered this problem in all of them.
I have the following problem:
I was receiving a multipart/form-data like this
public CasaVenda storeCasaVendaOld(#RequestParam("dormitorios") Integer dormitorios, #RequestParam("preco") Double preco, #RequestParam("foto_1") MultipartFile foto_1){
I receive some numbers along with an image. This is a typical first attempt of beginner's implementation.Validate will require code to be writeen in the controller and I have to receive far more parameters than described here, so it's a bad implementation.
I thought about receiving a model
public CasaVenda storeCasaVenda(#Valid #RequestBody CasaVenda casa)
Now I can validate using annotations and so. The problem is with the file. Is there a simple solution to receive the file in one post request or should I split the process of seding the overall data and the files spareted? I mean I can make the process of the resource creation two steps, first it enters the overall data and afterwards it includes the photos.
Its pretty easy to define an object:
public class MyObject {
private Integer dormitorios;
private Double preco;
...
getters/setters/constructors/etc.
...
// I'm not sure whether you can place a MultipartFile here as well to process image,
// however it doesn't make sense to validate it anyway
}
Then you can use this object in the controller, it will map all the query params to the fields of the object automatically by spring:
public CasaVenda storeCasaVendaOld(MyObject myObject) {
}
Now, you can place Validation annotations inside MyObject and it will be validated, just do not use #RequestParam annotation before the object...
I've got a Spring #RequestMapping with a couple of #PathVariables, and the first one is necessary to narrow down to the second one - you can see in the example below that I need to get the Department in order to get the Module. Using plain String #PathVariables I can do it like this:
#RequestMapping("/admin/{dept}/{mod}/")
public String showModule(#PathVariable String dept, #PathVariable String mod) {
Department department = dao.findDepartment(dept);
Module module = department.findModule(mod);
return "view";
}
But I'm keen to use Spring's Converter API to be able to specify the Department directly as the #PathVariable. So this works after I've registered a custom Converter class:
#RequestMapping("/admin/{dept}/")
public String showDept(#PathVariable Department dept) {
return "view";
}
But the Converter API doesn't give access outside of the single argument being converted, so it's not possible to implement the Converter for Module. Is there another API I can use? I'm eyeing up HandlerMethodArgumentResolver - has anyone solved a problem like this, or are you sticking to String #PathVariables?
I'm using Spring 3.1.
I haven't done it like this but one way I thought of was to make a separate converter for the both of them:
#RequestMapping("/admin/{deptAndModule}/")
public String showDept(#PathVariable DepartmentAndModule deptAndModule) {
return "view";
}
And have the converter able to take an input of the form "deptid-modid" e.g. "ch-c104". It wouldn't be possible to separate them with a slash as the request wouldn't match the RequestMapping pattern of /admin/*/.
In my case, the requirements have changed slightly so that module codes are fully unique and don't need to be scoped to department. So I don't need to do this any more. If I did, I would probably eschew the automatic Module conversion and do it manually in the method.