
はじめに
株式会社KYOSOでは現在、SAP BTP(PaaS)上でSAP Fioriアプリケーションの開発や、技術支援をしています。
バックエンドにJavaを使用したCloud Application Programming Model(以下 CAP)の技術検証過程で、データソース切り替え方法について調査いたしました。CAPを使用することによって、従来必要であったCRUD操作実装の工数を、大幅に削減することが可能になり、データソースの切り替え処理も簡易的になります!
CAPに関しては日本語で記載されている記事が少ないので、tipsを共有できれば幸いです。
Cloud Application Programming Model(CAP)とは?
CAPとは、CDSを使用して、データモデルを定義します。ODataプロトコルをベースにしたサービスを作成・公開することができ、自動で生成されたサービスはデフォルトでCRUD処理に対応しています。
CAPで開発されたバックエンドサービスとSAP Fioriアプリケーションは、SAP BTP上にデプロイされます。このため、開発からデプロイでのプロセスが効率的かつスムーズに行えます。
Node.jsとJavaでの開発が可能で、今回はJavaを使用しています。
検証内容
下記図のように、エンティティベースで追加するデータのデータソースを切り替えます。
CAPは”prod-jp10”のBTP環境にデプロイされますが、”Secondary”として定義したエンティティに対し登録を行う場合は”trial-us10”のBTP環境の中にある、HANA DBのSecondaryテーブルに対して登録を行います。

実装方法
では実際にCAPを使用したエンティティベースでのデータソース切り替えを行います。
以下、環境情報です。
- CAP:Java
- Javaバージョン:17
- cdsバージョン:7.5.1
- ODataバージョン:v2
OData作成
まず初めに、CAPプロジェクト内のCDSファイルに、ODataの設定を記述します。
(今回はCF環境にデプロイ検証済みのCAPプロジェクトが、あらかじめ用意されていることを前提とします)
今回はエンティティによるデータの切り替え検証のため2つのエンティティを用意します。
db/data/logictest.cds
namespace T;
// 1つ目の”prod-jp10”HANA DBにあるPrimaryテーブルを定義したエンティティ
entity Primary {
key ID : Integer;
name : String;
}
// 2つ目の”trial-us10”HANA DBにあるSecondaryテーブルを定義したエンティティ
entity Secondary {
key ID : Integer;
name : String;
}
上記を紐づけるserviceを作成します。
srv/logictest-service.cds
using { T as db } from '../db/logictest';
service LogictestService {
entity Primary as projection on db.Primary;
entity Secondary as projection on db.Secondary;
}
Odata公開の準備ができました。
application.yaml にデータソースの情報を記載
次にapplication.yaml ファイルに、データソースの情報を記載します。
datasourceに、primaryとsecondaryの2つのデータソースの情報を記載します。
srv/src/main/resources/application.yaml
spring:
jackson:
time-zone: Asia/Tokyo
datasource:
//一つ目の”prod-jp10”環境のHANADBの接続情報
primary:
url: jdbc:sap:*********.hana.prod-jp10.hanacloud.ondemand.com
username: {username}
password: {password}
driverClassName: com.sap.db.jdbc.Driver
//二つ目の”trial-us10”環境のHANADBの接続情報
secondary:
url: jdbc:sap:**********.hana.trial-us10.hanacloud.ondemand.com
username: {username}
password: {password}
driverClassName: com.sap.db.jdbc.Driver
データソース切り替え設定のJavaファイルを作成
上記で記載した、application.yaml ファイルのDB情報を呼び出すためのJavaを作成します。
srv/src/main/resources/application.yaml
▼application.yamlを表示
@Configuration
public class DataSourceConfiguration {
//application.yamlファイルからデータソース設定を読み取ってDataSourcePropertiesオブジェクトにマッピング
@Bean
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties()
{
return new DataSourceProperties();
}@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties()
{
return new DataSourceProperties();
}@Bean
//リクエストの属性 readEndPointRegion を取得し、その値に基づいてどちらのデータソースを使用するかを決定
@Primary
public DataSource routingDataSource(DataSourceProperties primaryDataSourceProperties,DataSourceProperties secondaryDataSourceProperties)
{
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
//return DatabaseContextHolder.get();
String temp = getReadEndPointRegion();
return temp;
}
};
//dataSourceMapで"primary" および "secondary" データソースを保持
Map<Object, Object> dataSourceMap = new ConcurrentHashMap<>();
dataSourceMap.put("primary", primaryDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build());
dataSourceMap.put("secondary", secondaryDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build());
routingDataSource.setTargetDataSources(dataSourceMap);
//setDefaultTargetDataSourceにより変更がない場合は"primary"に記載されているデータソースを使用
routingDataSource.setDefaultTargetDataSource(dataSourceMap.get("primary"));
routingDataSource.afterPropertiesSet();
return routingDataSource;
}
public String getReadEndPointRegion() {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
return (String) requestAttributes.getAttribute("readEndPointRegion", RequestAttributes.SCOPE_REQUEST);
}
}
上記ファイルの概要:
・primaryDataSourceProperties()
@ConfigurationProperties アノテーションを使って、application.yamlファイルのdatasource.primaryの設定をDataSourcePropertiesオブジェクトにマッピング
・secondaryDataSourceProperties()
同様に、”spring.datasource.secondary”の設定をマッピング
・routingDataSource
AbstractRoutingDataSourceがリクエストの属性 readEndPointRegionの値を取得
この値に基づいてどちらのデータソースを使用するかを決定
・dataSourceMap
マップで “primary” および “secondary” データソースを保持
・setTargetDataSources()
dataSourceMapをルーティングデータソースに設定
・setDefaultTargetDataSource()
デフォルトのデータソースを “primary” に
・getReadEndPointRegion()
RequestContextHolderを使ってリクエストの属性readEndPointRegion を取得
上記を記載することにより、カスタムロジックを作成時に設定するリクエストの属性
readEndPointRegionの値に応じて、データソースを選択することができます。
カスタムロジックを作成
LogictestService.handler.LogictestService.javaにカスタムロジックを書き
LogictestService.Secondaryエンティティの登録が行われた際に
Secondaryに定義したHANADB情報に切り替えて登録を行います。
LogictestService.java
@Before(event = CqnService.EVENT_CREATE, entity = "LogictestService.Secondary")
public void onBeforeCreate(Stream<Dbtest> product, CdsCreateEventContext context) {
RequestContextHolder.currentRequestAttributes().setAttribute("readEndPointRegion", "secondary",
RequestAttributes.SCOPE_REQUEST);
}
RequestContextHolder.currentRequestAttributes().setAttribute() を使用してリクエストの属性 readEndPointRegion に “secondary” という値を設定します。
これにより、リクエストに関連するデータソースを secondary に切り替えるための指示が DataSourceConfiguration クラスのgetReadEndPointRegion() メソッドに与えられます。
登録テスト
CF環境にデプロイ後
ポストマンでLogictestService.Secondaryへの登録を行います。

Secondaryに定義した”trial-us10”環境のHANADBテーブルに登録できているを確認できました!

LogictestService.Primaryに対してPOSTを行うと、Primaryエンティティにはデータソースの情報変更のカスタムロジックを作成していないので、Primaryデータソースとして定義したprod-jp10のHANADBのテーブルに対して登録が行われることが確認できます。


*2024年6月10日現在、私が調査したところ、actionを使用したカスタムロジックでデータソース切り替えを一回のリクエストの中で複数回行うことができませんでした。ご注意ください。
最後に
いかがでしたしたか?
今回はエンティティに対して、EVENT_CREATEが行われた際にデータソースを切り替える記載をカスタムロジックで作成しました。
こちらを応用することによってエンティティに対してEVENT_READが行われた際はPrimaryのデータソースに情報を取得し、同じエンティティに対してEVENT_CREATEが行われた場合はSecondryのデータソースに登録するなど、定義したエンティティやCRUD操作の何が行われるかを分岐として、データソースを簡易的に切り替えることが可能になります!
一度設定さえしてしまえばデータソースを簡易的に切り替えられるので、参考になれば幸いです。
最後までご覧いただきありがとうございました。
投稿者プロフィール

-
2021年入社、BS事業部所属。
SAP BTPでのSide-By-Side開発に関わっております。
現在は、CAPの標準化とSAP Fiori を用いた開発案件を担当しております。