[OCI]Oracle Functions - Node.JSによるATPへの接続 (2019/08/02)

Oracle Functions - Node.JSによるATPへの接続 (2019/08/02)

https://blogs.oracle.com/developers/oracle-functions-connecting-to-atp-with-nodejs
投稿者:Todd Sharp

前回の記事では、Javaベースのサーバーレスファンクションを使ってATPに接続する方法を見ました。
それは素晴らしい結果でしたが、Node.JSベースのファンクションを使って同じタスクを達成する方法をお見せしたいと思いました。
この記事では、データを保存する方法に少しひねりを加えて、ちょうどそれを行います。

開始するには、Fn CLIを使って新しいFnアプリケーションを作成します。今回はcreate appコールで直接設定値を渡します。

$ fn create app --annotation oracle.com/oci/subnetIds='["ocid1.subnet.oc1.phx..."]' --config DB_PASSWORD='[DB PASS]' --config DB_USER='[DB USER]]' --config CONNECT_STRING='[CONNECT STRING FROM tnsnames.ora]' fn-atp-node-json


注意: 機密情報を含む設定変数は、常に暗号化する必要があります。その方法については、OCI でのKey Managementの使い方のガイドを参照してください。

さて、最初のファンクションを作りますが、前回のようにinit-imageを使うのではなく、ノードランタイムを使って、自分たちで変更してみましょう。

fn init --runtime node fn-atp-node-json-insert


生成されたfunc.yamlファイルを開き、runtimeの値をdockerに変更します。
下図のようにformat、memory、timeout、idle_timeoutの値を追加します。

schema_version: 20180708
name: fn-atp-node-json-insert
version: 0.0.91
runtime: docker
format: http-stream
memory: 256
timeout: 120
idle_timeout: 1800


ファンクションのルートにDockerfileを作成します。
先ほどと同じようにウォレットが必要になります(ダウンロードしてファンクションのルートに置いてください)。
しかし、今回はNodeが適切な接続を行うためにOracle Instant Clientも必要になります。
そして、その依存関係を取得してDockerfileにインストールします。Dockerfile全体は以下のようになります。

FROM oraclelinux:7-slim

RUN yum -y install oracle-release-el7 oracle-nodejs-release-el7 &&
\ yum-config-manager --disable ol7_developer_EPEL &&
\ yum -y install oracle-instantclient19.3-basiclite nodejs &&
\ rm -rf /var/cache/yum &&
\ groupadd --gid 1000 --system fn &&
\ useradd --uid 1000 --system --gid fn fn

COPY wallet/* /usr/lib/oracle/19.3/client64/lib/network/admin/

WORKDIR /function
ADD package.json package-lock.json func.js func.yaml /function/
RUN npm install
ENTRYPOINT ["node", "func.js"]


このDockerfileでは、ベースイメージとしてOracle Linuxを使用し、
インスタントクライアントの依存関係を手動でインストールするのではなく、Oracle yumリポジトリを経由してインストールしています。
また、ウォレットファイルをコピーして、接続に使用できるようにしています。最後に、node func.jsをentrypointに設定します。

次に、ファンクションルートからnpm install oracledbとnpm install dateformatを実行します。
これにより、Oracle Nodeパッケージと日付書式設定ライブラリがインストールされるので、それらをクエリに使用することができます。
このデモで読み書きに使用するテーブルを作成する必要があります。今回はATPのJSONカラムサポートを利用するので、テーブルはとてもシンプルなものになります。

CREATE TABLE JSON_DEMO
(
ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY INCREMENT BY 1 MAXVALUE 9999999999999999999999999999 MINVALUE 1 CACHE 20 NOT NULL
, CAPTURED_AT TIMESTAMP(6)
, DATA CLOB
, CONSTRAINT JSON_DEMO_PK PRIMARY KEY ( ID )
)


では、ファンクションを見てみましょう。func.jsを開き、以下のように入力します。

const fdk = require('@fnproject/fdk');
const oracledb = require('oracledb');
oracledb.outFormat = oracledb.OBJECT;
oracledb.fetchAsString = [oracledb.CLOB];

let pool;

fdk.handle( async function(input){
if( !pool ) {
pool = await oracledb.createPool({
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
connectString: process.env.CONNECT_STRING,
});
}
const connection = await pool.getConnection();
const records = await connection.execute("select * from json_demo");
const result = records.rows.map((row) => {
return {
id: row.ID,
capturedAt: row.CAPTURED_AT,
data: JSON.parse(row.DATA),
}
});
await connection.close();
return result;
}, {});


ここでは、必要なライブラリ(FDK for NodeとOracle DBパッケージ)を取り込み、ファンクションが呼び出されたときに呼び出される単一のメソッドを作成します。
このファンクションの中で、DBへの接続を確立し、すべてのレコードをクエリしてオブジェクトの配列として返します。
デプロイして呼び出してテストすることができますが、明らかにこの時点では結果は空の配列になるので、挿入を処理するファンクションも作成しましょう。
上記のプロジェクト全体をコピーして貼り付け、func.yaml内のファンクション名を変更して挿入ファンクションを作成しますが、代わりにfunc.jsを変更して挿入を処理するようにします。

const fdk = require('@fnproject/fdk');
const oracledb = require('oracledb');
const dateFormat = require('dateformat');

oracledb.outFormat = oracledb.OBJECT;
oracledb.fetchAsString = [oracledb.CLOB];

let pool;

fdk.handle( async function(input){
if( !pool ) {
pool = await oracledb.createPool({
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
connectString: process.env.CONNECT_STRING,
});
}
const connection = await pool.getConnection();
const insert = await connection.execute("insert into json_demo (data, captured_at) values (:data, to_timestamp(:capturedAt, 'yyyy-mm-dd HH24:mi:ss'))",
{
data: JSON.stringify(input),
capturedAt: dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss')
},
{ autoCommit: true }
);
await connection.close();
return {insert: insert, complete: true};
}, {});


このファンクションをデプロイし、永続化したいデータを含む JSON オブジェクトを渡して呼び出します。JSONオブジェクトは、有効なJSONであれば何でも構いません。

echo '{"isDemo": true, "key": 5, "isAwesome": true, "isEasy": true}' | fn invoke fn-atp-node-json fn-atp-node-json-insert


この時点で読み込みファンクションを呼び出して、前の挿入の結果を見ることができます。

trsharp@MacBook-Pro-2 ~/Projects/fn/fn-atp-node-json/throwaway$ fn invoke fn-atp-node-json fn-atp-node-json-read

[{"id":66,"capturedAt":"2019-06-11T21:33:04.000Z","data":{"isDemo":true,"key":5,"isAwesome":true,"isEasy":true}}]


上記の例の代替として、ファンクションはSimple Oracle Data Access (SODA)を使用して、
他のNoSQL実装に馴染みのあるスタイルでデータを処理することもできます。SODA は定義済みのテーブルやスキーマを必要とせず、
従来の SQL の代わりにドキュメント コレクションと簡略化された API を使用して動作します(API のみを使用することに制限されるわけではありませんが、
必要に応じて従来の SQL を使用してコレクションに問い合わせを行うことはもちろん可能です)。
必要であれば、もちろん従来の SQL を使ってコレクションを照会することができます。SODA を使うファンクションの例を以下に示します。

const fdk = require('@fnproject/fdk');
const oracledb = require('oracledb');

oracledb.outFormat = oracledb.OBJECT;
oracledb.fetchAsString = [oracledb.CLOB];
oracledb.autoCommit = true;

fdk.handle( async function(input){

let connection;
const result = [];

try {
connection = await oracledb.getConnection({
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
connectString: process.env.CONNECT_STRING,
});

const soda = connection.getSodaDatabase();
const collectionName = 'soda_collection';
const collection = await soda.createCollection(collectionName);

const document = input;
await collection.insertOne(document);

const documents = await collection.find().getDocuments();

documents.forEach(function(element) {
result.push( {
id: element.key,
createdOn: element.createdOn,
lastModified: element.lastModified,
document: element.getContent(),
} );
});
}
catch(err) {
console.error(err);
}
finally {
if (connection) {
try {
await connection.close();
} catch(err) {
console.error(err);
}
}
}

return result;
}, {});


以下のようにSODAファンクションを呼び出すとします。

$ echo '{"id": 1, "temp": 58}' | fn invoke fn-atp-node-json fn-atp-node-json-soda
$ echo '{"id": 2, "temp": 60}' | fn invoke fn-atp-node-json fn-atp-node-json-soda


soda_collectionと呼ばれるテーブルに2つのレコードができます。
このテーブルはSODA APIを使用して "例題によるクエリ "の方法でクエリを行うことができます(詳細はSODA Nodeのドキュメントを参照してください)が、
次のようにSQLでクエリを行うこともできます(BLOBとして保存されているので、JSONドキュメントをvarchar2にキャストする必要があることに注意してください)。

select id, utl_raw.cast_to_varchar2(json_document) as doc
from soda_collection sc
where sc.json_document.temp < 60;

/*
ID DOC
----- -----
E05734C093684FBEBFDEC3404099F959 {"id":1,"temp":58}
*/


もう一つのオプションは、ドキュメントを従来のテーブル形式に変換して、ビューで使用したり、json_tableを使用してリレーショナルテーブルに結合したりすることです。

select id, jt.temp, jt.docId
from soda_collection sc,
json_table(json_document, '$' COLUMNS (temp number PATH '$.temp', docId varchar2(50) PATH '$.id')) as jt

/*
ID TEMP DOCID
----- ----- -----
E05734C093684FBEBFDEC3404099F959 58 1
B675F6056E374F9ABF76C6E35A280ED5 60 2
*/


次回の記事では、OCI Java SDKを使用してOracle Functionsを呼び出す方法を見ていきます。

Photo by Paul Esch-Laurent on Unsplash

コメント

このブログの人気の投稿

Oracle Database 19cサポート・タイムラインの重要な更新 (2024/11/20)

Oracle APEXのInteractive Gridで、Oracle Formsと比較して、重複行の検証を制御/通過させる方法 (2022/07/21)

Oracle APEX 24.1の一般提供の発表 (2024/06/17)