前言
SAP云应用程序编程模型(CAP)是一个语言、库和工具框架,用于构建企业级服务和应用程序。它引导开发人员沿着一条经过验证的最佳实践的“黄金之路”前进,并为反复出现的任务提供大量开箱即用的解决方案。
我们这就来看看SAP BTP的CAP到底提供了哪些便利。也以PostgreSQL作为数据库的后端为例 ,看看如何简单的应用CAP相关技术和技术栈。CAP不仅可以快速生成云上的应用,也可以生成本地可以运行的应用。
CAP框架的特点是混合了经过验证并被广泛采用的开源和SAP技术,如下图所示。
image-20240310084908551
在开源技术基础上,CAP主要添加了:
-
Core Data Services (CDS):我们用于领域模型和服务定义的通用建模语言。
-
Service SDKs and runtimes :主为要node.js和Java. 提供实现和使用服务的库,以及自动服务众多请求的通用的程序实现。
基于CDS的运行架构图如下所示:
image-20240310085301264
整个CAP里头,支持Node.js和Java两种技术栈。我们本篇就先简要介绍一下Node.js下的使用。友情提示:Node.js在SAP BTP云平台下的生态当中,占据着十分重要的地位。基本上除了Java以外,使用Node.js是最多的,其次还有GO相关技术栈。
环境搭建
安装Node.js:
从https://nodejs.org/en 下载最新的LTS长期稳定版,如:https://nodejs.org/dist/v20.11.1/node-v20.11.1-x64.msi
安装CAP的cds-sdk
npm add -g @sap/cds-dk
cds #> run the installed CLI
安装GIT
步骤: 略
git version
安装sqlite
只在WINDOWS平台上安装。 从:https://sqlite.org/download.html 下载即可。
从Precompiled Binaries for Windows 那一部分找。
下载完,将sqlite-tools-win-x64-3450100.zip解压,里边的sqlite3.exe就是要用到的sqlite3应用程序。
将sqlite3.exe添加到%PATH%环境变量里头。验证:
E:\work\3rdparty\postgres\postgres>sqlite3
SQLite version 3.45.1 2024-01-30 16:01:20 (UTF-16 console I/O)
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>
sqlite3是cds环境默认的数据库后端。主要用于开发测试使用。
安装mvn, java JDK (略)
这一部分是为Java技术栈提供的。只用Node.js时,可以不用装
安装MS vscode
直接从https://code.visualstudio.com/ 下载安装即可。
推荐安装以下扩展插件:
-
SAP CDS Language Support
-
ESLint
-
REST Client
-
SQLite Viewer
-
Rainbow CSV
安装配备PostgreSQL数据库
这个我们有足够的理由略。公众号以及官方文档里有大量的介绍。本文假定你已经提前准备好了PostgreSQL的运行环境。
实例
下面, 我们就来一个简单的示例来介绍一下使用CAP/cds来进行相关开发,最终能访问到PostgreSQL数据库。
1、初始化工程
cds init bookshop
E:\work\3rdparty\postgres\cap\bookshop>tree .
卷 Work 的文件夹 PATH 列表
卷序列号为 000000D0 3938:438F
E:\WORK\3RDPARTY\POSTGRES\CAP\BOOKSHOP
├─.vscode
├─app
├─db
└─srv
它会自动在下边生成app、db、srv三个子目录。我们可以用code bookshop直接打开这个工程。看到上边完整的目录结构。
package.json则定义了Node.js的运行包的依赖及启动定义。
2、定义一些schema
文件db/schema.cds, 内容如下:
using { Currency, managed, sap } from '@sap/cds/common';
namespace sap.capire.bookshop;
entity Books : managed {
key ID : Integer;
title : localized String(111);
descr : localized String(1111);
author : Association to Authors;
genre : Association to Genres;
stock : Integer;
price : Decimal(9,2);
currency : Currency;
}
entity Authors : managed {
key ID : Integer;
name : String(111);
books : Association to many Books on books.author = $self;
}
/** Hierarchically organized Code List for Genres */
entity Genres : sap.common.CodeList {
key ID : Integer;
parent : Association to Genres;
children : Composition of many Genres on children.parent = $self;
}
这时,我们如果运行cds watch, 可以看到,它把上边定义的schema, 部署到sqlite的内存模式的数据库里头去了。这是默认行为。
E:\work\3rdparty\postgres\cap\bookshop>cds watch
cds serve all --with-mocks --in-memory?
live reload enabled for browsers
___________________________
[cds] - loaded model from 2 file(s):
db\schema.cds
C:\Users\Think\AppData\Roaming\npm\node_modules\@sap\cds-dk\node_modules\@sap\cds\common.cds
[cds] - connect using bindings from: { registry: '~/.cds-services.json' }
[cds] - connect to db > sqlite { database: ':memory:' }
/> successfully deployed to in-memory database.
[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - launched at 2024/3/10 17:58:59, version: 7.7.0, in: 6.550s
[cds] - [ terminate with ^C ]
No service definitions found in loaded models.
Waiting for some to arrive...
而当你打开浏览器访问:http://localhost:4004 它也能为你打开一个初具雏形的页面。我们需要在后边进行适当补充。
同时,可以使用如下命令,将schema定义转化成json, yml, sql等多种形式:
cds db/schema.cds -2 json
cds db/schema.cds -2 yml
cds db/schema.cds -2 sql
--看看它转化成的SQL语句的样子:(节选)
CREATE TABLE sap_capire_bookshop_Books (
createdAt TIMESTAMP_TEXT,
createdBy NVARCHAR(255),
modifiedAt TIMESTAMP_TEXT,
modifiedBy NVARCHAR(255),
ID INTEGER NOT NULL,
title NVARCHAR(111),
descr NVARCHAR(1111),
author_ID INTEGER,
genre_ID INTEGER,
stock INTEGER,
price DECIMAL(9, 2),
currency_code NVARCHAR(3),
PRIMARY KEY(ID)
);
CREATE TABLE sap_capire_bookshop_Authors (
createdAt TIMESTAMP_TEXT,
createdBy NVARCHAR(255),
modifiedAt TIMESTAMP_TEXT,
modifiedBy NVARCHAR(255),
ID INTEGER NOT NULL,
name NVARCHAR(111),
PRIMARY KEY(ID)
);
CREATE TABLE sap_capire_bookshop_Genres (
name NVARCHAR(255),
descr NVARCHAR(1000),
ID INTEGER NOT NULL,
parent_ID INTEGER,
PRIMARY KEY(ID)
);
CREATE TABLE sap_common_Currencies (
name NVARCHAR(255),
descr NVARCHAR(1000),
code NVARCHAR(3) NOT NULL,
symbol NVARCHAR(5),
minorUnit SMALLINT,
PRIMARY KEY(code)
);
.......
这里考虑到PostgreSQL中表名或其它对象名的63个字符长度的限制,我们还真不能把包名得太长,一不小心就会超长了。
3、定义相关服务(service)
定义完schema之后,我们就可以定义一下服务(service)了。
文件 svc/admin-service.cds
using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(requires:'authenticated-user') {
entity Books as projection on my.Books;
entity Authors as projection on my.Authors;
}
文件 srv/cat-service.cds
using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
@readonly entity Books as select from my.Books {*,
author.name as author
} excluding { createdBy, modifiedBy };
@requires: 'authenticated-user'
action submitOrder (book: Books:ID, quantity: Integer);
}
这里解释一下这两个service的逻辑:
admin-service: 它需要进行user的认证,认证通过以后,就可以基于Books和Authors两个schema描述的entity进行正常的访问了。
cat-service: 它直接可以进行浏览数据 ,能以只读方式访问Books, 顺带访问Author(根据作者名字). 但是会排除createdBy, modifiedBy两个字段。同时,如果提供了用户认证,则可以按照Book的ID和数量来提交订单(action:submitOrder)。
这里感觉整个过程,基本上没怎么写代码,全部给你生成了相应的逻辑。
这次我们再看看效果:
cds watch
......
[cds] - serving AdminService { path: '/odata/v4/admin' }
[cds] - serving CatalogService { path: '/browse' }
[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - launched at 2024/3/10 20:03:40, version: 7.7.0, in: 991.416ms
[cds] - [ terminate with ^C ]
这里我们看到了/odata/v4/admin的rest路径了。使用浏览器访问,看到如下效果:
image-20240310200645298
访问:http://localhost:4004/odata/v4/admin/$metadata 时,可以使用alice这个缺省用户,不用输入密码即可得到admin权限。
4、配置访问使用数据库
默认方式使用的就是sqlite内存方式启动的数据库。我们一步步演化推进:
添加如下两个csv格式的数据文件:
db/data/sap.capire.bookshop-Books.csv
ID,title,author_ID,stock
201,Wuthering Heights,101,12
207,Jane Eyre,107,11
251,The Raven,150,333
252,Eleonora,150,555
271,Catweazle,170,22
db/data/sap.capire.bookshop-Authors.csv
ID,name
101,Emily Brontë
107,Charlotte Brontë
150,Edgar Allen Poe
170,Richard Carpenter
我们修改一下package.json, 指定一下数据库,添加内容如下:
"cds": { "requires": {
"db": {
"kind": "sqlite",
"credentials": { "url": "db.sqlite" }
}
}}
这个时候,我们只要运行cds deploy就可以将数据deploy到sqlite数据库当中。文件名为db.sqlite.
E:\work\3rdparty\postgres\cap\bookshop>cds deploy
> init from db\data\sap.capire.bookshop-Books.csv
> init from db\data\sap.capire.bookshop-Authors.csv
/> successfully deployed to db.sqlite
E:\work\3rdparty\postgres\cap\bookshop>sqlite3 db.sqlite
SQLite version 3.45.1 2024-01-30 16:01:20 (UTF-16 console I/O)
Enter ".help" for usage hints.
sqlite> .tables
AdminService_Authors
AdminService_Books
AdminService_Books_texts
AdminService_Currencies
AdminService_Currencies_texts
AdminService_Genres
AdminService_Genres_texts
CatalogService_Books
CatalogService_Books_texts
CatalogService_Currencies
CatalogService_Currencies_texts
CatalogService_Genres
............
sap_capire_bookshop_Authors
sap_capire_bookshop_Books
sap_capire_bookshop_Books_texts
sap_capire_bookshop_Genres
sap_capire_bookshop_Genres_texts
sap_common_Currencies
sap_common_Currencies_texts
sqlite> select * from sap_capire_bookshop_Books;
2024-03-10T13:37:12.624Z|anonymous|2024-03-10T13:37:12.624Z|anonymous|201|Wuthering Heights||101||12||
2024-03-10T13:37:12.624Z|anonymous|2024-03-10T13:37:12.624Z|anonymous|207|Jane Eyre||107||11||
2024-03-10T13:37:12.624Z|anonymous|2024-03-10T13:37:12.624Z|anonymous|251|The Raven||150||333||
2024-03-10T13:37:12.624Z|anonymous|2024-03-10T13:37:12.624Z|anonymous|252|Eleonora||150||555||
2024-03-10T13:37:12.624Z|anonymous|2024-03-10T13:37:12.624Z|anonymous|271|Catweazle||170||22||
你会发现它建了很多表。上边两个数据文件的数据也都添加进去了。
如果我们访问:http://localhost:4004/odata/v4/admin/Books 都能看到对应的数据了。
{
"@odata.context": "$metadata#Books",
"value": [{
"createdAt": "2024-03-10T13:44:54.391Z",
"createdBy": "anonymous",
"modifiedAt": "2024-03-10T13:44:54.391Z",
"modifiedBy": "anonymous",
"ID": 201,
"title": "Wuthering Heights",
"descr": null,
"author_ID": 101,
"genre_ID": null,
"stock": 12,
"price": null,
"currency_code": null
},
...
}
截至目前为止,我们看到的还是连接到sqlite数据库。大多数功能都是直接提供给你的。
我们看看,切换到PostgreSQL数据库是啥样的?该如何做?
对于Node.js而言,有如下依赖关系:
Database | Package | Remarks |
---|---|---|
SAP HANA Cloud | @sap/cds-hana | recommended for production |
SQLite | @cap-js/sqlite | recommended for development |
PostgreSQL | @cap-js/postgres | maintained by community + CAP team |
可以使用 npm add @cap-js/postgres
来添加对PostgreSQL的依赖。
我这里是提前准备好的一个VM上的PostgreSQL环境,CentOS7.9, PG14.x, 用户名mydb, 密码test123,它的数据库名也为mydb。提前准备好。
E:\work\3rdparty\postgres\postgres>psql -h 192.168.0.20 -U mydb -p 5555
用户 mydb 的口令:
psql (14.4, 服务器 14.11)
输入 "help" 来获取帮助信息.
mydb=> \d
没有找到任何关系.
mydb=>
如果要设成PostgreSQL的数据库连接环境,在工程根目录下边建一个文件.env。内容如下:
cds.requires.db.[pg].kind = postgres
cds.requires.db.[pg].credentials.host = 192.168.0.20
cds.requires.db.[pg].credentials.port = 5555
cds.requires.db.[pg].credentials.user = mydb
cds.requires.db.[pg].credentials.password = test123
cds.requires.db.[pg].credentials.database = mydb
这是一种配置方式,另一种方式是使用直接在package.json中修改添加相关的数据库类型及连接信息:
"cds": {
"requires": {
"db": {
"[sqlite]": { "kind": "sqlite", "impl": "@cap-js/sqlite", "credentials": { "url": "db.sqlite" } },
"[pg]": {"kind": "postgres", "impl": "@cap-js/postgres",
"credentials": {
"host": "192.168.0.20",
"port": 5555,
"user": "mydb",
"password": "test123",
"database": "mydb"
}
}
}
}
},
上边[pg]部分定义的就是与PostgreSQL相关的内容。
我们可以使用下边的命令诊断一下:(注 --profile 用于指定是哪种profile)
E:\work\3rdparty\postgres\cap\bookshop>cds env requires.db --profile pg
{
impl: '@cap-js/postgres',
dialect: 'postgres',
vcap: { label: 'postgresql-db' },
schema_evolution: 'auto',
kind: 'postgres',
credentials: {
host: '192.168.0.20',
port: 5555,
user: 'mydb',
password: 'test123',
database: 'mydb'
}
}
5、验证访问PostgreSQL
我们再次启动这个应用:
cds watch --profile pg
.......
[cds] - connect using bindings from: { registry: '~/.cds-services.json' }
[cds] - connect to db > postgres {
host: '192.168.0.200',
port: 5555,
user: 'mydb',
password: '...',
database: 'mydb'
}
[cds] - using auth strategy {
kind: 'mocked',
impl: 'node_modules\\@sap\\cds\\lib\\auth\\basic-auth'
}
[cds] - serving AdminService { path: '/odata/v4/admin' }
[cds] - serving CatalogService { path: '/browse' }
[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - launched at 2024/3/13 05:07:19, version: 7.7.1, in: 16.285s
[cds] - [ terminate with ^C ]
同时,数据库部分,需要对SCHEMA以及数据表中的数据进行初始化:
E:\work\3rdparty\postgres\cap\bookshop>cds deploy --profile pg
> init from db\data\sap.capire.bookshop-Books.csv
> init from db\data\sap.capire.bookshop-Authors.csv
/> successfully deployed to 192.168.0.20:5555
我们再次访问PG里头的数据库:mydb:
mydb=# \d
List of relations
Schema | Name | Type | Owner
--------+---------------------------------------+-------+-------
public | adminservice_authors | view | mydb
public | adminservice_books | view | mydb
public | adminservice_books_texts | view | mydb
public | adminservice_currencies | view | mydb
public | adminservice_currencies_texts | view | mydb
public | adminservice_genres | view | mydb
public | adminservice_genres_texts | view | mydb
public | catalogservice_books | view | mydb
public | catalogservice_books_texts | view | mydb
public | catalogservice_currencies | view | mydb
public | catalogservice_currencies_texts | view | mydb
public | catalogservice_genres | view | mydb
public | catalogservice_genres_texts | view | mydb
public | cds_model | table | mydb
public | localized_adminservice_authors | view | mydb
public | localized_adminservice_books | view | mydb
public | localized_adminservice_currencies | view | mydb
public | localized_adminservice_genres | view | mydb
public | localized_catalogservice_books | view | mydb
public | localized_catalogservice_currencies | view | mydb
public | localized_catalogservice_genres | view | mydb
public | localized_sap_capire_bookshop_authors | view | mydb
public | localized_sap_capire_bookshop_books | view | mydb
public | localized_sap_capire_bookshop_genres | view | mydb
public | localized_sap_common_currencies | view | mydb
public | sap_capire_bookshop_authors | table | mydb
public | sap_capire_bookshop_books | table | mydb
public | sap_capire_bookshop_books_texts | table | mydb
public | sap_capire_bookshop_genres | table | mydb
public | sap_capire_bookshop_genres_texts | table | mydb
public | sap_common_currencies | table | mydb
public | sap_common_currencies_texts | table | mydb
我们会发现创建了8张表以及近24个视图。自动创建的东西看来不少。
如果我们去访问网页:http://localhost:4004/browse/Books
确实能得到标准的OData数据(json格式输出):
{"@odata.context":"$metadata#Books","value":[{"ID":201,"descr":null,"price":null,"stock":12,"title":"Wuthering Heights","author":"Emily Brontë","genre_ID":null,"createdAt":"2024-03-12T21:10:32.074Z","modifiedAt":"2024-03-12T21:10:32.074Z","currency_code":null},{"ID":207,"descr":null,"price":null,"stock":11,"title":"Jane Eyre","author":"Charlotte Brontë","genre_ID":null,"createdAt":"2024-03-12T21:10:32.074Z","modifiedAt":"2024-03-12T21:10:32.074Z","currency_code":null},{"ID":251,"descr":null,"price":null,"stock":333,"title":"The Raven","author":"Edgar Allen Poe","genre_ID":null,"createdAt":"2024-03-12T21:10:32.074Z","modifiedAt":"2024-03-12T21:10:32.074Z","currency_code":null},{"ID":252,"descr":null,"price":null,"stock":555,"title":"Eleonora","author":"Edgar Allen Poe","genre_ID":null,"createdAt":"2024-03-12T21:10:32.074Z","modifiedAt":"2024-03-12T21:10:32.074Z","currency_code":null},{"ID":271,"descr":null,"price":null,"stock":22,"title":"Catweazle","author":"Richard Carpenter","genre_ID":null,"createdAt":"2024-03-12T21:10:32.074Z","modifiedAt":"2024-03-12T21:10:32.074Z","currency_code":null}]}
而这也是SAP产品规范中很重要的一环。它的大部分产品的输出结果就是按照OData规范,输出成Json格式的。
至此,这个简单的实验结束 。
总结 :
本文只是简要的介绍了一下CAP框架下,使用cds (core data service)生成框架,快速搭建一个Node.js应用,访问和管理存储到PostgreSQL中的企业数据。CAP在Node.js技术栈下,目前支持三种数据库(sqlite, PostgreSQL, HANA),目前,不支持SAP Sybase ASE。
只要环境搭建好,使用访问还是非常方便的。而且整个应用会全部使用Node.js。因为本文侧重于访问数据库这一块。如果你要想全面了解CAP开发框架,那么,关于安全、UI、云平台部署等方面,也需要进行相关的了解,可以仔细阅读参考部分的网址。里边各节都有相关介绍。
参考
https://cap.cloud.sap/docs/: SAP (The Cloud Application Programming Model)