メッセージを取得する、パート1
著者まえがき: この記事の元の記事は2009年10月に掲載されたものです。 それ以降、私は数多くのクライアントの数多くのモダナイゼーション プロジェクトに携わってきましたが、どのプロジェクトでも、今回(および次回)の記事に記した内容を何らかの形で使用してきました。記事の内容は、フリーフォームRPG用に、また、元記事の掲載以降にRPGに導入された、いくつかのコーディングの機能強化に合わせるべく、アップデートを行っています。
アプリケーションをモダナイズ(または新たなアプリケーションを作成)しようとするときに検討すべき基本原則の1つは、アプリケーション、ビジネス ロジック、およびデータベース処理を階層化(すなわちインターフェースを分離)させることです。考え方としては、どのコンポーネントも、他のコンポーネントに影響を及ぼすことなく変更できるようにすることであり、より重要なことに、同じビジネス ロジックおよびデータベース ルーチンを複数のインターフェースで利用できるようにすることです。
それはそれで結構なことですが、対処が必要と思われる、ちょっとした問題点が2つあります。すなわち、ビジネス ロジックまたはデータベース ルーチンがエラーを起こしたらどうなるでしょうか。エラーの発生をどうやってインターフェースに通知するのでしょうか。
言い換えれば、お互いについて何も分からないのに、異なるコンポーネント間でどのようにしてメッセージを送信し合うのでしょうか。
従来のグリーン スクリーンの世界では、メッセージングは、プロセス ロジックとスクリーンの間できっちりと統合されていました。DDSでERRMSGまたはERRMSGIDキーワードを使用していたかもしれませんし、あるいは、プログラム メッセージ キューおよびメッセージ サブファイルを利用していたかもしれません。しかし、プログラム メッセージ キューの手法は、WebリクエストやSQLサブプロシージャーで使えるでしょうか。
今回の記事(および次回の記事)では、インターフェースを問わずにメッセージを処理することを可能にする手法について見て行きます。これらの記事で使用されているコードが含まれているライブラリーは、 http://www.systemideveloper.com/downloads/messagesV2.zipでダウンロードできます。
はじめに
私はこれまでずっと、メッセージ ファイルの大ファンでしたので、この「新たな」データ構造でもそれらを使い続けようと思っています。私がメッセージ ファイルで非常に気に入っている点は、2次レベル メッセージ テキスト、重大度コード、および変数パラメーターを定義できるところです。
けれども、私がメッセージ ファイルについて気に入らないと思うのは、メッセージIDがRPGプログラム内にハード コーディングされるのを目にする時です。もちろん、プログラムでメッセージIDを使用する必要はありますが、メッセージIDを名前付き定数として定義し、それらをすべてのプログラムに含められるコピー メンバーに置きたいと私は思うのです。そうすれば、どのプログラムも、利用可能なすべてのメッセージIDのリストを持つことになります。以下のコード片は、これらのメッセージIDのいくつかの例を示したものです。ここでは、定数名はすべて大文字という表記規則(ほとんどのプログラミング言語で一般的なもの)を使用しています。
dcl-C ERR_NOTFOUND 'ALL9001';
dcl-C ERR_CHANGED 'ALL9002';
dcl-C ERR_DUPLICATE 'ALL9003';
dcl-C ERR_CONSTRAINT 'ALL9004';
dcl-C ERR_TRIGGER 'ALL9005';
dcl-C ERR_UNKNOWN 'ALL9006';
dcl-C ERR_NOT_NUMBER 'ALL9007';
dcl-C ERR_NOT_DATE 'ALL9008';
メッセージを格納する
アプリケーションの各コンポーネントが、他のコンポーネントについて何も知ることができないとすると、コンポーネント間でメッセージを送信し合うことは不可能ということになります。
そうする代わりに、メッセージを格納しておき、現在、何件のメッセージが格納されているかを知らせるサブプロシージャーや、メッセージを取得するためのサブプロシージャーを使用する方式を採るということです。
では、メッセージをどのようにして格納するのでしょうか。最初に思い浮かぶのはメッセージ キューまたはデータベースかもしれませんが、どちらも必要ありません。メッセージはデータ構造配列に簡単に格納できます。すべてのメッセージ サブプロシージャーが、1つのモジュール内でコーディングされ、メッセージ フォーマット データ構造配列は同じモジュールに保持されます。
以下に、メッセージ データ構造のフォーマットを示します。このデータ構造はコピー メンバーで定義され、すべてのプログラムに含められます。
// Format in which error messages are stored.
dcl-Ds def_MsgFormat qualified template;
msgId char(7);
msgText char(80);
severity int(10);
help char(500);
forField char(25);
end-Ds;
このメッセージ データ構造は、メッセージID、1次レベル メッセージ テキスト、メッセージの重大度、2次レベル メッセージ テキスト、およびメッセージが格納されるフィールドの名前を格納します。
メッセージ モジュール
ここでは、グローバル定義と、それぞれのメッセージ サブプロシージャーについて見てみましょう。
メッセージ モジュール(以下参照)のグーバル定義は、次のものから成ります。
- メッセージ フォーマットと、現在格納されているメッセージの件数の配列。メッセージの件数は200件あれば十分と思われますが、必要に応じて件数を増やすこともできます。
- メッセージを格納するメッセージ ファイルの名前を識別するデータ構造。
- Retrieve Message from Message File(QMHRTVM)およびReceive Message from Message Queue(QMHRCVPM)APIのプロトタイプ。
**free
/include QCpySrc,StdHSpec
ctl-Opt NoMain;
// To create the required service program...
// Current library set to MESSAGES
// CRTRPGMOD MODULE(UTILMSGS)
// CRTSRVPGM SRVPGM(UTILITY) MODULE(UTIL*)
// SRCFILE(UTILITY) TEXT('Utility Procedures')
/include utility,putilMsgs
dcl-Ds messages LikeDS(Def_MsgFormat) dim(200) inz;
dcl-S msgCount int(10);
// Message File used for retrieving message
dcl-Ds msgF;
msgFile char(10) Inz('APPMSGF');
msgFileLib char(10) Inz('MESSAGES');
end-Ds;
// Prototype for QMHRTVM API
dcl-Pr retrieve_MessageFromMsgF extPgm('QMHRTVM');
msgInfo char(3000) options(*varSize);
msgInfoLen int(10) const;
formatName char(8) const;
msgId char(7) const;
msgF char(20) const;
replacement char(500) const;
replacementLength int(10) const;
replaceSubValues char(10) const;
returnFCC char(10) const;
usec char(256);
end-Pr;
// Prototype for QMHRCVPM API
dcl-Pr receive_Msg extPgm('QMHRCVPM');
msgInfo char(3000) options(*VarSize);
msgInfoLen int(10) const;
formatName char(8) const;
callStack char(10) const;
callStackCtr int(10) const;
msgType char(10) const;
msgKey char(4) const;
waitTime int(10) const;
msgAction char(10) const;
errorForAPI like(APIError);
end-Pr;
メッセージを消去する
u_clear_Messages()サブプロシージャーは、名前が示す通りの処理を行うだけです。すなわち、メッセージ フォーマット データ構造配列を消去し、メッセージ カウントを0にセットします。
//--------------------------------------------------
// Clear messages
dcl-Proc u_clear_Messages export;
dcl-Pi *n end-Pi;
msgCount = 0;
clear messages;
end-Proc;
メッセージを追加する
u_add_Message()サブプロシージャーは、メッセージ フォーマット データ構造配列に、必要なメッセージを追加し、メッセージ カウントを増やします。
//--------------------------------------------------
// Add Message - Add a pre-defined message to the message list
// - Field Name is optional and may be omitted
// - Message data is optional
dcl-Proc u_add_Message export;
dcl-Pi *n;
msgId char(7) const;
forFieldIn char(25) const
options(*Omit:*noPass);
msgData char(500) const
options(*noPass);
end-Pi;
// Format RTVM0300 for data returned from QMHRTVM
dcl-Ds RTVM0300 qualified;
bytesreturned int(10);
bytesAvail int(10);
severity int(10);
alertIndex int(10);
alertOption char(9);
logIndicator char(1);
messageId char(7);
*n char(3);
noSubVarFmts int(10);
CCSIDIndText int(10);
CCSIDIndRep int(10);
CCSIDTextRet int(10);
dftRpyOffset int(10);
dftRpyLenRet int(10);
dftRpyLenAvl int(10);
messageOffset int(10);
messageLenRet int(10);
messageLenAvl int(10);
helpOffset int(10);
helpLenRet int(10);
helpLenAvl int(10);
SVFOffset int(10);
SVFLenRet int(10);
SVFLenAvl int(10);
end-Ds;
//** reserved
//** defaultReply
//** message
//** messageHelp
// Based variable used to retrieve text from RTVM0300
dcl-S textPtr pointer;
dcl-S text char(500) Based(textPtr);
dcl-S repData char(500);
dcl-S forField like(forFieldIn);
if %parms() > 2;
repData = msgData;
endIf;
if %parms() > 1;
if %addr(forFieldIn) <> *null;
forField = forFieldIn;
endIf;
endIf;
retrieve_MessageFromMsgF(RTVM0300
:%Len(RTVM0300)+350
:'RTVM0300'
:MsgId
:MsgF
:RepData
:%Len(%Trim(RepData))
:'*YES'
:'*YES'
:APIError);
msgCount += 1;
messages(msgCount).msgId = msgId;
messages(msgCount).forField = forField;
if (APIError.bytesAvail = 0);
messages(msgCount).severity = RTVM0300.severity;
if (RTVM0300.messageLenRet > 0);
textPtr = %Addr(RTVM0300) + RTVM0300.messageOffset;
messages(msgCount).msgText = %SubSt(text: 1:
RTVM0300.messageLenRet);
endIf;
if (RTVM0300.helpLenRet > 0);
textPtr = %Addr(RTVM0300) + RTVM0300.helpOffset;
messages(msgCount).help = %SubSt(Text: 1:
RTVM0300.helpLenRet);
endIf;
else;
messages(msgCount).severity = 99;
messages(msgCount).msgText = '*** Expected Message Not Found ***';
endIf;
end-Proc;
このサブプロシージャーでは、3つのパラメーターを指定できますが、必須なのは1番目(メッセージID)のみです。2番目のパラメーターは、メッセージが関連付けられるフィールドの名前を識別し、3番目のパラメーターはメッセージに対する変数データを格納します。
このサブプロシージャーは、RTVM0300フォーマットでQMHRTVM APIを使用して、メッセージ ファイル(グローバル定義のmsgFデータ構造で識別される)から指定のメッセージを取得します。addMessage()の呼び出し時にメッセージ データが供給された場合は、取得時にそのメッセージ データが自動的に挿入されます。お分かりのとおり、メッセージ テキストおよび2次レベル テキストを取得するのに多少のポインター操作が必要になります。
もちろん、このルーチンでは、要求されたメッセージが確かに存在していること(存在しないメッセージIDを要求したのではないこと)のチェックを行います。
メッセージ カウントを取得する
u_message_Count()サブプロシージャーは、現在、格納されているメッセージの数を返すだけです。
//--------------------------------------------------
// Message Count - returns the number of stored messages
dcl-Proc u_message_Count export;
dcl-Pi *n int(10) end-Pi;
return msgCount;
end-Proc;
格納されているメッセージを取得する
u_get_Message()サブプロシージャーは、1番目のパラメーターで指示されたメッセージを取得します。返されるデータは、メッセージ フォーマット データ構造です。このサブプロシージャーは、有効な格納メッセージが要求されているかどうかをチェックします。
//--------------------------------------------------
// Clear messages
//--------------------------------------------------
// Get Message - Get the Message identified by the No.
dcl-Proc u_get_Message export;
dcl-Pi *n;
forMessage int(10) const;
msgFormat likeDs(def_MsgFormat);
end-Pi;
if forMessage > 0 and forMessage <= msgCount;
msgFormat = messages(forMessage);
else;
clear msgFormat;
msgFormat.msgText = '*** Message Not Found ***';
endIf;
return;
end-Proc;
次回の記事に続く
次回の記事では、これらのメッセージ サブプロシージャーがアプリケーションでどのように使用されるかについて見て行きます。また、有用と思われる、他のいくつかのメッセージ サブプロシージャーについても見て行きます。
IBM Championにして『Re-engineering RPG Legacy Applications』の著者であるPaul Tuohy氏は、IBMミッドレンジ界におけるアプリケーションのモダナイゼーションおよび開発テクノロジーの分野で非常に著名なコンサルタント兼トレーナーです。現在は、アイルランドのダブリンのコンサルタント会社、ComCon社のCEOを務める傍ら、「System i Developer」コンソーシアムの運営にもパートナーとして参画しています。Susan Gantner氏およびJon Paris氏とともに、年2回、RPG & DB2 Summit を主催しています。