In our REST APIs, we want to use path-based versioning for REST endpoints.
Let's say we have a /v1/students GET endpoint. We have a corresponding controller method like:
#GetMapping("/v1/students")
public List<Student> getStudents(){
return studentService.getStudents();
}
Inside a controller class called SchoolControllerV1.java (along with 20 different other endpoints.)
And months later we have a breaking change in response structure and we need a new version like /v2/students GET.
#GetMapping("/v2/students")
public List<StudentV2> getStudents(){
return studentService.getStudentsV2();
}
Inside a controller class called SchoolControllerV2.java (along with 20 different other endpoints.)
The problem is the other 19 controller methods which are exactly identical in V1 and V2 controllers are duplicated.
What is the best way of having multiple versions of controllers on a Spring Boot application?
Related
I have a #RestController with a GET method to list all the instances of a Resource R
I want swagger-ui to list thrice this GET method like this:
findRByFoo
findRByBar
findRByFooAndBar
that corresponds to the following GET petitions:
/resources?foo=myFoo
/resources?bar=myBar
/resources?foo=myFoo&bar=myBar
so that the clients of my API Rest don't have to guess that they can search by foo or bar and can simply look at the swagger-ui (version 2.9.2) and use those API calls
However, given that the three methods are at the /resources path, swagger simply lists one of them and only one.
The question is, How may I list the three API calls?
Edit: It seems to be a limitation of Swagger 2 Unable to add multiple operations on same path in swagger rest API documentation #1378, so let me rephrase the question as:
How may I circumvent this limitation?
Does it mean that the design of my API Rest is not as Rest as should be?
I rewrited the RController to have a single GET
#RestController("/path-to-r")
public class RController
{
#GetMapping
public List<R> findR(Optional<String> foo, Optional<String> bar)
{
....
}
}
And added genericModelSubstitutes as explained in Java 8 Optional #RequestParam lost from the swagger-ui #1848
The final result is such that when the clients of my API click in the combo they can see the two optional parameters to include in the GET petition
In my spring controller, I have 2 rest api methods. example: getUser, getRole
One client is accessing it by like "/api/v1".
Now I want to update one of the methods. i.e., getRole. So the new method/version will be "/api/v2".
But no change in methods of v1. i.e., "/api/v1".
How to handle the rest methods with both versions in the same project ?
I mean, getUser rest API should support both "/api/v1" and "/api/v2".
And getRole rest API should support both versions but different functionality (example: database changed, logic changed).
In simple words,
1. getUser will have 1 method which supports both versions.
2. getRole will have 2 methods for each versions.
Please help me here.
If you want to do use separate method for both version you can by defining different value in #RequestMapping
method 1
#RequestMapping(
value = "baseurl/v1/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
and
#RequestMapping(
value = "baseurl/v2/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
but if you want to handle it in same method you can using
method 2: use #PathVariable
#RequestMapping(
value = "baseurl/{version}/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public returnType methodName(#PathVariable("version") String version){
// code to check version
}
method 3: use #RequestHeader
#RequestMapping(
value = "baseurl/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public returnType methodName(#RequestHeader("version") String version){
// code to check version
}
But as you said you have different version of api's prefer to manage them in separate controller using #RequestMapping at class level
method 4:
#RestController
#RequestMapping("/v1")
public class anyController {
}
and
#RestController
#RequestMapping("/v2")
public class anyController {
}
It really depends on your use case and what all is changing. There are technically many different paths you can take, but I will describe two that sound like they would work for you.
Create path param for v1 and add conditional that checks to see if the path param is v2 and call different method.
Create new path for api/v2, add changes functionality and call v1 endpoint method.
This really gets into specifics and should probably be evaluated on which way you should take it, depending on how the existing code is implemented.
I would do the following approach
Define a version at the application level (Start with /v1) -- this version is changed only when you want to make bigger changes to your whole API and is usually stable.
Resources (roles, users) etc. should be versioned additionally using content negotiation via the headers
e.g.
GET /v1/users/503deb67-ff6e-4d1c-a225-408b910b7252/ HTTP/1.1
Accept: application/vnd.yourorg.users-v1+json
Here the client uses content negotiation for the specific resource she is interested in via the HTTP header
See https://www.mashery.com/blog/ultimate-solution-versioning-rest-apis-content-negotiation and http://blog.ploeh.dk/2015/06/22/rest-implies-content-negotiation/ for more details
There are many ways, but most likely you want to keep the code as decoupled as possible. This makes keeping the versions in separate classes a good option:
V1Controller.java:
#RestController
#RequestMapping("/api/v1")
public class V1Controller{
// version 1 api
}
V2Controller.java :
#RestController
#RequestMapping("/api/v2")
public class V2Controller{
// version 2 api
}
But for a even higher degree of decoupling you could have two different webapps deployed on the same Tomcat or Jetty server - version 1 under the context path "/api/v1" and version 2 on the context path "/api/v2". The webapps would simply be builds of different versions of the same code (or builds of different branches - if you're using git).
I usually add objects in my jsp requestScopes using Controllers.
For example, if I need to list categories in "localhost/products/viewall", I simply change my ProductsController adding something like
#RequestMapping("/products/viewall")
public void viewCategories(Model model) {
List<Category> categories = service.findAllCategories();
model.addAttribute("categories", categories);
}
so, this method adds a list of categories to my requestScope.
I need to do the same, but for all the pages of the website (since the variable I need will be used in the layout of the site).
How can I add something to all the pages requestScopes with Spring?
I think you have at least two possible options for this:
Using an MVC Interceptor. With an interceptor you can perform common operations for all requests. You can extend HandlerInterceptorAdapter and add common model data in postHandle
Using the #ModelAttribute annotation within an Controller. You can use this to add common data for all request mappings within a controller. You can also use an #ControllerAdvice (with #ModelAttribute annotated methods inside) if you want provide model data to all controllers. The section Using #ModelAttribute on a method should provide some additional information for this.
I'm currently working on a spring based web application and have a special requirement that seems not (at least not out of the box) be provided by spring MVC. The application serves data for multiple users each organized in their own "company". Once a user has logged in, I'm able to identify to which company he belongs to.
The application itself is built with multiple "modules", each with it's own domain objects, DAO, Service and Controller classes. The idea behind this concept is that I can for example extend a certain controller class (let's say to use a different service class) based upon the user and here is my problem.
Since i do not want to change my request paths for certain users, I'm currently looking for a way how to serve a request issued on a certain request path with different instances of a controller based upon the user issuing the request.
I came up with the idea to attach a HTTP Header Field for the company
Example:
X-Company:12345
and have my controllers configured like this:
#Controller
#RequestMapping(value="/foo/")
public class FooController {
// ...
}
#Controller
#RequestMapping(value="/foo" headers="X-Company=12345")
public class SpecialFooController extends FooController {
// ...
}
However this is not possible, since spring MVC treats each header (except Content-Type and Accept) as a kind of restriction, so in my case it would handle all requests with the FooController instead of the SpecialFooController unless i add a "headers" restriction on the FooController as well, which is not practicable.
Is there some way how to customize this behaviour or some direction one could point me to look for? Or maybe someone has another idea how to achieve this. It'll be highly appreciated.
Thanks!
I'am not sure but I think you can do this with HandlerMapping. Have a look at the documentation
To take your own suggestion, you can use the #RequestHeader annotation in your controller methods:
#Controller
public class MyController {
#RequestMapping("/someAction")
public void myControllerMethod(#RequestHeader('X-Company-Id') String companyId) {
}
}
Or you could use #PathVariable:
#Controller
public class MyController {
#RequestMapping("/{companyId}/someAction")
public void myControllerMethod(#PathVariable("companyId") String companyId) {
}
}
Using this approach would mean that it is in fact different URLs for each company, but if you can set the company id header, I guess you also can suffix the URLs with the company id.
But there are also other possibilities. You could write an interceptor that puts the company id in a session or request variable. Then you wouldn't have to add the annotation to every controller method. You could also use a subdomain for each company, but that wouldn't look too pretty if the company id is a random alphanumeric string. E.g: companyone.mydomain.com, companytwo.mydomain.com
Edit
#RequestMapping can be added to the controller level as you know, so you should be able to do
#Controller
#RequestMapping("/controller/{companyId}")
as the base url, if that's a better option.
I was able to meet the requirement by making usage of a customized RequestCondition. By defining your own annotation that can be placed at the type and method level of a controller. Extending the RequestMappingHandlerMapping by your own implementation and overriding the getCustomTypeCondition() and getCustomMethodCondition() methods translates a controller annotation into your own RequestCondition.
When a request comes in, the custom RequestCondition will be evaluated and the annotated controller(method) will then be called to serve the request. However this has the downside, that one needs to remove a servlet-context.xml file and switch to the WebMvcConfigurationSupport class instead in order to be able to use your customized RequestMappingHandlerMapping class.
This question was also discussed here.
Edit:
A pretty good example using this can be found here.
I would like to implement custom metric or statistics to my spring boot rest web service using actuator but i am not able to find simple tutorials.
For example:
how to show how many times a certain controller was called and what exact parameter field was filled?
how can i create a metric that when its URL is called, it runs certain query and shows back a json with some result
This seems like a good scenario for AOP (Aspect Oriented Programing) as this will allow you to separate this statistic logic from the business logic.
Have a look at Spring doc for more info about AOP and how to achieve that with Spring.
You can then define a pointcut on your controller and have a service for counting (and probably then storing) the data.
Refer below link
AOP Example
For point two the solution is to create an endpoint class (it can be or not a rest controller class). For example:
#Component
#RestControllerEndpoint(id = "pfm-statistics")
public class StatisticsEndpoint {
#GetMapping(value = "/", produces = "application/vnd.openxmlformats-
officedocument.spreadsheetml.sheet")
#ResponseBody
public byte[] generateStatisticsAsExcel() {
...
Note that the ID is the path to be called from URL. We can create a simple endpoint too and just return a string if we want. In this case instead of #RestControllerEndpoint annotation we can use #Endpoint, as a side note, the id should always contain dash