メニューボタン
IBMi海外記事2018.10.25

メッセージを取得する、パート1

Paul Tuohy 著

著者まえがき: この記事の元の記事は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 を主催しています。

あわせて読みたい記事

PAGE TOP