JSON-API in einer Spring-Anwendung
1. Überblick
In diesem Artikel untersuchen wirthe JSON-API spec und wie dies in eine von Spring unterstützte REST-API integriert werden kann.
Wir werden dieKatharsis-Implementierung der JSON-API in Java verwenden - und wir werden eine Katharsis-basierte Spring-Anwendung einrichten - also brauchen wir nur eine Spring-Anwendung.
2. Maven
Schauen wir uns zunächst unsere Maven-Konfiguration an. Wir müssen unserenpom.xml die folgende Abhängigkeit hinzufügen:
io.katharsis
katharsis-spring
3.0.2
3. Eine Benutzerressource
Schauen wir uns als nächstes unsere Benutzerressource an:
@JsonApiResource(type = "users")
public class User {
@JsonApiId
private Long id;
private String name;
private String email;
}
Beachten Sie, dass:
-
Die Annotation@JsonApiResourcewird verwendet, um unsere RessourceUserzu definieren
-
Die Annotation von@JsonApiIdwird verwendet, um die Ressourcenkennung zu definieren
Und ganz kurz - die Persistenz für dieses Beispiel wird hier ein Spring Data-Repository sein:
public interface UserRepository extends JpaRepository {}
4. Ein Ressourcen-Repository
Lassen Sie uns als Nächstes unser Ressourcen-Repository diskutieren. Jede Ressource sollte einResourceRepositoryV2haben, um die darauf verfügbaren API-Operationen zu veröffentlichen:
@Component
public class UserResourceRepository implements ResourceRepositoryV2 {
@Autowired
private UserRepository userRepository;
@Override
public User findOne(Long id, QuerySpec querySpec) {
return userRepository.findOne(id);
}
@Override
public ResourceList findAll(QuerySpec querySpec) {
return querySpec.apply(userRepository.findAll());
}
@Override
public ResourceList findAll(Iterable ids, QuerySpec querySpec) {
return querySpec.apply(userRepository.findAll(ids));
}
@Override
public S save(S entity) {
return userRepository.save(entity);
}
@Override
public void delete(Long id) {
userRepository.delete(id);
}
@Override
public Class getResourceClass() {
return User.class;
}
@Override
public S create(S entity) {
return save(entity);
}
}
Ein kurzer Hinweis hier - das ist natürlichvery similar to a Spring controller.
5. Katharsis-Konfiguration
Da wirkatharsis-spring verwenden, müssen wir nurKatharsisConfigV3 in unsere Spring Boot-Anwendung importieren:
@Import(KatharsisConfigV3.class)
Und konfigurieren Sie die Katharsis-Parameter in unserenapplication.properties:
katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/
Damit können wir beginnen, die API zu verbrauchen. zum Beispiel:
-
GET “http://localhost:8080/users“: um alle Benutzer zu erhalten.
-
POST “http://localhost:8080/users”: um neuen Benutzer hinzuzufügen und mehr.
6. Beziehungen
Lassen Sie uns als Nächstes erläutern, wie Entitätsbeziehungen in unserer JSON-API behandelt werden.
6.1. Rollenressource
Lassen Sie uns zunächst eine neue Ressource einführen -Role:
@JsonApiResource(type = "roles")
public class Role {
@JsonApiId
private Long id;
private String name;
@JsonApiRelation
private Set users;
}
Stellen Sie dann eine Viele-zu-Viele-Beziehung zwischenUser undRole her:
@JsonApiRelation(serialize=SerializeType.EAGER)
private Set roles;
6.2. Rollenressourcen-Repository
Sehr schnell - hier ist unserRoleRessourcen-Repository:
@Component
public class RoleResourceRepository implements ResourceRepositoryV2 {
@Autowired
private RoleRepository roleRepository;
@Override
public Role findOne(Long id, QuerySpec querySpec) {
return roleRepository.findOne(id);
}
@Override
public ResourceList findAll(QuerySpec querySpec) {
return querySpec.apply(roleRepository.findAll());
}
@Override
public ResourceList findAll(Iterable ids, QuerySpec querySpec) {
return querySpec.apply(roleRepository.findAll(ids));
}
@Override
public S save(S entity) {
return roleRepository.save(entity);
}
@Override
public void delete(Long id) {
roleRepository.delete(id);
}
@Override
public Class getResourceClass() {
return Role.class;
}
@Override
public S create(S entity) {
return save(entity);
}
}
Es ist wichtig zu verstehen, dass dieses einzelne Ressourcen-Repo den Beziehungsaspekt nicht behandelt - dies erfordert ein separates Repository.
6.3. Beziehungs-Repository
Um die Viele-zu-Viele-Beziehung zwischenUser -Role zu verarbeiten, müssen wir einen neuen Repository-Stil erstellen:
@Component
public class UserToRoleRelationshipRepository implements RelationshipRepositoryV2 {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Override
public void setRelation(User User, Long roleId, String fieldName) {}
@Override
public void setRelations(User user, Iterable roleIds, String fieldName) {
Set roles = new HashSet();
roles.addAll(roleRepository.findAll(roleIds));
user.setRoles(roles);
userRepository.save(user);
}
@Override
public void addRelations(User user, Iterable roleIds, String fieldName) {
Set roles = user.getRoles();
roles.addAll(roleRepository.findAll(roleIds));
user.setRoles(roles);
userRepository.save(user);
}
@Override
public void removeRelations(User user, Iterable roleIds, String fieldName) {
Set roles = user.getRoles();
roles.removeAll(roleRepository.findAll(roleIds));
user.setRoles(roles);
userRepository.save(user);
}
@Override
public Role findOneTarget(Long sourceId, String fieldName, QuerySpec querySpec) {
return null;
}
@Override
public ResourceList findManyTargets(Long sourceId, String fieldName, QuerySpec querySpec) {
User user = userRepository.findOne(sourceId);
return querySpec.apply(user.getRoles());
}
@Override
public Class getSourceResourceClass() {
return User.class;
}
@Override
public Class getTargetResourceClass() {
return Role.class;
}
}
Wir ignorieren die singulären Methoden hier im Beziehungs-Repository.
7. Test
Lassen Sie uns abschließend einige Anforderungen analysieren und wirklich verstehen, wie die JSON-API-Ausgabe aussieht.
Wir werden mit dem Abrufen einer einzelnen Benutzerressource beginnen (mit id = 2):
{
"data":{
"type":"users",
"id":"2",
"attributes":{
"email":"[email protected]",
"username":"tom"
},
"relationships":{
"roles":{
"links":{
"self":"http://localhost:8080/users/2/relationships/roles",
"related":"http://localhost:8080/users/2/roles"
}
}
},
"links":{
"self":"http://localhost:8080/users/2"
}
},
"included":[
{
"type":"roles",
"id":"1",
"attributes":{
"name":"ROLE_USER"
},
"relationships":{
"users":{
"links":{
"self":"http://localhost:8080/roles/1/relationships/users",
"related":"http://localhost:8080/roles/1/users"
}
}
},
"links":{
"self":"http://localhost:8080/roles/1"
}
}
]
}
Imbissbuden:
-
Die Hauptattribute der Ressource befinden sich indata.attributes
-
Die Hauptbeziehungen der Ressource sind indata.relationships angegeben
-
Da wir@JsonApiRelation(serialize=SerializeType.EAGER) für die Beziehungroles verwendet haben, ist es im JSON enthalten und befindet sich im Knotenincluded
Weiter - Lassen Sie uns die Sammlungsressource mit den Rollen abrufen:
{
"data":[
{
"type":"roles",
"id":"1",
"attributes":{
"name":"ROLE_USER"
},
"relationships":{
"users":{
"links":{
"self":"http://localhost:8080/roles/1/relationships/users",
"related":"http://localhost:8080/roles/1/users"
}
}
},
"links":{
"self":"http://localhost:8080/roles/1"
}
},
{
"type":"roles",
"id":"2",
"attributes":{
"name":"ROLE_ADMIN"
},
"relationships":{
"users":{
"links":{
"self":"http://localhost:8080/roles/2/relationships/users",
"related":"http://localhost:8080/roles/2/users"
}
}
},
"links":{
"self":"http://localhost:8080/roles/2"
}
}
],
"included":[
]
}
Das schnelle Mitnehmen hier ist, dass wir alle Rollen im System erhalten - als Array im Knotendata
8. Fazit
JSON-API ist eine fantastische Spezifikation - endlich fügen wir etwas Struktur in die Art und Weise ein, wie wir JSON in unseren APIs verwenden, und unterstützen wirklich eine echte Hypermedia-API.
In diesem Artikel wurde eine Möglichkeit untersucht, es in einer Spring-App einzurichten. Unabhängig von dieser Implementierung ist die Spezifikation selbst meiner Meinung nach eine sehr vielversprechende Arbeit.
Der vollständige Quellcode für das Beispiel ist überon GitHub verfügbar. Es ist ein Maven-Projekt, das importiert und unverändert ausgeführt werden kann.