API JSON dans une application Spring

API JSON dans une application Spring

1. Vue d'ensemble

Dans cet article, nous allons commencer à explorerthe JSON-API spec et comment l'intégrer dans une API REST Spring.

Nous allons utiliser l'implémentationKatharsis de JSON-API en Java - et nous allons configurer une application Spring basée sur Katharsis - donc tout ce dont nous avons besoin est une application Spring.

2. Maven

Jetons d'abord un coup d'œil à notre configuration maven - nous devons ajouter la dépendance suivante dans nospom.xml:


    io.katharsis
    katharsis-spring
    3.0.2

3. Une ressource utilisateur

Jetons ensuite un œil à notre ressource utilisateur:

@JsonApiResource(type = "users")
public class User {

    @JsonApiId
    private Long id;

    private String name;

    private String email;
}

Notez que:

  • L'annotation@JsonApiResource est utilisée pour définir notre ressourceUser

  • L'annotation@JsonApiId est utilisée pour définir l'identifiant de la ressource

Et très brièvement - la persistance de cet exemple va être un référentiel Spring Data ici:

public interface UserRepository extends JpaRepository {}

4. Un référentiel de ressources

Ensuite, parlons de notre référentiel de ressources - chaque ressource doit avoir unResourceRepositoryV2 pour publier les opérations d'API disponibles dessus:

@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);
    }
}

Une note rapide ici - c'est bien sûrvery similar to a Spring controller.

5. Configuration de Katharsis

Comme nous utilisonskatharsis-spring, tout ce que nous devons faire est d'importerKatharsisConfigV3 dans notre application Spring Boot:

@Import(KatharsisConfigV3.class)

Et configurez les paramètres Katharsis dans nosapplication.properties:

katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/

Avec cela - nous pouvons maintenant commencer à consommer l'API; par exemple:

6. Des relations

Voyons ensuite comment gérer les relations d'entités dans notre API JSON.

6.1. Ressource de rôle

Tout d'abord, introduisons une nouvelle ressource -Role:

@JsonApiResource(type = "roles")
public class Role {

    @JsonApiId
    private Long id;

    private String name;

    @JsonApiRelation
    private Set users;
}

Et puis configurez une relation plusieurs-à-plusieurs entreUser etRole:

@JsonApiRelation(serialize=SerializeType.EAGER)
private Set roles;

6.2. Référentiel de ressources de rôle

Très rapidement - voici notre référentiel de ressourcesRole:

@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);
    }
}

Il est important de comprendre ici que ce référentiel de ressources unique ne gère pas l'aspect relation - qui prend un référentiel distinct.

6.3. Référentiel de relations

Afin de gérer la relation plusieurs-à-plusieurs entreUser -Role, nous devons créer un nouveau style de référentiel:

@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;
    }
}

Nous ignorons les méthodes singulières ici, dans le référentiel de relations.

7. Test

Enfin, analysons quelques requêtes et comprenons vraiment à quoi ressemble la sortie de l'API JSON.

Nous allons commencer à récupérer une seule ressource utilisateur (avec 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"
            }
        }
    ]
}

À emporter:

  • Les principaux attributs de la ressource se trouvent dansdata.attributes

  • Les principales relations de la ressource se trouvent dansdata.relationships

  • Comme nous avons utilisé@JsonApiRelation(serialize=SerializeType.EAGER) pour la relationroles, il est inclus dans le JSON et trouvé dans le nœudincluded

Ensuite, récupérons la ressource de collection contenant les rôles:

{
    "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":[

    ]
}

La conclusion rapide ici est que nous obtenons tous les rôles du système - sous forme de tableau dans le nœuddata

8. Conclusion

JSON-API est une spécification fantastique - il nous faut enfin ajouter une structure à la manière dont nous utilisons JSON dans nos API et créer une véritable API Hypermedia.

Cet article explore une façon de le configurer dans une application Spring. Mais quelle que soit cette mise en œuvre, la spécification elle-même est - à mon avis - un travail très très prometteur.

Le code source complet de l'exemple est disponible suron GitHub. C'est un projet Maven qui peut être importé et exécuté tel quel.