ASPでは、当初からデータアクセスの方法として、ActiveXオブジェクトのADOを使うことが推奨されてきました。ところが、このADOについては、RDBMS大手のオラクル純正のオブジェクト「oo4o」との熾烈な論争が繰り広げられた経過があり、また、多くの技術者が指摘しているとおりADOの未成熟さの問題もあいまって、データアクセスにADOを用いることは、まだ十分には定番とはなっていません(本稿執筆時点での最新ADOバージョンは、MDAC2.6 RTM)。
けれども、汎用的なODBC接続による幅広いデータソースの活用や、柔軟なその技法で、初心者から安心して利用できるオブジェクトであることのメリットは、システム開発における設計の統括を容易にし、全体としての管理コストを大幅に下げてくれることは重要なポイントであると思います。
理想的な三階層システムのビジネスロジックは、クライアントとの純粋なソケット通信を行うアプリとして構成されることが望ましいのですが、HTTPなどの既存の手順を流用したり独自のプロトコルを定めて開発しなければなりません。もちろん、独自にActiveXオブジェクトを作成して手軽に用いることはできますが、ネットワークやクライアントのプラットフォームを限定してしまうことになります。
一方、Webアプリとして開発するADOを使ったASPアプリは、純粋なソケット通信アプリを簡易なスクリプト言語で開発可能で、かつ、クライアントのプラットフォームを選びませんので、その意味ではひとつのビジネスロジック層であると言えます(三階層モデルWebアプリ)。
ただ、一般的にはクライアントアプリとして(標準入出力のみを行うコンソールに過ぎない)ブラウザソフトを用いることになりますので、印刷機能などについては、かなりの制限(サーバー側ですべての印刷ジョブを集中処理しなければならないなど)を受けることになりますし、セッションの追跡によるユーザーの管理やRDBMSとの接続管理では、相当の配慮が必要となります。
クライアントサイドで用いるブラウザの詳細な制御(IMEの制御ひとつをとってみても面倒ですが)については、マルチプラットフォームを犠牲にするならば、JScriptやVBScriptを使って実現することはできますが、本稿では、ASPを対象としていますので、そのことについての検討は行いません。
ただし、標準的なHTMLを用いた画面制御はASPが吐き出す標準出力で行うものなので、例えば、以下で検討するように、ADO経由で得たデータで生成するドロップダウンリストなどにおける、データアクセスの効率化とデータの整合性保持などは、まさにADOとASPの緊密な連携方法を探る格好のサンプルとなります。
Visual BasicやVCを使ってフォームのクラスでデータの表現をあれこれと考える部分、それはまさにクライアントアプリ設計の領域ですが、ASPアプリは、スクリプト部分はビジネスロジック層アプリであるとともに、HTMLの送出部分は、実はクライアントアプリでもある、という二面性があるということになります。
まず最初に、次の「global.asa」ファイルの例を見てください。
<!--METADATA TYPE="TypeLib" FILE="C:\Program Files\Common Files\System\ado\msado21.tlb" --> <OBJECT RUNAT=Server SCOPE=Session ID=objCon PROGID="ADODB.Connection"></OBJECT> <SCRIPT RUNAT=Server LANGUAGE=VBScript> Sub Session_OnStart() Session("ConnectionString") = "DSN=ora_svr;UID=scott;PWD=tiger" End Sub </SCRIPT>
ここでは、ASPのスクリプトリファレンスに従い、まずADOのタイプライブラリに関し、「adovbs.inc」ファイルのSSI(サーバーサイドインクルード)ではなく、「TypeLibrary宣言」によってメモリ内に取り込んでいます。
次に、「<OBJECT>宣言」を用いて、データアクセスに用いるADOのConnectionオブジェクトをSessionを単位に「objCon」という名前で作成します。また、同時に、SessionオブジェクトのOnStartイベントを利用し、このアプリで使うデータベース接続文字列を、コレクションに追加します。
上記のコードは、ASPの基本的な機能を用いてADOオブジェクトを配置していく最初のステップです。こうした「global.asa」でのオブジェクト配置を用いなければ、実際の各ページでは次のようなコーディングになるはずです。
Set objCon = CreateObject("ADODB.Connection") objCon.Open "DSN=ora_svr;UID=scott;PWD=tiger" '具体的なデータベースとのやりとり objCon.Close Set objCon = Nothing
一方、「global.asa」を用いたコーディングでは、次のようになります。
objCon.Open Session("ConnectionString") '具体的なデータベースとのやりとり objCon.Close
大きな違いは、ADOのConnectionオブジェクトに関しては、いちいちCreateObjectをする手間を省くとともに、いったんクローズした後も、ADOの接続プール機能を使うようにオブジェクトを残します。実際は、ひとつのアプリが継続して用いられている間は、セッションが継続しているわけですから、Nothingを使って、いちいちオブジェクトをメモリから消去する必要はありません。ASPは、セッションが切れた時に、「objCon」オブジェクトを自動的に破棄してくれます。
なお、接続文字列をSessionコレクションに保存していますが、この程度の情報の一環共有は、例えば別のファイルに定数ばかりを書いてインクルードすることも、あるいはアプリ独自の「.ini」ファイルに書いて利用することもできますので、必須の方法ではありません。
また、次の「global.asa」ファイルの例を見てください。
<!--METADATA TYPE="TypeLib" FILE="C:\Program Files\Common Files\System\ado\msado21.tlb" --> <OBJECT RUNAT=Server SCOPE=Session ID=objCon PROGID="ADODB.Connection"></OBJECT> <OBJECT RUNAT=Server SCOPE=Session ID=objDicSection PROGID="Scripting.Dictionary"></OBJECT> <SCRIPT RUNAT=Server LANGUAGE=VBScript> Sub Session_OnStart() Session("ConnectionString") = "DSN=ora_svr;UID=scott;PWD=tiger" objCon.Open Session("ConnectionString") Set objCmd = CreateObject("ADODB.Command") objCmd.ActiveConnection = objCon Set objRS = CreateObject("ADODB.Recordset") objRS.CursorLocation = adUseClient strSQL = "SELECT * FROM SectionMaster ORDER BY SectionCode" objCmd.CommandText = strSQL objCmd.CommandType = adCmdText objCmd.CommandTimeout = 30 objRS.Open objCmd Do While Not objRS.EOF objDicSection.Add CLng(objRS("SectionCode")), CStr(objRS("SectionName")) objRS.MoveNext Loop objRS.Close Set objRS = Nothing Set objCmd = Nothing objCon.Close End Sub
ここでは、先ほどの例に加えて、最初にセッションが確立した際に、Dictionaryオブジェクトを使って、ドロップダウンリストで用いるデータをいったん保存し、同一セッション内で繰り返し使うようにします。この例では、所属セクション情報は、ほとんど更新される可能性がなく、また、アプリ内で繰り返しフォーム上のオブジェクトとしてドロップダウンリストを配置するケースです。
実際のページ内で上記のDictionaryオブジェクトからデータを取得する場合には、次のようなコーディングになります(<SELECT>タグ内での<OPTION>タグ出力例)。
arrSectionCode = objDicSection.Keys arrSectionName = objDicSection.Items For i = 0 To UBound(arrSectionCode) EchoSrc = EchoSrc & OPTIONLine(arrSectionCode(i), False, arrSectionName(i)) Next '上記のOPTIONLine関数の定義は次のとおり strQuote = Chr(34) strCR = vbCrLf Function OPTIONLine(strValue, blnSelected, strView) OPTIONLine = "<OPTION" If strValue <> "" Then OPTIONLine = OPTIONLine & " value=" OPTIONLine = OPTIONLine & strQuote & strValue & strQuote End If If blnSelected = True Then OPTIONLine = OPTIONLine & " selected" End If OPTIONLine = OPTIONLine & ">" If strView <> "" Then OPTIONLine = OPTIONLine & strView End If OPTIONLine = OPTIONLine & strCR End Function
少し見づらいコードとなていますが、最初にKeysメソッドとItemsメソッドを使って、Dictionaryオブジェクト内のデータをそれぞれ一次元配列として取得します。そして、その内容を順番に<OPTION>タグ内に記述していきます。
こうすることで、アプリ内で用いるデータのうち、定数扱い的な情報がデータベースに保存されている場合で、それらを同一セッション内で繰り返し使う場合は、データアクセス等のネットワークトラフィックのオーバーヘッドを減らすことができます。
ADOを使ったASPアプリに関わらず、クライアント・サーバー・モデルでは、サーバーのリソースについて十分な検討が必要です。通常の、二階層モデルの場合は、データベースサーバーの負荷を中心に考えますが、三階層モデルの場合は、ビジネスロジック層でのリソース検討が新しい課題となります。
例えば、上記のデータベース接続(ADOのConnectionオブジェクトの配置)はSession単位としましたが、スコープをApplication単位とした場合は、Webサーバーとデータベースサーバーとの接続は、より効率化されることになります。
けれども、Dictionaryオブジェクトの例で示した定数的情報の維持については、不変を前提とはできないので、整合性を考慮すると、やはりSession単位での読み込みが必要となります。その場合は、セッションが確立されるたびに、配列データであるDictionaryオブジェクトがメモリ内に個々読み込まれるわけですから、データのサイズ等を十分に考慮して、必要に応じて最大セッション数について制限を加える必要が生じます。
イントラアプリの場合のように、クライアント数が予測できる場合はさほど気にならないかもしれませんが、インターネットアプリの場合は、特に注意が必要です。搭載している物理メモリ容量を十分に吟味しなければなりまん。
このように、三階層モデルWebアプリの場合、データベースサーバーのリソース節約(Webサーバーがデータベースサーバーに対する「クライアント」ですよね)と、データベースサーバーとWebサーバー間及びWebサーバーとクライアントマシン間のネットワークトラフィックは抑制される傾向にありますが、その分、これまでクライアント・サーバーの双方にかかっていた負荷が、Webサーバーに一気にかかる。というケースもありますから、慎重に検討しなければなりません。
二階層モデルの場合、データベースサーバーへのアクセスが裸のままの状態であったため、セキュリティの問題も含め、ネットワークトラフィックの制御は、ほとんど難しいと考えられてきました。
例えば、下手なクエリー(三階層モデルWebアプリにおいてもクエリーは上手でなければなりません。「AbsolutePage」プロパティなどの適切な手法についてはまたいずれ検討します)やクライアントサイドのバッチ処理の連発により、平面的なネットワークは大混雑、というようなことは日常茶飯のことでした。
一方、三階層モデルWebアプリの導入により、こうした交通混雑がどのように解消されるのか、また、されるべきなのか、ということになります。
ひとつには、先に書いたように、データアクセスの一元化によるトラフィック抑制そのものが実現すると同時に、データベースサーバーとWebサーバーの通信をクライアントマシンとの通信とにスイッチングすることも可能になるため(Webサーバーをマルチホームホストにすれば、さらに効果は向上します)、さらなるトラフィック抑制とセキュリティ向上が実現します。
もうひとつは、Webサーバーとクライアントマシンとのトラフィックそのものに、あらかじめ制限を設けることで、他のトラフィックへの影響を総合的に調整することが可能になる点です。
例えば、ある組織がインターネット接続をしている場合、インターネット接続の帯域は、電気通信事業者等への専用線使用料負担等に限界があるため、一定に制限されているのと同様に、Webアプリへのアクセスに対して帯域制限をかけることで、組織内のネットワーク利用における適切なトラフィック管理が実現するわけです。
特に、適切なクエリーとサーバーサイドのバッチ処理が実現するならば、もともと、必要な帯域は相当程度に低くに抑えられるはずです。
こうした手法は、広い意味での「QoS」ということになると思います。かつて、汎用機の集中処理からクライアント・サーバーの分散処理への流れの中で、「業務システムが遅くなるなんて、許せない」という議論が結構多く見られたはずですが、それらは、クライアント・サーバー・モデルやネットワーク運用の技術が未成熟なまま、とにかく、「クラサバ化」することが目的になってしまっていた、という過渡期の現象であったように思います。