1. Обзор
В этой статье мы начнем изучать спецификацию JSON-API и то, как ее можно интегрировать в REST API, поддерживаемый Spring.
Мы будем использовать реализацию JSON-API Katharsis на Java — и мы настроим приложение Spring на основе Katharsis — так что все, что нам нужно, — это приложение Spring.
2. Мавен
Во-первых, давайте посмотрим на нашу конфигурацию maven — нам нужно добавить следующую зависимость в наш pom.xml
:
<dependency>
<groupId>io.katharsis</groupId>
<artifactId>katharsis-spring</artifactId>
<version>3.0.2</version>
</dependency>
3. Пользовательский ресурс
Далее давайте взглянем на наш пользовательский ресурс:
@JsonApiResource(type = "users")
public class User {
@JsonApiId
private Long id;
private String name;
private String email;
}
Обратите внимание, что:
Аннотация @JsonApiResource
используется для определенияпользователя нашего ресурса.
Аннотация @JsonApiId
используется для определения идентификатора ресурса.
И очень кратко — постоянством для этого примера будет репозиторий Spring Data:
public interface UserRepository extends JpaRepository<User, Long> {}
4. Репозиторий ресурсов
Далее давайте обсудим наш репозиторий ресурсов — каждый ресурс должен иметь ResourceRepositoryV2
для публикации доступных на нем операций API:
@Component
public class UserResourceRepository implements ResourceRepositoryV2<User, Long> {
@Autowired
private UserRepository userRepository;
@Override
public User findOne(Long id, QuerySpec querySpec) {
Optional<User> user = userRepository.findById(id);
return user.isPresent()? user.get() : null;
}
@Override
public ResourceList<User> findAll(QuerySpec querySpec) {
return querySpec.apply(userRepository.findAll());
}
@Override
public ResourceList<User> findAll(Iterable<Long> ids, QuerySpec querySpec) {
return querySpec.apply(userRepository.findAllById(ids));
}
@Override
public <S extends User> S save(S entity) {
return userRepository.save(entity);
}
@Override
public void delete(Long id) {
userRepository.deleteById(id);
}
@Override
public Class<User> getResourceClass() {
return User.class;
}
@Override
public <S extends User> S create(S entity) {
return save(entity);
}
}
Небольшое замечание — это, конечно, очень похоже на контроллер Spring .
5. Конфигурация катарсиса
Поскольку мы используем katharsis-spring
, все, что нам нужно сделать, это импортировать KatharsisConfigV3
в наше приложение Spring Boot:
@Import(KatharsisConfigV3.class)
И настраиваем параметры Катарсиса в нашем application.properties
:
katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/
Теперь мы можем начать использовать API; Например:
- ПОЛУЧИТЬ «
http://localhost:8080/users
»: чтобы получить всех пользователей. - POST «
http://localhost:8080/users
»: добавить нового пользователя и т. д.
6. Отношения
Далее давайте обсудим, как обрабатывать отношения сущностей в нашем JSON API.
6.1. Ролевой ресурс
Во-первых, давайте представим новый ресурс — Роль
:
@JsonApiResource(type = "roles")
public class Role {
@JsonApiId
private Long id;
private String name;
@JsonApiRelation
private Set<User> users;
}
Затем настройте отношение «многие ко многим» между User
и Role
:
@JsonApiRelation(serialize=SerializeType.EAGER)
private Set<Role> roles;
6.2. Репозиторий ресурсов ролей
Очень быстро — вот наш репозиторий ресурсов ролей :
@Component
public class RoleResourceRepository implements ResourceRepositoryV2<Role, Long> {
@Autowired
private RoleRepository roleRepository;
@Override
public Role findOne(Long id, QuerySpec querySpec) {
Optional<Role> role = roleRepository.findById(id);
return role.isPresent()? role.get() : null;
}
@Override
public ResourceList<Role> findAll(QuerySpec querySpec) {
return querySpec.apply(roleRepository.findAll());
}
@Override
public ResourceList<Role> findAll(Iterable<Long> ids, QuerySpec querySpec) {
return querySpec.apply(roleRepository.findAllById(ids));
}
@Override
public <S extends Role> S save(S entity) {
return roleRepository.save(entity);
}
@Override
public void delete(Long id) {
roleRepository.deleteById(id);
}
@Override
public Class<Role> getResourceClass() {
return Role.class;
}
@Override
public <S extends Role> S create(S entity) {
return save(entity);
}
}
Здесь важно понимать, что этот единственный репозиторий ресурсов не обрабатывает аспект отношений — для этого требуется отдельный репозиторий.
6.3. Репозиторий отношений
Чтобы обрабатывать отношения «многие ко многим» между пользователем и
ролью
, нам нужно создать новый стиль репозитория:
@Component
public class UserToRoleRelationshipRepository implements RelationshipRepositoryV2<User, Long, Role, Long> {
@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<Long> roleIds, String fieldName) {
Set<Role> roles = new HashSet<Role>();
roles.addAll(roleRepository.findAllById(roleIds));
user.setRoles(roles);
userRepository.save(user);
}
@Override
public void addRelations(User user, Iterable<Long> roleIds, String fieldName) {
Set<Role> roles = user.getRoles();
roles.addAll(roleRepository.findAllById(roleIds));
user.setRoles(roles);
userRepository.save(user);
}
@Override
public void removeRelations(User user, Iterable<Long> roleIds, String fieldName) {
Set<Role> 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<Role> findManyTargets(Long sourceId, String fieldName, QuerySpec querySpec) {
final Optional<User> userOptional = userRepository.findById(sourceId);
User user = userOptional.isPresent() ? userOptional.get() : new User();
return querySpec.apply(user.getRoles());
}
@Override
public Class<User> getSourceResourceClass() {
return User.class;
}
@Override
public Class<Role> getTargetResourceClass() {
return Role.class;
}
}
Здесь, в репозитории отношений, мы игнорируем единичные методы.
7. Тест
Наконец, давайте проанализируем несколько запросов и действительно поймем, как выглядит вывод JSON-API.
Мы собираемся начать извлечение одного пользовательского ресурса (с id = 2):
ПОЛУЧИТЬ http://localhost:8080/users/2
{
"data":{
"type":"users",
"id":"2",
"attributes":{
"email":"tom@test.com",
"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.relationships.
- Поскольку мы использовали
@JsonApiRelation(serialize=SerializeType.EAGER)
для отношенияролей
, он включен в JSON и находится в включенном узле.
Далее — получаем ресурс коллекции, содержащий Роли:
ПОЛУЧИТЬ http://localhost:8080/роли
{
"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":[
]
}
Быстрый вывод здесь заключается в том, что мы получаем все роли в системе — в виде массива в узле данных .
8. Заключение
JSON-API — это фантастическая спецификация, которая, наконец, добавляет некоторую структуру в то, как мы используем JSON в наших API, и действительно обеспечивает настоящий Hypermedia API.
В этой части был рассмотрен один из способов его настройки в приложении Spring. Но независимо от этой реализации, сама спецификация, на мой взгляд, является очень многообещающей работой.
Полный исходный код примера доступен на GitHub . Это проект Maven, который можно импортировать и запускать как есть.