ディレクトリサービスという言葉を耳にすると、多くのエンジニアは「複雑で古めかしいプロトコル」という印象を抱くかもしれません。

しかし、2011年9月、Node.jsの世界に登場したldapjsは、その常識を覆そうとしました。

IBMのTivoli Directory ServerなどでLDAPの深淵に触れてきたMark Cavage氏によって開発されたこのライブラリは、LDAPをモダンな非同期プログラミングの文脈で再定義したのです。

本記事では、2011年当時の背景を振り返りつつ、なぜNode.jsがLDAPという歴史あるプロトコルと相性が良かったのか、その設計思想を探ります。

LDAPの起源と複雑な歴史

LDAP (Lightweight Directory Access Protocol) の歴史を紐解くと、それは電話帳のような情報を高速に検索したいという電気通信事業者のニーズに行き着きます。

そのベースとなったのは X.500 という規格ですが、これはOSI参照モデル上で動作する、あまりにも巨大で複雑な「何でもあり」の仕様でした。

その後、インターネットの普及に伴い、IPネットワーク経由で X.500 ディレクトリへアクセスするための「軽量なゲートウェイ」として誕生したのがLDAPです。

1990年代初頭、ミシガン大学の大学院生たちによって作成されたスタンドアロンの実装が、現在の多くのLDAP製品の「祖」となりました。

しかし、この歴史がLDAPに「負の遺産」をもたらしました。

多くの商用・オープンソースのLDAP実装は、20年以上前のコードをベースに、継ぎ足しで機能拡張が行われてきたのです。

そのため、プロトコル自体が複雑であることに加え、実装が古くバギーであるという評判が定着してしまいました。

しかし、エンジニアたちの間では、LDAPは「元祖NoSQL」としてのポテンシャルを秘めていることも指摘されていました。

LDAPの真価:階層構造と高度な検索

LDAPの最大の特徴は、データを階層構造 (ツリー構造) で管理する点にあります。

これは、組織図やマシンのグループ管理、SaaSの購読管理など、多くの現実世界のユースケースに自然にフィットします。

例えば、ある企業のエンジニアのデータは、報告ラインに基づいたツリーの下に配置されます。

LDAPは、このツリーの任意の場所を起点として、強力なフィルタリング構文を用いた検索が可能です。

LDAPの検索フィルタ例

LDAPでは、以下のような論理演算子を用いた柔軟なクエリが記述できます。

  • 全ての「ソフトウェアエンジニア」を探す: (title=Software Engineer)
  • シアトル在住の「ソフトウェアエンジニア」を探す: (&(title=Software Engineer)(l=Seattle))

このように、ブール演算やワイルドカードを駆使したリッチな検索性能は、単なるキーバリューストア以上の価値を提供します。

また、ディレクトリサービスとして確立された標準規格であるため、多くのエンタープライズ製品との親和性が高いというメリットもあります。

なぜ今、Node.jsでLDAPなのか

Mark Cavage氏は、既存のLDAP実装が抱えていた大きな課題として、大量の同時接続と非同期操作への対応不足を挙げていました。

かつての実装は接続ごとにプロセスを生成するモデルが多く、モダンなWebスケールの要求には耐えられませんでした。

そこで白羽の矢が立ったのがNode.jsです。

ldapjs がNode.jsを採用した理由は、主に以下の4つの課題を解決するためでした。

  1. 接続指向と非同期処理: Node.jsはイベント駆動型の非同期サーバープラットフォームであり、大量の持続的なコネクションを効率的に管理することに長けています。
  2. 多様なユースケースへの対応: 従来の「サーバーとストレージエンジンが一体化」したモデルではなく、Express のように柔軟にハンドラを記述できるフレームワークが必要でした。
  3. レプリケーションの困難さ: CAP定理が広く理解される前に設計された古いLDAPのレプリケーションモデルは非常に複雑でした。Node.jsであれば、バックエンドに RiakCouchDB といったモダンな分散データベースを採用できます。
  4. プロトコルの取捨選択: プロトコルの全機能を実装するのではなく、実際のユースケースの80%を解決する重要な20%の機能に注力することで、軽量さを維持しました。

ldapjsの設計思想と開発体験

ldapjs は、JavaScript開発者にとって馴染み深い「Sinatraスタイル」や「Expressスタイル」のAPIを提供します。

開発者は ASN.1 というバイナリプロトコルの詳細や、難解なRFCを完全に理解していなくても、純粋なJavaScriptオブジェクトとしてLDAPの操作を扱うことができます。

概念LDAPの用語一般的なDBの概念
データの単位エントリ (Entry)レコード / ドキュメント
データの属性属性 (Attribute)カラム / フィールド
一意の識別子DN (Distinguished Name)プライマリキー / パス

この抽象化により、ldapjs はリリース直後から大きな反響を呼びました。

Thunderbirdなどのメールソフトと連携するアドレスブックの実装や、CouchDBとの統合など、コミュニティによって多くの応用例が生み出されたのです。

実装例:ldapjsによるシンプルなディレクトリサーバー

ここでは、ldapjs を使用して、検索リクエストに対して固定のユーザー情報を返すシンプルなサーバーの実装例を紹介します。

JavaScript
const ldap = require('ldapjs');

// サーバーの作成
const server = ldap.createServer();

// 検索リクエストに対するハンドラの設定
server.search('o=my-company', (req, res, next) => {
  const obj = {
    dn: req.dn.toString(),
    attributes: {
      objectclass: ['organization', 'top'],
      o: 'my-company'
    }
  };

  // フィルタに一致するかチェック(簡易的な実装)
  if (req.filter.matches(obj.attributes)) {
    res.send(obj);
  }

  res.end();
  return next();
});

// 1389ポートで待機
server.listen(1389, '127.0.0.1', () => {
  console.log('LDAP server listening at: ' + server.url);
});

このコードを実行すると、指定したDN(o=my-company)配下での検索に対して応答を返すようになります。

既存の重厚なLDAP製品を立ち上げることなく、数行のJavaScriptコードでLDAPインターフェースを構築できる点は、ldapjs の真骨頂と言えるでしょう。

まとめ

2011年に登場した ldapjs は、古くからあるLDAPというプロトコルを、Node.jsというモダンな武器で「再発見」させたプロジェクトでした。

複雑なプロトコルの詳細を隠蔽し、非同期I/Oの恩恵を最大限に活かした設計は、今なおディレクトリサービスが必要とされる場面において重要な選択肢となっています。

LDAPは決して「押し入れの奥に眠る古い骨董品」ではありません。

適切なツールと設計思想を用いることで、現代のWebアプリケーションにおいても非常に強力なデータ管理基盤となり得るのです。

もし、あなたがユーザー管理や階層データの扱いに悩んでいるなら、一度 ldapjs を通じてLDAPの世界に触れてみてはいかがでしょうか。