capireのCookbook(Reuse, Compose, and Integrate)翻訳


はじめに

本ドキュメントは2022年3月7日時点のcapireCookbookの一部であるReuse, Compose, and Integrateを短く日本語ドキュメントにまとめたものです。以降、Reuse、ComposeおよびIntegrateはそれぞれ”あるオブジェクトの再利用すること”、”別オブジェクトと結合させて新たなオブジェクトを用意すること”、“あるオブジェクトに別オブジェクトの機能を埋め込むこと”を意味するキーワードとして利用します。

本ドキュメントは上記時点のCookbookを参照してまとめられています。最新の情報は必ずCookbookをご確認ください。また、本ドキュメントにおける翻訳は単語の正確な翻訳よりも文脈の掴みやすさを重視して翻訳しています。正確な情報を入手される場合も必ずCookbookをご確認ください。

翻訳まとめ

Reuse, Compose, and Integrate

CAPのプロジェクトを再利用したり拡張したい場合に、どのような手段を取ることが可能か紹介します。

目次

  1. はじめに

    1. 使用シナリオ

    2. サンプルプロジェクト

    3. 試すときに必要な用意

  2. インポートによるパッケージの再利用

    1. npmレジストリからnpm add/installでインポート

    2. その他のソースからインポート

    3. 再利用における“組込み”と“統合”

  3. インポートされたモデルの再利用

    1. using from命令

    2. index.cdsを使用した組込み

    3. その他組込み方法

    4. インポートした定義の使用および拡張

  4. インポートしたコードの再利用

    1. Node.jsの場合

  5. インポートしたUIの再利用
  6. サービスの統合

1. はじめに

パッケージのインポートによって、CAPはモデル、コード、初期データやi18nバンドルを再利用可能です。

1-1. 使用シナリオ

CAPのreuse、composeおよびintegrateテクニックを使用することで、開発者は下記のようなさまざまな再利用シナリオに対応可能です。

  1. Verticalized/Composite Solutions(垂直型compose手法) — 開発者はいくつかのパッケージ(もしくはサービス)を選択し、マッシュアップするように新たなCAPアプリケーションを開発します。

  2. Prebuilt Extension Packages(事前定義の拡張パッケージ) — 上記垂直型compose手法で利用されることなどを前提に、開発者は新たな拡張パッケージを用意します。

  3. Prebuilt Integration Packages(事前定義の統合パッケージ) — 上記垂直型compose手法で利用されることなどを前提に、開発者は新たな統合パッケージを用意します。

  4. Prebuilt Business Data Packages(事前定義ビジネスデータのパッケージ) — 開発者は初期データをパッケージにして提供します。この対象にはLanguages, Countries, Regions, Currenciesなどのデータが一般に含まれます。

  5. Customizing SaaS Solutions(利用側によるアプリケーション拡張) — 開発者によってCAPアプリケーションを提供される側の人員が提供されたアプリケーションを拡張してカスタマイズします。

1-2. サンプルプロジェクト

SAPはその公式GitHubでcap/samplesをサンプルプロジェクトとして公開しています。

  • @capire/bookshop は本サンプルプロジェクトにおける基本的な機能を提供し、再利用されます。

  • @capire/common はいくつかのパッケージで再利用される共通機能を“事前定義の拡張パッケージ”として提供し、初期データを”事前定義ビジネスデータのパッケージ”として提供します。

  • @capire/reviews は再利用されるパッケージです。

  • @capire/orders は再利用されるパッケージです。

  • @capire/bookstore は上記パッケージたちを利用した”利用側によるアプリケーション拡張”のパッケージです。

1-3. 試すときに必要な用意

以下の手順でcap/samplesを試していただくことが可能です。

1) cap/samplesをクローンし、ローカルのnpm registryに登録する
git clone https://github.com/sap-samples/cloud-cap-samples samples
cd samples
npm install
npm run registry

@capire/...パッケージを別プロジェクトから参照させる場合、上記npm runコマンドは動かし続けてください。

2) 起動する
cds init sample
cd sample
npm i
# ... run the upcoming commands in here

2. インポートしたパッケージの再利用

CAPはnpmの仕組みを用いてパッケージを再利用可能です。下記の図はパッケージ再利用の基本的な手順です。

2-1. npmレジストリからnpm add/installでインポート

npm add/installを用いて開発中のパッケージに対して外部パッケージをインポート可能です。

npm add @capire/bookshop @capire/common

npm addnpm installのエイリアスです。詳しくはnpm docsをご参照ください。

上記のコマンドによりnode_modulesディレクトリ配下にインポートしたパッケージが配置され、package.jsonに下記の内容が追記されます。

{ "name": "sample", "version": "1.0.0", "dependencies": { "@capire/bookshop": "^1.0.0", "@capire/common": "^1.0.0", ... }
}

2-2. その他のソースからインポート

npmレジストリからのインポートに加え、ローカルのソースをインポートすることも可能です。ローカルの他CAPプロジェクトやtarball(.tgz)を使用できます。

npm add ~/Downloads/@capire-bookshop-1.0.0.tgz
npm add ../bookshop

2-3. 再利用における“組込み”と“統合”

パッケージを再利用するためにインポートすると、デフォルトではインポートされたすべての要素がインポート先のプロジェクトに含まれます(いわば”組込まれた”もしくは”埋め込まれた”というような状態になります)。例えば以下のような要素を含みます。

  • ドメインモデル(db/)

  • サービス定義(srv/.*.cds)

  • サービス実装(srv/.*.js)

  • i18nバンドル

  • 初期データ(db/data/.*csv)

CAPではこのような組込みだけではなく、上記要素の一部だけを利用するような統合する使用方法も存在します。その方法は以下の“6. サービスの統合”に記載されています。

3. インポートされたモデルの再利用

すべてのインポートされた要素はインポート先のCAPプロジェクトに組み込まれていますが、それらのうちのどれをしようするかはインポート先の開発者に委ねられています。

補足:インポート先で参照しているすべてのインポート元モデルは公開されます。一方で、参照されていないすべてのインポート元モデルは公開されません。

3-1. using from命令

using命令を使って下記のようにモデルをインポートできます。

using from '@capire/bookshop';
using from '@capire/common';

cdsコンパイラはnode_modules配下にあるインポートされたコンテンツを見つけてこれを取り込みます。

3-2. index.cdsを使用した組込み(全公開)

上記のusing from命令はインポートされたパッケージがindex.cdsを持っていることを期待します。このindex.cdsはインポートされるプロジェクトのルートに配置して、どの要素を公開するか宣言します。実装例はこちらを参照してください。

using from './db/schema';
using from './srv/cat-service';
using from './srv/admin-service';

これにより、cds watch起動時などに下記ログを参照できます。これによって組み込まれたアプリが再利用されていることを把握できます。

[cds] - connect to db > sqlite { database: ':memory:' }
> filling sap.common.Currencies from common/data/sap.common-Currencies.csv
> filling sap.common.Currencies_texts from common/data/sap.common-Currencies_texts.csv
> filling sap.capire.bookshop.Authors from bookshop/db/data/sap.capire.bookshop-Authors.csv
> filling sap.capire.bookshop.Books from bookshop/db/data/sap.capire.bookshop-Books.csv
> filling sap.capire.bookshop.Books_texts from bookshop/db/data/sap.capire.bookshop-Books_texts.csv
> filling sap.capire.bookshop.Genres from bookshop/db/data/sap.capire.bookshop-Genres.csv
/> successfully deployed to sqlite in-memory db
​
[cds] - serving AdminService { at: '/admin', impl: 'bookshop/srv/admin-service.js' }
[cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' }

3-3. その他組込み方法(部分公開)

もしすべてではなく部分的に公開したい場合、using from命令を下記のように書き換えます。

using { CatalogService } from '@capire/bookshop/srv/cat-service';

このとき下記のようにcds watchのログは確認できます。

[cds] - connect to db > sqlite { database: ':memory:' }
> filling sap.capire.bookshop.Authors from bookshop/db/data/sap.capire.bookshop-Authors.csv
> filling sap.capire.bookshop.Books from bookshop/db/data/sap.capire.bookshop-Books.csv
> filling sap.capire.bookshop.Books_texts from bookshop/db/data/sap.capire.bookshop-Books_texts.csv
> filling sap.capire.bookshop.Genres from bookshop/db/data/sap.capire.bookshop-Genres.csv
/> successfully deployed to sqlite in-memory db
​
[cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' } <- Catalogサービスのみ公開されています

3-4. インポートした定義の使用および拡張

インポートしたモデルは下記のように組み合わせて拡張するように使用することもできます。

using { sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews';
​
// インポートしたreviewとbookshopをここで拡張(マッシュアップ)しています
extend Books with { reviews : Composition of many Reviews on reviews.subject = $self.ID; rating : Decimal;
}

4. インポートしたコードの再利用

モデルに加えてサービス実装もインポートする場合、インポート元のモデル定義に@implアノテーションでサービス実装を紐づけます。多くのサンプルコードではcat-service.cdsにサービス定義を紐づけるときは同名のjsファイルを用意して紐づけを自動で実現していますが、その代わりに@implアノテーションを使用します。

その手順は以下の通りです。

a. @implアノテーションを追加
using { CatalogService } from '@capire/bookshop';
annotate CatalogService with @impl:'srv/my-cat-service-impl';
b. @implアノテーションで指したファイル(srv/my-cat-service.impl.js)を用意
module.exports = cds.service.impl (function(){ this.on (...) // add your event handlers
})
c. 下記のようにインポートした実装を呼び出し
const base_impl = require ('@capire/bookshop/srv/cat-service') // インポートした定義をインポート
module.exports = cds.service.impl (async function(){ this.on (...) // add your event handlers await base_impl.call (this,this) // 定義を呼び出し
})

5. インポートしたUIの再利用

server.jsを用意し、expressでルート追加をするのとおなじように再利用を定義します。

const express = require('express')
const cds = require('@sap/cds')
​
// Add routes to UIs from imported packages
cds.once('bootstrap',(app)=>{ app.serve ('/bookshop') .from ('@capire/bookshop','app/vue') app.serve ('/reviews') .from ('@capire/reviews','app/vue') app.serve ('/orders') .from('@capire/orders','app/orders')
})
​
...
module.exports = cds.server

6. サービスの統合

上記のような再利用ではなく、開発中のアプリの一部に対して外部サービスをライブラリのように使用する(統合する)方法も利用できます。この方法を使用することで、開発するアプリケーションたちをマイクロサービスとして構成できます。これは以下の手順により実装します。

a. 外部サービスのAPI定義をインポート

外部サービスのAPI定義を2. インポートしたパッケージの再利用の方法に従ってインポートします。もしくは外部サービスのAPI定義ファイル(edmx)のみが手元にある場合、cds importコマンドによりサービスをインポートします。

 "dependencies": { "@capire/bookshop": "^1.0.0", "@capire/reviews": "^1.0.0", "@capire/orders": "^1.0.0", "@capire/common": "^1.0.0", ...
},
b. インポートしたサービスをサービス定義内で使えるように設定

package.json内のcds.requiresオブジェクトに下記のようにインポートするサービス情報を設定します。cds importでサービス定義をインポートしている場合、modelの指す場所は典型的にはsrv/extenal/<your-imported-definition>.edmxです。例えば、@capire/reviews@capire/ordersを利用するプロジェクトを定義する場合であればこのpackage.jsonのように定義します。

"cds": { "requires": { "ReviewsService": { "kind": "odata", "model": "@capire/reviews" // @capire/reviewsで定義されたodataサービスがReviewServiceという名前で使えるようになる }, "OrdersService": { "kind": "odata", "model": "@capire/orders" },
}
}
c. インポートしたサービスの呼び出し

設定した接続先はcds.connect.to()を使って他のサービス同様に取得可能です。例えば、下図のようにCatalogServiceを定義し、そこからインポートしたReviewsServiceを呼び出す場合のソースコードは以下のように実装します。

// 1. package.jsonで設定したCatalogServiceとReviewsServiceに接続するためのクライアントを取得
const CatalogService = await cds.connect.to ('CatalogService')
const ReviewsService = await cds.connect.to ('ReviewsService')
​
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => { console.debug ('> delegating request to ReviewsService') const [id] = req.params, { columns, limit } = req.query.SELECT
​ // 2. クライアントを用いてサービス実行 return ReviewsService.tx(req).read ('Reviews',columns).limit(limit).where({subject:String(id)})
}))

上記実装例の完全版はこちらに配置されています。