1. Overview
1.概述
In this article, we’ll start exploring the JSON-API spec and how that can be integrated into a Spring backed REST API.
在本文中,我们将开始探索JSON-API规范以及如何将其集成到Spring支持的REST API中。
We’ll use the Katharsis implementation of JSON-API in Java – and we’ll set up a Katharsis powered Spring application – so all we need is a Spring application.
我们将使用Java中JSON-API的Katharsis实现–我们将建立一个由Katharsis驱动的Spring应用程序–所以我们所需要的只是一个Spring应用程序。
2. Maven
2.Maven
First, let’s take a look at our maven configuration – we need to add the following dependency into our pom.xml:
首先,让我们看看我们的maven配置–我们需要在pom.xml中添加以下依赖项。
<dependency>
<groupId>io.katharsis</groupId>
<artifactId>katharsis-spring</artifactId>
<version>3.0.2</version>
</dependency>
3. A User Resource
3.一个用户资源
Next, let’s take a look at our User resource:
接下来,让我们看一下我们的用户资源。
@JsonApiResource(type = "users")
public class User {
@JsonApiId
private Long id;
private String name;
private String email;
}
Note that:
请注意,。
- @JsonApiResource annotation is used to define our resource User
- @JsonApiId annotation is used to define the resource identifier
And very briefly – the persistence for this example is going to be a Spring Data repository here:
很简单,这个例子的持久性将是一个Spring Data仓库。
public interface UserRepository extends JpaRepository<User, Long> {}
4. A Resource Repository
4.一个资源库
Next, let’s discuss our resource repository – each resource should have a ResourceRepositoryV2 to publish the API operations available on it:
接下来,让我们讨论一下我们的资源库–每个资源都应该有一个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);
}
}
A quick note here – this is of course very similar to a Spring controller.
在此简单说明一下–这当然是非常类似于Spring控制器。
5. Katharsis Configuration
5.Katharsis配置
As we are using katharsis-spring, all we need to do is to import KatharsisConfigV3 in our Spring Boot Application:
由于我们使用的是katharsis-spring,我们需要做的就是在Spring Boot应用程序中导入KatharsisConfigV3。
@Import(KatharsisConfigV3.class)
And configure Katharsis parameters in our application.properties:
并在我们的application.properties中配置Katharsis参数。
katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/
With that – we can now start consuming the API; for example:
有了这些–我们现在可以开始消费API了;比如说。
- GET “http://localhost:8080/users“: to get all users.
- POST “http://localhost:8080/users“: to add new user, and more.
6. Relationships
6.关系
Next, let’s discuss how to handle entities relationships in our JSON API.
接下来,让我们讨论如何在我们的JSON API中处理实体关系。
6.1. Role Resource
6.1.角色资源
First, let’s introduce a new resource – Role:
首先,让我们介绍一种新的资源–Role。
@JsonApiResource(type = "roles")
public class Role {
@JsonApiId
private Long id;
private String name;
@JsonApiRelation
private Set<User> users;
}
And then set up a many-to-many relation between User and Role:
然后在User和Role之间建立一个多对多的关系。
@JsonApiRelation(serialize=SerializeType.EAGER)
private Set<Role> roles;
6.2. Role Resource Repository
6.2.角色资源库
Very quickly – here is our Role resource repository:
非常快–这里是我们的Role资源库。
@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);
}
}
It is important to understand here is that this single resource repo doesn’t handle the relationship aspect – that takes a separate repository.
这里需要了解的是,这个单一的资源库并不处理关系方面的问题–这需要一个单独的资源库。
6.3. Relationship Repository
6.3.关系存储库
In order to handle the many-to-many relationship between User–Role we need to create a new style of repository:
为了处理User–Role之间的多对多关系,我们需要创建一个新式的存储库。
@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;
}
}
We’re ignoring the singular methods here, in the relationship repository.
我们在这里忽略了关系库中的单数方法。
7. Test
7.测试
Finally, let’s analyze a few requests and really understand what the JSON-API output looks like.
最后,让我们分析几个请求,真正了解JSON-API的输出是什么样子。
We’re going to start retrieving a single User resource (with id = 2):
我们将开始检索一个单一的用户资源(id = 2)。
GET http://localhost:8080/users/2
GET 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"
}
}
]
}
Takeaways:
经验之谈。
- The main attributes of the Resource are found in data.attributes
- The main relationships of the Resource are found in data.relationships
- As we used @JsonApiRelation(serialize=SerializeType.EAGER) for the roles relationship, it is included in the JSON and found in node included
Next – let’s get the collection resource containing the Roles:
接下来–让我们获取包含角色的集合资源。
GET http://localhost:8080/roles
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":[
]
}
The quick take-away here is that we get all Roles in the system – as an array in the data node
这里的快速收获是,我们得到了系统中的所有角色–作为data节点中的一个数组。
8. Conclusion
8.结论
JSON-API is a fantastic spec – finally adding some structure in the way we use JSON in our APIs and really powering a true Hypermedia API.
JSON-API是一个非常棒的规范–终于为我们在API中使用JSON的方式添加了一些结构,并真正为真正的超媒体API提供了动力。
This piece explored one way to set it up in a Spring app. But regardless of that implementation, the spec itself is – in my view – very very promising work.
这篇稿子探讨了在Spring应用中设置它的一种方法。但不管这种实现方式如何,在我看来,该规范本身是非常非常有前途的工作。
The complete source code for the example is available over on GitHub. It’s a Maven project which can be imported and run as-is.
该例子的完整源代码可在GitHub上获得。它是一个Maven项目,可以按原样导入和运行。