JSON API в приложении Spring

JSON API в приложении Spring

1. обзор

В этой статье мы начнем изучатьthe JSON-API spec и то, как его можно интегрировать в REST API, поддерживаемый Spring.

Мы будем использовать реализацию JSON-APIKatharsis на Java - и мы настроим приложение Spring на базе Katharsis - так что все, что нам нужно, это приложение Spring.

2. специалист

Во-первых, давайте взглянем на нашу конфигурацию maven - нам нужно добавить следующую зависимость в нашpom.xml:


    io.katharsis
    katharsis-spring
    3.0.2

3. Пользовательский ресурс

Затем давайте взглянем на наш пользовательский ресурс:

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

    @JsonApiId
    private Long id;

    private String name;

    private String email;
}

Обратите внимание, что:

  • Аннотация@JsonApiResource используется для определения нашего ресурсаUser

  • Аннотация@JsonApiId используется для определения идентификатора ресурса

И очень кратко - постоянство для этого примера будет здесь репозиторием Spring Data:

public interface UserRepository extends JpaRepository {}

4. Репозиторий ресурсов

Затем давайте обсудим наш репозиторий ресурсов - каждый ресурс должен иметьResourceRepositoryV2 для публикации операций API, доступных на нем:

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

Небольшое примечание - это, конечно,very similar to a Spring controller.

5. Конфигурация Катарсиса

Поскольку мы используемkatharsis-spring, все, что нам нужно сделать, это импортироватьKatharsisConfigV3 в наше приложение Spring Boot:

@Import(KatharsisConfigV3.class)

И настройте параметры Катарсиса в нашемapplication.properties:

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

Теперь мы можем начать использовать API; например:

6. Отношения

Затем давайте обсудим, как обрабатывать отношения сущностей в нашем JSON API.

6.1. Ресурс роли

Во-первых, давайте представим новый ресурс -Role:

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

    @JsonApiId
    private Long id;

    private String name;

    @JsonApiRelation
    private Set users;
}

А затем установите отношение «многие ко многим» междуUser иRole:

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

6.2. Репозиторий ресурсов роли

Очень быстро - вот наш репозиторий ресурсовRole:

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

Здесь важно понимать, что это единое хранилище ресурсов не обрабатывает аспект отношений - для этого требуется отдельный репозиторий.

6.3. Репозиторий отношений

Чтобы обрабатывать отношения «многие ко многим» междуUser -Role, нам необходимо создать новый стиль репозитория:

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

Мы игнорируем особые методы здесь, в репозитории отношений.

7. Test

Наконец, давайте проанализируем несколько запросов и действительно поймем, как выглядит вывод JSON-API.

Мы собираемся начать получение одного ресурса User (с 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"
            }
        }
    ]
}

Takeaways:

  • Основные атрибуты ресурса находятся вdata.attributes

  • Основные отношения Ресурса находятся вdata.relationships

  • Поскольку мы использовали@JsonApiRelation(serialize=SerializeType.EAGER) для отношенияroles, он включен в JSON и находится в узлеincluded

Далее - давайте получим ресурс коллекции, содержащий роли:

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

    ]
}

Быстрый вывод: мы получаем все роли в системе в виде массива в узлеdata.

8. Заключение

JSON-API - это фантастическая спецификация - наконец, добавим некоторую структуру в том, как мы используем JSON в наших API-интерфейсах, и действительно обеспечим истинный Hypermedia API.

В этом материале был рассмотрен один из способов его установки в приложении Spring. Но независимо от этой реализации, сама спецификация, на мой взгляд, очень и очень многообещающая работа.

Полный исходный код примера доступен черезon GitHub. Это проект Maven, который можно импортировать и запускать как есть.