在Spring Boot中整合Katharsis,來快速開發JSON API的Web應用

2023-02-04 06:01:32

1 簡介

我們進行Web API開發的時候,經常會使用Json格式的訊息體,而Json格式非常靈活,不同的人會有不同的設計風格和實現,而JSON API提供了一套標準。但它並不提供直接實現。

Katharsis是JSON API的Java實現,使用它可以快速開發出Json based的Web介面,還能快速的整合到Spring中。今天我們就來試試如何在Spring Boot中使用Katharsis。

2 整合過程

2.1 新增依賴

我們在Spring Boot中新增依賴如下,包括常規的starter、jpa和h2,而整合Katharsis只需要katharsis-spring即可。

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
  </dependency>
  <dependency>
    <groupId>io.katharsis</groupId>
    <artifactId>katharsis-spring</artifactId>
    <version>3.0.2</version>
  </dependency>
</dependencies>

2.2 Entity類

我們定義兩個Entity,一個是學生,一個是教室,而教室物件會包含多個學生。

學生:

@JsonApiResource(type = "students")
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    @JsonApiId
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
}

教室:

@JsonApiResource(type = "classrooms")
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Classroom {
    @JsonApiId
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "classrooms_students", joinColumns = @JoinColumn(name = "classroom_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"))
    @JsonApiRelation(serialize= SerializeType.EAGER)
    private Set<Student> students;
}

註解JsonApiResource這個會指定資源名稱,這個會影響api的URL,註解JsonApiId則指定資源的id。

2.3 資源操作類

Katharsis使用ResourceRepository來對資源進行增刪改查操作,這個跟資料庫的增刪改查類似,但屬於更高一層。抽象到資源RESTful的資源層面,然後再呼叫資料庫的Repository來操作,這裡統一實現ResourceRepositoryV2介面。

@Component
public class StudentResourceRepository implements ResourceRepositoryV2<Student, Long> {

    private final StudentRepository studentRepository;

    public StudentResourceRepository(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    @Override
    public Student findOne(Long id, QuerySpec querySpec) {
        Optional<Student> student = studentRepository.findById(id);
        return student.orElse(null);
    }

    @Override
    public ResourceList<Student> findAll(QuerySpec querySpec) {
        return querySpec.apply(studentRepository.findAll());
    }

    @Override
    public ResourceList<Student> findAll(Iterable<Long> ids, QuerySpec querySpec) {
        return querySpec.apply(studentRepository.findAllById(ids));
    }

    @Override
    public <S extends Student> S save(S entity) {
        return studentRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        studentRepository.deleteById(id);
    }

    @Override
    public Class<Student> getResourceClass() {
        return Student.class;
    }

    @Override
    public <S extends Student> S create(S entity) {
        return save(entity);
    }

}

而資料庫方面我們使用JPA實現即可:

public interface StudentRepository extends JpaRepository<Student, Long> {
}

上面的程式碼是針對Student資源型別的,對Classroom類似,就不把程式碼列出來了。

2.4 設定

為了使用Katharsis,我們需要在設定中引入KatharsisConfigV3,我們直接在Spring Boot啟動類中引入即可:

@SpringBootApplication
@Import(KatharsisConfigV3.class)
public class KatharsisExample {
    public static void main(String[] args) {
        SpringApplication.run(KatharsisExample.class, args);
    }
}

Spring Boot的組態檔如下:

server.port=8080
server.servlet.context-path=/

katharsis.domainName=https://www.pkslow.com
katharsis.pathPrefix=/api/katharsis

spring.datasource.url = jdbc:h2:mem:springKatharsis;DB_CLOSE_DELAY=-1
spring.datasource.username = sa
spring.datasource.password =

spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.globally_quoted_identifiers=true

katharsis.pathPrefix是URL字首,而katharsis.domainName會影響Self Link等返回。

2.5 初始化資料

為了方便測試,初始化一些資料進行查詢:

@Component
public class InitData {

    private final ClassroomRepository classroomRepository;

    private final StudentRepository studentRepository;

    public InitData(ClassroomRepository classroomRepository, StudentRepository studentRepository) {
        this.classroomRepository = classroomRepository;
        this.studentRepository = studentRepository;
    }

    @PostConstruct
    private void setupData() {
        Set<Student> students = new HashSet<>();
        Student student = Student.builder().name("Larry Deng").build();
        students.add(student);
        studentRepository.save(student);
        student = Student.builder().name("Eason").build();
        students.add(student);
        studentRepository.save(student);
        student = Student.builder().name("JJ Lin").build();
        students.add(student);
        studentRepository.save(student);

        Classroom classroom = Classroom.builder().name("Classroom No.1").students(students)
                .build();
        classroomRepository.save(classroom);
    }
}

至此則整合完畢了。

3 測試

整合完後啟動,就可以用curl或Postman開始測試了:

查詢一個student:

$ curl http://localhost:8080/api/katharsis/students/1 | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   9828      0 --:--:-- --:--:-- --:--:-- 23166
{
  "data": {
    "id": "1",
    "type": "students",
    "attributes": {
      "name": "Larry Deng"
    },
    "links": {
      "self": "https://www.pkslow.com/api/katharsis/students/1"
    }
  }
}

查詢多個student:

$ curl http://localhost:8080/api/katharsis/students | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   429    0   429    0     0   3633      0 --:--:-- --:--:-- --:--:--  3830
{
  "data": [
    {
      "id": "1",
      "type": "students",
      "attributes": {
        "name": "Larry Deng"
      },
      "links": {
        "self": "https://www.pkslow.com/api/katharsis/students/1"
      }
    },
    {
      "id": "2",
      "type": "students",
      "attributes": {
        "name": "Eason"
      },
      "links": {
        "self": "https://www.pkslow.com/api/katharsis/students/2"
      }
    },
    {
      "id": "3",
      "type": "students",
      "attributes": {
        "name": "JJ Lin"
      },
      "links": {
        "self": "https://www.pkslow.com/api/katharsis/students/3"
      }
    }
  ],
  "meta": {
    "totalResourceCount": null
  }
}

查詢一個教室:

$ curl http://localhost:8080/api/katharsis/classrooms/4 | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   834    0   834    0     0  62462      0 --:--:-- --:--:-- --:--:--  116k
{
  "data": {
    "id": "4",
    "type": "classrooms",
    "attributes": {
      "name": "Classroom No.1"
    },
    "relationships": {
      "students": {
        "data": [
          {
            "id": "3",
            "type": "students"
          },
          {
            "id": "2",
            "type": "students"
          },
          {
            "id": "1",
            "type": "students"
          }
        ],
        "links": {
          "self": "https://www.pkslow.com/api/katharsis/classrooms/4/relationships/students",
          "related": "https://www.pkslow.com/api/katharsis/classrooms/4/students"
        }
      }
    },
    "links": {
      "self": "https://www.pkslow.com/api/katharsis/classrooms/4"
    }
  },
  "included": [
    {
      "id": "1",
      "type": "students",
      "attributes": {
        "name": "Larry Deng"
      },
      "links": {
        "self": "https://www.pkslow.com/api/katharsis/students/1"
      }
    },
    {
      "id": "2",
      "type": "students",
      "attributes": {
        "name": "Eason"
      },
      "links": {
        "self": "https://www.pkslow.com/api/katharsis/students/2"
      }
    },
    {
      "id": "3",
      "type": "students",
      "attributes": {
        "name": "JJ Lin"
      },
      "links": {
        "self": "https://www.pkslow.com/api/katharsis/students/3"
      }
    }
  ]
}

新增一個學生:

$ curl --header "Content-Type: application/json"   --request POST   --data '{
    "data": {
        "type": "students",
        "attributes": {
            "name": "Justin"
        }
    }
}'   http://localhost:8080/api/katharsis/students | jq .

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   249    0   135  100   114  11202   9459 --:--:-- --:--:-- --:--:-- 41500
{
  "data": {
    "id": "6",
    "type": "students",
    "attributes": {
      "name": "Justin"
    },
    "links": {
      "self": "https://www.pkslow.com/api/katharsis/students/6"
    }
  }
}

修改:

$ curl --header "Content-Type: application/json"   --request PATCH   --data '{
    "data": {
        "id":"6",
        "type": "students",
        "attributes": {
            "name": "Justin Wu"
        }
    }
}'   http://localhost:8080/api/katharsis/students/6 | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   273    0   138  100   135   4425   4329 --:--:-- --:--:-- --:--:-- 10920
{
  "data": {
    "id": "6",
    "type": "students",
    "attributes": {
      "name": "Justin Wu"
    },
    "links": {
      "self": "https://www.pkslow.com/api/katharsis/students/6"
    }
  }
}

刪除:

$ curl --header "Content-Type: application/json"   --request DELETE   --data '{
    "data": {
        "id":"6",
        "type": "students",
        "attributes": {
            "name": "Justin Wu"
        }
    }
}'   http://localhost:8080/api/katharsis/students/6 | jq .

至此,我們已經覆蓋了增刪改查操作了。而我們在程式碼中並沒有去寫我們之前常寫的Controller,卻可以存取對應介面。因為Katharsis預設已經幫我們實現了,可以檢視類:io.katharsis.core.internal.dispatcher.ControllerRegistry

4 程式碼

程式碼請看GitHub: https://github.com/LarryDpk/pkslow-samples