初心者でもできる、Alibaba Cloud TablestoreとPython or Node.jsで始めるRESTful APIサービス構築方法

2023年2月22日掲載

キービジュアル

Alibaba Cloud Tablestoreは、大量の構造化データ及び半構造化データを保存したり処理する、フルマネージド型NoSQLデータベースサービスです。Tablestoreを使用すると、オンラインデータをミリ秒以内にクエリして取得し、保存されたデータに対して多次元分析を実行することができます。

本記事では、Alibaba Cloud上のTablestoreとPython or Node.jsを使って、簡単なRESTful APIサービスを構築する方法をご紹介します。

目次

1.全体像

Tablestoreを使った、RESTful APIサービス作成方法としての流れ及び全体像は次の図の通りになります。

2.TableStoreインスタンスの作成とテーブルの準備

この構築作業に入る前に、Alibaba Cloudのアカウントを持っていること、およびTablestoreサービスを有効にします。Tablestoreはいくつかの方法でインスタンスとテーブルを準備することができますが、今回はコンソールから起動し、その他のデータに対する操作に焦点を当て、様々な方法で準備します。チャットサービス(IM Service)を想定し、このテーブルを使用してメッセージを保存します。

Tablestoreのコンソールページにアクセスし、次の画像通りにポップアップウィンドウで新規インスタンスを作成します。現在、日本リージョンでは、キャパシティインスタンスのみ対応しています。

 
  1. Create Instance」ボタンをクリックし、ポップアップウィンドウを開きます。
  2. 作成フォームに必要な値を入力します。
  3. Save」ボタンをクリックし、ターゲットインスタンスを作成します。

完了すると、成功のメッセージが表示され、作成されたインスタンスがリストに表示されます。

次はテーブル作成に移ります。選択したインスタンスの詳細ページに移動し、「Create Table」でテスト用テーブルを作成します。

 
  1. Create Table」ボタンをクリックし、ポップアップウィンドウを表示させます。
  2. 作成フォームに必要事項を記入し、特に主キーを記入します。テーブル内のレコードを識別するための主キーカラムです。主キーカラムの名前を入力し、データ型を選択します。「Add Primary Key Column」をクリックして、主キーカラムを追加します。
  3. OK」ボタンをクリックし、対象のテーブルを作成します。

STRINGINTEGERBINARYのデータ型は、主キーカラムでサポートされています。今回、idにINTEGERカラムを、groupにSTRINGカラムを追加します。このカラムは異なる方法でデータを分離するために使用しています。

必要に応じて、以下のように詳細設定を有効にすることができます。ここではデフォルトの設定を使用します。

すると、対象のテーブルが作成され、リストに表示されます。

テーブルの詳細を確認します。

3.コンソールからのデータに対する基本操作

データ挿入の作業に移ります。テーブルの詳細ページの 「Query Data」 タブに 「Insert」 ボタンがあります。

  1. テーブルの詳細ページで「Query Data」 タブに移動します。

  2. Insert」ボタンをクリックすると、ポップアップウィンドウが表示されます。

  3. 主キーの値を入力します。

  4. Add Column」 ボタンをクリックし、新しいカラムの行を表示します。「date」という名前の新しいカラムを追加します。

  5. 新しいカラムの値を入力します。

  6. OK」ボタンをクリックすると、対象のレコードが挿入されます。

操作が完了すると、結果テーブルに新しいレコードが取得されます。

最初はコンソールから同じように新しいデータを追加します。

1行または特定の範囲内のデータを照会することができます。

次はテストとして、1行のデータをクエリしてみます。コンソール画面でGUIによる操作からです。

  1. Search」 ボタンをクリックして、ポップアップ検索ウィンドウを開きます。

  2. モードを「Get Row」に設定します。

  3. 主キーに特定の値を設定します。

  4. OK」ボタンをクリックして、検索処理を実行します。

以下、検索結果を確認します。データが取得出来ていることが確認できます。

もし、関連するデータがない場合は、空欄のリストを表示します。

今度は、テーブルから特定の範囲を持つデータを取得するようクエリしてみます。

  1. search」ボタンをクリックして、ポップアップ検索ウィンドウを開きます。

  2. モードを「Range Search」に設定します。

  3. Start Primary Key Column」と「End Primary Key Column」を指定します。

  4. OK」ボタンをクリックして、検索を実行します。

そうすると、関連する検索結果が正常に表示されます。

今度はデータ更新を試します。特定の1行をピックアップして、以下のように更新します。

  1. 選択したデータのチェックボックスにチェックを入れると、「Update」ボタンが有効になります。

  2. Update」ボタンをクリックすると、更新処理のためのポップアップウィンドウが表示されます。

  3. Add Column」をクリックして、更新対象のデータに対して新しい列を追加します。

  4. 新しいカラムのキーと値を入力します。

  5. OK」ボタンをクリックして、更新処理を実行します。

そうすると、結果一覧に更新されたデータが表示されます。

コンソールからの更新操作は、単一データのみ対応していますのでご注意ください。チェックボックスで複数のデータを選択した場合、「Update」ボタンは無効となります。

今度はデータを削除してみます。削除操作は、更新操作と似ていて、コンソールから複数のデータをサポートします。ある特定のデータを削除することを例として説明します。

  1. 選択したデータのチェックボックスにチェックを入れると、「Delete」ボタンが有効になります。

  2. Delete」ボタンをクリックすると、確認のポップアップウィンドウが表示されます。

  3. OK」ボタンをクリックすると、削除が実行されます。

削除したデータを再度検索すると「No data available」という通知が表示されます。

4.Tablestore CLIによるデータへの基本操作

コンソールからTablestoreを一通り操作したので、今度はTablestore CLIを使って操作をします。この操作にあたり、Tablestore CLI のインストールと設定が必要になります。

Tablestore CLI はAlibaba CloudからCLIツールとして配布されており、複数のOSにも対応しています。

<参考>
https://www.alibabacloud.com/help/en/tablestore/latest/download-the-tablestore-cli

ここではWindowsを例にとって説明します。ダウンロードしたTablestore CLIパッケージを解凍します。Tablestore CLIのルートディレクトリに移動し、ts.exeファイルをダブルクリックするか、コマンドラインでtsを実行し、Tablestore CLIを起動します。

関連する設定項目を設定するには、`config`コマンドを実行します。コマンドは endpointinstanceidkey の4つのパラメータを受け取ります。最初の2つはテーブルストアのインスタンス、最後の2つはアクセスキーの情報です。


既存のインスタンスがない場合は、id と key だけを設定し、`create_instance` コマンドでインスタンスを作成することもできます。

create_instance -d <DESCRIPTION> -n <INSTANCE_NAME> -r <REGION>

今回は事前に準備したインスタンスとテーブルを使用するので、一度に4つのパラメータを設定します。

もし、インスタンスのエンドポイントがわからない場合は、インスタンスの詳細ページやヘルプドキュメントで確認することができます。

Tablestore CLIでインスタンスを記述します。設定が正しければ、インスタンス情報を含む応答が得られます。

describe_instance -r <REGION> -n <INSTANCE_NAME>

テーブルを指定するために `use` コマンドを実行します。テーブル名がわからない場合は、`list`コマンドを実行して確認します。

以下の手順で特定のテーブルに対する操作を開始する前に、特にターゲット・インスタンスとテーブルの構成が準備できていることを確認します。そうでない場合、以下のようなエラーメッセージが表示されます。

上述通り、TablestoreのCLI導入は無事完了したので、今度はTablestore CLIで簡単な操作を試してみます。

クエリデータ

特定の主キーを持つデータ行を `get` コマンドで読み込みます。このコマンドは、後で操作結果を確認するために頻繁に使用します。

データ挿入

`put` コマンドは、データを一つずつ挿入するために使用します。パラメータ pkattr が必要です。pk の値はプライマリキーの値を含む配列で、attr は属性カラムとしての JSON 配列にしなければなりません。

put --pk '[1,"cli"]' --attr '[{"c":"date", "v":"20220825"}, {"c":"os", "v":"Windows"}]'

行データのJSONファイルを準備し、それを元にデータを挿入することも可能です。

{
    "PK": {
        "Values": [
            2,
            "cli"
        ]
    },
    "Attr": {
        "Values": [
            {
                "c": "date",
                "v": "20220825"
            },
            {
                "c": "os",
                "v": "Windows10"
            }
        ]
    }
}

データ更新

レコードの更新は、挿入操作とほとんど同じです。コマンド `update` で、パラメータ pk と attr に正しい値を設定します。JSONデータファイルもこのコマンドで受け付けることができます。

データ削除

テーブルからデータ行を削除するために、`delete`コマンドで正しい主キー値を設定します。

次はTablestore CLIでデータを一括処理するというバッチオペレーション操作を試してみます。

クエリデータ

データテーブルをスキャンして、データテーブルの全データまたは指定した行数のデータを取得することができます。

パラメータ o を使用すると、スキャンデータをローカルのJSONファイルにエクスポートすることができます。既存の属性カラムをすべてエクスポートしたくない場合は、パラメータ c を試してみてください。

生成されたJSONファイルを確認すると、パラメータに従って属性カラムがエクスポートされていることが分かります。

データを挿入

一方、元データを上記と同じフォーマットに基づいたJSONファイルで準備していた場合。それを `import` コマンドで一気に挿入することができます。

5.Node.js SDKでRESTful APIサービスを構築

Tablestore CLIによる操作は概ね把握できたと思うので、今度はNode.jsを使って、TablestoreによるRESTful APIを作ります。この構築にはTablestoreのNode.js SDKが必要なので、Node.js SDKを事前に導入する必要があります。Tablestoreは、Node.js SDKを含む、複数のプログラミング言語用のSDKを提供しています。このSDKを使用するには、npmコマンド `npm install tablestore` でプロジェクトにインストールするだけです。

<参考>
Tablestore Node.js SDK Overview
https://www.alibabacloud.com/help/en/tablestore/latest/node-js-sdk-preface

参考情報として、Alibaba Cloudによる、Node.js SDKの紹介ドキュメントサンプルコードを準備しています。

Node.js SDKの主な使い方は、Tablestoreクライアントインスタンスの初期化と、各関数の主キー、属性カラム、条件などのパラメータオブジェクトを準備することです。詳細は、以下の手順で確認できます。

<参考>
Tablestore Node.js SDK概要
https://www.alibabacloud.com/help/en/tablestore/latest/node-js-sdk

Tablestore Node.js SDKサンプルコード
https://github.com/aliyun/aliyun-tablestore-nodejs-sdk/tree/master/samples

Node.js SDKの導入が完了したら、​Node.js SDKでRESTful API構築に入ります。Node.js SDKの使い方を説明するために、SDKとexpressパッケージをベースにRESTful APIを構築します。

データを読みやすくするために、APIサーバを起動する前にテーブルデータをクリアしておきます。

テーブルデータをクリアしたら、RESTful APIを構築するために、プロジェクトフォルダの準備に移ります。既存のテーブルが、あるチャットグループのメッセージを保存するために使用されていると仮定します。主キーidgroupは、メッセージidとチャットグループ名でマッピングされます。

node_ali_tablestoreという名前のプロジェクトフォルダを作成し、依存パッケージであるtablestoreexpressをインストールします。

そして、そのフォルダに以下のファイルを追加します。

  • ts.js → テーブルストアとの接続とデータ操作

  • server.js → expressサーバとルートの設定

  • message.js → シンプルなコントローラ

最後に、プロジェクトフォルダを以下のようにします。

.
├── node_modules
├── message.js
├── package.json
├── server.js
└── ts.js

プロジェクトフォルダの準備ができたら、コードを準備します。ts.jsは、SDKに基づいた機能を用意してくれます。クライアントを初期化し、クライアントを通じて関連する機能を呼び出すことになります。

"use strict";


const TableStore = require('tablestore');
var Long = TableStore.Long;
let client;
let tableName = process.env.tableName;


/**
 * Generate and return TableStore client with specific configuration
 * @returns TableStore client
 */
function getClient() {
    if (!client) {
        client = new TableStore.Client({
            accessKeyId: process.env.accessKeyId,
            secretAccessKey: process.env.secretAccessKey,
            endpoint: process.env.endpoint,
            instancename: process.env.instancename
        });
    }
    return client;
}


/**
 * Scan the TableStore data based on default range
 * @returns
 */
async function getRange() {
    client = getClient();
    var params = {
        tableName: tableName,
        direction: TableStore.Direction.FORWARD,
        inclusiveStartPrimaryKey: [{ "id": TableStore.INF_MIN }, { "group": TableStore.INF_MIN }],
        exclusiveEndPrimaryKey: [{ "id": TableStore.INF_MAX }, { "group": TableStore.INF_MAX }],
        limit: 10
    };
    return new Promise((resolve, reject) => {
        client.getRange(params, function (err, data) {
            if (err) {
                console.log('error:', err);
                return;
            }
            resolve(data);
        });
    });
}


/**
 * Scan the TableStore data based on default range
 * @param {*} id
 * @param {*} group
 * @param {*} sender
 */
async function getRow(id, group, sender = null) {
    client = getClient();
    var params = {
        tableName: tableName,
        primaryKey: [{ 'id': Long.fromNumber(id) }, { 'group': group }],
    };
    if (null != sender) {
        // Support CompositeCondition
        // var condition = new TableStore.CompositeCondition(TableStore.LogicalOperator.AND);
        // condition.addSubCondition(new TableStore.SingleColumnCondition('sender', sender, TableStore.ComparatorType.EQUAL));
        // condition.addSubCondition(new TableStore.SingleColumnCondition('sender', sender, TableStore.ComparatorType.EQUAL));
        var condition = new TableStore.SingleColumnCondition('sender', sender, TableStore.ComparatorType.EQUAL);
        params.columnFilter = condition;
    }
    return new Promise((resolve, reject) => {
        client.getRow(params, function (err, data) {
            if (err) {
                console.log('error:', err);
                if (err.code == 400) {
                    resolve({ "row": [] });
                }
                return;
            }
            resolve(data);
        });
    });
}


/**
 * Put row in the target TableStore table
 * @param {*} id
 * @param {*} group
 * @param {*} sender
 * @param {*} message
 */
async function putRow(id, group, sender, message) {
    client = getClient();
    var params = {
        tableName: tableName,
        condition: new TableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null),
        primaryKey: [{ 'id': Long.fromNumber(id) }, { 'group': group }],
        attributeColumns: [
            { 'sender': sender },
            { 'message': message }
        ],
        returnContent: { returnType: TableStore.ReturnType.Primarykey }
    };
    return new Promise((resolve, reject) => {
        client.putRow(params, function (err, data) {
            if (err) {
                console.log('error:', err);
                return;
            }
            resolve(data);
        });
    });
}


/**
 * Put row in the target TableStore table
 * @param {*} id
 * @param {*} group
 */
async function deleteRow(id, group) {
    client = getClient();
    var params = {
        tableName: tableName,
        condition: new TableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null),
        primaryKey: [{ 'id': Long.fromNumber(id) }, { 'group': group }],
    };
    return new Promise((resolve, reject) => {
        client.deleteRow(params, function (err, data) {
            if (err) {
                console.log('error:', err);
                return;
            }
            resolve(data);
        });
    });
}


module.exports = { putRow, getRange, getRow, deleteRow }

message.jsはコントローラとして動作し、結果を収集し、server.jsが読みやすい形式にパースします。

"use strict";


const ts = require('./ts');


async function listMessages() {
    var result = await ts.getRange();
    if (result.rows.length == 0) {
        return { "rows": 0, "results": "No data" };
    } else {
        return { "rows": result.rows.length, "results": "Get top 10 messages successfully.", "data": result.rows };
    }
}


async function getMessageByGroup(id, group) {
    var result = await ts.getRow(id, group);
    if (result.row.length == 0) {
        return { "rows": 0, "results": "No data" };
    } else {
        return { "rows": result.row.length, "results": "Get target message successfully.", "data": result.row };
    }
}


async function getMessageByGroupAndSender(id, group, sender) {
    var result = await ts.getRow(id, group, sender);
    if (result.row.length == 0) {
        return { "rows": 0, "results": "No data" };
    } else {
        return { "rows": result.row.length, "results": "Get target message successfully.", "data": result.row };
    }
}


async function saveMessage(id, group, sender, message) {
    var result = await ts.putRow(id, group, sender, message);
    if (result.consumed.capacityUnit.write == 0) {
        return { "rows": 0, "results": "Failed to save message." };
    } else {
        return { "rows": result.row.length, "results": "Save target message successfully.", "data": result.row };
    }
}


async function deleteMessage(id, group) {
    var result = await ts.deleteRow(id, group);
    return { "rows": 1, "results": "Delete target message successfully.", "data": result };
}


module.exports = { listMessages, getMessageByGroup, getMessageByGroupAndSender, saveMessage, deleteMessage }

server.js は express server のエントリポイントであり、対応するルートを定義しています。例として、RESTful APIを準備します。

  • ルートリクエストのウェルカムメッセージ

  • トップ10メッセージのクエリ

  • メッセージIDとチャットグループ名を指定してのメッセージ参照

  • メッセージID、チャットグループ名、送信者名を指定してのメッセージ参照

  • メッセージの保存

  • メッセージの削除

"use strict";


const express = require('express');
const app = express();
const message = require('./message');
app.use(express.json());


app.get('/', (req, res) => {
    res.end('Welcome to Tablestore RESTful API demo!');
});


app.get('/api/msg', async (req, res) => {
    var results = await message.listMessages();
    res.json(results).end();
});


app.get('/api/msg/:id/:group', async (req, res) => {
    var id = parseInt(req.params.id);
    var group = req.params.group;


    var results = await message.getMessageByGroup(id, group);
    res.json(results).end();
});


app.get('/api/msg/:id/:group/:sender', async (req, res) => {
    var id = parseInt(req.params.id);
    var group = req.params.group;
    var sender = req.params.sender;
    var results = await message.getMessageByGroupAndSender(id, group, sender);
    res.json(results).end();
});


app.post('/api/msg', async (req, res) => {
    var id = req.body.id;
    var group = req.body.group;
    var sender = req.body.sender;
    var msg = req.body.message;


    var results = await message.saveMessage(id, group, sender, msg);
    res.json(results).end();
});


app.delete('/api/msg/:id/:group', async (req, res) => {
    var id = parseInt(req.params.id);
    var group = req.params.group;
    var results = await message.deleteMessage(id, group);
    res.json(results).end();
});


const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));



ここまで、コードら実行ファイルの準備ができたら、サーバーを起動し、RESTful APIをテストします。`npm run start`コマンドを実行し、expressサーバーを起動します。

`curl`コマンドでRESTful APIをテストします。期待する想定として、メッセージが無事取得できればOKです。

RESTful APIでトップ10メッセージを取得しますが、テーブルをクリアしているため、`No data`というメッセージが返されます。

curl -X GET http://localhost:5000/api/msg

RESTful APIとSDKのputRow()関数を使用して、以下のメッセージを保存します。

curl -H "Content-Type: application/json" -X POST -d "{\"id\":\"20220830101\",\"group\":\"restful\",\"sender\":\"bob\",\"message\":\"This is testing message.\"}" http://localhost:5000/api/msg
img-alibaba-tablestore-restfulapi-blog-20230222-46.png

メッセージデータの保存に成功したら、top10メッセージクエリ、id-groupクエリ、id-group-senderクエリで対象データを取得します。

curl -X GET http://localhost:5000/api/msg
curl -X GET http://localhost:5000/api/msg/20220830101/restful
curl -X GET http://localhost:5000/api/msg/20220830101/restful/bob

もしスクリプトがクエリ条件と一致する行データがない場合は、`No data`というメッセージも表示されます。

curl -X GET http://localhost:5000/api/msg/20220830101/restful/bob121

以下のように、RESTful APIで行データを削除します。

curl -X DELETE http://localhost:5000/api/msg/20220830101/restful

6. Python SDKでRESTful APIサービスを構築

Node.js によるRESTful APIは構築できたので、今度はPythonとflaskパッケージを使ってRESTful APIを作ってみます。この構築にはNode.js SDKと同様にTablestoreのPython SDKが必要なので、Tablestore Python SDKを事前に導入する必要があります。Tablestoreは、Python SDKを含む、複数のプログラミング言語用のSDKを提供しています。

python_ali_tablestoreという仮想環境によるプロジェクトフォルダを準備し、以下のコマンドで仮想環境を起動します。

py -m venv .venv
.venv\scripts\activate

pip installコマンドでflask-restfulとtablestoreのパッケージをインストールします。

フォルダに以下のファイルを追加します。

  • ts.py → テーブルストアに接続し、データを操作します。

  • server.py → flaskのサーバとルーティングの設定

最後に、プロジェクトフォルダを以下のようにします。

.
├── .venv
├── server.py
└── ts.py

プロジェクトフォルダ配下の構成ら準備ができたら、コードを準備します。ts.py は、Tablestore サービスとの通信を処理する OTSManager クラスを定義しています。

from tablestore import *
import os


class OTSManager:


    def __init__(self):
        self.endpoint = os.getenv('OTS_ENDPOINT')
        self.access_key = os.getenv('OTS_ID')
        self.access_secret = os.getenv('OTS_SECRET')
        self.instance = os.getenv('OTS_INSTANCE')
        self.table = os.getenv('OTS_TABLE')
        self.client = OTSClient(self.endpoint, self.access_key, self.access_secret, self.instance)
   
    def get_row(self, id, group, sender=None):
        try:
            primary_key = [('id',id), ('group',group)]
            columns_to_get = [] # 取得する列のリスト、あるいは行全体を取得したい場合は空のリストを指定します
            if sender:
                condition = SingleColumnCondition('sender', sender, ComparatorType.EQUAL)
            else:
                condition = None
            consumed, return_row, next_token = self.client.get_row(self.table, primary_key, columns_to_get, condition)
            if return_row:
                return {"status": "Success", "readCU": consumed.read, "data": {"primaryKey": return_row.primary_key, "attrColumn": return_row.attribute_columns}}
            else:
                return {"status": "Failed", "errMessage": "No target data row."}
        except OTSClientError as e:
            return {"status": "Failed", "HTTPStatus": e.get_http_status(), "errMessage": e.get_error_message()}
        except OTSServiceError as e:
            return {"status": "Failed", "HTTPStatus": e.get_http_status(), "errMessage": e.get_error_message(), "errCode": e.get_error_code()}
           
    def put_row(self, id, group, sender, message):
        try:
            primary_key = [('id',id), ('group',group)]
            attribute_columns = [('sender',sender), ('message',message)]
            row = Row(primary_key, attribute_columns)
            condition = Condition(RowExistenceExpectation.EXPECT_NOT_EXIST) # Expect not exist:この行が存在しない場合のみ、テーブルに入れる。
            consumed, return_row = self.client.put_row(self.table, row, condition)
            return {"status": "Success", "writeCU": consumed.write}
        except OTSClientError as e:
            return {"status": "Failed", "HTTPStatus": e.get_http_status(), "errMessage": e.get_error_message()}
        except OTSServiceError as e:
            return {"status": "Failed", "HTTPStatus": e.get_http_status(), "errMessage": e.get_error_message(), "errCode": e.get_error_code()}


    def delete_row(self, id, group):
        try:
            primary_key = [('id',id), ('group',group)]
            row = Row(primary_key)
            condition = Condition(RowExistenceExpectation.IGNORE)
            consumed, return_row = self.client.delete_row(self.table, row, condition)
            return {"status": "Success", "writeCU": consumed.write}
        except OTSClientError as e:
            return {"status": "Failed", "HTTPStatus": e.get_http_status(), "errMessage": e.get_error_message()}
        except OTSServiceError as e:
            return {"status": "Failed", "HTTPStatus": e.get_http_status(), "errMessage": e.get_error_message(), "errCode": e.get_error_code()}

server.pyは、flaskのサーバーとルートをRESTful APIとして設定します。

from flask import Flask, jsonify
from flask_restful import Api, Resource, reqparse
from ts import OTSManager
 
class Message(Resource):
    def __init__(self):
        self.manager = OTSManager()
        super().__init__()


    def get(self, id, group):
        return jsonify(self.manager.get_row(int(id), group))
 
    def post(self, id, group):
        args = reqparse.RequestParser() \
            .add_argument('sender', type=str, location='json', required=True, help="Empty sender") \
            .add_argument('message', type=str, location='json', required=True, help="Empty message") \
            .parse_args()
 
        return jsonify(self.manager.put_row(int(id), group, args['sender'], args['message']))
 
 
    def delete(self, id, group):
        return jsonify(self.manager.delete_row(int(id), group))
 
 
app = Flask(__name__)
api = Api(app, default_mediatype="application/json")
api.add_resource(Message, '/api/msg/<id>/<group>')
app.run(host='0.0.0.0', port=5001, use_reloader=True)

ここまで、コードら実行ファイルの準備ができたら、サーバーを起動し、RESTful APIをテストします。`python server.py`コマンドを実行し、flask サーバを起動します。

curl コマンドで API 経由でテーブルに新しいメッセージを挿入できるかテストします。

curl -H "Content-Type: application/json" -X POST -d "{\"sender\": \"bob\", \"message\":\"Testing message from Python\"}"  http://localhost:5001/api/msg/20220905001/python

追加されたメッセージをテーブルから確認します。

curl -X GET http://localhost:5001/api/msg/20220905001/python

テーブルからメッセージを削除します。

curl -X DELETE http://localhost:5001/api/msg/20220905001/python

グループ情報が間違っているリターンメッセージを確認します。

curl -X GET http://localhost:5001/api/msg/20220905001/python11

7. 補足事項: flaskサーバーの起動時にprotocパッケージでエラーが発生する場合の対処方法

protobufのバージョンによっては、現在のインストールパッケージの*pb2.pyファイルと互換性がないため、手動で*pb2.pyファイルを生成して解決を試みることができます。これは次のように行います。

  1. 現在のバージョンのprotocを使用して、protoファイルに対応するコードを順番に生成します。

    protoc --python_out=. tablestore/protobuf/search.proto
    protoc --python_out=. tablestore/protobuf/table_store.proto
    protoc --python_out=. tablestore/protobuf/table_store_filter.proto

  2. 生成された3つのファイルのサフィックスをpb2.pyに変更し、インストールディレクトリのtablestore/protobuf/ディレクトリにコピーして、元の*pb2.pyファイルを置き換えます。

上記の解決方法の詳細は、Alibabaのヘルプドキュメント(現在は中国語のみ)で確認することができます。関連するエラーメッセージは以下の通りです。

……
TypeError: Descriptors cannot not be created directly.
If this call came from a _pb2.py file, your generated code is out of date and must be regenerated with protoc >= 3.19.0.
If you cannot immediately regenerate your protos, some other possible workarounds are:
 1. Downgrade the protobuf package to 3.20.x or lower.
 2. Set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python (but this will use pure-Python parsing and will be much slower).

protocによる解決に慣れていない場合は、エラーメッセージからの示唆をもとに解決することができます。ここでは例として、protobuf パッケージを 3.19.0 にダウングレードしています。

8. 補足事項: APIによるエラーの修正方法

レスポンスからOTSAuthFailedエラーが発生した場合、テーブルストアクライアントの初期化処理に起因する可能性があります。アクセス情報、エンドポイント、インスタンスの値をご確認ください。

レスポンスから Internal Server Error が発生した場合は、stderr 情報を確認し、エラーメッセージを元にコードの誤りを修正します。

これが完了すると、サーバーは更新されたコードを検知して再読み込みし、flaskサーバーを自動的に再起動します。

9. 補足事項: SQLモードのTablestore CLIでOTSInternalServerErrorを修正する方法

Tablestoreでは、データテーブルに対してマッピングテーブルを作成し、その情報をSQLモードで問い合わせることができます。

Tablestore CLIでコマンド `sql` を実行すると、簡単にSQLモードに移行することができます。

しかし、現在、日本Regionではサポートされていません。日本インスタンスでSQLモードを使用すると、OTSInternalServerErrorが表示されます。

関連するエラーメッセージは以下の通りです。

tablestore@SQL> show tables;
OTSInternalServerError Internal server error. 0005e70b-af4d-1323-8e6c-ca0b01cb1bee
tablestore@SQL>

SQLモードはコンソールからも利用可能です。このモードをサポートしているインスタンスでは、テーブル管理ページに「Query by Executing SQL Statement」タブが表示されます。詳細な手順はこちらで確認できますので、参考にします。

<参考>
Manage the Wide Column model in the Tablestore console
https://www.alibabacloud.com/help/en/tablestore/latest/manage-the-wide-column-model-in-the-tablestore-console#section-oes-zpq-fri

 

10.さいごに

Alibaba Cloud TablestoreとNode.js もしくは Pythonで始めるRESTful APIサービス構築方法をご紹介しました。この方法は初心者向け記事としても、チャットサービス(IM Service)向けにも適用できますし、10分もせずにRESTful APIサービスを完成することができます。

Tablestore は NoSQLデータベースとして、Webやモバイルなどのアプリケーションから簡単にアクセスできるよう、Node.jsやPythonなど幅広い言語でのクライアントを提供しています。そのため、どんな言語でも通信プロトコルを意識せずにRESTful APIサービスとして展開することができます。RESTful APIサービスを構築してみたい方は是非参考にしてみるといいでしょう。

関連サービス

Alibaba Cloud

Alibaba Cloudは中国国内でのクラウド利用はもちろん、日本-中国間のネットワークの不安定さの解消、中国サイバーセキュリティ法への対策など、中国進出に際する課題を解消できるパブリッククラウドサービスです。

MSPサービス

MSP(Managed Service Provider)サービスは、お客さまのパブリッククラウドの導入から運用までをトータルでご提供するマネージドサービスです。

おすすめの記事

条件に該当するページがございません