Todays problem:
- at least 2 different APIs (CRUD / Bulk)
- non-changed code-paths / REST-Endpoints should work like before
- they should show a different set of attributes in the same object
First Try – Jackson mixins
Mixins have to be registered in the global ObjectMapper that also includes the Spring-MVC instance and is therefore used by nearly the whole application.
The first requirement is no problem if we are only using two different sets of different views.
The second requirement should also be no real Problem because mixins are registered onto a specific type
objectMapper.addMixIn(User.class, BulkUser.class)
This makes it quite handy because you can build a simple output-class for every endpoint that should be handled differently. If we use a simple example based on a fictional User
class for example:
class BulkUser extends User {
}
interface BulkUserMixin {
@JsonIgnore
String getUsername();
}
This makes mixins really handy to quickly change the system-wide representation of a specific class.
The Problem: Mixins only work for specifc Types and not for subtypes.
Therefore, if the User
or for that matter the BulkUser
have a getter like:
class User {
// ...
Address getAddress();
// ...
}
the nested class Address
would not be directly affected without registering a mixin for that exact class.
Conclusion: Because the attributes that should be hidden from the bulk api are nested in types, that are also used in the „normal“ api that should expose everything, mixins are not the tool of choice.
Second Idea
The second idea that has presented itself after debugging the first try with mixins is to replace the now used APIs for accessing the User
and Address
.
This would be to use POJOs (Plain-Old-Java-Object) with seperately implemented DTOs (Data-Transfer-Object) to build completely seperated views on the fundamental objects.
After a quick counting on the positions that directly one of these objects, this idea was not implemented because time is essential in this stage of the associated project.
My Solution – @JsonView
After some digging around at the com.fasterxml.jackson.annotation
package there seemed another interesting concept that could be implemented:
@JsonView
This annotation can be used to build seperate views of the same object.
If we take our example from the first try and slightly modify it, then we’re able to simple show different representations of that object without any big modifications.
class User {
Long getId();
@JsonView(ViewRepo.API.class)
String getUsername();
@JsonView({ViewRepo.API.class, ViewRepo.Bulk.class})
Address getAddress();
}
With this simple annotation at the model-level we can decide on which call we want to display what attribute. But to get it fully working we need the same annotation also on the method that is called via REST.
@RestController
public class ApiEndpoint {
@GetMapping("/api")
@JsonView(ViewRepo.API.class)
public Object showUsers() {
// ....
}
}
@RestController
public class BulkEndpoint {
@GetMapping("/bulk")
@JsonView(ViewRepo.Bulk.class)
public Object showUsers() {
// ....
}
}
Now if we call out REST-Endpoint /bulk
we are presented with an object that should lacks the username
attribute but includes everything else.
{
"id": 1,
"address": {
"country": "Germany",
/* .... */
}
}
For contrast our /api
should still include everything. This works because everything that is not annotated at all, is still serialized every time.
And for everyone who wonders what the class (or interface) ViewRepo
looks like:
public interface ViewRepo {
interface API {}
interface Bulk {}
}
And yeah, you can absoutely use inheritance here to include one view into another one with something like the following.
public interface ViewRepo {
interface API {}
interface Bulk {} // This is used by normal users
interface AdminBulk extends Bulk {} // Used to show more
}
Afterword
This was just another day in the life of a mainly Java programmer who just had some time to write a „whole“ article instead of just posting some random code-snippets.
Schreibe einen Kommentar