この特集のトップページへ
Chapter 6:ビジネスロジックの設計



6.2.1 新規顧客の登録
●新規顧客登録のメソッド
 では,List 6-1に示したAddCustomerメソッドを実装してゆくことにする。AddCustomerメソッドは,顧客情報テーブルに新しいレコードを追加するものであるから,普通に考えれば,ADOコンポーネントを使い,「Chapter 3 データストア層の構築」で作成したbusinesssampleDBデータベースに接続してINSERT文などを使って顧客情報テーブルにレコードを追加するように実装すればよい。

 しかし本稿では,冒頭で説明したように汎用性を高めるため,ビジネスロジックから直接データベースにアクセスさせるのではなく,DataObj.というデータオブジェクトを経由してデータベースにアクセスさせる。

 Table 6-2でも簡単に紹介したが,顧客情報テーブルにアクセスするデータオブジェクトは,DataObj.Customerという名前のCOMコンポーネントに実装する。そこで,顧客情報テーブルにレコードを追加するAddRecordメソッドをDataObj.Customerコンポーネントに用意する。DataObj.CustomerコンポーネントのAddRecordメソッドには,ADOコンポーネントを使って実際に顧客情報テーブルにレコードを追加する処理を実装する。そして,Business.CustomerコンポーネントのAddCustomerメソッドからDataObj.CustomerコンポーネントのAddRecordメソッドを呼び出し,ビジネスロジック層からデータオブジェクトを経由して間接的にデータベースにアクセスするという手法をとる(Fig.6-3)。

Fig.6-3 データオブジェクトを通じたデータベースのアクセス
fig6_03

 こうしておけば,将来データベースサーバー製品を交換したり,データベースの構造を変更したりしたときでも,Business.Customerコンポーネントは変更せず,DataObj.Customerコンポーネントだけを変更すればよい。

 ではまず,DataObj.CustomerコンポーネントのAddRecordメソッドから実装してゆこう。どのようなプログラムでもよいが,ここでは,List 6-2に示すプログラムとして実装する。

 List 6-2は少々長いが,その理由はエラー処理のためである。

○データベース接続文字列の定義
 2行目で定義しているg_DBConnection定数は,データベース接続文字列として使おうとしている定数である。この例では“Driver=SQL Server;Server=(local);UID=sa;Database=businesssampleDB;”となっているとおり,ローカルで動作しているSQL Server(またはMSDE)にユーザーsaで接続し,businesssampleDBデータベースを利用するという意味になっている。別のサーバーで動作しているSQL Server(またはMSDE)に接続したければ“Server=”の部分を変更すればよいし,ユーザー名を変更したければ“UID=”の部分を変更すればよい。なお,ここでは指定していないが,パスワードを指定したいのであれば,“PID=パスワード”の項目を付け加える。ちなみに,このg_DBConnection定数は,すぐあとに説明するAddRecordメソッドの92行目でデータベースを開く際に用いられている。

○エラーチェックするプロシージャ
 4〜55行目は,エラーチェックするためのプロシージャである。これらのプロシージャは,すぐあとに説明するAddRecordメソッド内の82〜88行目から呼び出され,与えられた文字列がデータベーステーブルで定義したフィールド長を越えないかどうかをチェックする。最大格納長を越えていた場合には,Err.Raiseメソッドを使って実行時エラーを発生させる。なお,実行時エラーのエラー番号(エラーコード)は,Errorcodeという列挙型に別途定義して利用する。Errorcode列挙型は,List 6-3のように定義する。

 たとえば,10行目では,次のようにしてErrorcode.Err_NAMETOOLONGという実行時エラーを発生させている。

Err.Raise Errorcode.Err_NAMETOOLONG, App.Title, _
          "顧客名は64文字以内でなければなりません"

 この場合は,List 6-3に示したように,「vbObjectError + 513 + 100 + 1」で求められる番号のエラーが返されることになる(ちなみに,vbObjectErrorは,Visual Basicが標準エラーとして使う実行時エラーの最大番号を示す定数である)。

 もちろん,List 6-3に示したように列挙型のエラーコードをわざわざ定義して利用しなくとも,Err.Raiseメソッドの呼び出し時に,直接値を書き込んでしまってもかまわない。たとえば,10行目は次のように表記することもできる。

Err.Raise vbObjectError + 513 + 100 + 1, App.Title, _
          "顧客名は64文字以内でなければなりません"

 しかし,利用するエラーコードを列挙型にしてまとめておくと,(1)エラーコードを列挙型で一元管理できるために見通しがよくなる,(2)Public宣言した列挙型は外部に公開されるので,Visual BasicでこのCOMコンポーネントを参照設定したときに,オブジェクトブラウザを使ってエラーコードの一覧を参照することができる,という2つのメリットが生じる。これらのメリットを活かすため,このサンプルプログラムでは,COMコンポーネントから実行時エラーを発生させる必要があるときには,List 6-3Errorcode列挙型で定義したエラーコードを使ってゆくものとする。以後の実装において,新しい実行時エラーを定義する必要があったときには,随時,Errorcode列挙型にエラーコードを追加してゆき,Err.Raiseメソッドに直接エラーコード番号を記述しないようにする。

 ところで,4〜55行目に定義されているエラーチェックのプロシージャは,不要といえば不要ともいえる。なぜなら,データベーステーブルに定義したフィールド長よりも長い文字列を格納しようとすると,ADOコンポーネント側で実行時エラーが発生するからである。しかし,このとき発生するADOコンポーネントの実行時エラーは,「多段階のOLE DBの操作でエラーが発生しました。各OLE DBの状態の値をチェックしてください。作業は終了しませんでした」というわかりづらいものなので,あえてプログラム側でフィールド長を越えないかどうかを事前に確かめ,わかりやすいエラーメッセージを返すようにした。

 ちなみに,4〜55行目に定義されているエラーチェックのプロシージャにおいて,引数をByValではなくByRefで渡しているのは,高速化のためである。引数として渡された値を変更したいということが目的ではない。4〜55行目のプロシージャは,AddRecordメソッド内の82〜88行目から呼び出される。この場合,同じプログラム(COMコンポーネント)内から呼び出されるので,同一のメモリ空間での呼び出しとなる。よって,値をコピーして渡すByVal渡しよりも,メモリのポインタを渡すByRef渡しのほうが高速となる(「5.8.2 ネットワークでの動作を前提とした設計」を参照)。

AddRecordメソッドの実装
 AddRecordメソッドの実装は,58行目からである。AddRecordメソッド内の処理は,ADOコンポーネントを使ってデータベースに接続し,顧客情報テーブルにレコードを追加する典型的なものである。具体的には,82〜88行目の部分で引数のエラーチェックをしたのち,92行目で顧客情報テーブルを開き,98〜109行目で各フィールドに値を設定し,最後に112行目でADODB.RecordsetオブジェクトのUpdateメソッドを呼び出し,データベースに反映させるという処理をしている。

 AddRecordメソッドのなかでCOM+に依存する部分は,2つある。

 1つは,72行目でGetObjectContext関数を使ってObjectContextオブジェクトを取得し,データの取得に成功したら,ObjectContextオブジェクトのSetCompleteメソッドを呼び出してトランザクションをコミット(122行目),失敗したならばSetAbortメソッドを呼び出してトランザクションをアボートしているという箇所(131行目)である。トランザクションをコミットおよびアボートする仕組みについては,すでに「5.4 COM+のトランザクション機能」で説明したとおりである。

 もう1つは,78行目でObjectContextオブジェクトのGetOriginalCallerNameメソッドを使って,COMオブジェクトを呼び出したユーザーのアカウント名を取得している箇所である。GetOriginalCallerNameメソッドは,名前に「Original」と含まれていることからもわかるように,COM+の管理下にある最初のCOMオブジェクト(ルートオブジェクト)に備わるメソッドを呼び出したユーザーのアカウント名を返すものである。GetOriginalCallerNameメソッドに似たメソッドとしてGetDirectCallerNameメソッドがあるものの,GetDirectCallerNameメソッドは,このメソッド自身(この場合にはAddRecordメソッド)を直接呼び出したユーザーのアカウント名を返すものなので,使い分けには注意してほしい(「5.2.2 実際に実行するユーザー」を参照)。

 ところで,58行目から定義しているAddRecordメソッドの引数は,すべてVariant型になっている点に注目していただきたい。AddRecordメソッドの引数は先頭から順に,顧客名,よみがな,郵便番号,住所,電話番号,FAX番号,摘要を示すものである。これらは文字列であるからVariant型ではなくString型でもかまわない。しかし,よみがな,郵便番号,住所,電話番号,FAX番号,摘要に関しては,Null値を許している(Table 6-4参照)。そのため,もしString型にしてしまうと,引数としてNull値を与えることができなくなってしまう。そのため,ここでは引数の型をVariant型とした。

 また,引数をVariant型にしておくと,いかなる値でも渡せるため,型のエラーチェックがビジネスロジック内でできるというメリットもある。たとえば,数値を引数とする何らかのメソッドがあったとする。このとき,当然数値以外の値を与えた場合にはエラーとなるのだが,Long型として引数を指定していた場合には,プレゼンテーション層からそのメソッドを呼び出す時点で型が正しいかどうかをチェックすることになる。つまり,数値として正しい値かどうかの判定はプレゼンテーション層側の仕事となる。しかし,メソッドの引数をVariant型としておけば,プレゼンテーション層は引数のチェックをすることなくメソッドを呼び出すことができる。この場合,数値として有効かどうかを判断するのはビジネスロジック側の仕事となる。

 Variant型をメソッドの引数とした場合,そのほかの型(Long型やString型など)に比べ,若干メソッドの呼び出し速度は低下する。しかし,型のエラーチェックをビジネスロジック側に備えることにより,プレゼンテーション層側では事前に型をチェックする必要がなくなる。そのため,プレゼンテーション層はユーザーインタフェースの実装だけに専念できる。このメリットは大きい。特に,ユーザーが直接入力する値を引き渡すような場合には,ユーザーが入力した値の正当性をプレゼンテーション層側で判定する必要がなくなるので,プレゼンテーション層のプログラムがすっきりして見やすくなる。

prevpg.gif Chapter 6 6/92 nextpg.gif