JSON-API in einer Spring-Anwendung

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:

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.