«1. Обзор
В этой статье мы начнем изучать спецификацию JSON-API и то, как ее можно интегрировать в REST API, поддерживаемый Spring.
Мы будем использовать реализацию JSON-API Katharsis в Java — и мы настроим приложение Spring на основе Katharsis — так что все, что нам нужно, — это приложение Spring.
2. Maven
Во-первых, давайте посмотрим на нашу конфигурацию 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
Поскольку мы используем katharsis-spring, все, что нам нужно сделать, это импортировать KatharsisConfigV3 в наше приложение Spring Boot:
@Import(KatharsisConfigV3.class)
И настроить параметры Katharsis в нашем application.properties: ~ ~~
katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/
Теперь мы можем начать использовать API; например:
-
GET «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;
}
А затем настроим отношение «многие ко многим» между пользователем и ролью:
@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):
GET 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.relationships Поскольку мы использовали @JsonApiRelation(serialize=SerializeType.EAGER) для отношений ролей, они включены в JSON и находятся в включенном узле ~ ~~ Далее — давайте получим ресурс коллекции, содержащий роли:
GET 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":[
]
}
8. Заключение
JSON-API — это фантастическая спецификация, наконец-то добавляющая некоторую структуру в то, как мы используем JSON в наших API, и действительно поддерживающая настоящий Hypermedia API.
В этой статье мы рассмотрели один из способов его настройки в приложении Spring. Но независимо от этой реализации, сама спецификация, на мой взгляд, является очень многообещающей работой.
Полный исходный код примера доступен на GitHub. Это проект Maven, который можно импортировать и запускать как есть.
«