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


はじめに

最近、Fiori elementsがOData V4に対応したり、BTPのABAP environmentでOData V4のODataサービスを作成できるようになったりと、OData V4の使用する環境が整ってきました。
OData V4を使ったフリースタイルアプリの作り方のチュートリアルも出てきています。

  1. SAP-samples/ui5-cap-event-app
  2. 【SAPUI5】ODataV4を触ってみる

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

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

トピック

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

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

作成するアプリ

注文の一覧と詳細画面から成るアプリです。Fiori elementsっぽい見た目ですが、あえてフリースタイルで作成します。

バックエンドはCAPで作成します。Orders, OrderItems, Statusesという3つのエンティティを持っています。Orders、およびOrderItemsはドラフト対応しています。

ステップ

  1. CAPプロジェクトの作成(今回の記事)
  2. UI5で一覧画面を作成
  3. UI5で詳細画面を作成
  4. CAPのイベントハンドラを実装
  5. Side Effectsを使ってヘッダの合計金額を更新

CAPプロジェクトの作成

プロジェクトの構成は以下のようになっています。

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がインストールされていること

cds deploy --to sqlite

その結果、sqlite.dbというファイルができます。

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

sqlite3.exe
.open sqlite.db

テーブルを見てみます。

.tables

これだけのテーブルができています。

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つ追加されています。

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 startcds watchなどでCAPサービスを起動します。ブラウザでhttp://localhost:4004/を開きます。

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

<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になっています。

ドラフトバージョンを見る

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

①キーを指定してアクセス

URIを以下のように指定すると、ドラフトバージョンに直接アクセスできます。
http://localhost:4004/ordering/Orders(ID=<有効バージョンと同じID>,IsActiveEntity=false)
ドラフトバージョンでは、IsActiveEntityがfalseになっています。

IsActiveEntity=trueとすると、有効バージョンが表示されます。

②Navigationでアクセス

OrdersエンティティのNavigationPropertyとして、以下がありました。

<NavigationProperty Name="SiblingEntity" Type="OrderingService.Orders"/>

このSiblingEntityを経由して、有効バージョンからひもづくドラフトバージョンにアクセスすることができます。
http://localhost:4004/ordering/Orders(ID=<ID>,IsActiveEntity=true)/SiblingEntity

ヘッダと明細をセットで取得

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

まとめ

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