【UI5・CAP】OData V4でドラフト対応したアプリを作る （1）CAPプロジェクトの作成

##はじめに
最近、[Fiori elementsがOData V4に対応したり](https://blogs.sap.com/2020/11/27/sap-fiori-elements-now-support-odata-v4/)、BTPのABAP environmentでOData V4のODataサービスを作成できるようになったりと、OData V4の使用する環境が整ってきました。
OData V4を使ったフリースタイルアプリの作り方のチュートリアルも出てきています。

1. [SAP-samples/ui5-cap-event-app](https://github.com/SAP-samples/ui5-cap-event-app)
2. [【SAPUI5】ODataV4を触ってみる](https://qiita.com/ichi_highball/items/4e05189074d61cd51b41)

自分でもOData V4の使い方について理解しておきたいと思い、サンプルアプリを作成してみることにしました。今回のシリーズでは、上記のチュートリアルを参考にOData V4でドラフトに対応したフリースタイルのUI5を作成します。

ソースコードは以下に格納しています。
https://github.com/miyasuta/odata-v4-freestyle

###トピック

- ドラフト対応したエンティティへのアクセス方法
- CAPのイベントハンドラ実装
- UI5でのドラフトの扱い、OData V4の扱い
- SideEffectsについて

※開発環境は、VS Codeを使用します。

##作成するアプリ
注文の一覧と詳細画面から成るアプリです。Fiori elementsっぽい見た目ですが、あえてフリースタイルで作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/cdfddc96-baf6-8cb2-b7ea-3443833b9512.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/dc694406-8102-b8e9-0ab8-496fd077b94b.png)

バックエンドはCAPで作成します。Orders, OrderItems, Statusesという3つのエンティティを持っています。Orders、およびOrderItemsはドラフト対応しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/731061e8-5e54-3107-00e1-fb3bd7ff17d3.png)

##ステップ
1. CAPプロジェクトの作成（今回の記事）
1. [UI5で一覧画面を作成](https://qiita.com/tami/items/64ec2426b7225bab1575)
1. [UI5で詳細画面を作成](https://qiita.com/tami/items/53d57ee97089db38fc6a)
1. [CAPのイベントハンドラを実装](https://qiita.com/tami/items/63d6bf772c31d88a108b)
1. [Side Effectsを使ってヘッダの合計金額を更新](https://qiita.com/tami/items/e00883883cb0a7c293c7)

##CAPプロジェクトの作成
プロジェクトの構成は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/fefe582b-aae5-9d50-0132-c4ecfa154239.png)

###db/schema.cds
OrdersエンティティはCompositionとしてOrderItemsを持っています。これでOrders->OrderItemsという親子関係ができます。

```
using { cuid, managed, sap } from '@sap/cds/common';
namespace demo.ordering;

entity Orders : cuid, managed {
    orderId: Integer @title : 'ID';
    date: Date @title : 'Date';
    time: Time @title : 'Time';
    comment: String @title : 'Comment';
    totalAmount: Integer @title : 'Total Amount';
    to_status: Association to Statuses @title : 'Status';
    to_items: Composition of many OrderItems on to_items.to_parent = $self;
}

entity OrderItems : cuid, managed {
    itemNumber: Integer @title: 'Item No';
    product: String @title: 'Product';
    quantity: Integer @title: 'Quantity';
    price: Integer @title: 'Price';
    amount: Integer @title: 'Amount';
    to_parent: Association to Orders;
}

entity Statuses : sap.common.CodeList  {
    key ID : Integer
}
```

###srv/ordering-service.cds
Ordersに`@odata.draft.enabled`をつけることにより、Ordersエンティティはドラフト対応します。OrderItemsはOrdersの子なので自動的にドラフト対応します。

```
using { demo.ordering as demo } from '../db/schema';
service OrderingService {
    @odata.draft.enabled
    entity Orders as projection on demo.Orders;    
    entity OrderItems as projection on demo.OrderItems;    

    @readonly
    entity Statuses as projection on demo.Statuses;
}
```

##ドラフト対応したテーブルを見てみる
以下のコマンドでローカルのsqlite DBにデプロイします。
※[sqlite](https://sqlite.org/download.html)がインストールされていること

```
cds deploy --to sqlite
```
その結果、sqlite.dbというファイルができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/68407243-225d-9843-b7cb-6258012cb222.png)

以下のコマンドでsqliteを実行します。
参考：https://sqlite.org/cli.html

```
sqlite3.exe
.open sqlite.db
```

テーブルを見てみます。

```
.tables
```

これだけのテーブルができています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ccce725b-bb62-90b3-8c07-39d3b30b0901.png)

Ordersに関係するところだけピックアップすると、以下のようになっています。（同じセットがOrderItemsに対しても存在します）
No.3～5はどのように使い分けられているのか、正直よくわかりません。

|No|テーブル名  |種類  |説明  |
|---|---|---|---|
|1|demo_ordering_Orders|テーブル|有効データ格納用|
|2|OrderingService_Orders_drafts  |テーブル  |ドラフトデータ格納用  |
|3|OrderingService_Orders  |ビュー  |No.1からデータを取得するビュー  |
|4|localized_demo_ordering_Orders  |ビュー  |No.1からデータを取得するビュー  |
|5|localized_OrderingService_Orders|ビュー  |No.4からデータを取得するビュー  |


以下のコマンドでテーブル定義を見ることができます。

```
.schema
```

ドラフトテーブルの定義は以下のようになっており、有効データ格納用と比べると、ドラフト管理のための項目が4つ追加されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/83f00aae-3554-f28a-40e3-333d0163d7aa.png)

SQLを発行してテーブルの中身を見ることもできます。

```
sqlite> select * from OrderingService_Orders_drafts;
4acdac5f-5390-4f0c-9c57-33384c88aa65|2021-05-18T02:46:09.084Z|anonymous|2021-05-18T21:27:44.833Z|anonymous|100|2021-05-04|09:00:00|Hello draft|10500|1|0|1|0|85c3dbaf-3f6c-48fe-b19c-7a86a2c7c71f
```

##ブラウザからデータを取得してみる
ブラウザからの有効データ、ドラフトデータへのアクセス方法について確認しておきましょう。
※ドラフトデータが必要なため、ステップ3の後に確認可能となります。

`npm start`や`cds watch`などでCAPサービスを起動します。ブラウザで`http://localhost:4004/`を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f5e103cf-25d7-8f77-b3dd-1da89940c819.png)


$metadataを確認しておきます。以下はOrdersエンティティの定義です。ID, IsActiveEntityの2項目がキーになっています。

```xml
<EntityType Name="Orders">
	<Key>
		<PropertyRef Name="ID"/>
		<PropertyRef Name="IsActiveEntity"/>
	</Key>
	<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
	<Property Name="createdAt" Type="Edm.DateTimeOffset" Precision="7"/>
	<Property Name="createdBy" Type="Edm.String" MaxLength="255"/>
	<Property Name="modifiedAt" Type="Edm.DateTimeOffset" Precision="7"/>
	<Property Name="modifiedBy" Type="Edm.String" MaxLength="255"/>
	<Property Name="orderId" Type="Edm.Int32"/>
	<Property Name="date" Type="Edm.Date"/>
	<Property Name="time" Type="Edm.TimeOfDay"/>
	<Property Name="comment" Type="Edm.String"/>
	<Property Name="totalAmount" Type="Edm.Int32"/>
	<NavigationProperty Name="to_status" Type="OrderingService.Statuses">
		<ReferentialConstraint Property="to_status_ID" ReferencedProperty="ID"/>
	</NavigationProperty>
	<Property Name="to_status_ID" Type="Edm.Int32"/>
	<NavigationProperty Name="to_items" Type="Collection(OrderingService.OrderItems)" Partner="to_parent">
		<OnDelete Action="Cascade"/>
	</NavigationProperty>
	<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false" DefaultValue="true"/>
	<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false" DefaultValue="false"/>
	<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false" DefaultValue="false"/>
	<NavigationProperty Name="DraftAdministrativeData" Type="OrderingService.DraftAdministrativeData" ContainsTarget="true"/>
	<NavigationProperty Name="SiblingEntity" Type="OrderingService.Orders"/>
</EntityType>
<EntityType Name="Statuses
```

###有効バージョンを見る
Ordersエンティティを見てみます。`/Orders`のパスでは、有効バージョンのみ表示されます。1件目のデータにはドラフトが存在するので、HasDraftEntityが**true**になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/22ee0877-9dea-d92c-d4e1-3e053c7344d0.png)

###ドラフトバージョンを見る
ドラフトバージョンへのアクセス方法は2つあります。

####①キーを指定してアクセス
URIを以下のように指定すると、ドラフトバージョンに直接アクセスできます。
`http://localhost:4004/ordering/Orders(ID=<有効バージョンと同じID>,IsActiveEntity=false)`
ドラフトバージョンでは、IsActiveEntityが**false**になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b81d0282-c43c-3c15-2e9a-25e3731a19d2.png)
`IsActiveEntity=true`とすると、有効バージョンが表示されます。

####②Navigationでアクセス
OrdersエンティティのNavigationPropertyとして、以下がありました。

```xml
<NavigationProperty Name="SiblingEntity" Type="OrderingService.Orders"/>
```
このSiblingEntityを経由して、有効バージョンからひもづくドラフトバージョンにアクセスすることができます。
`http://localhost:4004/ordering/Orders(ID=<ID>,IsActiveEntity=true)/SiblingEntity`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3f9cb1c0-56a2-85c4-97fe-8684d1e70ca5.png)

###ヘッダと明細をセットで取得
ヘッダ、明細のドラフトをセットで取得することもできます。
`http://localhost:4004/ordering/Orders(ID=<ID>,IsActiveEntity=false)?$expand=to_items`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d2f9a129-3118-e1a4-e9d6-023e269171ea.png)

##まとめ

- ドラフト対応したエンティティの場合、有効バージョン用とドラフト用のテーブルがそれぞれ作成される
- 有効バージョンとドラフトバージョンのIDは同じ
- 有効バージョンにはIsActiveEntity=true、ドラフトバージョンにはIsActiveEntity=falseでアクセスできる