3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Node.jsとJavaでのCAP開発の違い

Last updated at Posted at 2023-05-13

はじめに

私は普段Node.jsを使ってCAPの開発をしているのですが、Javaでの開発はどのようなものだろうと思い以下のチュートリアルを実施してみました。この記事では、その結果わかったNode.jsでの開発との違いについて記載します。

トピック

  1. プロジェクトの作り方
  2. プロジェクト構成
  3. ローカルでの実行方法
  4. ODataサービスのURL
  5. イベントハンドラの作り方
  6. 定数やインターフェースを定義したファイルが自動生成される
  7. デプロイの方法
  8. Javaのバージョンについて

1. CAPプロジェクトの作り方

Node.jsの場合

cds init <プロジェクト名>

Javaの場合

mvn -B archetype:generate -DarchetypeArtifactId=cds-services-archetype -DarchetypeGroupId=com.sap.cds \
  -DarchetypeVersion=RELEASE -DjdkVersion=11 \
  -DgroupId=<グループID> -DartifactId=<プロジェクト名> -Dpackage=<パッケージ名>

別の方法として、ドキュメントには以下のコマンドが紹介されていました。こちらはNode.jsに近いです。

cds init <PROJECT-ROOT> --add java

2. プロジェクト構成

以下に主要なディレクトリ、ファイルだけ抜粋したJavaのプロジェクト構成を示します。DB用のCDS(schema.cds)とサービス用のCDS(services.cds)の定義方法はNode.jsと同じです。
違いとしては、イベントハンドラのソースが/srv/src/java/.../と深い階層にある点、package.jsonの代わりにpom.xmlを使って依存するモジュールを指定する点です。また、デプロイにはmta.yamlではなくmanifest.ymlを使います(後述)。

.
├── db
│   ├── data
│   ├── src
│   └── schema.cds
├── srv
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── ...
│   │               └── handlers
│   │                   └── OrdersService.java
│   │               └── Application.java
│   ├── pom.xml
│   └── services.cds
├── .cdsrc.json
├── manifest.yml
├── package.json
└── pom.xml

3. ローカルでの実行方法

Node.jsの場合

cds watch

Javaの場合

//初回はまずビルドする
mvn clean install

//アプリケーションを起動する
mvn clean spring-boot:run

Node.jsではcds watchコマンドを1回実行すると、何か変更をするたび自動的に再起動がかかります。一方Javaは、変更を反映させるためには都度mvn clean spring-boot:runを実行する必要があります。

2023/10/11追記

  • spring-boot-devtoolsを使うと、Javaのソースを変更したときに自動的に再起動が行われます。
  • mvn cds:watchを使うと、.cdsファイルを変更したとき自動的に再起動が行われます。
    これらのツールは併用することができます。

4. ODataサービスのURL

※CAPのversion7からは、Node.jsのサービスパスがJavaに合わせてodata/v4となりました。
https://cap.cloud.sap/docs/releases/archive/2023/jun23#changed-default-service-path

Node.jsの場合
image.png
Javaの場合
image.png
Javaではodata/v4が先頭につきます。

5. イベントハンドラの作り方

Node.jsの場合
srvフォルダの下に.cdsファイルと同じ名前で拡張子を.jsにしたファイルを作成するのが慣例です。イベントハンドラをサービスごとに分けたければ、サービスごとに.cdsファイルを分ける必要があります。
image.png

Javaの場合
handlersフォルダの下にサービスごとに.javaファイルを作ります。
image.png
デコレーター@ServiceNameでサービス名を指定するようになっています。

@Component
@ServiceName(OrdersService_.CDS_NAME)
public class OrdersService implements EventHandler{
    @Autowired
    PersistenceService db;

    @Before(event = CdsService.EVENT_CREATE, entity = OrderItems_.CDS_NAME)

似たような仕組みをNode.jsで実現するためのツールとして、cds-routing-handlersというオープンソースのモジュールがあります。試してみたことがありますが、ローカル開発が難しくて挫折したので、Javaでは標準でこのような仕組みが用意されていてよいと思いました。

6. 定数やインターフェースを定義したファイルが自動生成される

Javaの場合、ビルドしたときにsrv/src/gen/.../フォルダに定数やインターフェースを定義したファイルが自動生成されます。
image.png
ファイルには2種類あり、<サービス名>.javaと<サービス名>_.javaの形式のものがあります。よく似ていますが違いはアンダースコアの有無です。
アンダースコア付きの方は、サービスで定義されたエンティティや項目の名前(すなわち定数)を定義したファイルです。アンダースコアなしの方は、エンティティのデータにアクセスするためのセッターやゲッターなどが定義されています。

使い方
イベントハンドラの中でこれらのファイルをインポートします。

import cds.gen.ordersservice.OrderItems;
import cds.gen.ordersservice.OrderItems_;
import cds.gen.ordersservice.Orders;
import cds.gen.ordersservice.Orders_;
import cds.gen.sap.capire.bookstore.Books;
import cds.gen.sap.capire.bookstore.Books_;

以下のように処理の中で使うことができます。

    @Before(event = CdsService.EVENT_CREATE, entity = OrderItems_.CDS_NAME) //定数を使用
    public void validateBookAndDecreaseStock(List<OrderItems> items) { //インターフェースを使用
        for (OrderItems item : items) {
            String bookId = item.getBookId(); //インターフェースを使用
            Integer amount = item.getAmount(); //インターフェースを使用

            // check if the book that should be ordered is existing
            CqnSelect sel = Select.from(Books_.class).columns(b -> b.stock()).where(b -> b.ID().eq(bookId)); //定数を使用
            Books book = db.run(sel).first(Books.class)
                    .orElseThrow(() ->new ServiceException(ErrorStatuses.NOT_FOUND, "Book does not exist"));

これにより、項目名などをハードコーディングする必要がなくなります。スペルミスをすればコーディングしているときに気づくので、実行時にエラーが起きることがなくなります。この仕組みをNode.jsでも取り入れたのがtypeScriptなのだと実感しました。

関連記事:CAPでTypeScriptを使ってみる (cds2types)

7. デプロイの方法

Node.jsの場合
mta.yamlファイルを作成して全てのモジュール(db、サービス)を同時にデプロイします。HDIやXSUAAのサービスインスタンスはデプロイ時に作成されます。

Javaの場合
Javaの場合(というか、実施したチュートリアルでは)事前にHDIサービスインスタンスを作成し、
cds deployコマンドでHANA Cloudにデプロイします。
サービスモジュールはmanifest.ymlに指定してcf pushでデプロイします。バインドするXSUAAなどのサービスインスタンスは事前に作成しておきます。cf pushはサービスモジュールだけデプロイするので、比較的早く終わります。

manifest.yml
---
applications:
- name: bookstore
  path: srv/target/bookstore-exec.jar
  random-route: true
  services:
  - bookstore-hana
  - bookstore-xsuaa

※JavaでもMTAによるデプロイは可能です。

8. Javaのバージョンについて

開発中、Javaのバージョンには何度か足をすくわれました。バージョンはプロジェクトを生成するときにDjdkVersionで指定します。指定しない場合は17となっていました。

pom.xml
	<properties>
		<!-- OUR VERSION -->
		<revision>1.0.0-SNAPSHOT</revision>

		<!-- DEPENDENCIES VERSION -->
		<jdk.version>17</jdk.version>
		<cds.services.version>1.34.1</cds.services.version>
		<spring.boot.version>2.7.11</spring.boot.version>
		<cds.install-cdsdk.version>6.8.0</cds.install-cdsdk.version>

		<cds.install-node.downloadUrl>https://nodejs.org/dist/</cds.install-node.downloadUrl>
	</properties>

これをビルドした際、BASにインストールされているバージョンとアンマッチだったためエラーになりました。
image.png

バージョンを11に変えると、ビルドは成功しました。しかし今度はデプロイ後、インスタンスの起動でエラーになりました。Cloud Foundryではバージョン8でないと実行できないようです(※)。バージョンを8に変えてビルド、デプロイすると、起動は成功しました。

※SapMachineを使うとJava 11を使うことができます。
https://help.sap.com/docs/btp/sap-business-technology-platform/sapmachine
image.png

素人考えで、バージョンは新しいほど良いもののように思えるのですが、バージョン17が出ている中で8や11が使われているのはなぜなのでしょうか。別の世界線なのでしょうか。。

良かった点、悪かった点

CAPでJavaを使ってみて、Node.jsよりよかった点、悪かった点を挙げます(個人の感想です)。

良かった点

  • 型付けされているので、コーディング時にエラーに気づくことができ、実行時にスペルミスによるエラーが起きない
  • イベントハンドラのファイルを任意の単位で作成できるので、ソースコードを短く保てる
  • デプロイ対象がサービスモジュールのみなので、デプロイが早い

悪かった点

  • ローカルで実行する場合、都度ビルドする必要があり、待ちが発生する
  • プロジェクト構成が複雑(イベントハンドラにたどり着くまでの階層が深い)
3
1
0

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
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?