LoginSignup
1

posted at

updated at

【SAP BTP】CAP Service SDK for JavaでOData APIを作ってみる(2)

はじめに

CAP(SAP Cloud Application Programming Model)を利用してBTP環境にデプロイするODataAPIを作ってみます。(Java)

前回の記事ではサンプルを使ってCAPアプリケーション開発の全体的な流れを確認できたので、今回は自分で定義したデータモデルをサービスとして公開してみます。
また、カスタムロジックでODataリクエスト処理(CRUD処理)を試してみます。
【前回】
【SAP BTP】CAP Service SDK for JavaでOData APIを作ってみる(1)

【PostgreSQLにつないでみる】
【SAP BTP】CAPのAPI(Java)をPostgreSQLで使ってみる

前提

(1)があるので事前準備等は参考にしてください
CAPプロジェクトの作成までの記載が完了していることを前提としています。

プロジェクトを新規作成する

CAPのサンプルに追加する形でも問題ないのですが、わかりやすくするために新しいJavaアプリケーションとして作成します。
MTAプロジェクトのルートディレクトリ配下で以下を実行します。

$ mvn archetype:generate -DarchetypeArtifactId="cds-services-archetype" -DarchetypeGroupId="com.sap.cds" -DarchetypeVersion="RELEASE" "-DinteractiveMode=true"

group ID、artifact ID等は以下を設定しています。

内容
groupId: jp.co.kyoso.demo
artifactId: customservice
package: ※デフォルト

customserviceディレクトリが作成されます。

CDSの作成

データ定義の作成

srvを右クリックして「New File」を押下します。
image.png

user-service.cdsという名称で.cdsファイルを作成します。
内容は以下の通りです。

service UserService {
    entity Users {
        key ID : Integer;
        FirstName : String(100);
        LastName : String(100);
        Age : Integer;
    }
}

.cdsファイルにはODataサービスとして公開したいデータ定義を記載します。

次に、作成した.cdsファイルのデータ定義をコンパイルしてmetadataのxmlを作成してもらいます。
Javaアプリケーションのルートディレクトリに移動し、以下のコマンドを実行します。

$ cd customservice/
$ mvn clean install

image.png

Build Successするとgenedmxディレクトリが作成され、
データ定義をもととしたJavaクラスとmetadata.xmlが出来上がります。
image.png

テスト実行①

この段階で一度テスト実行をします。

Javaアプリケーションのルートディレクトリで以下のコマンドを実行します。

$ mvn spring-boot:run

成功するとサービスが立ち上がり、metadataを参照できることが確認できます。
image.png

イベントハンドラーの作成

Javaクラスを作成

CDSを作成できたので、次はODataAPIによるCRUD操作を処理するイベントハンドラーを作成します。
srv/src/main/java ディレクトリの中にあるApplication.javaの存在する階層にhandlersディレクトリを作成します。
image.png

作成したhandlersディレクトリの中に新しいJavaクラスファイルUserService.javaを作成します。

UserService.java
package jp.co.kyoso.demo.customservice.handlers;

import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Component;

import com.sap.cds.services.cds.CdsCreateEventContext;
import com.sap.cds.services.cds.CdsReadEventContext;
import com.sap.cds.services.cds.CdsService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.handler.annotations.ServiceName;

@Component
@ServiceName("UserService")
public class UserService implements EventHandler {
    
    private Map<Object, Map<String, Object>> users = new HashMap<>();

    // UsersエンティティのCreateイベント
    @On(event = CdsService.EVENT_CREATE, entity = "UserService.Users")
    public void onCreate(CdsCreateEventContext context) {
        context.getCqn().entries().forEach(e -> users.put(e.get("ID"), e));
        context.setResult(context.getCqn().entries());
    }

    // UsersエンティティのReadイベント
    @On(event = CdsService.EVENT_READ, entity = "UserService.Users")
    public void onRead(CdsReadEventContext context) {
        context.setResult(users.values());
    }

}

テスト実行②

イベントハンドラーを追加したので、再度テスト実行を行います。
先ほど実行したアプリケーションが生きている場合は一度停止し、再起動します。

Usersエンティティ(/odata/v4/UserService/Users)へアクセスするとデータが存在しない状態です。
image.png

イベントハンドラーにはCreateイベントの処理を登録しているので、POSTメソッドでHTTPリクエストを実施し、データを追加してみます。

PostMan等のRestクライアントを利用する場合はDeveloper Toolから認証情報のCookie(workspaces-xxxxxxx_WSR_SESSIONID)を取ってきて設定します。
【Cookieの参照】
image.png
【Cookieの設定】
image.png

HTTPヘッダにContent-Type : application/json を設定し、以下のBodyをPOSTで送信します。

{
    "ID": 1, 
    "FirstName": "KYOSO", 
    "LastName": "花子",
    "Age": 20
}

レスポンスがHTTPステータス:201で返却されます。
image.png

POSTでのリクエストを実行後、再度GETリクエストを実行すると先ほど登録したデータが参照できることが確認できました。
image.png

DBへの接続

APIが実行できることを確認できたのですが、この状態でDeploy等をしてもまだDBへつながっていない状態なので永続的なデータとしては扱えません。
DBへ接続できるようにしていきます。

CDSファイルの作り替え

dbディレクトリ配下にschema.cdsファイルを作成します。
image.png

schema.cdsに以下を記載します。
@sap/cds/commonにはCDSで利用できる様々な機能があります。
今回はmanaged をつけてみました。

schema.cds
namespace demo;

using { managed } from '@sap/cds/common';

entity Users : managed {
    key ID    : Integer;
    FirstName : String(100);
    LastName  : String(100);
    Age       : Integer;
}

schema.cdsにデータ構造定義を記載したので、最初に作っていたuser-service.cdsの中身を修正してschema.cdsを参照するようにします。

user-service.cds
using { demo as db } from '../db/schema';

service UserService {
    entity Users   as projection on db.Users;
}

テストデータを準備する

ついでなので、テスト実行用のデータファイルを準備します。
db/dataディレクトリを作成し、配下にdemo-Users.csvを作成します。
image.png

.csvファイルの命名規則はschema.cdsに記載した内容から設定する必要があり、フォーマットは<namespace>-<entity>.csvです。
今回はnamespaceがdemoで用意するデータの対象entityがdemoなのでdemo-Users.csvになります。

demo-Users.csv
ID;FirstName;LastName;Age
1;csv;太郎;100
2;csv;次郎;101
3;csv;三郎;99

イベントハンドラーの編集

CAP SDKを使ったDB接続にはODataサービスに必要な機能がそろっているとのことで、イベントハンドラーが不要になるみたいです。
そのため、先ほど作成したhandler/UserService.javaがなくてもOData APIとして基本的な機能は動くようになりました。

ただ、カスタムロジックを入れたいときはイベントハンドラーが必要になります。
学習目的でデータ取得時の結果を編集してみます。

UserService.java
package jp.co.kyoso.demo.customservice.handlers;

import java.util.stream.Stream;

import org.springframework.stereotype.Component;

import com.sap.cds.services.cds.CdsService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.ServiceName;

import cds.gen.userservice.Users;

@Component
@ServiceName("UserService")
public class UserService implements EventHandler {
    
	@After(event = CdsService.EVENT_READ, entity = "UserService.Users")
	public void addText(Stream<Users> users) {
        // Ageが100歳以上の場合はLastNameに" (over 100!!)"を追記します。
		users.filter(b -> b.getAge() > 100).forEach(b -> b.setLastName(b.getLastName() + " (over 100!!)"));
	}
}

テスト実行③

またテスト実行を行います。
先ほど実行したアプリケーションが生きている場合は一度停止し、再起動します。

Usersエンティティ(/odata/v4/UserService/Users)へアクセスするとテストデータが取得できます。
また、イベントハンドラーに追加したカスタムロジックも機能しています。
(csv次郎さんに" (over 100!!)"がついてます。)

schema.cdsに@sap/cds/commonのmanagedをつけているので、定義には含まれてないですがcreatedAt,createdBy,modifiedAt,modifiedByのカラムが追加されています。

image.png

ロジックは入れていませんが、POSTによる追加も可能です。
image.png

BTP環境へデプロイ

DBへの接続もできるようになったと思うので、MTAをBuildしてデプロイします。

mta.yamlに以下を記載します。

mya.yaml
_schema-version: "3.2"
ID: mtademo
version: 0.0.1
modules:
  # サンプルのJavaModule(不要)
  # - name: capdemo_api
  #   type: java
  #   path: capdemoservice
  #   parameters:
  #     buildpack: java_buildpack
  #   properties:
  #     SPRING_PROFILES_ACTIVE: cloud
  #   build-parameters:
  #     builder: custom
  #     commands: [ mvn clean package ]
  #     build-result: "srv/target/*-exec.[wj]ar"
  #   provides:
  #     - name: srv-api # required by consumers of CAP services (e.g. approuter)
  #       properties:
  #         srv-url: ${default-url}
  #   requires:
  #    - name: useDemoShared
  #      properties:
  #         JBP_CONFIG_RESOURCE_CONFIGURATION: '[tomcat/webapps/ROOT/META-INF/context.xml:
  #            {"service_name_for_DefaultDB" : "~{hdi-container-name}"}]'

  # customserviceのJavaModule
  - name: capCustom_api
    type: java
    path: customservice
    parameters:
      buildpack: java_buildpack
    properties:
      SPRING_PROFILES_ACTIVE: cloud
    build-parameters:
      builder: custom
      commands: [ mvn clean package ]
      build-result: "srv/target/*-exec.[wj]ar"
    provides:
      - name: capCustom-api # required by consumers of CAP services (e.g. approuter)
        properties:
          srv-url: ${default-url}
    # resources -> useDemoSharedに定義したHANA DBを使う
    requires:
     - name: useDemoShared
       properties:
          JBP_CONFIG_RESOURCE_CONFIGURATION: '[tomcat/webapps/ROOT/META-INF/context.xml:
             {"service_name_for_DefaultDB" : "~{hdi-container-name}"}]'
resources:
  # 作成済みのHANA DBサービスをresourcesに記載
  - name: useDemoShared
    parameters:
      service-name: demoShared
      service: hanatrial
      service-plan: securestore
      config:
        schema: sampleSchema
    properties:
      hdi-container-name: '${service-name}'
    type: org.cloudfoundry.existing-service

MTAをBuildしてデプロイを実行します。

$ mbt build
$ cf deploy mta_archives/mtademo_0.0.1.mtar

HANA DBにテーブル追加

APIを実行する前にCDSに定義されているデータ構造をHANA DBに作成します。

CREATE TABLE demo_Users (
  ID INTEGER NOT NULL,
  FirstName NVARCHAR(100),
  LastName NVARCHAR(100),
  Age INTEGER,
  createdAt TIMESTAMP,
  createdBy NVARCHAR(255),
  modifiedAt TIMESTAMP,
  modifiedBy NVARCHAR(255),
  PRIMARY KEY(ID)
);

CREATE VIEW UserService_Users AS SELECT
  Users_0.createdAt,
  Users_0.createdBy,
  Users_0.modifiedAt,
  Users_0.modifiedBy,
  Users_0.ID,
  Users_0.FirstName,
  Users_0.LastName,
  Users_0.Age
FROM demo_Users AS Users_0;

INSERT INTO "DEMO_USERS" VALUES(
	1/*ID <INTEGER>*/,
	'KYOSO'/*FIRSTNAME <NVARCHAR(100)>*/,
	'太郎'/*LASTNAME <NVARCHAR(100)>*/,
	49/*AGE <INTEGER>*/,
	''/*CREATEDAT <TIMESTAMP>*/,
	'HANA DB'/*CREATEDBY <NVARCHAR(255)>*/,
	''/*MODIFIEDAT <TIMESTAMP>*/,
	'HANA DB'/*MODIFIEDBY <NVARCHAR(255)>*/
);
INSERT INTO "DEMO_USERS" VALUES(
	2/*ID <INTEGER>*/,
	'KYOSO'/*FIRSTNAME <NVARCHAR(100)>*/,
	'次郎'/*LASTNAME <NVARCHAR(100)>*/,
	120/*AGE <INTEGER>*/,
	''/*CREATEDAT <TIMESTAMP>*/,
	'HANA DB'/*CREATEDBY <NVARCHAR(255)>*/,
	''/*MODIFIEDAT <TIMESTAMP>*/,
	'HANA DB'/*MODIFIEDBY <NVARCHAR(255)>*/
);

demo_Usersテーブルを作成し、データを投入しました。
UserService_UsersのViewも作成しています。
image.png

BTPにデプロイしたOData APIを実行する

デプロイに成功したら実行してみます。
image.png

HANA DBに投入した値が取得できることが確認できました。
image.png

$filterのクエリオプションも実行可能です。(/odata/v4/UserService/Users?$filter=ID eq 1)
image.png

ついでにKYOSO花子さんのデータをPOSTメソッドで登録します。
image.png

DBに登録できることが確認できました。
自動で登録・更新日付等も登録されています。
image.png

おわりに

CAP Service SDK for Javaを使ってODataAPIが構築できました。
CDS等はもっと多くの機能があります。また、本来であればDBのテーブル等はデータ定義をデプロイして作成するべきところなのかなと思っています。
実際に開発していくとなると調査すべき課題が多いので、調べていきたいと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1