1. 概要

この記事では、 JSON-API仕様と、それをSpringベースのRESTAPIに統合する方法について説明します。

JavaでJSON-APIのKatharsis実装を使用し、Katharsisを利用したSpringアプリケーションをセットアップするので、必要なのは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 アノテーションは、リソースUserを定義するために使用されます
  • @JsonApiId アノテーションは、リソース識別子を定義するために使用されます

そして、ごく簡単に言えば、この例の永続性は、ここでSpringDataリポジトリになります。

public interface UserRepository extends JpaRepository<User, Long> {}

4. リソースリポジトリ

次に、リソースリポジトリについて説明します。各リソースには、使用可能なAPI操作を公開するためのResourceRepositoryV2が必要です。

@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 を使用しているため、SpringBootアプリケーションにKatharsisConfigV3をインポートするだけです。

@Import(KatharsisConfigV3.class)

そして、application.propertiesでKatharsisパラメーターを構成します。

katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/

これで、APIの使用を開始できます。 例えば:

  • GET“ http:// localhost:8080 / users “:すべてのユーザーを取得します。
  • POST“ http:// localhost:8080 / users “:新しいユーザーなどを追加します。

6. 関係

次に、JSONAPIでエンティティの関係を処理する方法について説明します。

6.1. 役割リソース

まず、新しいリソース– Role を紹介しましょう:

@JsonApiResource(type = "roles")
public class Role {

    @JsonApiId
    private Long id;

    private String name;

    @JsonApiRelation
    private Set<User> users;
}

次に、UserRoleの間に多対多の関係を設定します。

@JsonApiRelation(serialize=SerializeType.EAGER)
private Set<Role> roles;

6.2. 役割リソースリポジトリ

非常に迅速に–これが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);
    }
}

ここで理解することが重要なのは、この単一のリソースリポジトリはリレーションシップの側面を処理しないということです。つまり、別のリポジトリを使用します。

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出力がどのように見えるかを実際に理解しましょう。

単一のユーザーリソース(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にあります。
  • roles関係に@JsonApiRelation(serialize = SerializeType.EAGER)を使用したため、JSONに含まれ、ノードincludeに含まれています。

次へ–ロールを含むコレクションリソースを取得しましょう。

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":[

    ]
}

ここでの簡単なポイントは、システム内のすべてのロールをdataノードの配列として取得することです。

8. 結論

JSON-APIは素晴らしい仕様です。最終的に、APIでJSONを使用する方法に構造を追加し、真のハイパーメディアAPIを実際に強化します。

この記事では、Springアプリで設定する1つの方法について説明しました。 しかし、その実装に関係なく、仕様自体は、私の見解では、非常に有望な作業です。

この例の完全なソースコードは、GitHubから入手できます。 これは、そのままインポートして実行できるMavenプロジェクトです。