IO インターフェイス
将来の移植性/拡張性を見すえるなら、
MLサーバの設計においても
Unix における Vnode/VFS interface (vnode(9)参照)
のような構造をあらかじめ導入しておくべきでしょう。
例: NetBSD の vnode 構造体。
struct vnode {
...
voff_t v_size; /* size of file */
int v_numoutput; /* num pending writes */
long v_writecount; /* ref count of writers */
...
int (**v_op)(void *); /* vnode ops vector */
...
void *v_data; /* private data for fs */
};
v_op の先に、
vop_open()
vop_read()
vop_getattr()
などが定義されます。
つまり
struct vnode の **v_op (vnode operation vector) にあたるものが、
IO に使われるクラスの各メソッドです。
&fml8; では
IO::Adapter クラスという抽象化されたインターフェイスを使います。
このクラスは、
たいていはメールアドレスの一覧という行指向のリスト型データに対する
IO を抽象化したものです。
IO::Adapter の基本形
&fml8; の設計上の手本となるクラスは
IO::Adapter
といえるでしょう。
実装もすでに完成形であり、
primitive なメソッドは何か?
などについても十分考えぬかれています。
IO::Adapter
クラスは
KEY => VALUE
もしくは
KEY => [ VALUE, VALUE2, VALUE3 ]
のいづれかの型のデータ構造を抽象化していると考えられます。
つまり、これは RDBMS の基礎理論同様の表型データ構造です。
KEY1 VALUE1-1 "" ""
KEY2 VALUE2-1 VALUE2-2 VALUE2-3
KEY3 VALUE3-1 VALUE3-2 VALUE3-3
KEY4 VALUE4-1 VALUE4-2 VALUE4-3
ユーザリストを管理する上で必要最低限の基本メソッド群は、
IO::Adapter の
open()
close()
および、そのオブジェクトへの IO である
add(KEY, ARGV) (ARGV はクラス依存のデータ渡しのためにある引数)
delete(KEY)
find(KEY or REGEXP)
get_next_key()
があれば十分のようです。
少なくとも、ユーザ管理は、これらのメソッドだけで十分書けます。
メソッド
前述のように IO::Adapter の基本メソッドは次の通りです。
open()
close()
add(KEY, ARGV) (ARGV はクラス依存のデータ渡しのためにある引数)
delete(KEY)
find(KEY or REGEXP)
get_next_key()
もちろん、オブジェクトを生成するのは new() ですので、
これら以外に new() だけは必要です:)
必要なら適宜、ディストラクタも定義して下さい。
new()
オブジェクトを生成するのは new() です。
たとえば IO::Adapter であれば、
$obj = new IO::Adapter マップ;
もしくは
$obj = new IO::Adapter マップ, パラメータ(ハッシュリファレンス);
などとオブジェクトタイプを引数(マップ)で指定するため、
マップに応じた初期化を行ないます。
呼び出しの基本形は引数の多い方に合わせるので、次のようになります。
my $config = $curproc->config();
my $obj = new IO::Adapter $map, $config;
HOOK で利用する際にも、つねに、この形で書いておくべきです。
open()
ファイルであれば open(2)、
RDBMS であれば SQL サーバへの接続を確立する、
といった具合です。
close()
すなおに open() の逆の動作をします。
add(KEY, ARGV)
KEY (プライマリキー)もしくは
KEY および KEY に付随するデータをオブジェクトに書き込みます。
なお、ARGV はクラス依存のデータ渡しのためにある引数で、
この引数が使われないこともあります。
Unix とは異なり、
オブジェクトの構造に一定の型があります。
型とは RDBMS のようなテーブルの形です。
また、プライマリキーとなるのは通常メールアドレスです。
この前提が多くの場面で正しいため、
メールアドレスをプライマリキーにしたテーブル型が基本的なデータ構造といえます。
delete(KEY)
KEY および KEY に付随するデータ構造を削除します。
find(KEY) / find(REGEXP)
オブジェクト内からプライマリキーに該当するデータを探します。
探す対象を正規表現で指定できるように作る方が便利です。
正規表現検索が使えると、ユーザ検索などで重宝します。
返り値は STR か ARRAY_REF (KEY に対する [ VALUE, VALUE2, VALUE3 ])です。
get_next_key()
プライマリキーの一覧を取り出したい場合が多々あります。
そこで
while ($obj->get_next_key()) { ... }
のような表現を可能とするために、このメソッドが実装されています。
これはハッシュの FIRST_KEY() と NEXT_KEY() にあたるといえます。
しかしながら、
我々の場合は open() などのメソッドが別途用意されているため、
FIRST_KEY() と NEXT_KEY() のように2つに分ける必要はありません。
XXX すなおに、key() もしくは get_key() でも良い気がする?
議論
返り値を ARRAY_REF 型で欲しい場合?
どういうデータ構造が欲しいのでしょうか?
PRIMARY KEY の一覧でしょうか?
プライマリキーを全部取り出したい場合は?
定石は全部読んでみること、つまり get_next_key() を呼びまくるコードです。
専用のメソッドがあった方が便利でしょうけど、
そのようなコードは、まず書かれないので、
専用メソッドがなくても問題はないと考えます。
もし、
あるとしたら get_primary_keys() みたいな名前のメソッド?があり、
ARRAY_REF で返すでしょうか?
でも、
find('*', { all => 1 }) を実行すれば、
すべての KEY の値がARRAY_REF で返りますので専用メソッドは不要でしょう。
(KEY1 KEY2)
返り値が HASH_REF の場合?
これこそ、どういうデータ構造が欲しい場合でしょうか?
返り値が HASH_REF とは、次のような構造が返り値になる場合です。
返り値 = {
変数1 => 値1、
変数2 => 値2、
}
メールアドレスに属性をつける場合、こういった型での返り値が欲しいでしょうか。
たとえば、まとめ送り(&fml4; の例)。
メールアドレス => {
送り間隔 => 3時間、
ファイル圧縮 => しない
フォーマット => mime/multipart
};
注意:
&fml8; では、まとめ送りの複雑なオプションはサポートしていませんので、
この操作は不要です。
結局、実装する必要がないというのが現状だと考えます。