PromiseベースのTable Storage SDK

  • June 15, 2021
  • d_yama
  • Azure

Node SDK v12

azure-sdk-for-jsのモジュールとしてAzure Table Storageを操作するための新しいSDKが公開されています。

このモジュールはNode.js、ブラウザの両方で動作します。

yarn add @azure/data-tables

テーブル上のエンティティの操作

操作クライアントの作成

テーブル上のエンティティを操作するにはTableClientオブジェクトが必要になります。接続文字列から作成する方法と、Storageアカウント名とキーから作成する方法があります。

const connectionString = ''
const tableName = ''

const tableClient = TableClient.fromConnectionString(connectionString, tableName)
const account = ''
const accountKey = ''
const tableName = ''

const credential = new AzureNamedKeyCredential(account, accountKey)
const tableClient = new TableClient(
  `https://${account}.table.core.windows.net`,
  tableName,
  credential
)

エンティティの取得

TableClientクラスのgetEntityメソッドから取得できる。getEntityのシグネチャは以下のようになっています。

getEntity<T extends object = Record<string, unknown>>(
	partitionKey: string,
	rowKey: string,
	options?: GetTableEntityOptions
): Promise<GetTableEntityResponse<TableEntityResult<T>>>;

TableStorageのレコードはPartitionKeyRowKeyが複合主キーとなるため、その2つを指定すれば一意なエンティティを取得できます。

型パラメータにPartitionKeyRowKeyを除いたエンティティの型情報を渡すことによって、クエリの結果が片付けされて扱うことができます。

export declare type TableEntityResult<T> = T & {
	etag: string;
    partitionKey?: string;
    rowKey?: string;
    timestamp?: string;
};

クエリ

なにかしらの条件にもとづいてエンティティを取得する場合はTableClientクラスのlistEntitiesメソッドを使用する。listEntitiesのシグネチャは以下のようになっています。

listEntities<T extends object = Record<string, unknown>>(
	options?: ListTableEntitiesOptions
): PagedAsyncIterableIterator<TableEntityResult<T>, TableEntityResult<T>[]>;

引数に渡すListTableEntitiesOptions型は以下のように定義されています。

export declare type ListTableEntitiesOptions = OperationOptions & {
    queryOptions?: TableEntityQueryOptions;
    disableTypeConversion?: boolean;
};

TableEntityQueryOptionsにてWHERE句に相当する条件とSLELECT句に相当する条件を指定します。

export declare interface TableEntityQueryOptions {
    filter?: string;
    select?: string[];
}

selectについては必要なカラム名だけを指定すればよいのですが、filterについてはOData filter形式で記述する必要があります。odata関数という[[Tagged Template Literals]]を利用した関数が用意されているため、そちらを使ってクエリを記述するのがオススメです。

const pk1 = 'pk-A'
const pk2 = 'pk-B'
const query = odata`PartitionKey eq ${pk1} or PartitionKey eq ${pk2}`
// queryは「PartitionKey eq 'pk-A' or PartitionKey eq 'pk-B'」

なお、もう一つのオプションであるdisableTypeConversionプロパティについてですが、こちらをtrueに設定すると、プロパティはJavaScript組み見込み型に型変換されず、文字列形式の値と型タイプのRecordの状態でオブジェクトが返されます。 例えば、あるカラムがInt32の値であった場合、{value: "123", type: Int32"}となります。このオプションはデフォルトではfalseとなっています。

戻り値はPagedAsyncIterableIterator型であり、これはAsyncIterableなオブジェクトである。したがってfor awaitを使うことによって、取得できたエンティティを列挙することができます。

export interface PagedAsyncIterableIterator<T, PageT = T[], PageSettingsT = PageSettings> {
  next(): Promise<IteratorResult<T, T>>;
  [Symbol.asyncIterator](): PagedAsyncIterableIterator<T, PageT, PageSettingsT>;
  byPage: (settings?: PageSettingsT) => AsyncIterableIterator<PageT>;
}

また、このAsyncIterableなオブジェクトは遅延評価がなされるため、実際に値の取り出しが評価されるまでTableStorageへのHTTPリクエストは送信されません。 TableStorageはエンティティのリストを取得する場合、1回のHTTPリクエストで最大1000件までしか取得できない制約がある。1000件を超える場合はページングされるため、レスポンスヘッダを確認して再度HTTPリクエストを送信して後続のエンティティを取得する必要がある。このSDKでは1000件を超えるエンティティを取得しようとしたとき、AsyncIteratorから1001件目のエンティティを取り出そうとしたタイミングでHTTPリクエストが送信されるよう設計されています。 旧来のazure-storageの場合は、Continuation Tokenを使って自分で後続ページを取得する必要があったが、本SDKではlistEntitiesを使うことによってページングについては透過的に扱うことができます。

また、前述のように1件ずつIteratorResultを得る以外にもPagedAsyncIterableIteratorオブジェクトのbyPageメソッドを使えば、ページ単位でのIteratorResultを取得することもできます。

挿入

TableClientクラスのcreateEntityメソッドを使うことによってエンティティの挿入ができます。

createEntity<T extends object>(
	entity: TableEntity<T>,
	options?: OperationOptions
): Promise<CreateTableEntityResponse>;

挿入するエンティティはTableEntity<T>型となっており、その定義は以下のようになっています。

export declare type TableEntity<T extends object = Record<string, unknown>> = T & {
    partitionKey: string;
    rowKey: string;
};

旧来のazure-storageでは、ヘルパーメソッドを使ってエンティティのプロパティをそれぞれ専用の型に変換する必要がありましたが、本SDKでは組み込み型をそのまま使うことができます。数値型だけ注意する必要があって、Int32として扱う場合はnumber型として、Int64として扱う場合はBigInt型として数値を設定する必要があります。

なお、createEntityメソッドでは、エンティティに設定したpartitionKeyとrowKeyの組が既にテーブルに存在する場合は例外がスローされます。

更新

TableClientクラスのupdateEntityメソッドを使うことによってエンティティの更新ができます。

updateEntity<T extends object>(
	entity: TableEntity<T>,
	mode?: UpdateMode,
	options?: UpdateTableEntityOptions
): Promise<UpdateEntityResponse>;

使い方はcreateEntityメソッドとほぼ同様ですが、特徴的なのは第二引数のUpdateModeです。TableStorageはエンティティの更新方法としてMergeReplaceの二つを用意しています。Mergeの場合は引数に与えられたエンティティ内に存在するプロパティだけが更新されます。引数に与えられたエンティティ内に存在しないプロパティについては、更新前の値そのままとなります。一方Replaceはその名の通り置き換えとなります。引数に与えられたエンティティ内に存在しないプロパティについては、置き換え後はそのカラムはnullとして記録されます。

updateEntityの類似メソッドとしてupsertEntityが存在するが、こちらは該当するエンティティが存在しない場合は挿入操作を行うメソッドとなります。(updateEntityメソッドは該当するエンティティが存在しない場合は例外をスローする)

削除

TableClientクラスのdeleteEntityメソッドを使います。主キーであるPartitionKeyとRowKeyを指定して使用します。該当するエンティティが存在しない場合は例外をスローします。

deleteEntity(
	partitionKey: string,
	rowKey: string,
	options?: DeleteTableEntityOptions
): Promise<DeleteTableEntityResponse>;

エンティティグループトランザクション

Table Storageには、同一パーティションにあるエンティティに対してアトミックなトランザクションを実現するエンティティグループトランザクションという機能があります。同一パーティションに存在するエンティティに限る、1回のトランザクションで操作できるエンティティの数は100までといった制限はありますが、APIのコール数1回で処理が完了するため何度もAPIを叩いて複数件のエンティティを処理するよりもはるかに早いです。またアトミック性も保証されているため、要件にマッチするなら積極的に使っていきたい機能です。azure-storageでも利用できた機能ですが、本SDKでもTableClientクラスのsubmitTransactionメソッド経由で使うことができます。

submitTransaction(actions: TransactionAction[]): Promise<TableTransactionResponse>;

引数に渡すTransactionAction[]ですが、どんなオブジェクトになるかは例を見るのが早いです。

const actions: TransactionAction[] = [
   ["create", {partitionKey: "p1", rowKey: "1", data: "test1"}],
   ["delete", {partitionKey: "p1", rowKey: "2"}],
   ["update", {partitionKey: "p1", rowKey: "3", data: "newTest"}, "Merge"]

アクションとして含ませることができるのは、createdeleteupdateupsertの4つです。updateupsertについてはMergeReplaceかも指定することができます。

Profile
d_yama
元Microsoft MVP for Windows Development(2018-2020)
Sub-category : Windows Mixed Reality
Search