LoginSignup
4
0

More than 1 year has passed since last update.

PharoからOracle/MySQLに接続するnode.jsのapiサーバーを構築する

Last updated at Posted at 2021-05-13

はじめに

いままでSqueak4.4とPharo7でWebアプリサーバーを構築していましたが、DBXTalkのインストールが失敗するためOracleデータベースへの接続にはperlのDBI/DBDライブラリを利用していました。
ただし、perlを呼ぶOSProcessはWindowsでは動かないためLinux版Pharoでしか開発/テストが出来ず、WindowsPCで開発してLinuxサーバーで運用というスタイルが取れないのがこれまでの悩みでした。

今回Pharo8へのマイグレーションを行う際に、データベースへのアクセスをnode.jsに代行させるようにしたらすべて解決出来たので共有します。
ついでにMySQLの接続もサポートしました。

概念図

PharoでSeaside3をWebサーバーとして起動します。
node.jsはapiサーバーとして機能します(今回はWindows10PCに立てましたがWindowsでもLinuxでも好きな環境に立てて下さい)
Metacello new.png

ソースコード

node.js

server.jsはサーバーとしてポート3000をリッスンして起動します。
運用環境ではpm2で自動起動するといいと思います。

server.js
let restify = require("restify");
let server = restify.createServer();
server.use(restify.plugins.bodyParser());
const oracledb = require("oracledb");
const mysql = require("mysql");
const util = require("util");

let pool;

const init = async () => {
    try {
        await oracledb.createPool({
            user: "admin",
            password: "pass",
            connectString: "oracle.example.com/example",
            poolMax: 4,
            poolMin: 0
        });
        console.log("Connection pool started");

        pool = mysql.createPool({
            host: "localhost",
            user: "admin",
            password: "pass",
            timezone: "jst",
            multipleStatements: true
        });
    } catch (err) {
        console.error("init() error: " + err.message);
    }
}

const execOracle = async (sql, binds, options) => {
    let connection;
    try {
        connection = await oracledb.getConnection();
        const result = await connection.execute(sql, binds, options);
        return result;
    } catch (err) {
        console.error(err);
    } finally {
        if (connection) {
            try {
                await connection.close();
            } catch (err) {
                console.error(err);
            }
        }
    }
}

const execMysql = async (request) => {
    pool.query = util.promisify(pool.query);
    try {
        const result = await pool.query(request);
        return result;
    } catch (err) {
        throw new Error(err);
    }
}

const closePoolAndExit = async () => {
    console.log("\nTerminating");
    try {
        await oracledb.getPool().close(10);
        console.log("Pool closed");
        pool.end();
        process.exit(0);
    } catch (err) {
        console.error(err.message);
        process.exit(1);
    }
}

(async () => {
    try {
        server.post("/oracle", async (req, res, next) => {
            const request = JSON.parse(req.body.request);
            const sql = request.sql;
            const binds = request.binds;
            const options = request.options;
            const result = await execOracle(sql, binds, options);
            res.json(result);
            return next();
        });

        server.post("/mysql", async (req, res, next) => {
            const request = JSON.parse(req.body.request);
            const result = await execMysql8(request);
            res.json(result);
            return next();
        });

        process.once("SIGTERM", closePoolAndExit).once("SIGINT", closePoolAndExit);

        await init();
        server.listen(process.env.PORT || 3000, function() {
            console.log("Server started.");
        });
    } catch (err) {
        console.error(err);
    }
})();

Smalltalkライブラリ

Pharoに以下のライブラリをロードします

SeasideとNeoJSONのインストール
Metacello new
  baseline:'Seaside3';
  repository: 'github://SeasideSt/Seaside:master/repository';
  load.

Gofer it
  smalltalkhubUser: 'SvenVanCaekenberghe' project: 'Neo';
  configurationOf: 'NeoJSON';
  loadStable.

Pharo

テストで使用したコードは以下の通りです。
実際に使う際にはラッパークラスを作って運用することになると思います。
apiサーバーからはJSONでデータが帰ってくるので、NeoJSONReaderでSmalltalkオブジェクトに変換することで簡易的にORマッパーも兼ねられました。

Oracle接続テスト
NeoJSONReader fromString: (
   ZnClient new
      url: 'http://localhost:3000/oracle';
      formAt: 'request' put: (NeoJSONWriter toString:(
         Dictionary new
            at: 'sql' put: 'SELECT * FROM hoge WHERE id=:1';
            at: 'options' put: (Dictionary new at:'outFormat' put: 4002; yourself);
            at: 'binds' put: (Array with: 1);
            yourself
      ));
      post
).

MySQLでもコネクションプールを使うので接続先のデータベースは都度選択する想定です。

MySQL接続テスト
NeoJSONReader fromString: (
   ZnClient new
      url: 'http://localhost:3000/mysql';
      formAt: 'request' put: (NeoJSONWriter toString:(
         Dictionary new
            at: 'sql' put: 'use fuga; SELECT * FROM hoge WHERE id=?';
            at: 'timeout' put: 40000;
            at: 'values' put: (Array with: 1);
            yourself
      ));
      post
).

まとめ

これで、WindowsPCで開発してLinuxサーバーで運用するスタイルが確立出来ました。

余談

PharoのLibCクラスのresultOfCommand:を使えばOSProcess同様にWindowsのコマンドが実行できるのがわかったので、最初はnode.jsのプログラムをPharoからLibCで呼び出すようにしましたが、Oracleデータの取得に1300ミリ秒(内データベース接続に900ミリ秒)もかかってしまうため実用になりませんでした。
プログラムを毎回起動するとデータベース接続も毎回行われるため、コネクションプールを使う=サーバー化することで、データの取得が20ミリ秒に短縮出来ました。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0