API JSON em um aplicativo Spring
1. Visão geral
2. Maven
Primeiro, vamos dar uma olhada em nossa configuração de maven - precisamos adicionar a seguinte dependência em nossopom.xml:
io.katharsis
katharsis-spring
3.0.2
3. Um recurso de usuário
A seguir, vamos dar uma olhada em nosso recurso de usuário:
@JsonApiResource(type = "users")
public class User {
@JsonApiId
private Long id;
private String name;
private String email;
}
Observe que:
-
A anotação@JsonApiResource é usada para definir nosso recursoUser
-
A anotação@JsonApiId é usada para definir o identificador do recurso
E muito brevemente - a persistência para este exemplo será um repositório Spring Data aqui:
public interface UserRepository extends JpaRepository {}
4. Um Repositório de Recursos
A seguir, vamos discutir nosso repositório de recursos - cada recurso deve ter umResourceRepositoryV2 para publicar as operações de API disponíveis nele:
@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);
}
}
Uma nota rápida aqui - é clarovery similar to a Spring controller.
5. Configuração Katharsis
Como estamos usandokatharsis-spring, tudo o que precisamos fazer é importarKatharsisConfigV3 em nosso aplicativo Spring Boot:
@Import(KatharsisConfigV3.class)
E configure os parâmetros do Katharsis em nossoapplication.properties:
katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/
Com isso - agora podemos começar a consumir a API; por exemplo:
-
OBTER “http://localhost:8080/users“: para obter todos os usuários.
-
POST “http://localhost:8080/users“: para adicionar um novo usuário e mais.
6. Relacionamentos
A seguir, vamos discutir como lidar com relacionamentos de entidades em nossa API JSON.
6.1. Recurso de Função
Primeiro, vamos apresentar um novo recurso -Role:
@JsonApiResource(type = "roles")
public class Role {
@JsonApiId
private Long id;
private String name;
@JsonApiRelation
private Set users;
}
E, em seguida, configure uma relação muitos para muitos entreUser eRole:
@JsonApiRelation(serialize=SerializeType.EAGER)
private Set roles;
6.2. Repositório de recursos de funções
Muito rapidamente - aqui está o nosso repositório de recursosRole:
@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);
}
}
É importante entender aqui que este repo de recurso único não lida com o aspecto do relacionamento - que leva um repositório separado.
6.3. Repositório de Relacionamento
Para lidar com a relação muitos para muitos entreUser -Role, precisamos criar um novo estilo de repositório:
@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;
}
}
Estamos ignorando os métodos singulares aqui, no repositório de relacionamento.
7. Test
Por fim, vamos analisar algumas solicitações e realmente entender a aparência da saída JSON-API.
Vamos começar a recuperar um único recurso de usuário (com 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"
}
}
]
}
Aprendizado:
-
Os principais atributos do Recurso são encontrados emdata.attributes
-
Os principais relacionamentos do Recurso são encontrados emdata.relationships
-
Como usamos@JsonApiRelation(serialize=SerializeType.EAGER) para o relacionamentoroles, ele é incluído no JSON e encontrado no nóincluded
A seguir - vamos obter o recurso de coleta que contém as funções:
{
"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":[
]
}
A conclusão rápida aqui é que obtemos todas as funções no sistema - como uma matriz no nódata
8. Conclusão
A JSON-API é uma especificação fantástica - finalmente adicionando alguma estrutura na maneira como usamos o JSON em nossas APIs e realmente potenciando uma verdadeira API Hypermedia.
Esta peça explorou uma maneira de configurá-la em um aplicativo Spring. Mas, independentemente dessa implementação, as especificações em si são - na minha opinião - um trabalho muito, muito promissor.
O código-fonte completo para o exemplo está disponível emon GitHub. É um projeto Maven que pode ser importado e executado como está.