既存JavaシステムのDBに対して簡単にWebAPIを生やすことを夢見て


背景とやりたいこと

既存のモノリシックなシステムのDBが持つデータに対して、REST APIでアクセスしたいとき、どうしますか?
そのシステム担当にREST API公開お願いしますと頼もうとすると、ちょっとよく分からないし、基盤影響とか、いろいろ考えないといけないし、それより今忙しいし、、と真面目に掛け合ってくれないということがあるかもしれません。

そんなときに、既存の環境を使いつつすごく簡単にREST形式のWebAPIを生やすことができないか?と考えました。

過去の検討

自身、2017年ごろに一度調べたことがあったのですが、その際の構成は以下の感じです。

  • 既存システム構成の例

  • REST API生やした後の構成

軽量なSpringBootで作ったツールの構成としては、Spring Boot+Spring Data REST+SpringFox という構成が楽なのでは無いか?と考えました。
当時のSpringコミュニティの中で、それぞれ注目を集めていたプロジェクトだったためです。

Spring Data REST:SpringDataシリーズと連携し、外からのアクセス部分をRESTAPI化してくれる

SpringFox:Springプロジェクト非公式。自分のプロジェクト内のRest APIを自動で探し出し、OpenAPIドキュメントを自動で生成してくれる。また、ドキュメント上からAPIをコールするクライアント(Swagger-UI)を用意してくれる。

少し試したところ、結構これがいい感じに動いていたので、良さげと思っていました。
一方、最近改めて見るとSpringFoxのリリースの最後が2018年の6月で止まっており、
Springのバージョンも4系までの対応。
Spring5系への対応のIssueも切られているが対応がFixしていない状態ということで、
他の代替できるものが無いか調べ、サンプルを作ってみることにしました。

今回の検討

最初にお題となるアプリを作ることにしました。既存のモノリスシステム内でStudentテーブルがあり、それをREST APIで突つきたいという想定です。

アプリ作成

Spring Initializr からアプリを作成するところからスタートです。
以下設定で作りました。

取得したいテーブルのEntityを作ります。キーは必要ですが、取得したいフィールドだけでよいです。

Student.java
@Entity
public class Student {
    @Id
    private String id;
    private String name;
    private String className;
    // 以降Getter,Setterなど

次にDBにアクセスするRepositoryを作ります。まずは、Data REST感なく、Controllerから呼べるように普通に作ります。

StudentRepository.java
@Repository
public interface StudentRepository extends PagingAndSortingRepository<Student,String> {
    Iterable<Student> findAll();
}

Controllerは以下の実装となります。

StudentRestController.java
@RestController
public class StudentRestController {
    @Autowired
    private StudentRepository studentRepository;

    @GetMapping("/students") Iterable<Student> getStudents() {
        return studentRepository.findAll();
    }
}

今回はH2を使ってDBアクセスするので、諸々の設定を入れておきます。後で実験しやすいように初期SQLを流したり、アプリ終了時のデータを永続化させる設定などを入れています。

application.properties
# datasource
spring.datasource.driver-class-name=org.h2.Driver
# DBのファイルとしての永続化先はh2dbフォルダに保存。データ初期化の際にON CONFLICTを使いたかったのでPostgreSQLモードにしておく。
spring.datasource.url=jdbc:h2:./h2db/sandbox;MODE=PostgreSQL
spring.datasource.username=dev
spring.datasource.password=dev
# resources/sdata.sqlを使ったDBのデータ初期化をアプリ立ち上げ都度実施
spring.datasource.initialization-mode=always

# データをファイルに永続化
spring.jpa.hibernate.ddl-auto=update

# h2 for debug tool
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.h2.console.settings.web-allow-others=true

この状態にしておき、resources下に以下ファイルを置いておくとテーブル作成、初期データ投入(あれば何もしない)をやってくれます。Spring Dataの機能でしょうか?便利ですね。

data.sql
CREATE TABLE IF NOT EXISTS STUDENT (
 ID VARCHAR(255) NOT NULL,
 CLASS_NAME VARCHAR(255),
 NAME VARCHAR(255),
 PRIMARY KEY(ID)
);
-- 一意制約発生を避けるため、ON CONFLICTを使用
INSERT INTO STUDENT VALUES ('1','A CLASS','TAKA')
    ON CONFLICT DO NOTHING;
INSERT INTO STUDENT VALUES ('2','A CLASS','KASHI')
    ON CONFLICT DO NOTHING;
INSERT INTO STUDENT VALUES ('3','B CLASS','KIKUCHI')
    ON CONFLICT DO NOTHING;

長かったですが、これでSpringBoot+REST ControllerでのWebAPI開発完了です。
ここまではSpringDataRESTを使ってないことに留意してください。
SpringBootアプリを立ち上げ、Curlでアクセスした結果は以下の通りです。
( 参考: jqはJSONを整形、加工するコマンドラインツールです)

続いて、SpringDataRESTを組み込んで行きます。といいつつ、3ステップ(最短2ステップ)のみなので至極簡単です。
pom.xmlへの依存の追加。

pom.xml
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>

先ほどのStudentRepositoryクラスへのRepositoryRestResourceアノテーションの追加

StudentRepository.java
@Repository
@RepositoryRestResource
        (collectionResourceRel = "students", path = "students")
public interface StudentRepository extends PagingAndSortingRepository<Student,String> {

(任意)SpringDataRESTで自動作成されるAPIの基底パスの追加

application.properties
# SpringDataRESTで自動作成されるAPIの基底パス
spring.data.rest.basePath=/api

これで完了です。アプリを再立ち上げし、localhost:8080/apiにアクセスしてみましょう。SpringDataRESTによってREST APIが自動生成されたことを確認することができます。

api/studentsというURIのAPIが作成されてますので、アクセスしてみると以下の結果を得ることが出来ます。
先ほど自作したAPIより情報量が多いことに気づくでしょうか?
SpringDataRESTはHATEOASされているので、情報が多く、JavaScriptなどクライアントからAPIを利用するには優位なのです。

ここまでで、DBに対してWebAPIを簡単に(?)作成することが出来ましたので、
最後にOpenAPI、SwaggerUI対応をします。

SpringFoxの代替は?

SpringDataRest,OpenAPIでググって一番先に出てきたSpringdoc OpenAPIを使ってみました。
おそらくSpring公式なんですかね、URL見る限り。
公式が出たのでSpringFoxの開発が下火になったのであれば納得の流れですね。。

導入方法ですが、依存先に追加するだけです。SpringFoxのときはJavaConfig作成が必須だった記憶があるのですが、こちらはStarterを用意してくれているようです。

pom.xml
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.1.44</version>
        </dependency>

依存先追加後、アプリを再立ち上げし、http://localhost:8080/swagger-ui/index.html?url=/v3/api-docsにブラウザでアクセスすることで、SwaggerUIを触ることができます!
WebAPIの一覧や、実際にここからWebAPIを実行することもできるので最高です。

とぬか喜びしていましたが、よく見ると、このUIで表示されているのは最初にRESTController
を作ったものしかなく、Spring Data RESTで作ったものは作成してくれてません。

SpringFoxではやってくれていたのですが、、と思い調べていたら、以下のIssueにたどり着きました。

Documentation is available on the official page: https://springdoc.github.io/springdoc-openapi-demos/
Spring Data Rest is not a priority. It will be supported on a future release.

このコメントでCloseされてました。。。
さらに下の別の方のコメントでは、SpringFoxからSpringDocに乗り換えようとしているのだけれど、この機能が無いので乗り換えられないよ、的なコメントも。

なんてこった。SpringDataRestなんてものを使って楽しようとした自分が悪いのか。
確かにSpringDataRestを使ってプロジェクトを開始して、途中でやめた事例などもあり、
依存しすぎはNGという理解はあったものの、ちゃちゃっと作る分には良さそうと思ったのですが。。。

結論

ということで、DB直アクセスWebAPI構築をみんな大好きSpringBootを使って簡単に実現することは現時点では難しそうです。

ここまで読み進められた方であれば、RESTControllerから組み上げる形のほうが分かりやすいと思う人もいるでしょう。
現時点では、その方法で地道に作っていくしかなさそうです。

身近にJava実行環境がどこにでもがあるのでSpringBootがお手軽で良いのでは?と思いましたが、その発想に縛られず、今後調べていきます。

検証で作成したソースコード

今後の参考

DB固有になりますが、PostgRESTなるものがあるそうです。
https://qiita.com/kanedaq/items/0c3097604d0e86afd1e3

MSクラウドのDBであるCosmosDBではRESTAPIがサポートされています。
https://docs.microsoft.com/ja-jp/rest/api/cosmos-db/

製品としては多数ありますね。CDATA API Serverとか。でも有償製品ではなくOpenなものでちゃちゃっとやりたいです。なんとか。