Рубрики
Без рубрики

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

Узнайте, как использовать спецификацию JSON-API для питания API Гипермедиа в веб-приложении Java/Spring.

Автор оригинала: baeldung.

1. Обзор

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

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

2. Maven

Во – первых, давайте взглянем на нашу конфигурацию 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 аннотация используется для определения нашего ресурса Пользователя
  • @@Jsonapi аннотация используется для определения идентификатора ресурса

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

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) {
        Optional user = userRepository.findById(id); 
        return user.isPresent()? user.get() : null;
    }

    @Override
    public ResourceList findAll(QuerySpec querySpec) {
        return querySpec.apply(userRepository.findAll());
    }

    @Override
    public ResourceList findAll(Iterable ids, QuerySpec querySpec) {
        return querySpec.apply(userRepository.findAllById(ids));
    }

    @Override
    public  S save(S entity) {
        return userRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public Class getResourceClass() {
        return User.class;
    }

    @Override
    public  S create(S entity) {
        return save(entity);
    }
}

Краткое примечание здесь – это, конечно, очень похоже на пружинный контроллер .

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

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

@Import(KatharsisConfigV3.class)

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

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

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

  • ПОЛУЧИТЬ ” http://localhost:8080/users “: чтобы получить всех пользователей.
  • СООБЩЕНИЕ ” http://localhost:8080/users “: добавить нового пользователя и многое другое.

6. Отношения

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

6.1. Ролевой ресурс

Во – первых, давайте представим новый ресурс – Роль :

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

    @JsonApiId
    private Long id;

    private String name;

    @JsonApiRelation
    private Set users;
}

А затем установите отношение “многие ко многим” между Пользователем и Ролью :

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

6.2. Хранилище ресурсов ролей

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

@Component
public class RoleResourceRepository implements ResourceRepositoryV2 {

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public Role findOne(Long id, QuerySpec querySpec) {
        Optional role = roleRepository.findById(id); 
        return role.isPresent()? role.get() : null;
    }

    @Override
    public ResourceList findAll(QuerySpec querySpec) {
        return querySpec.apply(roleRepository.findAll());
    }

    @Override
    public ResourceList findAll(Iterable ids, QuerySpec querySpec) {
        return querySpec.apply(roleRepository.findAllById(ids));
    }

    @Override
    public  S save(S entity) {
        return roleRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        roleRepository.deleteById(id);
    }

    @Override
    public Class getResourceClass() {
        return Role.class;
    }

    @Override
    public  S create(S entity) {
        return save(entity);
    }
}

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

6.3. Хранилище отношений

Чтобы справиться с отношениями “многие ко многим” между ПользователемРолью , нам нужно создать новый стиль репозитория:

@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.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public void addRelations(User user, Iterable roleIds, String fieldName) {
        Set roles = user.getRoles();
        roles.addAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public void removeRelations(User user, Iterable roleIds, String fieldName) {
        Set roles = user.getRoles();
        roles.removeAll(roleRepository.findAllById(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) {
        final Optional userOptional = userRepository.findById(sourceId);
        User user = userOptional.isPresent() ? userOptional.get() : new User();
        return  querySpec.apply(user.getRoles());
    }

    @Override
    public Class getSourceResourceClass() {
        return User.class;
    }

    @Override
    public Class getTargetResourceClass() {
        return Role.class;
    }
}

Мы игнорируем сингулярные методы здесь, в хранилище отношений.

7. Тест

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

Мы собираемся начать извлечение одного пользовательского ресурса (с):

ПОЛУЧИТЬ http://localhost:8080/users/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"
            }
        }
    ]
}

Еда на вынос:

  • Основные атрибуты ресурса находятся в data.attributes
  • Основные отношения ресурса находятся в data.relations
  • Как мы использовали @JsonApiRelation(serialize=Тип сериализации.() для отношений ролей он включен в JSON и находится в узле included

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

ПОЛУЧИТЬ http://localhost:8080/roles

{
    "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, и действительно приводящая в действие настоящий Гипермедиа API.

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

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