1. 概述
本文將開始探索 JSON-API 規範,並介紹如何將其集成到 Spring 驅動的 REST API 中。
我們將使用 Katharsis 框架在 Java 中實現 JSON-API,並搭建一個基於 Katharsis 的 Spring 應用,只需一個 Spring 應用即可。
2. Maven
首先,讓我們來看一下我們的 Maven 配置——我們需要將以下依賴項添加到我們的 <em pom.xml</em> 中:
<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 註解用於定義我們的資源 User
- @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,我們只需要在我們的 Spring Boot 應用程序中導入 KatharsisConfigV3 即可:
@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;
}然後,建立 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. 關係存儲
為了處理 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;
}
}我們在這裏忽略單例方法,在關係倉庫中。
7. 測試
最後,讓我們分析幾個請求並真正瞭解 JSON-API 輸出的格式。
我們將開始檢索一個單個 User 資源(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) 對 roles 關係進行序列化,因此該關係包含在 JSON 中,並且位於節點 included 中。
接下來,讓我們獲取包含角色的集合資源:
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 是一項絕佳的規範——它終於為我們API中的JSON添加了結構,並真正實現了超媒體API。
本文探討了一種在Spring應用中設置它的方法。無論這種實現方式如何,從我個人的角度來看,這項規範本身都非常具有前景。