検索

クリアフィルター
記事
Toshihiko Minamoto · 2021年10月19日

%Statusを使ったデバッグのヒント

## はじめに ObjectScriptで複雑な問題を解決している場合、おそらく%Status値を使用したコードがたくさんあることでしょう。 オブジェクトの観点(%Save、%OpenIdなど)から永続クラスを操作したことがある場合は、ほぼ確実にその状況に遭遇したことがあるでしょう。 %StatusはInterSystemsのプラットフォームでローカライズ可能なエラーメッセージのラッパーを提供します。 OKステータス($$$OK)は1に等しいだけであるのに対し、不良ステータス($$$ERROR(errorcode,arguments...))は0、スペース、エラーに関する構造化情報を含む$ListBuildリストとして表されます。 [$System.Status(クラスリファレンスを参照)](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.Status)は、%Status値を操作するための便利なAPIをいくつか提供しています。クラスリファレンスを役立てられるので、ここでは繰り返しません。 このトピックに関する有用な記事/質問もほかにいくつかあります(最後のリンクをご覧ください)。 この記事では、コーディングのベストプラクティスではなく、いくつかのデバッグのコツや手法に焦点を当てています(ベストプラクティスについては、最後のリンクをご覧ください)。 ## お題のコード例 注意: このようなコードは絶対に書かないようにしてください! 常にステータスをチェックし、それを返すか、例外としてスロー($$$ThrowStatus(someErrorStatus) など)すれば、デバッグがはるかに簡単になります。 Class DC.Demo.MaskedErrorStatus Extends %Persistent { Property Answer As %TinyInt; ClassMethod Run() As %Status {     Set instance = ..%New()     Set instance.Answer = 9000     Do instance.%Save()          Set instance = ..%OpenId(1,,.sc)     Set instance.Answer = 42     Do instance.%Save()          Quit $$$OK } } ターミナルから実行すると、例外がスローされます。何かが明らかにうまく行っていません。 USER>d ##class(DC.Demo.MaskedErrorStatus).Run()    Set instance.Answer = 42  ^ <INVALID OREF>zRun+5^DC.Demo.MaskedErrorStatus.1 ## %Statusのデバッグのコツ#1: $System.OBJ.DisplayError() 常に $System.OBJ.DisplayError() を実行して、作成された最後のエラーステータスを出力できます。 これが機能するのは、($System.Status.Error 経由で)エラーステータスが作成されるたびに、変数 %objlasterror にそのステータスが設定されるためです。 また、同様に%objlasterrorをzwriteすることもできます。 上記の場合は、次のようになります。 USER 2d1>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' ## %Statusのデバッグのコツ#2: スタックトレース %Statusごとに、エラーが作成された場所のスタックトレースがあります。 ステータスをzwriteすることで、そのトレースを閲覧することができます。 USER 2d1>zw %objlasterror %objlasterror="0"_$lb($lb(5809,"DC.Demo.MaskedErrorStatus","1",,,,,,,$lb(,"USER",$lb("e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1","e^%Open+16^%Library.Persistent.1^1","e^%OpenId+1^%Library.Persistent.1^1","e^zRun+4^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' */ ステータスごとに、より分かりやすいテキスト($System.OBJ.DisplayError()または$System.Status.GetErrorText(someStatus) )でスタックトレースを確認したいですか? これは、^%oddENV("callererrorinfo",$namespace)=1 または 2 に設定することで実現できます。 以下に、その効果を示します。 USER>set ^%oddENV("callererrorinfo",$namespace)=1 USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' [%LoadData+18^DC.Demo.MaskedErrorStatus.1:USER] USER>set ^%oddENV("callererrorinfo",$namespace)=2 USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' [e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1 e^%Open+16^%Library.Persistent.1^1 e^%OpenId+1^%Library.Persistent.1^1 e^zRun+4^DC.Demo.MaskedErrorStatus.1^1 d^^^0:USER] USER>k ^%oddENV("callererrorinfo",$namespace) USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' これは実際には、開発環境のみで使用するのが適切であることに注意してください。ユーザーにはコードの内部を見られたくないはずです。 (ユーザーに直接%Status値を表示することを避けて、よりユーザーフレンドリーなアプリケーション固有のエラーメッセージを表示するのが最善ですが、これについては、別のトピックとしましょう。) ## %Statusのデバッグのコツ#3: 極上の zbreak この辺からトリッキーになってきます。このコードスニペットの場合では、根本的な原因は、前述のコードスニペットの%Save() から%Statusが確認されていないことにあります。 何がうまく行かなかったのかを見つけるのが非常に難しい、はるかに複雑な例を想像するのは簡単です。特に、プラットフォームコードのずっと後の方で発生しているエラーであれば、尚更です。 これに対処するためにインタラクティブデバッガに飛びつく以外で私が好んで使用してる方法は、ターミナルで非常に極上の[zbreak](https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS_ZBreak)コマンドを使用することです。 USER>zbreak *%objlasterror:"N":"$d(%objlasterror)#2":"set ^mtemptl($i(^mtemptl))=%objlasterror" これはどういう意味でしょうか? zbreak <%objlasterrorが変更される時点>:<デバッガでは何もしない>:<%objlasterrorが定義されており値がある限り(定義済みから未定義にならない場合など)>:<整数がサブスクリプトのジャーナルされていないグローバル(mtempから始めることで、トランザクション中に%Statusが作成され、ログを見るより先にロールバックされてしまうの防ぐため。また、コミットされたコードやデータベースで誰かがこれを見つけた時に私に連絡できるように、グローバルの一部に私のイニシャルを使っています)で、次のサブスクリプトのグローバルにエラーステータスをセットする処理を実行> zbreak に関する補足: 現在定義されているブレークポイント/ウォッチポイントは、引数を指定せずに 'zbreak' を実行することで確認できます。また、ブレークポイントの使用が終わったら、break "off" などを実行して、これらのブレークポイントをオフにできますし、する必要があります。以下に例を示します。 USER>zbreak BREAK: No breakpoints %objlasterror F:E S:0 C:"$d(%objlasterror)#2" E:"set ^mtemptl($i(^mtemptl))=%objlasterror" USER>break "off" USER>zbreak BREAK: No breakpoints No watchpoints では、問題のあるメソッドがウォッチポイントを設定して実行されるとどうなるでしょうか? USER>zbreak *%objlasterror:"N":"$d(%objlasterror)#2":"set ^mtemptl($i(^mtemptl))=%objlasterror" USER>d ##class(DC.Demo.MaskedErrorStatus).Run() Set instance.Answer = 42 ^ <INVALID OREF>zRun+5^DC.Demo.MaskedErrorStatus.1 USER 2d1>zw ^mtemptl ^mtemptl=6 ^mtemptl(1)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb(,"USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 */ ^mtemptl(2)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb(,"USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0")),"0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb(,"USER",$lb("e^EmbedErr+1^%occSystem^1"))))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127- > ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(3)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb("zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1","USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 */ ^mtemptl(4)="0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb("EmbedErr+1^%occSystem","USER",$lb("e^EmbedErr+1^%occSystem^1"))))/* ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(5)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb("zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1","USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0")),"0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb("EmbedErr+1^%occSystem","USER",$lb("e^EmbedErr+1^%occSystem^1"))))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127- > ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(6)="0 "_$lb($lb(5809,"DC.Demo.MaskedErrorStatus","1",,,,,,,$lb(,"USER",$lb("e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1","e^%Open+16^%Library.Persistent.1^1","e^%OpenId+1^%Library.Persistent.1^1","e^zRun+4^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' */ 多少ノイズがありますが、重要な問題がすぐに現れます。 /\* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 \*/ %TinyInt を使用しないことを想定すべきでした! (また、より重要なことは、呼び出すメソッドが返す%Status値を必ず確認する必要があります。) ## 関連資料 [私が気に入っている、エラー処理とレポートのコーディングパターン](https://community.intersystems.com/post/try-catch-block-i-usually-use-intersystems-objectscript#comment-7751) [Caché ObjectScriptメソッドの%Statusとその他の戻り値](https://community.intersystems.com/post/status-vs-other-return-values-cach%C3%A9-objectscript-methods) [%objlasterrorについて](https://community.intersystems.com/post/about-objlasterror) [$$$envCallerErrorInfoGetの設定方法](https://community.intersystems.com/post/how-set-envcallererrorinfoget-windows-get-location-information-within-exception#comment-95586) [ObjectScriptによるエラー処理スニペット](https://community.intersystems.com/post/objectscript-error-handling-snippets) [ZBREAKコマンド](https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS_ZBreak)
記事
Toshihiko Minamoto · 2021年10月20日

PythonとIRISグローバルを使用したブログの作成

インターネットを使うようになってから (1990年代後半)、PythonとIRIS グローバルを使ってブログを書いていますが、常にCMS (コンテンツ管理システム) でブログ、ソーシャルメディア、さらには企業ページに情報を簡単に投稿できるようにしていました。 数年後、自分がマークダウンファイルに収めて使ってきたすべてのコードをgithubに入れました。 ネイティブAPIでデータをIntersystems IRISに入れて永続化するのはとても簡単だったので、このアプリケーションを作成して少しSQLを忘れ、キー・バリュー・データベースモデルを受け入れることにしました! ![picture](https://raw.githubusercontent.com/renatobanzai/iris-multimodel-suite/master/img/blog.gif) ## ブログとは? これはWEB LOGの略名で、基本的にはユーザーが書いてページに投稿、公開できるプラットフォームです。 ## マークダウンフォーマットとは? マークダウンとは、フォーマットしたテキストを作成するマークアップ言語であり、HTMLより簡単で、多くのCMSプラットフォームで人気があります。 例: ``` # Header 1 ## Header 2 ### Header 3 ``` 結果: # Header 1 ## Header 2 ### Header 3 *********** ## IRISで作成するメリットとは? IRIS Globalsを使用して各投稿のデータを永続化しながら、各データコンサルタントはキー・バリュー・データベースとして動作するIRISのスピードというメリットを得ます。すでに作業でIris Instanceを使用している場合は、このようなアプリケーションを作成するために別の技術を使用する必要はありません。 ## そしてPythonとは? Pythonは最も人気の高いプログラミング言語の一つです。チューリング完全言語であり、開発課題のほとんどを楽にしてくれるオープンソースライブラリがたくさんあります。 私は、PythonとIrisを統合するために、IRISNative APIを使用しました。 ## データベースモデル キー値 私のエンジンは、これでもかというほどシンプルに動作します。 各投稿を永続化するために、サブスクリプトが「post」、その次のサブスクリプトが投稿IDのグローバル「^blog」を作成します。 このグローバルに投稿コンテンツを入れて、終わりです! これだけで、テーブル、インデックスなどを作成する必要はありません。 ``` ^blog("post", "1") = "# post 1 content..." ^blog("post", "2") = "# post 2..." ^blog("post", "3") = "# post 3 markdown content..." ``` ## HTMLでマークダウンをレンダリングするには? これで、オープンソースモジュール使用可能なPythonのメリットを無限に得るこquoとができます。私の選択は、たった1行のコマンドで簡単にマークダウンをレンダリングできるDashライブラリでした(^_^) ```python import dash_core_components as dcc import dash import irisnative #creating a connection with an IRIS Instance conn = irisnative.createConnection("host","port","namespace","username","password") obj_iris = irisnative.createIris(conn) #getting the content of one post with id 1 content = obj_iris.get("blog", "post", "1") #creating the dash application app = dash.Dash(__name__) #rendering a markdown value rendered_markdown_in_html = dcc.Markdown(content) #showing on the page the rendender markdown app.layout = html.Div([rendered_markdown_in_html]) ``` ## すべての投稿を表示するには? サブスクリプト「^blog("post",)」繰り返して、上記と同じ方法でレンダリングされたマークダウンをページにプリントできます。 データベースをモデル化し、これよりも速く簡単に動作するフォームを作ったことがありますか?回答はコメント欄に書いてください! ## 自分でビルドしたくないけど、動作する様子は見たい! 簡単です!ここをクリックして、*御覧ください。* http://iris-multimodel-suite.eastus.cloudapp.azure.com/blog-post (サイトは既に閉鎖されています。ご了承ください) # この記事とアプリケーションをお楽しみいただけましたか? ここではマルチモデルコンテストでの私のアプリケーションの一部をご説明しています。よろしければ、私のアプリに投票してください。
記事
Toshihiko Minamoto · 2023年1月24日

ZPM簡単実装クックブック

ZPM は、InterSystems IRIS データプラットフォーム用のアプリケーションやモジュールと連携するように設計されています。モジュールを管理するためのCLIであるZPN Clientと、モジュールやメタ情報のデータベースであるThe Registryの2つのコンポーネントで構成されています。ZPM を使用して、モジュールの検索、インストール、アップグレード、削除、公開を行うことができます。ZPMを使用すると、ObjectScriptクラス、フロントエンドアプリケーション、Interoperabilityプロダクション、IRIS BIソリューション、IRISデータセット、またはEmbedded Pythonホイールなどのあらゆるファイルをインストールできます。  今日、このクックブックは3つのセクションについて説明します。 1. ZPMのインストール 2. モジュールの作成 3. Registry内のモジュールの検索、インストール、公開   1. ZPMのインストール * ZPM最新バージョンのダウンロード(1つのXMLファイルです)[ダウンロードリンク](https://pm.community.intersystems.com/packages/zpm/latest/installer) * ダウンロードしたXMLをIRISにインポートし、IRISターミナルを開き、次のようにコマンドを入力するだけでIRISにデプロイされます。 _write $SYSTEM.OBJ.Load("C:\zpm.xml", "c")_ 注意:"C:\zpm.xml"は、ダウンロードしたXMLファイルのパスです。このステップはしばらく時間がかかるかもしれません。 * インストールが完了したら、_zpm_ とタイプして Enter キーを押すと、zpm シェルが表示されます。 ![](/sites/default/files/inline/images/images/screenshot_2022-10-07_111845.png) 2. モジュールの作成 モジュールの作成を始める前に、ダウンロード用のファイルを格納したフォルダを用意する必要があります。そこで、zpm というフォルダをC ドライブに 作成しました。 コマンド _generate C:/zpm_ を実行します。 必要な項目をすべて指定すると、最初のモジュールが正常に生成され、次のような画面も表示されます。 ![](/sites/default/files/inline/images/images/screenshot_2022-10-07_151452.png) 注意:  1. モジュールのバージョン(module version)はセマンティックバージョニングを使用しています  2. モジュールソースフォルダ(module source folder)は、すべてのクラスファイルが格納されているフォルダです。 3. zpm にはウェブアプリケーション(Web application)と依存関係(Dependencies)を追加するオプションもありますが、この例では空白にしておきます。 ここで、ファイルエクスプローラを開くと、以下のスクリーンショットのように、「module.xml」というファイルが表示されるはずです。 ![](/sites/default/files/inline/images/images/screenshot_2022-10-07_161718.png) コマンド "_load C:\ZPM__" をタイプします。タイプすると、モジュールが再ロードされ、検証され、コンパイルされ、アクティブ化されます。  ![](/sites/default/files/inline/images/images/screenshot_2022-10-12_110126.png)   3. Registry内のモジュールの検索、インストール、公開 最新のRegistryで利用可能なパッケージを検索します: _zpm:USER>search_ 最新のRegistry からパッケージをインストールする 例として、zpmshowというモジュールをパブリックRegistryにインストールする方法を説明します: _zpm:USER>install zpmshow _(コマンドは、 "moduleName" をインストールします)。 ロード後、モジュールを公開します: _zpm:USER>publish myFirstZPMDemo_ _zpm:USER>search_ を使って公開を確認することができます。この場合、"myfirstzpmdemo 0.1.0 "が最新のRegistryにあることがわかります。 ![](/sites/default/files/inline/images/images/screenshot_2022-10-12_110359.png) 注意:モジュールを公開しているときに、次のようなエラーが発生した場合:_ "エラー! 発行モジュールにおいて、何か問題が発生しました(ERROR! Publishing module, something went wrong)" と表示される場合、_最新のRegistry のステータスが有効で利用可能であることを確認してください。 zpm:USER>repo -list, _で最新のRegistryの状態を確認することができます。   ![](/sites/default/files/inline/images/images/screenshot_2022-10-10_152312.png)   ビデオ:[こちらをクリックしてください](https://www.loom.com/share/0ca097f0dea4476ea294841295f972b2%C2%A0%C2%A0)
記事
Mihoko Iijima · 2023年3月5日

VSCode:RESTサービス実行中プロセスにアタッチしてデバッグする方法

開発者の皆さん、こんにちは! 1つ前の記事では、VSCodeのObjectScriptエクステンションに追加されたRESTサービスのデバッグツールについてご紹介しましたが、この記事では、RESTクライアントなどからHTTP要求を実行し、処理中のIRIS内プロセスにアタッチしながらデバッグを行う方法についてご紹介します。 事前準備などは、前回の記事と一緒になりますのでこの記事では割愛します。 図解のクラスでお試しいただく場合は、こちら👉https://github.com/Intersystems-jp/RESTDebug-VSCode をご利用ください。 この方法では、HTTP要求時に実行しているIRIS内プロセスを特定する必要があります。 そのため、デバッグ対象のメソッドで処理を待機させるため、hang 20のようにhangコマンドをコード中に追加して試します。 手順は以下の通りです。 1) プロセスにアタッチしてデバッグを行うため、launch.jsonを作成する。 VSCodeのデバッグ用ファイル:launch.jsonファイルを作成し、アタッチ用の設定を行います。 作成方法詳細は、VSCode:プロセスにアタッチしてデバッグする方法の「手順1:launch.json の用意」をご参照ください。 例) { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "objectscript", "request": "attach", "name": "アタッチデバッグ練習", "system": true, "processId": "${command:PickProcess}" } ] } 2) デバッグ対象メソッドにHangコマンド追加します。 Hangを利用して処理を数秒待機させ、その間に実行中プロセスを特定します。ブレークポイントを置きたい行の前あたりにhangコマンドを追加してください(20~30秒待機するように記述します)。 hang 20 VSCodeでコードを編集するためには、ワークスペース内にクラス定義用ファイル(*.cls)を配置する必要があります。 サーバ側にあるコードに追記したい場合は、ワークスペースにエクスポートしてから追記します。 エクスポートは、サーバ側の対象コードを右クリックし「Export」を選択します。(下図参照) Export実行後、ワークスペースに移動します。エクスポートファイルは、/src/パッケージ名/クラス名.clsの形式でワークスペースにエクスポートされます。 対象クラスを開き、コードを追記します。 コードの追記が終わったら、Ctrl+Sで保存します(保存+コンパイルが実施されます)。 3) 外部クライアントからHTTP要求を実行します。 例は、PostmanからPOST要求でクエリパラメータとBodyに文字列を指定しています。 Sendボタンを実行すると、2) で指定したHangコマンドが実行されるため、指定秒数待機します。 この間に、アタッチ対象プロセスを探します。 4) アタッチ対象プロセスにアタッチする。 3) の実行後すぐ、VSCodeのデバッグウィンドウを開き、launch.jsonで指定したデバッグ名(例では、「アタッチデバッグ練習」)をクリックし、プロセスリストを表示させます。 プロセスリストには「RESTディスパッチクラス名.*」(*には数字が入ります)の形式で処理中ルーチン名が表示されます。 対象プロセスを見つけたら行をクリックしプロセスにアタッチします。 注意:指定したhangの秒数の間にプロセスを見つけてアタッチしてください。 5) デバッグを実行する。 Hangコマンドの実行が終わると、ブレークマークのところでデバッグが一時停止します。 後は、デバッグ用アイコンを利用してデバッグを進めるだけです。 6) デバッグを終了する。 最後に、デバッグ停止のボタンを押して終了します。 メモ: アタッチ用プロセス一覧に対象プロセスが表示されない場合は、一旦VSCodeを再起動して再度お試しください。 アタッチのデバッグを実行するために追加したhangコマンドはデバッグ終了時必ず削除/コメント化してください。
記事
Megumi Kakechi · 2020年9月16日

HTMLからRESTを使って画像ファイルをアップロードする方法

これはInterSystems FAQ サイトの記事です。 HTMLからRESTを使って画像ファイルをアップロードする方法をご紹介します。 1.はじめに、以下のようなhtmlとクラスを作成してください。 *UploadTest.html <html lang="ja"> <head> <title>Upload</title> </head> <body> <input id="up" type="file" /> <button id="btn">Upload</button> <div></div> <script type="text/javascript"> const sendfile = function(e) { let up = document.getElementById("up"); let file = up.files[0]; let fd = new FormData(); fd.append("imgfile", file); let xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { var result = document.querySelector('div'); xmlhttp.onload = function () { result.innerHTML = xmlhttp.responseText; }; }; xmlhttp.open("POST", "http://127.0.0.1:52773/csp/user/isjtest/uploadimg", true); xmlhttp.send(fd); } let btn = document.getElementById("btn"); btn.addEventListener("click", sendfile); </script> </body> </html> *User.MyREST.cls (IRIS/CachéサーバのUSERネームスペースに作成してください) ※こちらのサンプルでは、C:\tempフォルダにファイルをUploadしています。 適宜フォルダを作成して頂くか、任意のフォルダパスに変更して再コンパイルしてください。 Class User.MyREST Extends %CSP.REST { Parameter CONVERTINPUTSTREAM = 1; Parameter HandleCorsRequest = 1; XData UrlMap { <Routes> <Route Url="/uploadimg" Method="POST" Call="readMimeData" /> </Routes> } ClassMethod readMimeData() As %Status { set upload=$g(%request.MimeData("imgfile", 1)) set fname=%request.MimeData("imgfile",1).FileName set file=##class(%File).%New("c:\temp\"_fname) do file.Open("NWUK\BIN\") do file.CopyFrom(upload) set st = file.%Save() if st { write fname_" アップロード完了!!" } else { write fname_" アップロード失敗" } do file.Close() quit $$$OK } } 2. ウェブ・アプリケーション /csp/user/isjtest の定義を作成します。  管理ポータル:[システム管理]>[セキュリティ]>[アプリケーション]>  [ウェブ・アプリケーション]>[新しいウェブ・アプリケーションを作成]  RESTのディスパッチ・クラスに、1.で作成したUser.MyRESTクラスを指定します。(下記画像参照) 3. 必要に応じて UploadTest.html を編集し(※1)、Webサーバのドキュメントルート(※2)に配置します。  ※1. xmlhttp.openには、環境にあったIPアドレス・ポートを指定してお試しください。 xmlhttp.open("POST", "http://<サーバIP>:<IRIS/Cachéポート>/csp/user/isjtest/uploadimg", true);   ※2. 例:C:\inetpub\wwwroot 4. クライアントブラウザより以下を実行し、任意のファイルをUploadします。  http://localhost/UploadTest.html 5. 指定したフォルダにファイルがUploadされたことをご確認ください。 この投稿を参考にさせて頂き、初めてRESTでIRISへの接続をするプログラムを作成してみました。何となく思った通りに動作しそうな雰囲気です。アプリケーション開発環境での組み込みができていませんので、まだ何が起こるか不安がよぎっていますが、現時点では満足しています。本日は力尽きました... 私の場合、すでに構築済みの手法(フレームワーク、と呼べるほど大層なものではなりません)があり、その手法に合わせるために、敢えてJSONを利用していません。そのため、多くのRESTのサンプルコードがJSON前提ということもあり参考になるものが見当たらず、苦しみました。(自業自得、ですが..) ところで、上記のサンプルコード(UploadTest.html のJavaScript部分)についてです。 var result = document.querySelector('div');  のコードがありますが、「div」に対応するオブジェクト(タグ)が無いように思われます。その為?サーバーからのレスポンスが画面上に表示されないようです。ご確認頂ければ幸いです。 RESTでIRISへの接続をお試しいただきありがとうございます。また、サンプルコードへのご指摘ありがとうございます。確かに、タグが抜けておりました。大変失礼いたしました。サンプル(UploadTest.html)に足りないタグを追加しました。お手数をおかけしますが、ご確認をよろしくお願いいたします。 <button id="btn">Upload</button> <div></div> ←このタグを追加 ご確認とコードの改修、有難う御座いました。 重箱の隅をつつくような内容でしたがご対応頂き感謝いたします。
記事
Hiroshi Sato · 2020年7月2日

VisM.OCX(Caché Direct)を利用したアプリケーションをIRISに移行する方法

初めに VisM.OCXはVisual Basicでクライアント・サーバー型のアプリケーション開発を支援するためにInterSystemsが提供してきたツールです。 誕生から既に20年以上が経過した非常に古いテクノロジーです。 OCX規格(ActiveXコンポーネント)は、マイクロソフト社が推進してきた規格ですが、やがてマイクロソフト社が後継となる.Net Frameworkをリリースし、その新しいフレームワークへの移行を強力に推進すると同時に、OCX規格は非推奨機能となっています。 一方で下位互換性のため、.Net Framework配下でOCXを動作可能とする仕組みが用意されており、結果としてOCXは、.Net Framework環境下で動作可能です。 従って、VisM.OCXも.Net対応のプログラミング言語C#やVB.NETからそのまま利用することができます。 しかしながら.Net Framework上で動作するとは言え、.Net Frameworkが用意する安全性の高い資源管理や強固なセキュリティ機能の恩恵を受けることができず、いわゆるマネージドコードとして動作できないという制約を抱えています。 上記の状況を整理した結果、InterSystems社は、IRISをリリースするに際し、ViSM.OCXは、標準製品では動作せずに、特別なキーを適用することで動作可能になるような措置(使用を制限する)を適用しました。 その結果、VisM.OCXを使用するためには、InterSystems社にお問い合わせいただき、その特別なキーを取得するための申請を行う必要があります。 また広く皆様にIRISを使って評価していただきたいと考えて提供している無償のCommunityエディションでも動作しません。 ここでIRISでVisM.OCXアプリケーションを動かすのはハードルが高いと感じた方に朗報です。 VisM.OCXを使用せずに、ViSM.OCX対応アプリケーションをIRIS上で動作可能にするための移行ツール(Caché Directエミュレーター)を用意しました。 このツールは、Open Exchangeから取得可能です。 Open Exchangeサイト ここでは、このツールを使った移行作業を実際に簡単なVBアプリケーションを例として紹介していきたいと思います。 ここで説明しているADBKアプリケーションは、下記のGithubから入手可能です。 ADBKサンプル ADBKアプリケーション このサンプルアプリケーションは、20年以上前にVB6サンプルとして作成されました。 アドレス帳をCaché データベースに保存し、クライアントのVB6アプリケーションからVisM.OCXを使って、データの登録や検索を行う非常にシンプルなサンプルです。 VB6プロジェクトを.Netプロジェクトに変換する このVB6のアプリケーションをまず.Netアプリケーションに変換しなければなりません。 この変換を行うためには、最新のVisual Studioではだめで、Visual Studio 2008を別途インストールする必要があります。 変換方法は以下のサイトをご参考ください。 VB6アプリケーションをVB.NETに変換する方法 VisM.OCXの削除 次にその変換された.net プロジェクトファイルを新しいVisual Studio(現時点では2019が最新)で読み込みます。 まずメインフォームの右下に配置されているVisM.OCX(青い立方体のアイコン)が見えると思います。 この部品を削除します。(選択した後に、右クリックで削除メニュー項目が表示される) 次に右側に表示されているソリューションエクスプローラーの参照の所を開いて、AxVISMLibを選択して右クリックし、削除を選びます。 必ず部品の削除を先に行ってください。 ここで更新したファイルの保存を行い、プロジェクトを閉じます。 必要に応じて、この状態一式を別ディレクトリーにバックアップします。 C#エミュレータークラスを使用可能にする Caché DirectエミュレータをOpen Exchangeからダウンロードし、そのZipファイルを適当なディレクトリに展開します。 cacheDirectWapper.csを新規のC# クラスライブラリープロジェクトに取り込みます。 参照設定でIRISの.Netライブラリーを指定します。 c:\intersystems\IRIS\dev\dotnet\bin\v4.5InterSystems.Data.IRISClient.dll プロジェクトメニューからxxxのプロパティをクリックします。 xxxはプロジェクト名です。左側のペインからアプリケーションを選びます。 アセンブリ名は適当な名前にします。(CacheDirectEmulatorなど)対象のフレームワークが.Net Framework 4.5でなければ、.Net Framework 4.5に変更します。 左側のペインからビルドを選びます。構成をアクティブな(Release)またはReleaseにします。 ビルドします。(dllが作成されます) Caché Directエミュレーターの参照設定を追加する VB.Netのプロジェクトを再度開き、プロジェクトメニューから参照の追加を選択します。 作成されたdllを参照設定します。(dllとアプリケーションプロジェクトの.Net Frameworkのバージョンを合わせる必要があります) アプリケーション修正 それでは、VisM.OCX用に作られたアプリケーションをCaché Directエミュレータのインタフェースを使って動作するように変更しましょう。 まず、ADBKMain.vbを開きます。 まず、新しく追加したCaché Directエミュレータライブラリーを参照できるようにインポートする必要があります。 これは先頭にあるOption文の後ろに以下のように追加します。 Option Strict OffOption Explicit On'以下のインポートが必要Imports cdapp 続いてVisM.OCXの置き換えです。 このVBアプリは、直接VisM.OCXを使用するのではなく、それをラップするクラスを作成していました。従って、ここでは、VisM.OCXを直接置き換えるのではなく、そのラッパークラスを置き換える方式を採用します。 元々の定義は以下のようになっていました。 cVMClassというのが、VisMをラップしたクラスです。 Dim CacheDirect As New cVMClass これをCaché Directエミュレータクラスを参照するように変更します。 長いので途中で折り返しています。 Public WithEvents CacheDirect As cacheDirectWapper = New cacheDirectWapper("Server = localhost; Port=1972; Namespace=USER; Password = SYS; User ID = _system;") VisM.OCXの場合は、サーバーへの接続は、いくつかオプションがあるのですが、このエミュレーターはオブジェクト生成時にサーバー接続まで行う仕様になっています。 WithEvents句は、イベント処理が不要の場合は、指定しなくてもいいです。 これでCacheDirectを参照しているところは、ほとんど修正することなくそのまま使用できます。 但しフォームのロード処理にいくつか変更が必要でした。 Private Sub ADBKMain_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load CacheDirect.VisM = VisM1.GetOcx If Not Install("Address Book Demo Application", VisM1, "GLO", "^ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.gsa", "MAC", "ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.rsa") Then End End Sub ここで元々のラッパークラスでは、VisM.OCXをプロパティとして設定する仕様だったのですが、これは今回必要なくなりました。 次にデータやルーチンが初期インストールされていない場合にそれらをロードする仕組みを用意していたのですが、これは今はちゃんとライブラリーが用意されていて、それを使うほうが簡単なので、書き換えることにします。 Private Sub ADBKMain_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load 'CacheDirect.VisM = VisM1.GetOcx   'If Not Install("Address Book Demo Application", VisM1, "GLO", "^ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))",    "adbk.gsa", "MAC", "ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.rsa") Then End CacheDirect.Execute("=$DATA(^$ROUTINE(""ADBK""))") If CacheDirect.VALUE = 0 Then CacheDirect.P0 = "c:\temp" CacheDirect.P1 = "ck-d" CacheDirect.Execute("set P2=$system.OBJ.ImportDir(P0,,P1,.P2)") If CacheDirect.P2.Substring(0, 1) <> 1 Then MsgBox("Loadでエラーが発生しました" & CacheDirect.P2) End If End If End Sub 最後にオブジェクトの消滅に関してもより安全な方法を採用したために、書き換えが必要です。 Private Sub ADBKMain_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed 'UPGRADE_NOTE: オブジェクト CacheDirect をガベージ コレクトするまでこのオブジェクトを破棄することはできません。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6E35BFF6-CD74-4B09-9689-3E1A43DF8969"' をクリックしてください。 CacheDirect = Nothing End Sub Caché Directエミュレータクラスのendメソッドを呼び出すように変更します。 Private Sub ADBKMain_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed 'UPGRADE_NOTE: オブジェクト CacheDirect をガベージ コレクトするまでこのオブジェクトを破棄することはできません。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6E35BFF6-CD74-4B09-9689-3E1A43DF8969"' をクリックしてください。 'CacheDirect = Nothing CacheDirect.end() End Sub 続いてFindByName.vbの変更点を見てみましょう。 ここでもCacheDirectのインスタンス化を実施していましたが、今回はADBKMainクラスのCaché Directエミュレータクラスを共有できるので、ここで宣言する必要がなくなります。 以下の記述をコメントアウトします。 'Dim CacheDirect As New cVMClass 次に検索ボタンが押された時の処理を変更していきます。 Private Sub CmdFind_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdFind.Click Dim i As Object 'Dim NOL As Short' 名前のリストを取得 ListLookupName.Items.Clear() '以下の機能は未実装 CacheDirect.Clear() CacheDirect.PDELIM = Chr(1) CacheDirect.P0 = "^ADBK(""XNAME"")" CacheDirect.P1 = TxtSNAME.Text CacheDirect.Execute("Do GetList^VISMUTIL(P0,P1,P1,"" "")") If CDbl(CacheDirect.Error_Renamed) <> 0 Then ' MsgBox (" Error " & CacheDirect.ErrorName) Exit Sub End If ' 取得した名前リストをListBoxに展開 For i = 1 To CacheDirect.NoOfPLISTItem 'UPGRADE_WARNING: オブジェクト i の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 ListLookupName.Items.Add(CacheDirect.PLISTItem(i)) Next iEnd Sub ここでCacheDirect.Clear()はラッパークラスが独自に用意していたメソッドで、新しいエミュレータクラスには存在しません。 内容を確認した所、P0-P9,PLIST変数を初期化する処理のようで、処理を俯瞰する限り、ここで初期化する必要がないと判断し、コメントアウトすることにします。 ここでは、ADBKMainのエミュレータクラスを参照する必要があるため、すべてのCacheDirect参照の前にADBKMain.を付加する必要があります。 そして、VB6からVB.NETにコンバートする際に、Errorというキーワードは強制的に変換されるようで、Error_Renamedになっていたので、これを元のErrorに戻す必要がありました。 NoOfPLISTItemとPLISTItem(i)はラッパークラス独自の実装だったのですが、機能的には全く同じものがエミュレータークラスにあるので、そのメソッドに名前の変更を行いました。 Private Sub CmdFind_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdFind.Click Dim i As Object 'Dim NOL As Short ' 名前のリストを取得 ListLookupName.Items.Clear() '以下の機能は未実装 'CacheDirect.Clear() ADBKMain.CacheDirect.PDELIM = Chr(1) ADBKMain.CacheDirect.P0 = "^ADBK(""XNAME"")" ADBKMain.CacheDirect.P1 = TxtSNAME.Text ADBKMain.CacheDirect.Execute("Do GetList^VISMUTIL(P0,P1,P1,"" "")") 'If CDbl(CacheDirect.Error_Renamed) <> 0 Then If CDbl(ADBKMain.CacheDirect.Error) <> 0 Then MsgBox (" Error " & ADBKMain.CacheDirect.ErrorName) Exit Sub End If ' 取得した名前リストをListBoxに展開 'For i = 1 To CacheDirect.NoOfPLISTItem For i = 1 To ADBKMain.CacheDirect.getPLISTLength() 'UPGRADE_WARNING: オブジェクト i の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'ListLookupName.Items.Add(CacheDirect.PLISTItem(i)) ListLookupName.Items.Add(ADBKMain.CacheDirect.getPLIST(i)) Next iEnd Sub 次にイベント処理の変更です。 VisM.OCXでExecuteメソッドが実行されたことを補足するためのイベント処理が以下の様に定義されていました。 'Private Sub VisM1_Executed(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles VisM1.Executed Dim status As Object If VisM1.P9 <> "" Then 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("データエラー " & VisM1.P9, MsgBoxStyle.OKOnly, "データエラー") End If End Sub Handle以下をエミュレーターのイベントに書き換える必要があります。ここではVisM.OCXを直接参照していたので、その部分をエミュレータークラスを参照する様に変更します。 Private Sub CacheDirect_Executed(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CacheDirect.ExecuteEvent Dim status As Object If CacheDirect.P9 <> "" Then ' 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("データエラー " & CacheDirect.P9, MsgBoxStyle.OkOnly, "データエラー") End If End Sub Errorイベントに関しても同様の変更を行います。 'Private Sub VisM1_OnError(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles VisM1.OnError Dim status As Object 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("Cache' Direct Error " & VisM1.ErrorName, MsgBoxStyle.OKOnly, "Cache' Direct Error") 'エラーが発生した時点でのローカル変数をダンプする CacheDirect.PrintLocalVariable() End Sub Private Sub CacheDirect_OnError(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CacheDirect.ErrorEvent Dim status As Object ' 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("Cache' Direct Error " & CacheDirect.ErrorName, MsgBoxStyle.OkOnly, "Cache' Direct Error") ''エラーが発生した時点でのローカル変数をダンプする 'CacheDirect.PrintLocalVariable() MsgBox("エラーが発生しました" & CacheDirect.ErrorName, MsgBoxStyle.OkOnly) End Sub 次に検索した結果で検索するためのOKボタンが押された時の処理です Private Sub CmdOK_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdOK.Click Dim result() As String CacheDirect.Clear() CacheDirect.P0 = VB6.GetItemString(ListLookupName, ListLookupName.SelectedIndex) '選択されている名前に対応するデータを検索する。 CacheDirect.Execute(("Do GetData^ADBK(P0)")) 'If CDbl(CacheDirect.Error_Renamed) <> 0 Then Exit Sub End If '検索フォームを非表示 Me.Hide() '取得データをフォームフィールドに展開する 'CType(ADBKMain.Controls("TxtNAME"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 1) 'CType(ADBKMain.Controls("TxtZIP"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 2) 'CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 3) 'CType(ADBKMain.Controls("TxtTELH"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 4) 'CType(ADBKMain.Controls("TxtTELO"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 5) 'CType(ADBKMain.Controls("TxtAGE"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 6) 'CType(ADBKMain.Controls("TxtDOB"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 7) End Sub ここでもClearメソッドが呼ばれていますが、不要と判断し、コメントアウトしました。 先ほどと同様ErrorがError_Renameに強制的に変換されていました。 VB6.GetItemStringもVB6固有のメソッドになるので、書き換えが必要でした。 次にMPieceメソッドもラッパークラス固有の関数でしたが、内容を確認すると.NetのSplit関数を使うともっとシンプルに変換できることがわかりました。 先ほどと同様に先頭にADBKMain.を付加する必要があります。 Private Sub CmdOK_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdOK.Click Dim result() As String 'CacheDirect.Clear() 'CacheDirect.P0 = VB6.GetItemString(ListLookupName, ListLookupName.SelectedIndex) If (ListLookupName.SelectedIndex >= 0) Then ADBKMain.CacheDirect.P0 = ListLookupName.Items(ListLookupName.SelectedIndex).ToString() '選択されている名前に対応するデータを検索する。 ADBKMain.CacheDirect.Execute(("Do GetData^ADBK(P0)")) 'If CDbl(CacheDirect.Error_Renamed) <> 0 Then Exit Sub End If '検索フォームを非表示 Me.Hide() '取得データをフォームフィールドに展開する 'CType(ADBKMain.Controls("TxtNAME"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 1) 'CType(ADBKMain.Controls("TxtZIP"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 2) 'CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 3) 'CType(ADBKMain.Controls("TxtTELH"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 4) 'CType(ADBKMain.Controls("TxtTELO"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 5) 'CType(ADBKMain.Controls("TxtAGE"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 6) 'CType(ADBKMain.Controls("TxtDOB"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 7) result = ADBKMain.CacheDirect.P1.ToString().Split(Chr(2)) CType(ADBKMain.Controls("TxtNAME"), Object).Text = result(0) CType(ADBKMain.Controls("TxtZIP"), Object).Text = result(1) CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = result(2) CType(ADBKMain.Controls("TxtTELH"), Object).Text = result(3) CType(ADBKMain.Controls("TxtTELO"), Object).Text = result(4) CType(ADBKMain.Controls("TxtAGE"), Object).Text = result(5) CType(ADBKMain.Controls("TxtDOB"), Object).Text = result(6) End If End Sub 次にロード処理ですが、このフォーム上では何もする必要がないので、VisMコントロールのロード処理をコメントアウトします。 Private Sub FindByName_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load 'CacheDirect.VisM = CType(Controls("VisM1"), Object) End Sub フォームのクローズ処理では、エミュレータクラスの終了処理は不要なので、その処理をコメントアウトします。 Private Sub FindByName_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed 'UPGRADE_NOTE: オブジェクト CacheDirect をガベージ コレクトするまでこのオブジェクトを破棄することはできません。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6E35BFF6-CD74-4B09-9689-3E1A43DF8969"' をクリックしてください。 'CacheDirect = Nothing End Sub これで修正は終了で、アプリケーションも動作するはずです。 autoinstall.basとVmclass.clsは移行の結果不必要になったので、プロジェクトから削除します。 アプリケーション実行 エミュレータクラスのロード OpenExchangeのサイトからCacheDirect.Emulator.clsをダウンロードし、それをIRISにロード(USERネームスペース)します。 アプリケーションルーチン、グローバル、クラスのロード ADBKサンプルのGitHubからUser/ADBK.cls,ADBK.mac,VISMUTIL.mac,adbkglb.xmlをダウンロードし、c:\tempにコピーします。 アプリケーションの実行を行うと最初に必要なグローバル、クラス、ルーチンのロードを行うはずです。 最後に 全く無修正というわけにはいきませんが、既存の資産を生かしつつ、VisM.OCXを使用したアプリケーションの移行が簡単にできるということをご理解いただき、VisMアプリケーションの移行にチャレンジしていただきたいと思います。 もう一つ、今回はサーバー側の処理は全く修正していない点も強調しておきたいと思います。 ついでに今回のサンプルでも所々実施したように古い処理をリファクタリングするのにもいい機会だと思います。 Delphiなど.Netをサポートしていない開発ツールでVisM.OCXを使っていた人のためにODBC版も作ってみました。https://github.com/wolfman0719/CacheDirectODBCEmulator
記事
Toshihiko Minamoto · 2021年8月25日

Webをデバッグする

この記事では、Caché Webアプリケーション(主にREST)のテストとデバッグを外部ツールを用いて行うことについて説明します。 [パート2](https://community.intersystems.com/post/debugging-web-part-2)では、Cachéツールの使用について説明します。 サーバー側のコードを作成したのでクライアントからテストしたい、またはすでにWebアプリケーションが存在するが機能していない― そういったときに使用できるのがデバッグです。 この記事では、最も使いやすいツール(ブラウザ)から最も包括的なツール(パケットアナライザー)までを説明しますが、まずは、最も一般的なエラーとその解決方法について少し説明します。 ### エラー **401 Unauthorized** これは、本番環境へのデプロイ中に最も頻繁に発生するエラーだと思います。 ローカル開発サーバーには通常、最小限のセキュリティ設定か、バニラを除く通常セキュリティ設定が構成されています。 一方の本番サーバーには、より制限的なスキームが適用されます。 つまり、以下を確認してください。 * ログイン済みであること * アクセスするdatabase/table/procedure/row/columnへのアクセス権があること * 許可されていないユーザーがOPTIONSリクエストを実行できること **404 Not Found** 以下を確認してください。 * URLが正しいこと * 新しいアプリケーションであり、外部Webサーバーを使用している場合は、Webサーバーをリロードしてください。 **アプリケーションエラー** 最も簡単に見つけるには、スタックトレースを使用できます。 解決策は、完全にアプリケーション固有です。 ### デバッグツール **Webブラウザ** 必ず最初に利用できるデバッグツールはWebブラウザです。Chromeが推奨されますが、Firefoxでも十分にデバッグできます。 GETリクエストについては、URLをアドレスバーに入力すればテストできますが、その他すべてのリクエストには、Webアプリケーションかjsコードの記述が必要です。 一般的には以下のように行います。 * F12キーを押して、デベロッパーツールを開きます。 * [Network]タブに移動します。 * [Preserve Log]チェックボックスがオンになっていない場合は、それをオンにします。 * XHRリクエストのみを表示します。 * Webアプリケーションでバグのあるアクションを実行します。 ![](/sites/default/files/inline/images/snimok_21.png) ここから、リクエストを調べて再送信できます。 Firefoxでは、リクエストを繰り返す前にリクエストを編集することも可能です。 メリット: * いつでも利用可能 * 使いやすい(エンドユーザーは[Network]と[Console]タブのスクリーンショットを送信できます) * エンドユーザー環境 デメリット: * 部分送信/破損などのレスポンスを表示しない * 大規模なレスポンスでは速度が低下する * 大量のレスポンスでは速度が低下する * すべて手動で行われる **RESTクライアント** RESTクライアントは、Webアプリケーションのテスト向けに特別に作成されたスタンドアロンのWebアプリケーションまたはWebブラウザアドオンです。 私は[Postman](https://www.getpostman.com/)を使用していますが、似たようなものはたくさん存在します。 Postmanでのデバッグは次のようになります。 ![](/sites/default/files/inline/images/snimok_22.png) Postmanはリクエストをコレクションにグループ化して処理します。 リクエストは環境に送信可能です。 環境は変数のコレクションです。 たとえば、私のCACHE@localhost環境ホスト変数は、localhostに設定されており、userは_SYSTEMに設定されています。 リクエストが送信される際は、変数は選択した環境の値に置き換えられてリクエストが送信されます。 MDX2JSONプロジェクトのサンプルの[collection](https://github.com/intersystems-ru/Cache-MDX2JSON/blob/master/MDX2JSON.postman_collection.json)と[environment](https://github.com/intersystems-ru/Cache-MDX2JSON/blob/master/CACHE.postman_environment.json)はこちらにあります。 メリット: * 一度作成すれば、どこででも使用できる * リクエストの制御に優れている * レスポンスの「Pretty」表示 デメリット: * 連鎖リクエスト(request1に対するレスポンスがrequest2またはrequest2Bを強制できる)のデバッグは依然として手動 * 部分送信/破損などのレスポンスに失敗することがある **HTTPデバッグプロキシ** HTTP(S) トラフィックをログに記録するスタンドアロンアプリケーション。 ログに記録されたリクエストを変更して再送信することができます。 私は[Charles](https://www.charlesproxy.com/)と[Fiddler](http://www.telerik.com/fiddler)を使用しています。 ![](/sites/default/files/inline/images/snimok_23.png) メリット: * 部分送信/破損などのレスポンスを処理する * レスポンスの「Pretty」表示 * HTTPSトラフィックのサポートに優れている(パケットアナライザーより) * キャプチャセッションを保存できる デメリット: * リクエストを送信するために何か(Webアプリケーション/RESTクライアント/JSコード)が必要 パケットアナライザー ネットワークを通過するトラフィックを傍受してログに記録できるコンピュータープログラム。 データストリームがネットワークを流れる際に、スニファーが各パケットをキャプチャし、必要に応じてパケットの生データをデコードします。 これが最も包括的なオプションですが、適切に動作させるには、ある程度のスキルも必要となります。 私は[WireShark](https://www.wireshark.org/)を使用しています。 以下にインストールと使用方法を簡単に説明します。 1. ローカルパケットをキャプチャする場合は、[ループバック](https://wiki.wireshark.org/CaptureSetup/Loopback)について読み、前提条件のソフトウェア(Windows用npcap)をインストールします。 2. WireSharkをインストールします。 3. [キャプチャフィルタ](https://wiki.wireshark.org/CaptureFilters)を構成します(たとえば57772のトラフィックのみをキャプチャするファイルはport 57772とします)。 4. キャプチャを開始します。 5. [表示フィルタ](https://wiki.wireshark.org/DisplayFilters)を構成します(たとえば特定のIPへのhttpトラフィックのみを表示する場合は、ip.addr == 1.2.3.4 && httpとします)。 以下は、ポート57772(キャプチャフィルタ)のhttpトラフィック(表示フィルタ)をキャプチャした例です。   メリット: * 部分送信/破損などのレスポンスを処理する * 大量のトラフィックをキャプチャできる * 何でもキャプチャできる * キャプチャセッションを保存できる デメリット: * リクエストを送信するために何か(Webアプリケーション/RESTクライアント/JSコード)が必要 どれを使用するか それは目的によって異なります。 まず、リクエストをログに記録する(デバッグプロキシ、パケットアナライザー)かリクエストを生成(ブラウザ、RESTクライアント)することを目的とすることができます。 REST Web APIを開発している場合は、RESTクライアントを使用するのが、動作をテストする最速の方法です。 ただし、RESTクライアントからのリクエストが機能してもクライアントWebアプリケーションが機能しない場合は、httpデバッグプロキシとパケットアナライザーが必要となることがあります。 クライアントがあり、サーバー側APIを開発してそれを操作する場合は、httpデバッグプロキシかパケットアナライザーが必要となります。 4種類すべてのツールを理解し、使用中のものが作業に不十分となった場合に素早く切り替えられるようにしておくことをお勧めします。 適切なツールが明確である場合もあります。 私は最近、人気のあるhttp拡張プロトコル用のサーバー側APIを開発しましたが、その際の要件は次のとおりでした。 * クライアントがすでに作成済みであり、コードを変更できない。 * クライアントごとに動作が異なる。 * httpとhttpsでの動作が異なる。 * 認証タイプごとに動作が異なる。 * クライアントごとの1秒当たりのリクエスト数は最大100件である。 * 全員がRFCを無視する。 この要件で使用できるソリューションは1つしかありません。パケットアナライザーです。 または、JS消費用のREST APIを開発しているのであれば、テストに最適なツールはRESTクライアントです。 Webアプリケーションをデバッグする場合は、Webブラウザでデバッグを開始しましょう。 パート2では、Webデバッグに関してCaché側でできること(たくさんあります)について説明します。 皆さんは、クライアント側通信をデバッグする際にどのようなアプローチを採用していますか?
記事
Mihoko Iijima · 2021年6月23日

【GettingStarted with IRIS】チュートリアルを始めよう!その3:Interoperability(相互運用性)チュートリアル

開発者のみなさん、こんにちは! 2023/2/21追記 チュートリアルページが新しくなり「Developer Hub」に変わりました! Interoperability(相互運用性)チュートリアルについても新しくなりました。詳しくは「InterSystems Developer Hub:クリック1回で開始できるチュートリアル(4種)のご紹介」をご参照ください。 この記事では、GettingStarted ページの無料体験環境(Sandbox)で試せるチュートリアルの中から、「Connect Your Systems」(Interoperabilityのチュートリアル)の使い方をご紹介します。 GettingStarted ページでできることについては、こちらの記事でご紹介しています。 無料体験環境(Sandbox)の開始手続きについては、こちらの記事でご紹介しています。ぜひご参照ください。 Connect Your Systems では、IRIS の Interoperability メニューを利用してどのように異なるシステム同士を接続し、処理を連携させることができるのか、ビデオによる解説とチュートリアルをご用意しています。 (1) Interoperability 概要 : ビデオ(日本語字幕切り替え可)と概要説明 (オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/) (2) Combat Fraud with Transactions and AI:  ビデオのみ (オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/integration-fraud/) このページのビデオでは、InterSystems IRIS が複数のデータ・ストリームと機械学習モデルを統合して、レストランの POS で不正取引を検出する仕組みのデモをご紹介しています。 (3) Interoperability QuickStart: ビデオ(日本語字幕切り替え可)とチュートリアル (オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/) ※ ビデオは (1) Interoperability 概要 のビデオと共通です。 (4) Build a Smart Ticketing System(チュートリアル) (オリジナルページは👉https://gettingstarted.intersystems.com/interoperability/red-lights-part-2/) 以下、(1) (3) (4) について画面やチュートリアルの進め方をご紹介しています。ぜひご参照ください! (1) Interoperability 概要 オリジナルページ 👉https://gettingstarted.intersystems.com/interoperability/ に 3 分36 秒の概要解説ビデオがあります。日本語字幕を追加する方法は、図解をご参照ください。 IRIS の Interoperability(相互運用性)メニューを利用することで、異なるシステム同士のデータ共有がかつてないほど簡単になります! ビデオにもあるように、異なるシステム同士でデータを共有しようとすると、複数のシステムからデータを取り込む必要があったり、定義したロジックに従ってメッセージをルーティングしたり、共通プロトコルでアクセスできたり、異なるプロトコルだったり、複数の言語を使用したアクセスが必要になってくるなど、要件は様々です。 InterSystems IRIS® data platform の Interoperability メニューを利用することで以下の内容を実現できます。 メタデータやメッセージ・コンテンツに基づいてメッセージをルーティングできます。 ローコード UI を使用してビジネス・ロジックの作成や更新が行えます。 接続対象システムが使用しているフォーマットに合わせてデータを変更するようなデータ変換ロジックを作成できます。 すぐに使えるコンポーネントを使用して素早く統合処理を記述したり、独自のコードを作成してさらに柔軟性を高めることができます。 システムを流れるデータに機械学習アルゴリズムを適用することもできます。 統合されたデータを 1 つのリポジトリに保存し、保存されたデータを利用して分析を行うことができます。 トラブルシューティングや監査のためにシステムを流れるメッセージをトレースできます。 (3) Interoperability QuickStart (オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/) Interoperability クイックスタートチュートリアルのテーマは以下の通りです。 信号機にカメラが設置されていて、赤信号の違反を自動的に識別できるようになった、と仮定して以下の内容を実施しようとしています。 違反を識別したデータを後で分析できるようにデータベースに保存しておきたい。 違反した車が緊急車両でない場合、違反チケットを発行するため別のアプリケーションに違反者情報のデータを送信する。 このチュートリアルの中では、自動的に識別された違反データの中から、どの情報が違反チケットを発行する対象データであるかを判断するビジネス・ルールを作成し、データのルーティングを行います。 また、判断結果を送信する接続先システムが必要としているデータのみを送付できるように、データの変換も行います。 ビジネス・ルールやデータ変換は、InterSystems IRIS の管理ポータルを使用して開発できます。開発の他にも、Interoperability メニューが提供する機能を利用したメッセージのトレースなども確認していく予定です。 テーマに合わせた一連の連携の流れや接続先情報を設定保存するための定義として「プロダクション」があります。 プロダクションは、ビジネス・サービス、ビジネス・プロセス、ビジネス・オペレーションの3種類のコンポーネントを組み合わせ定義することができます。 それぞれのコンポーネント概要は以下の通りです。 ビジネス・サービス:外部システムと接続し、外部システムからの情報を受信します(プロダクションの中で一番最初に動くコンポーネントです)。 ビジネス・プロセス:ルーティングやメッセージ変換などのビジネスロジックを定義するコンポーネントでプロダクション内の処理の調整役になります。 ビジネス・オペレーション:外部システムに処理を依頼する役割があります(プロダクション内で使用していたデータなどを渡すことができます)。 メモ:このチュートリアルの流れでは、違反情報をデータベースに登録する流れの作成は含まれていません。 ① Set up InterSystems IRIS を開始してプロダクションを参照してみよう! (1) Sandbox を開始し、IDE を開きます。 https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/#set-up のページを開き、Sandboxを開始します。 下図のように、LOGINボタンが表示されている場合は、LOGINボタンをクリックし、ログインしてください。 LOGIN後、Sandbox の情報が表示されず「PROVISION SANDBOX」ボタンが表示されていたら「PROVISION SANDBOX」ボタンをクリックし、Sandbox を作成してください。 ユーザ登録がまだの方は、こちらの記事をご覧いただき、ユーザ登録を行ってください。 (2) 「Sandbox IDE」のリンクから IDE を開きます。また「Management Portal」のリンクから管理ポータルを開きます。 管理ポータル起動時、ログイン画面が表示されたら、ユーザ名: tech  パスワード:demo を入力してください。 (3) 管理ポータルの以下メニューを開きます。 Interoperability > 一覧 > プロダクション > Demo.RedLights の行を選択し「開く」ボタンをクリックします。 (4) プロダクション構成画面で と表示されている場合は、ボタンをクリックし、プロダクションを開始します。 プロダクション開始時「このプロダクションを開始してもよろしいですか?」のポップアップメッセージが表示されるので「OK」をクリックします。 プロダクション開始中の状態確認画面が表示され「完了しました」が表示されたら「OK」ボタンを押し画面を閉じて開始処理を完了します。 ② Monitor message transport データ送受信時のメッセージをトレース画面を利用して確認します。 ① SetUp でプロダクションを開始すると、プロダクションに定義している監視対象ディレクトリに用意されていたサンプルファイルが読み込まれ、処理されます。 ここでは、処理されたファイルの情報をメッセージのビジュアルトレース画面を使用して確認してみます。 1、メッセージを参照する (1) 管理ポータルの Interoperability > 表示 > メッセージ に移動します。 表示されているメッセージから、ソースの列に「RealTimeRedLightViolation」と表示されている行の一番先頭にあるメッセージを選択します。 (2) 画面右側にある「トレース」タブを選択し「完全トレースを表示」のリンクをクリックします。 (3) 開いた画面は「ビジュアルトレース」と呼びます。 この画面では、IRIS のプロダクションに設定されているコンポーネント間でどのようなメッセージが送受信されているかを確認できます。 (4) 全メッセージはデータベースに格納され、開発時やテスト時の送受信データの確認や、運用期間中にトラブルが発生した際のメッセージの確認など、様々な場面で利用できる情報です。 (5) ビジネス・サービス:RealTimeRedLightViolationでは、外部から入力された情報を元に、メッセージが作成されました。 以下の情報を確認してみましょう。 そのメッセージは、どのビジネス・プロセスに送信されたでしょうか。 ヒント:ビジネス・サービス:RealTimeRedLightViolationの列に表示されている の送付先がどこであるか確認します。 答え:Demo.TicketBPL ビジネス・オペレーション:To_TicketApplication へ送信されていない情報はどれでしょうか。 ヒント: をクリックし画面右側で「ボディ」タブを選択します。 をクリックし同様に、画面右側の「ボディ」タブを選択し、[1] と [3] のメッセージの違いを確認します。 答え:CARTYPE 2、他のメッセージを送信してみる (1) Sandbox の IDE を開きます。 https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/#set-up を開き「Sandbox IDE」のリンクをクリックします。 IDE を開き左画面で shared > Samples-Integration-RedLights > data > SampleFiles を開きます。 LocalRedLightViolation1-copy.csv を Ctrl + c でコピーし、shared/Samples-Integration-RedLights/data/In に Ctrl +v でペーストします。 再度、(1) の手順を利用してメッセージ・ビューワを確認すると、読み込んだ CSV からメッセージが作成されていることを確認できます。 ③ Consume CSV files using the built-in Record Mapper 管理ポータルの「レコード・マップ」は、CSV などの区切りマークで区切られたデータが含まれるファイルに対する入出力処理用コードの自動生成とプロダクションで使用するメッセージ定義を簡単に生成させることができる機能です。 チュートリアルで使用するレコードマップ定義は既に用意されていますが、サンプルの CSV ファイルを使用してCSVファイルのデータがどのように解析されるか、レコードマップ定義のサンプルファイル読み込みオプションを利用して確認します。 この他、CSV ウィザードもあり、CSV のヘッダを読み込み、対応するレコードマップを自動的に作成するメニューも用意されています。 (1) 直前に開いたメッセージ・ビューワ画面があれば、画面左上部の表示されている [Interoperability] メニューをクリックします。 次に、Interoperability > 構築 > レコード・マップ > 開くボタンクリック > Demo > RedLightViolationsRecordMap を選択します。 (2)  をクリックし、テストに使用したCSV ファイル /home/project/shared/Samples-Integration-RedLights/data/SampleFiles/LocalRedLightViolation1-copy.csv を選択します(ファイルの種類で「カンマ区切りファイル(*.csv)」か「すべてのファイル(*)」を選択しすると表示されます)。 サンプルファイル選択後の画面は以下の通りです。 サンプルファイルを表示させるユーティリティを利用して、意図した通りに情報が区切れるかを確認しながらレコード・マップ定義を作成できます。 サンプルファイルが目的通りの表示になる=レコードマップ定義を利用して正しく解析できることを意味しています。 画面表示中に cross-origin エラーが発生する場合は、管理ポータルの URL を https:// から http:// に変更してお試しください(s を付けずに表示してみてください)。 Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、サンプルファイルのアップロードを行う場合にこの対応が必要になることがあります。 (3) 画面上部の ホーム をクリックします。 次に、Interoperability > 構成 > プロダクション を選択します。 ビジネス・サービス:RealTimeRedLightViolation を選択し、画面右側の「設定」タブを参照し、RecordMap 定義を探します。 RecordMap 定義に先ほど参照した Demo.RedLightViolationsRecordMap が設定されていることをご確認ください。 プロダクションに含まれるコンポーネント(ビジネス・サービス/プロセス/オペレーション)は、プロダクション開始中でも設定変更が行え、変更を行ったコンポーネントのみに影響を与えることができます。 確認したレコードマップを使用しているビジネス・サービスは、予め構築済のレコードマップ用ビジネス・サービスを利用しているため、コードの記述は不要です。 区切りマーク付きファイルを処理する場合、レコード・マップ定義を利用することで、プロダクションに素早く新しい処理を追加することができます。 ④ Route messages, apply business logic and transformations 信号機から受信したデータは、チケットアプリケーションに送付される予定で、さらに分析用にデータベースに保存する予定です。 しかし、受信した全データをチケットアプリケーションに送付するのではなく、赤信号を無視した車両がパトカーや消防車などの許可された車両ではない場合に限り送信します。 チュートリアルのサンプルでは、受信したデータを正しく振り分けるための適切なルーティング・ロジックが作成されています。また、データベース保存用データとして必要な情報のみを送付するように設定されています。 メモ:このチュートリアルの流れでは、抽出データをデータベースへ保存するコンポーネントの作成は含まれていません。 以下の手順で、サンプルコードを確認しましょう。 (1) プロダクション構成でビジネス・プロセス:Demo.TicketBPL を選択し、右画面の「設定」タブを選択します。 情報を提供する設定 を展開し、クラス名: Demo.TicketBPL の右側に表示されている 虫眼鏡のアイコンをクリックしビジネス・プロセス・エディタを開きます。 サンプルコードは、ローコードエディタである、ビジネス・プロセス・エディタを使用しています。 この中では、許可された車種以外の違反情報を他のアプリケーションに送信するため、call アクティビティを使用して「ビジネス・オペレーション」を呼び出しています。 メモ: や をアクティビティと呼びます。 (2) ルールアクティビティ を選択します。右画面に表示される設定画面から ボタンをクリックします。 このルール画面を使用して、メッセージの処理方法を変更することができます。 サンプルでは、CarType に含まれる文字列が、許可された車種であるかをチェックしています。例では、ambulance、police、firetruck が来た時はホワイトリストとして違反チケット用アプリケーションに送付しない流れが設定されています。 ルール画面は、特定のユーザのみが修正できるようにセキュリティを追加することもできます。 ルール画面を通して、適切なユーザが処理の流れに影響のある条件値を変更するなど、ローコードで設定することができます。 チャレンジ問題:CarType が SWAT の場合も、許可された車種となるように新しいルールを追加してください。 (3) ビジネス・プロセス:Demo.TicketBPL のビジネス・プロセス・エディタの画面に戻ります(チュートリアルの手順では、別タブに開いています)。 もし、画面を開いていない場合は、ホーム > Interoperability > 構築 > ビジネス・プロセス から開いてください。  をクリックし、アクティビティの設定画面から ボタンをクリックします(アクティビティ選択後、右画面に表示される設定画面を少しスクロールすると「リクエスト・ビルダ」ボタンが見つかります)。 データベースに保存したい情報の抽出をこの画面で行っています。 左半分に表示されている情報は、ビジネス・プロセスに送付される情報です。右半分に表示されている情報は、呼び出し対象として設定しているビジネス・オペレーションに送付する情報です。 チュートリアルテーマのように簡単なデータ変換の場合は(例では、情報の一部をカットしているだけ)、ビジネス・プロセス内で情報抽出が行えます。 より複雑なデータ変換が必要なケースもあります。その場合は、上図と同様に線を引きながら設定できるデータ変換画面を使用して、アプリケーション全体から呼び出し可能なデータ変換定義を作成することもできます。 このチュートリアルの中では、ビジネス・サービスとビジネス・オペレーションに対してコードを記述していません(予め構築済のビジネス・サービス、ビジネス・オペレーション用コードを利用しているためコードの記述は不要でした)。 もちろん、ビジネス・サービス、ビジネス・オペレーションをコードを記述しながら開発することもできます。 例えば、TCP、FTP、SQL、RESTなど接続周りの処理を簡単に記述できるアダプタを使用して、接続先のシステムから送受信する情報に合わせてカスタムコードを記述しながら開発していく方法もあります。 (4) Build a Smart Ticketing System このチュートリアルでは、1つ前の (3) Interoperability QuickStart で確認した Red Light Violationアプリケーションをベースにしているため、説明の流れが (3) Interoperability QuickStart チュートリアルを完了した状態を想定して手順が記述されています。 このチュートリアルのテーマは シカゴの交通システムのデータに基づいて危険な交差点を特定するための処理を追加します。 です。以下の内容を作成します。 ファイル入力によりデータを受信し、判断結果をファイル出力するシンプルなインターフェースを使用します。 赤信号違反の数に基づいてハイリスクと判断された交差点のみをリストアップするロジックを追加します。 公開されている REST API を使用して人口密度に関するデータを取得する処理を追加します。 システムに適したフォーマットにデータを修正する処理を追加します。 ① Get set up (1) Sandbox の IDE を開きます。 https://gettingstarted.intersystems.com/interoperability/red-lights-part-2/#get-set-up のページを開き、アクセス情報を確認します。 LOGIN ボタンが表示される場合は、LOGIN ボタンをクリックして下さい。「PROVISION SANDBOX」のボタンが表示される場合は「PROVISION SANDBOX」ボタンをクリックします。 (2) IDE のターミナルウィンドウを利用して、以下のチュートリアルで使用するリポジトリをクローンします。 cd /home/project/shared git clone https://github.com/intersystems/Samples-Integration-SmartTicketing (3) IDE のメニュー InterSystems > Management Portal から管理ポータルを開きます。 (4) サンプルコードをインポートします。 管理ポータルのメニュー システムエクスプローラ > クラス を開き、 ボタンをクリックします。 /home/project/shared/Samples-Integration-SmartTicketing/Setup/CustomCensusComponents.xml をインポートファイルに指定し、インポートを行います。 インポートの操作で cross-origin エラーが発生する場合は、管理ポータルの URL を https:// から http:// に変更し、再度実行してみてください。 Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、ファイルのアップロードを成功させるにはこの方法が必要となる場合があります。 ② Add a new interface 新しいインターフェースを追加してみましょう! 最初に、レコードマップを使用してファイル入力を行うシンプルなビジネス・サービスを追加します。 チュートリアルを進めていくうちに、より複雑な内容も登場しますが、現時点では、レコードマップを使用することで入力したファイルから好みのフォーマットにマッピングする処理をとても簡単に作成できることを確認します。 IRIS には、多くの構築済コンポーネントがあります。レコードマップも構築済コンポーネントを使用するため、コーディングの必要はありません。 (1) 次の手順で、シカゴの交差点に関する情報を入力するための新しいレコードマップを使用するビジネス・サービスを作成します。 A) 管理ポータルの Interoperability > 構築 > レコードマップ を開きます。 B) ボタンをクリックし、ファイルに /home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv を指定します。残りの設定項目は以下の通りです。 サンプルファイル:/home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv レコード・マップ名:Demo.CameraViolationsMap セパレータ:, (変更なし) レコード終端文字:LF 文字エンコード:UTF-8 (変更なし) 3か所のチェックボックスをすべてチェック(「サンプルがヘッダ行を持つ」をチェックすることで、CSV ファイルの1行目をヘッダ行と認識します。) 設定が完了したら ボタンをクリックすると、以下の画面が開きます。 CSVファイルを指定してレコードマップを作成する操作で cross-origin エラーが発生する場合は、管理ポータルの URL を https:// から http:// に変更し、再度実行してみてください。 Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、ファイルのアップロードを成功させるにはこの方法が必要となる場合があります。 C)  以下のフィールドが正しく設定されているか確認します。足りない場合は をクリックして追加してください。 D)  ボタンをクリックし、レコードマップ定義と関連するメッセージクラスを生成します。 E) 管理ポータル > Interoperability > 構成 > プロダクション からプロダクション構成ページを開きます。 新しいビジネス・サービスを追加します。 赤枠のボタンをクリックします。 サービスクラスに EnsLib.RecordMap.Service.FileService を指定します(プルダウンで表示される名称には、似た名前が多いので気を付けて選んでください)。 サービス名に From_RedLightArchive を指定し、OK ボタンをクリックして追加します。 (現時点で、設定は完了していません) プロダクションが開始していない場合は、「開始する」ボタンをクリックし、開始してください。 F) 追加したビジネス・サービスの設定を完成させます。 コンポーネント名をクリックします(From_RedLightArchive の字の部分をクリックします)。 画面右側に設定欄が表示されます。以下の項目を設定してください。 設定完了後、「適用」のボタンを押し忘れないようにご注意ください! 有効:チェックします。 ファイル・パス:/home/project/shared/Samples-Integration-RedLights/data/In/ ファイル・スペック:Red_Light_Camera_Violations*.csv アーカイブ・パス:/home/project/shared/Samples-Integration-RedLights/data/SampleFiles/ RecordMap:Demo.CameraViolationsMap (2) 続いて、危険な交差点のリストを出力するビジネス・オペレーションを作成します。 A) 再度、CSVウィザードを利用してレコードマップ定義を作成します。 今回のレコードマップ名は Demo.HighRiskArchiveMap とします。定義内容も前回と同じで、レコードマップ名だけ異なります。ご注意ください。 CSV ウィザードは以下メニューからも移動できます。 管理ポータル > Interoperability > 構築 > CSVレコードウィザード 上記画面の「生成」ボタンを押し忘れないようにしてください。 B) 管理ポータル > Interoperability > 構成 > プロダクション からプロダクション構成ページを開きます。 新しいビジネス・オペレーションを追加します。 赤枠のボタンをクリックします。 オペレーションクラスに EnsLib.RecordMap.Operation.FileOperationを指定します(プルダウンに表示される名称には似た名前が多いので、気を付けて選んでください)。 オペレーション名に To_HighRiskArchiveを指定し、OK ボタンをクリックして追加します。 (現時点で、設定は完了していません) ビジネス・オペレーション:To_HighRiskArchive を選択します(コンポーネント名をクリックします)。 画面右側に設定欄が表示されます。以下の項目を設定してください。 設定完了後、「適用」のボタンを押し忘れないようにご注意ください! 有効:チェックします。 ファイル・パス:/home/project/shared/Samples-Integration-RedLights/data/Out/ ファイル名:%Q_HighRisk.txt RecordMap:Demo.HighRiskArchiveMap (3) ビジネス・サービス:From_RedLightArchive を選択します(コンポーネント名をクリックします)。 画面右側の設定タブの「ターゲット構成名」に作成したビジネス・オペレーション:To_HighRiskArchive を設定し、適用ボタンを押してください。 (4) プロダクションが実行中であることを確認します。 もし、停止している場合は「開始する」ボタンをクリックしてください。 (5) これでシンプルなビジネス・サービス、ビジネス・オペレーションの追加は終わりです。早速テストしてみましょう! A) Sandbox の IDE の左画面から /home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv ファイルを Ctrl + c でコピーし、/home/project/shared/Samples-Integration-RedLights/data/In/ のフォルダに Ctrl + v でペーストします。 B) 管理ポータルに戻りメッセージビューワを開きます。 Menu > メッセージ・ビューワ を選択します。 C) 直近で処理されたメッセージを選択し、画面右でトレースタブを選択し「完全トレースを表示」をクリックすると、トレース画面が開きます。 ここまでの動作が正常に終了している場合、ビジネス・オペレーション:To_HighRiskArchive で設定したファイル・パスのディレクトリ(/home/project/shared/Samples-Integration-RedLights/data/Out/)にファイルが出力されている予定です。 もしエラーが発生している場合は、ビジュアルトレース画面で の表示されます。その場合は、この記事の一番下に掲載している「トラブルシューティングガイド」をご参照ください。 ③ Add business orchestration logic 次に、どの交差点がハイリスクであるかを判断し、対象データのみをファイルに出力するルールセットを追加します。 ビジネス・プロセス・エディタを使い、条件のチェックやルールセットの適用などのロジックを追加し、対象となるデータを1つ前の演習で作成したビジネス・オペレーション:To_HighRiskArchive に送信し、ファイルが指定されたディレクトリに出力されるか確認します。 (1) ビジネス・プロセス・エディタを開きます。 管理ポータルの Interoperability > 構築 > ビジネス・プロセス を開きます。 (2) ボタンをクリックし、新しいビジネス・プロセスを以下の名称で作成します。 パッケージ名: Demo 名前:HighRiskIntersectionsBPL (3) 次に、ビジネス・プロセスの設定を追加します。 ビジネス・プロセスが受信する基本要求(リクエスト・クラス)と、呼び出し元に返送する基本応答(レスポンス・クラス)、このビジネス・プロセス内で使用できるコンテキストプロパティを定義します。 画面右の「コンテキスト」タブを選択し、以下の値を設定します。 リクエスト・クラス: Demo.CameraViolationsMap.Record レスポンス・クラス: Ens.Response(デフォルト値のまま) コンテキストプロパティ: IntersectionRisk データタイプに %String(MAXLEN=50) を設定 コンテキストプロパティ: VIOLATIONS データタイプに %Integer を設定 設定が完了したら、 ボタンをクリックし、保存してください(アクティビティの設定が途中でも保存できます。保存時エラーメッセージが出ますが気にせず保存してください)。 (4) 次に、Assign アクティビティを追加します。 アクティビティの追加は、アクティビティのプルダウン > Assing をクリックします。 追加後、<assign> アクティビティを <start> の下に接続します。 <start> の下についているアイコン からドラッグし <assign> アクティビティの上についているアイコン までドラッグし続け、線がくっついたところでドロップします。 次に <assign> アクティビティの設定を追加します。アクティビティを選択し、右画面で以下設定してください。 アクション: set プロパティ: context.VIOLATIONS 値: request.VIOLATIONS メモ:ビジネス・プロセスの「コンテキスト」タブで設定したプロパティは、ビジネス・プロセス内で使用できるコンテキストオブジェクトのプロパティで 変数 context で操作できます。request はビジネス・プロセスの起動のきっかけとなったメッセージのインスタンスを操作できる変数です。 設定が完了したら、 ボタンをクリックし、保存してください(アクティビティの設定が途中でも保存できます。保存時エラーメッセージが出ますが気にせず保存してください)。 (5) <rule> アクティビティを追加します。 どの交差点がハイリスクであるかを判断する流れを、ビジネス・プロセス内で直接書くこともできますが、ルールエディタで判断基準を変更できるように、ルール(<rule> アクティビティ)を組み込みことができます。 ルールエディタを利用する利点としては、ビジネス・プロセスのコードを変更せずに、判断基準となる値のみを変更できる点です。 追加する <rule> アクティビティでは、1日の交差点通過時の違反回数から very high、hight、low のいずれかの文字列が設定される予定です。 以下の手順で追加します。 A) <rule> アクティビティを追加します。 B) キャプションを設定します。 C) <rule> アクティビティから呼び出されるルールを作成します。 <rule> アクティビティを選択し ボタンをクリックし、出力される子画面で以下入力します。 パッケージ名: Demo 名前: IntersectionRiskRule コンテキスト・クラス: Demo.HighRiskIntersectionsBPL.Context 設定した「コンテキスト・クラス」の指定により、ビジネス・プロセスで使用してる特定の交差点の違反数を登録するコンテキスト・プロパティの値をルールに渡すことができます。 D) ルール・エディタに rule を追加します。 を選択し、 をクリックしてから を選択します。 E) 追加した rule に when を追加します。 を選択し、 をクリックしてから を選択します。 D) 1つの目の の をダブルクリックし、VIOLATIONS > 15 を条件式として設定します。 E) 1つ目の を選択し、 をクリックしてから を選択し、“very high” を value に設定します。 H) 同様の手順で、以下の図と同じ設定になるように定義してください。 I) 最後に、保存ボタンをクリックし、設定を保存します。 (6) ルールエディタは、新しいタブで開いています。 一旦ルールエディタのタブを閉じ、ビジネス・プロセス・エディタに戻ります。 ビジネス・プロセス・エディタの <rule> アクティビティの設定で、ルール名に Demo.IntersectionRiskRule を設定し、Result Location に context.IntersectionRisk を設定します。 設定が完了したら、 ボタンをクリックし、保存してください(アクティビティの設定が途中でも保存できます。保存時エラーメッセージが出ますが気にせず保存してください)。 (7) <rule> アクティビティを選択した状態で、<if> アクティビティを追加します。 メモ:接続したいアクティビティを選択した状態で新しいアクティビティを追加すると、予め線が結ばれた状態で追加されます。 <if> アクティビティのブロックの終わりを示す と <if> アクティビティがつながった状態で追加されます。 (8) <if> アクティビティを設定します。 名前: ハイリスクかどう 条件: (context.IntersectionRisk = "high") || (context.IntersectionRisk = "very high") (IntersectionRisk の値が "high" か "very high" の場合に、その情報をファイル出力するようにこの後の問題で作成します) (9) ハイリスクの交差点だと特定された場合、その情報をファイル出力します。 <if> アクティビティが真である場合の true の線を選択した状態で <call> アクティビティを追加します。 <call> アクティビティの設定は以下の通りです。 名前: HighRiskArchiveの呼び出し ターゲット: オペレーションを選択し、To_HighRiskArchive を設定 リクエストメッセージクラス: Demo.HighRiskArchiveMap.Record レスポンスメッセージクラス: Ens.Response (デフォルト設定のまま) (10) <call> アクティビティの ボタンをクリックします。 (11) ビジネス・プロセスの起動のきっかけとなった要求メッセージ(基本要求)の request の %Source プロパティ を <call> アクティビティで設定したターゲット構成へ送付する要求メッセージの callrequest の %Source プロパティにコピーします(線を結びます)。 (線を引くとき、色が変わるまでドロップしないようにしてください) 同様に、request.INTERSECTION から context.INTERSECTION へ線を引きます。 (12) 線を引いた後、OK ボタンをクリックします。 要求メッセージ・クラスの要求アクションに と設定されます。 (13) <if> アクティビティの設定を変更します。 ブロックの終わりを示す に false の線をつなぎます。 (14) 最後に、<if> アクティビティのブロックの終わりを示すアイコンから、<end> まで線を引き完成です。 (15) 忘れずに、コンパイルを実行してください。 コンパイル結果が子画面に表示されます。「コンパイルが正常に終了ました」と表示されるまでお待ちください。 (16) 作成したビジネス・プロセスをプロダクションに追加します。 管理ポータル > interoperability > 構成 > プロダクション を開きます。 (17) ビジネス・プロセスを追加します。 をクリックします。 (18) ビジネス・プロセス・クラスに作成した Demo.HighRiskIntersectionsBPL を設定し、有効にするのチェックをいれ、OKボタンをクリックします。 (19) ビジネス・サービス:From_RedLightArchive のターゲット構成名を変更します。 Demo.HighRiskInteractionsBPL に変更し適用ボタンをクリックします。 (20) テストを実行します。 先ほどのテストと同じファイル(/home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv)をCtrl + c でコピーし、/home/project/shared/Samples-Integration-RedLights/data/In に Ctrl + v でペーストしてテストを実行してください。 メッセージビューワを利用して、いくつかのメッセージがビジネス・プロセスで止まっていることを確認してください。 管理ポータル > Interoperability > 表示 > メッセージ ビジネス・プロセスで停止しているトレース例は以下の通りです。 をクリックすると、画面右側にルールのどの条件を通過したか、確認できる画面が表示されます。 のリンクをクリックすると、ルールエディタが開き、通過した場所を表示します。 以下のトレース例は、オペレーションまでメッセージが流れいてる例です(high のパタン)。 ④ Add routing 先ほどまでの流れでは、危険な交差点であることが判明した場合、その情報をファイル出力していました。 ここに、各交差点の人口密度に関する情報を取得し、危険な交差点情報の追加情報としてファイル出力に加えることで、危険な交差点に対してどのような対応がとれるかより良い方法を探ることができるようになります。 作成中のビジネス・プロセス:Demo.HighRiskIntersectionsBPL に、REST を使用して US Census が提供するサービスの呼び出しを追加し、交差点の緯度と経度から GeoJSON コードを取得するように変更します。 次に、人口情報を取得するため米国国勢調査が提供する別のサービスの呼び出しを行い、取得した人口密度情報をハイリスクの交差点情報に追加し、ファイル出力を行います。 (1) プロダクション構成ページにビジネス・オペレーションを追加します。 チュートリアルのサンプルとして用意のある Demo.ToCensusGeoServiceBO クラスを使用して以下の設定で新しいビジネス・オペレーションを追加します。 オペレーションクラス:  Demo.ToCensusGeoServiceBO 有効にする: チェックします (設定タブ)HTTPサーバ: tigerweb.geo.census.gov (設定タブ)URL: /arcgis/rest/services/TIGERweb/Tracts_Blocks/MapServer/5/ (設定タブ)SSL 設定: RESTSSL SSL 設定は作成していないため、以下の手順で追加します。 管理ポータル > システム管理 > セキュリティ > SSL/TLS 構成 > 新規構成の作成ボタンクリック > 構成名に RESTSSL を設定し保存ボタンクリック (2) プロダクションの設定を変更します。 管理ポータル > Interoperability > (USERネームスペースを選択)> 構成 > プロダクション を開き画面右側の「設定」タブの下の方に配置されている [開発とデバッグ]を展開し「テスト使用可能」をチェックし適用ボタンクリックします。 (3) ビジネス・オペレーション:Demo.ToCensusGeoServiceBO をテストします。 をクリック(コンポーネント名をクリック)し、画面右側の「アクション」タブを選択し ボタンをクリックします。 テスト画面が表示されるので以下入力し をクリックします。 Latitude: 41.95402905 Longtitude: -87.70807749 (4) もう1つ、ビジネス・オペレーションを追加します。 クラス名: Demo.ToCensusDS 有効にする: チェックします。 (設定タブ)HTTPサーバ: api.census.gov (設定タブ)URL: /data/2014/acs/acs5 (設定タブ)SSL設定: RESTSSL (5) ビジネス・プロセス: HighRiskIntersectionsBPL をビジネス・プロセス・エディタで開きます。 プロダクション構成画面で を選択し(コンポーネント名を選択し)、画面右側の設定タブの「情報を提供する設定」を展開し、クラス名の右側に表示されている虫眼鏡アイコン をクリックします。 (6) 以下の修正を行います。 Geo サービス(ビジネス・オペレーション:Demo.ToCensusGeoServiceBO)から取得した GeoJSON 情報を Census サービス(ビジネス・オペレーション:Demo.ToCensusDS)に渡すために、追加でコンテキスト・プロパテを設定します。 [コンテキスト] タブに移動し、Demo.GeoResponse タイプの GeoResponse プロパティと、%String(MAXLEN=50) タイプの Population プロパティを追加します。 (7) すべての交差点情報に緯度と経度のデータがあるとは限らないためファイル出力情報に追加できない場合もあります。 経度と緯度の情報がある場合、ない場合で処理を分けられるように新しい <if> アクティビティを追加します。 追加場所は、現在の <if> アクティビティと HighRiskArchiveオペレーション を呼び出す<call> アクティビティの間に追加します。 true の線を選択し、<if>アクティビティを追加します。 <if> アクティビティの設定内容は以下の通りです。 名前: Geo情報の有無 条件: $length(request.LATITUDE)>0 メモ:$length() は InterSystems ObjectScript の関数で、引数に指定した文字数を返します。 (8) (7)で追加した<if> アクティビティ(Geo情報の有無)で、経度と緯度の情報が取得できた場合、2 つの <call> アクティビティを追加します。 1 つ目の <call> アクティビティの設定は以下の通りです。 名前: Geo情報取得 ターゲット: Demo.ToCensusGeoServiceBO 非同期: チェックを外します(同期処理に変更します) 要求メッセージクラス: Demo.GeoRequest 要求アクション:  応答メッセージクラス: Demo.GeoResponse 応答アクション:  要求メッセージクラスの設定では、永続クラス > Demo > GeoRequest の順に選択します。 応答メッセージクラスの設定では、永続 クラス> Demo > GeoResponse の順に選択します。 要求アクションの設定は、 ボタンをクリックしても設定できます。 応答アクションの設定は、 ボタンからも設定できます。 2つ目の <call> アクティビティの設定は以下の通りです。 名前: 人口密度情報取得 ターゲット: Demo.ToCensusDS 非同期: チェックを外します(同期処理に変更します) 要求メッセージクラス: Demo.PopulationRequest 要求アクション:  応答メッセージクラス: Demo.PopulationResponse 応答アクション:  要求メッセージクラスの設定では、永続クラス > Demo > PopulationRequest の順に選択します。 応答メッセージクラスの設定では、永続クラス > Demo > PopulationResponse の順に選択します。 要求アクションの設定は、 ボタンをクリックしても設定できます。 State プロパティと同様に、Country、Tract、BackGroup も設定した例は以下の通りです。 応答アクションの設定は、 ボタンからも設定できます。 (9) Geo情報(経度と緯度の情報)がない場合、人口密度情報を取得できないため、人口密度情報を含まないハイリスク用のファイルを作成する必要があります。 Geo情報有無 の <if> アクティビティで false の場合は何も処理せず、ブロックの終わりに移動するように線を引きます。 (10) ビジネス・プロセスをコンパイルします。 (11) ハイリスクの交差点の情報を出力するファイルに人口密度情報を追加するため、レコードマップ:Demo.HighRiskArchiveMap を修正します。 管理ポータルの ホーム > Interoperability > 構築 > レコード・マップ > 開く > Demo.HighRiskArchiveMap で定義を開きます。 (レコードマップの画面は直近で操作した定義が表示されます。手順通りに進めていると Demo.HighRiskArchiveMap が開いた状態で画面が開きます。) PopulationDensity をカラムを追加し、データタイプを %String に設定します。 をクリックし新しいカラムを追加します。 修正が完了したら、忘れずに  ボタンをクリックします。ボタンを押した後の画面は以下ご参照ください。 (12) ビジネス・プロセス:Demo.HighRiskIntersectionsBPL のエディタに戻ります。 を選択し、を利用して値の割り当てを追加します。 context.Population を callrequest.PopulationDensity に割り当てを追加します(callrequest.PopulationDensity はレコードマップで追加したカラム名です)。 修正が完了したらコンパイルを行います。 (13) テストを行います。 Sandbox の IDE の左画面から /home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv ファイルを Ctrl + c でコピーし、/home/project/shared/Samples-Integration-RedLights/data/In/ のフォルダに Ctrl + v でペーストします。 メッセージビューワで確認します(管理ポータルの ホーム > Interoperability > 表示 > メッセージ)。 画面右側の「トレース」タブを参照しながら、追加した REST サービスを呼び出すオペレーションへの経路があるトレースを開き、流れを確認してください。 ⑤ Explore custom business components InterSystems IRIS には、コーディングをほとんど必要としないすぐに使えるコンポーネントが多く用意されています。 構築済コンポーネント以外では、InterSystems ObjectScript で作成するカスタム・コンポーネントもあり、カスタム・コンポーネントの利点としては、処理に必要なことなら何でもコードで追記することができます。 チュートリアルに登場したビジネス・オペレーションの Demo.ToCensusGeoServiceBO と Demo.ToCensusDS は、カスタム・コンポーネントとして作成しています。 以降の紹介では、InterSystems ObjectScript で記述されたカスタムコンポーネントのコードについてご説明します。 Sandbox の IDE を開きます(https://gettingstarted.intersystems.com/interoperability/red-lights-part-2/#get-set-up からアクセス情報をご確認ください)。 左画面で、shared/Samples-Integration-SmartTicketing/Setup/src/cls に移動し、Demo.ToCensusDS クラスを開きます。 このクラスの中のコードでは、REST エンドポイントを呼び出し、情報を取得して、呼び出したビジネス・プロセスにデータを返送している処理を記述しています。 以下、コードで登場する変数やメソッドについての補足です。 変数 tURL は、プロダクション構成の で設定した情報を ..Adapter.URL で取得し、ここに REST サービスを呼び出す時に必要となるクエリパラメータを追記しています。 GetURL()メソッドは、 と組み立てた URL(=変数 tURL)を利用して、GET 要求を実行し、HTTP 応答を受信しています。HTTP 応答は、参照渡しで指定された第2引数 tHttpResponse に登録されます。 ..JSONStreamToObject() メソッドは、ビジネス・オペレーションが使用している REST 用のアウトバウンドアダプタ(ビジネス・オペレーションに追加できるアダプタ)から提供されるメソッドで、GET 要求から得られた HTTP 応答のストリームに含まれる JSON データを​​ IRIS のダイナミックオブジェクト(=IRIS 内で JSON を操作するときに使用するインスタンス)​​​に変換しています。 Demo.GeoResponse のインスタンスを新規生成(set pResponse = ##class(Demo.GeoResponse).%New())している流れでは、HTTP 応答から変換した JSON のダイナミックオブジェクトを使用して、返送データを応答メッセージのプロパティに設定しています。インスタンスを設定している変数 pResponse は、メソッド:GetGeoInfo() の第2引数に参照渡しとして設定されている引数で、この変数に情報を設定することで、ビジネス・オペレーションの応答メッセージとして呼び出し元に返送されます。 /// Retrieves GeoJSON data from the USCensus using longitude and latitude. Method GetGeoInfo(pRequest As Demo.GeoRequest, Output pResponse As Demo.GeoResponse) As %Status { try { // Prepare and log the call // Append the city to the URL configured for adapter set tURL= ..Adapter.URL _ "query?geometry=" _ pRequest.Longitude _ "," _ pRequest.Latitude _ "&geometryType=esriGeometryPoint&outfields=*" _ "&returnGeometry=true&returnIdsOnly=false&inSR=4326&f=geojson" // Execute the call $$$TRACE("Executing call") set tSC=..Adapter.GetURL(tURL,.tHttpResponse) // Return the response if $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data) &&tHttpResponse.Data.Size { set tSC= $$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read()) } quit:$$$ISERR(tSC) if $IsObject(tHttpResponse) { // Instantiate the response object set pResponse = ##class(Demo.GeoResponse).%New() // Convert JSON into a Proxy Object set tSC = ..JSONStreamToObject(tHttpResponse.Data, .tProxy) if (tSC){ // Set response properties from the Proxy Object set pResponse.State = tProxy.features.GetAt(1).properties.STATE set pResponse.County = tProxy.features.GetAt(1).properties.COUNTY set pResponse.Tract = tProxy.features.GetAt(1).properties.TRACT set pResponse.BlockGroup = tProxy.features.GetAt(1).properties.BLKGRP $$$TRACE("Object created with " _ pResponse.State) } } }catch{ set tSC=$$$SystemError } quit tSC } Troubleshooting Guide 問題 対策 Sandbox が起動しない、ページが表示されない この記事のコメント欄でお知らせください メッセージビューワにメッセージが表示されない ビジネス・サービス:From_RedLightArchiveの設定タブにある「ターゲット構成名」が正しく設定されているかご確認ください。 チュートリアル前半では、ターゲット構成名には To_HighRiskArchive が設定されています。 チュートリアル公判では、Demo.HighRiskIntersectionsBPL が設定されています。 cross-originエラーが発生する 管理ポータルの URL を https:// から http:// に変更し、再度実行してみてください。 Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、共有フォルダ内のファイルに正常にアクセスするために必要になる場合があります。 ビジネス・オペレーション:Demo.ToCensusGeoServiceBO テスト時にエラーが発生 テスト時に入力したデータ(longitude、latitude)をご確認ください。(例に記載の文字列をご利用ください) テストボタンが無効になっていて押せない プロダクションの設定を確認します。設定タブの下の方にある「開発とデバッグ」を展開し、テスト使用可能のチェックを入れ 適用ボタンをクリックしてから再度お試しください。 メッセージビューワに表示されるメッセージ一覧に「Ens.ScheduleService」が表示されていて、チュートリアルの流れには登場していません。 このメッセージはバックグラウンド処理に関連したメッセージで無視いただけます。 メッセージ一覧にチュートリアルで使用しているメッセージが表示されていれば大丈夫です。
記事
Tomohiro Iwamoto · 2020年10月22日

VSCodeでのソースコード管理について

リモートや在宅での勤務が一般化しつつあります。 そのため、今までの集中型、オンサイトの開発体制を見直し、分散型の開発体制への移行を進めておられるユーザさんも多いのではないかと思います。 VSCodeを使用したIRISアプリケーションの開発が、コミュニティーを中心に広まり始めて久しいですが、Gitとの相性が良いこの開発ツールが今後さらに浸透していくことは間違いありません。あちらこちらで、その使いまわし方が語られていますが、ここでは、ソースコントロールとの関連を中心にご紹介したいと思います。 ObjectScript Extensionの使い方の基本については、こちらやこちらをご覧ください。 VSCode InterSystems ObjectScript Extensionのプロダクションリリース(V1.0.x)の配布が始まりました。 これに合わせて、今までのコミュニティーサポートに加え、InterSystemsによる公式サポートもアナウンスされています。よりいっそう安心してご利用いただけるようになりました。 目的 メインの開発ツールとしてVSCode+ObjectScript Extensionを使用している環境でのソースコード管理について、その流れを解説します。また、現時点ではVSCodeでビジュアル編集ができないInteroperability機能の編集内容(スタジオ、管理ポータルを使用します)をソースコード管理に加える方法を例示します。 前提 IRISのソースコード管理がその期待通りの動作をするためには、いくつか前提が存在します。 各利用者が専用のワークディレクトリ、ローカルレポジトリを持つ これは、Gitの利用環境ではごく普通のことです。これを共有してしまうと、コミット内容にあわせて編集対象をステージングするというGitの基本的な操作が出来なくなります。 一時的にデバッグ用途で作成したルーチン(test1, deb2)などを誤ってソースコード管理に追加するのは避けたいものです。 VSCodeとスタジオで同一のワークディレクトリ、ローカルレポジトリを使用する これを別にしてしまうと、同じファイルを別のローカルレポジトリにコミットするという危険を排除出来なくなります。 また、VSCode,スタジオ双方を使用した一連の修正をコミットするという操作が出来なくなります。 各利用者が編集時の接続先となる専用のIRIS環境を持つ これを共有してしまうと、利用者どうしの修正が逐次干渉しあい、またコミット前の修正を"ダーティリード"することになるため、分散開発のメリットが損なわれます。 環境例 これら前提を満たす、2種類の環境を例示します。 1. 全てローカル 各利用者がIRISインスタンス自体をローカルPC上にインストールして個人環境として使用しながら開発を進めます。 Gitのような分散型のソースコード管理と相性が良く、スタジオのソースコントロールとの共存も容易なため、お勧めです。 2. IRIS環境を共有 全利用者が共有のIRIS環境を使用します。前提を満たすためにネームスペースとそこに紐づけるデータベース、ワークディレクトリを、各利用者ごとに用意します。この場合、分散開発は実現しません。また、後述の手間がかかるため、ソースコード管理を徹底活用したいという向きにはあまりお勧めはしません。 利用者 ネームスペース データベース 用途 UserA MYAPP_USERA myapp_usera/IRIS.DAT ソースコード common/IRIS.DAT 共通ソースコード data_usera/IRIS.DAT データ UserB MYAPP_USERB myapp_userb/IRIS.DAT ソースコード common/IRIS.DAT 共通ソースコード data_userb/IRIS.DAT データ 通常、VSCodeのワークディレクトリはローカルPC上ですが、スタジオや管理ポータルを併用する場合(ソースコントロールフックを使用する場合)、VSCodeのワークディレクトリもリモート上に用意し、Remote Development extensionでSSH接続します。 これはLinux向けの機能ですが、WindowsであってもSSHとWSLを導入すればVSCodeのリモート接続対象になれます。 1,2共にIRISをDockerで稼働させることで、後述するブランチの切り替え時操作も非常に楽になります。魅力的な選択子なのですが実行環境がLinuxに限定されます。 使用するソースコード管理機能 VSCodeでは標準のソースコントロール機能(Git)を使用します。 スタジオ/管理ポータルでは、編集内容を保存時にワークディレクトリに出力するこちらのソースコード管理フックを使用します。 使用方法の例 上述の「全てローカル」の場合を例にとり使用方法の流れを俯瞰します。 全体の流れは、Gitを使用した共有リポジトリパターンの典型的な開発フローと何ら変わりありませんので、利用環境に合わせて適用いただくための参考としてご覧ください。 1. リモートリポジトリを作成 開発プロジェクトProject1用に、管理者がレポジトリ名:Project1, ブランチb1を作成。 cd \var\git git clone https://github.com/IRISMeister/Project1.git cd Project1 git checkout -b b1 git push --set-upstream origin b1 2. フォルダ構成の決定 決まりはありません。ここでは、下記のようなフォルダ構成とします。 フォルダ名: Project1 通常レポジトリ名と一致する。 C:\var\git\Project1 ローカルレポジトリ(.gitフォルダ)が存在 C:\var\git\Project1\... IRISと直接関係のないファイル(他プログラミング言語、リソース、Docker関連など)を配置 C:\var\git\Project1\src IRIS関連ファイル(cls, mac, incなど)を配置 3. 参加各位のローカルフォルダにgit clone ブランチをb1に切り替えて作業開始 cd \var\git git clone https://github.com/IRISMeister/Project1.git cd Project1 git checkout b1 4. ツールの初期設定 VSCodeでの設定 単にそのワークディレクトリを開くだけです。 cd \var\git\Project1 code . コンパイル・テスト実行環境として使用するための環境として、ローカルIRISのMYAPP(任意です)ネームスペースを接続先に指定します。 VSCodeでは、.VSCode/settings.jsonに下記のような接続情報を設定します。 "objectscript.conn": { "active": true, "host": "localhost", "port": 52773, "ns": "MYAPP", "username": "SuperUser", "password": "SYS" } よりセキュアなInterSystems Server Managerを使用いただきたいところですが本題から逸れてしまうので割愛いたします スタジオでの設定 BPM,DTL,Ruleの編集内容をソースコントロールに含める必要がある場合、スタジオになんらかのソースコントロールフックの導入が必要です。前述のソースコントロールフック(%ZScc.Basic)を導入・有効化し、ファイル出力先として、ワークディレクトリを指定します。 Set $NAMESPACE="MYAPP" Set ^ZScc("Basic","LocalWorkspaceRoot")="c:\var\git\Project1\" Set ^ZScc("Basic","Src")="src" その後、通常の接続操作でMYAPPネームスペースに接続します。これで当該ネームスペース上にてソースコントロールが有効化します。また、この設定は、管理ポータルでのBPM,DTL,Rule編集画面でも有効になります。 5. 開発作業 git pull - コンフリクト解消 - ローカルIRISへのImport - 開発作業 - git add/commit - git push この繰り返しになります。 主な編集作業はVSCodeで行います。Interoperabilityのプロダクション構成,BPL,DTL,Ruleだけは、スタジオもしくは管理ポータルで編集作業を行います。 プロダクション構成,BPL,DTL,Ruleもビジュアルな編集ではなく、ソースコードとしての編集であればVSCodeで可能です スタジオでのソースコード管理コマンドの実行方法はメニュー操作になります。 また、ソースコントロールフックの有効化を行うと、管理ポータルのBPL,DTL,Rule編集画面などにソースコード管理ボタンが提供されます。 左のアイコンでメニュー操作、右のアイコンで出力の確認を行います。 ただし、本例で使用する%ZScc.Basicのフックは、ソースコード保存時に、自動的にワークディレクトリに対象をエクスポートするだけですのでメニュー操作はほぼ不要です。 Git用のソースコントロールフック(%ZScc.Git)を選んだ場合、各Gitコマンドの発行が可能になりますが、本稿の対象外です。これとは別に、スタジオ単体利用時により特化したソースコード管理フックも公開されています。 開発作業のイメージ 他の利用者による修正を反映、コンフリクトがあれば解消します。修正内容をローカルIRISに反映します。画像(2) ローカルIRISを使用しながら開発・単体テストを実行します。画像(3) (必要に応じて)スタジオや管理ポータルでBMP,DTL,Ruleを新規作成・編集します。画像(3,4) これらの変更はVSCode上で、未ステージング状態の変更要素として認識されます。 適宜ローカルのレポジトリにコミットします。 VSCode(お勧め)、コマンドラインで実行します。画像(5) 適宜リモートレポジトリにプッシュします。 VSCode(お勧め)、コマンドラインで実行します。画像(6) 6. 自動化テスト 継続的にテスト実施するような環境を使用して、リモートリポジトリのb1ブランチのソースコードをテストします。 画像(7) Dockerを使える(プロダクション環境がLinux環境)と楽です。画像(8,9) こちらにGitHub Actionを使用して、IRISベースのDockerイメージを作成する例があります。 プロダクション環境がWindowsの場合はチャレンジングな項目になります。可能性としては、WindowsでDOSやPowerShellを起動可能なCI/CDツールとIRISの無人インストール機能を組み合わせるなどして実現していく事になると思います。画像(10) 7. リリース作業 管理者がb1ブランチをmasterブランチにマージします。画像(11) 8. 新規リリースに向け、b2ブランチを作成。以降、繰り返し。 ブランチとネームスペースについて シンプルな構造(ビルドに必要な全てのユーザ作成のソースコードが単一のデータベース上に存在、ブランチはmasterのみ)の採用が可能な小規模での開発が可能な場合は、これらを気にする必要はありません。 ネームスペースが指し示すソースコード保存用のデータベース(ルーチンのデフォルトデータベース)に関して配慮が必要です。 ネームスペースとソースコードの保存場所は、1対1とは限りません。パッケージマッピングなどにより複数のデータベースに跨っている可能性があります(共通関数などを別個のデータベースに配置している場合など)。これら共通関数は、使用するプロジェクトとは切り離してソースコード管理される可能性もあります。 そのことを考慮すると、ブランチ切り替えの際に、単純にネームスペースで認識できる既存のソースコードを「全部削除」して、切り替わったソースコードと置き換える、というオペレーションでは、どうしても、削除もれや削除し過ぎといったミスや無駄な再コンパイル発生を排除できません。 少々手間ではありますが、各ブランチに対応するソースコード保存専用のデータベースを個別に用意しておいて、ブランチを切り替える際には、ネームスペースのメインのソースコードの保存場所も切り替えるのが最も安全だと思います。 IRIS環境を共有する場合は、さらにこれらが各利用者ごとに分かれますので、ブランチ数×利用者数の数だけデータベースを作成することになり、かなりの手間となります。 ブランチ ネームスペース データベース 用途 master MYAPP myapp/master/IRIS.DAT ソースコード CommonPackage/IRIS.DAT 共通ソースコード DATA/IRIS.DAT データ b1 MYAPP myapp/b1/IRIS.DAT ソースコード 同上 共通ソースコード 同上 データ b2 MYAPP myapp/b2/IRIS.DAT ソースコード 同上 共通ソースコード 同上 データ 一方、開発時のIRISの稼働環境として、リモートレポジトリを使用して焼いたDockerイメージを使用すれば、切り替え操作は使用するコンテナイメージの選択という簡単な操作で済む(利用者から隠ぺいされる)ため、手間(なによりもオペミス)が大幅に削減できます。 IRIS DockerイメージはLinux(Ubuntu)ベースですので、プロダクション環境もLinuxである必要があります ブランチ イメージ名 ネームスペース データベース 用途 データベース保存場所 master MYIRIS:master MYAPP myapp/IRIS.DAT ソースコード イメージ内 CommonPackage/IRIS.DAT 共通ソースコード イメージ内 DATA/IRIS.DAT データ 外部データベース b1 MYIRIS:b1 MYAPP 同上 ソースコード イメージ内 同上 共通ソースコード イメージ内 同上 データ 外部データベース b2 MYIRIS:b2 MYAPP 同上 ソースコード イメージ内 同上 共通ソースコード イメージ内 同上 データ 外部データベース IRIS環境を共有した際の注意点 同一のネームスペースを複数の利用者が共有した場合、誰かの修正を他の人がVSCode経由で上書きしようとすると、ObjectScript Extensionから警告を受け、そのコンフリクト解消のための選択を促されます。 小規模開発であれば、これを使用して開発を進めることも可能です。VSCodeには、スタジオの操作環境に近い、サーバサイドを直接編集するモードもありますので、その利用を検討されるのも良いかもしれません。 このモードではワークディレクトリが使用されないためVSCodeのGitでIRISのソースコード管理をすることが出来ません。
記事
Toshihiko Minamoto · 2020年11月26日

CircleCI ビルドで GKE の作成を自動化する

[前回](https://community.intersystems.com/post/deploying-intersystems-iris-solution-gcp-kubernetes-cluster-gke-using-circleci)は GKE サービスを使用して IRIS アプリケーションを Google Cloud 上で起動しました。 また、クラスターを手動で(または [gcloud](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-cluster) を介して)作成するのは簡単ですが、最新の [Infrastructure-as-Code(IaC)手法](https://martinfowler.com/bliki/InfrastructureAsCode.html)では、Kubernetesクラスターの説明もコードとしてリポジトリに格納する必要があります。 このコードの記述方法は、IaC に使用されるツールによって決まります。 Google Cloud の場合は複数のオプションが存在し、その中には Deployment Manager と Terraform があります。 どちらが優れているかにつては意見が分かれています。詳細を知りたい場合は、この Reddit のスレッド「Opinions on Terraform vs. Deployment Manager?」と Medium の記事「Comparing GCP Deployment Manager and Terraform」を参照してください。  この記事では特定のベンダーとの結びつきが少なく、さまざまなクラウドプロバイダーで IaC を使用できる Terraform を選択します。 ここでは過去の記事を読み、Googleアカウントを作成し、前回の記事と同様に「開発」という名前のプロジェクトを作成しているものと仮定します。 この記事ではその ID は <PROJECT_ID> として表示されます。 以下の例では、それを自分のプロジェクトの IDに変更してください。  Google には無料枠がありますが、無料ではないことに注意してください。 必ず出費をコントロールするようにしてください。 また、ここではすでに元のリポジトリをフォークしていることを前提にしています。 この記事全体を通してこのフォークを「my-objectscript-rest-docker-template」と呼び、そのルートディレクトリを「<root_repo_dir>」として参照します。 コピーと貼り付けを簡単にするため、すべてのコードサンプルをこのリポジトリに格納しています。 次の図では、デプロイプロセス全体を 1 つの図で表しています。 では、次のように執筆時点での Terraform の最新バージョンをインストールしましょう。 $ terraform versionTerraform v0.12.17   インターネット上の多くの例では旧バージョンが使用されており、0.12 では多くの変更が加えられているため、ここではバージョンが重要になります。 ここでは GCP アカウントで Terraform に特定のアクションを実行(特定の API を使用)させたいと考えています。 これを可能にするには「terraform」という名前のサービスアカウントを作成し、Kubernetes Engine API を有効にしてください。 その実施方法についてはご心配なく。この記事を読み進めるだけで、あなたの疑問は解消します。 Web Console を使用することもできますが、ここでは gcloud ユーティリティを使った例を試してみましょう。 次の例では、数種類のコマンドを使用します。 これらのコマンドや機能の詳細については、次のドキュメントのトピックを参照指定してください。 gcloud iam service-accounts create 特定のリソースのサービスアカウントへの役割の付与 gcloud iam service-accounts keys create Google Cloud プロジェクトでの API の有効化 それでは、例を見ていきましょう。 $ gcloud init   前回の記事で gcloud を取り上げましたので、ここではセットアップの詳細は説明しません。 この例では、次のコマンドを実行します。 $ cd <root_repo_dir>$ mkdir terraform; cd terraform$ gcloud iam service-accounts create terraform --description "Terraform" --display-name "terraform"   次に、「Kubernetes Engine Admin」(container.admin)の他にいくつかのロールを terraform サービスアカウントに追加しましょう。 これらのロールは今後役に立つことでしょう。 $ gcloud projects add-iam-policy-binding <PROJECT_ID> \  --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \  --role roles/container.admin $ gcloud projects add-iam-policy-binding <PROJECT_ID> \  --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \  --role roles/iam.serviceAccountUser $ gcloud projects add-iam-policy-binding <PROJECT_ID> \  --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \  --role roles/compute.viewer $ gcloud projects add-iam-policy-binding <PROJECT_ID> \  --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \  --role roles/storage.admin $ gcloud iam service-accounts keys create account.json \--iam-account terraform@<PROJECT_ID>.iam.gserviceaccount.com   最後の入力では、account.json ファイルを作成していることに注意してください。 このファイルは必ず秘密にしてください。 $ gcloud projects list$ gcloud config set project <PROJECT_ID>$ gcloud services list --available | grep 'Kubernetes Engine'$ gcloud services enable container.googleapis.com$ gcloud services list --enabled | grep 'Kubernetes Engine'container.googleapis.com Kubernetes Engine API   次に、Terraform の HCL 言語で GKE クラスターを記述しましょう。 ここではいくつかのプレースホルダーを使用していますが、これらは実際の値に置き換えてください。 プレースホルダー 意味 例   <PROJECT_ID>   GCP のプロジェクト ID   possible-symbol-254507   <BUCKET_NAME>   Terraform のステート/ロック用のストレージ(一意である必要があります)   circleci-gke-terraform-demo   <REGION>   リソースが作成されるリージョン   europe-west1   <LOCATION>   リソースが作成されるゾーン   europe-west1-b   <CLUSTER_NAME>   GKE クラスター名   dev-cluster   <NODES_POOL_NAME>   GKE ワーカーノードのプール名   dev-cluster-node-pool   以下に実際のクラスターの HCL 構成を示します。 $ cat main.tfterraform {  required_version = "~> 0.12"  backend "gcs" {    bucket = "<BUCKET_NAME>"    prefix = "terraform/state"    credentials = "account.json"  }} provider "google" {  credentials = file("account.json")  project = "<PROJECT_ID>"  region = "<REGION>"} resource "google_container_cluster" "gke-cluster" {  name = "<CLUSTER_NAME>"  location = "<LOCATION>"  remove_default_node_pool = true  # In regional cluster (location is region, not zone)   # this is a number of nodes per zone   initial_node_count = 1} resource "google_container_node_pool" "preemptible_node_pool" {  name = "<NODES_POOL_NAME>"  location = "<LOCATION>"  cluster = google_container_cluster.gke-cluster.name  # In regional cluster (location is region, not zone)   # this is a number of nodes per zone  node_count = 1   node_config {    preemptible = true    machine_type = "n1-standard-1"    oauth_scopes = [      "storage-ro",      "logging-write",      "monitoring"    ]  }}   HCL コードを適切に整形できるよう、Terraform には次の便利な整形コマンドが用意されています。 $ terraform fmt   上記のコードスニペットは、作成されたリソースが Google によって提供され、リソース自体は google_container_cluster と google_container_node_pool であることを示しています。また、ここではコスト削減のために preemptible を指定しています。 また、デフォルトの代わりに独自のプールを作成しています。 次の設定を簡単に説明します。 terraform {  required_version = "~> 0.12"  backend "gcs" {    Bucket = "<BUCKET_NAME>"    Prefix = "terraform/state"    credentials = "account.json"  }}   Terraform はすべての実行結果をステータスファイルに書き込み、このファイルを他の作業に使用します。 このファイルは共有しやすいように、離れた場所に保存することをお勧めします。 一般的には Google バケットに保存されます。 このバケットを作成しましょう。 プレースホルダー <BUCKET_NAME> の代わりに自分のバケットの名前を使用してください。 バケットを作成する前に、<BUCKET_NAME> が使用できるかどうかを次のコマンドで確認してください。すべての GCP で一意である必要があるためです。 $ gsutil acl get gs://<BUCKET_NAME>   期待する応答: BucketNotFoundException: 404 gs://<BUCKET_NAME> bucket does not exist   「Busy」という応答があった場合、別の名前を選択する必要があります。 AccessDeniedException: 403 <YOUR_ACCOUNT> does not have storage.buckets.get access to <BUCKET_NAME>   Terraform の推奨どおりにバージョン管理も有効にしましょう。 $ gsutil mb -l EU gs://<BUCKET_NAME> $ gsutil versioning get gs://<BUCKET_NAME>gs://<BUCKET_NAME>: Suspended $ gsutil versioning set on gs://<BUCKET_NAME> $ gsutil versioning get gs://<BUCKET_NAME>gs://<BUCKET_NAME>: Enabled   Terraform はモジュール方式であり、GCP で何かを作成するにはGoogle Provider プラグインを追加する必要があります。 これを行うには、次のコマンドを使用します。 $ terraform init   Terraform が GKE クラスターを作成する際の実行計画を見てみましょう。 $ terraform plan -out dev-cluster.plan   コマンドの出力には、計画の詳細が含まれています。 特に問題なければ、次のコマンドでこの計画を実行しましょう。 $ terraform apply dev-cluster.plan   ちなみに、Terraform によって作成されたリソースを削除するには、このコマンドを <root_repo_dir>/terraform/ ディレクトリから実行してください。 $ terraform destroy -auto-approve   しばらくクラスターから離れて先に進みましょう。 ただし、何もかもリポジトリにプッシュされないように、先にいくつかのファイルを例外に追加しましょう。 $ cat <root_repo_dir>/.gitignore.DS_Storeterraform/.terraform/terraform/*.planterraform/*.json   Helm の使用 前回の記事では、Kubernetes のマニフェストを yaml ファイルとして <root_repo_dir>/k8s/ ディレクトリに保存し、それを「kubectl apply」コマンドを使用してクラスターに送信しました。  今回は別の手法を試してみましょう。最近バージョン 3にアップデートされた Kubernetes のパッケージマネージャーである Helm を使用します。 バージョン 2 には Kubernetes 側のセキュリティの問題があったため、バージョン 3 以降を使用してください(詳細については、Running Helm in production: Security best practices を参照してください)。 まず、Kubernetes のマニフェストを k8s/ ディレクトリからチャートとして知られる Helm パッケージにまとめます。 Kubernetes にインストールされている Helm チャートはリリースと呼ばれます。 最小構成では、チャートは次のような複数のファイルで構成されます。 $ mkdir <root_repo_dir>/helm; cd <root_repo_dir>/helm$ tree <root_repo_dir>/helm/helm/├── Chart.yaml├── templates│   ├── deployment.yaml│   ├── _helpers.tpl│   └── service.yaml└── values.yaml   これらのファイルの目的は、公式サイトで詳細に説明されています。 独自チャートを作成するためのベストプラクティスは、Helm ドキュメントの The Chart Best Practices Guide に記載されています。  次にファイルの内容を示します。 $ cat Chart.yamlapiVersion: v2name: iris-restversion: 0.1.0appVersion: 1.0.3description: Helm for ObjectScript-REST-Docker-template applicationsources:- https://github.com/intersystems-community/objectscript-rest-docker-template- https://github.com/intersystems-community/gke-terraform-circleci-objectscript-rest-docker-template   $ cat templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:  name: {{ template "iris-rest.name" . }}  labels:    app: {{ template "iris-rest.name" . }}    chart: {{ template "iris-rest.chart" . }}    release: {{ .Release.Name }}    heritage: {{ .Release.Service }}spec:  replicas: {{ .Values.replicaCount }}  strategy:    {{- .Values.strategy | nindent 4 }}  selector:    matchLabels:      app: {{ template "iris-rest.name" . }}      release: {{ .Release.Name }}  template:    metadata:      labels:        app: {{ template "iris-rest.name" . }}        release: {{ .Release.Name }}    spec:      containers:      - image: {{ .Values.image.repository }}:{{ .Values.image.tag }}        name: {{ template "iris-rest.name" . }}        ports:        - containerPort: {{ .Values.webPort.value }}          name: {{ .Values.webPort.name }}   $ cat templates/service.yaml{{- if .Values.service.enabled }}apiVersion: v1kind: Servicemetadata:  name: {{ .Values.service.name }}  labels:    app: {{ template "iris-rest.name" . }}    chart: {{ template "iris-rest.chart" . }}    release: {{ .Release.Name }}    heritage: {{ .Release.Service }}spec:  selector:    app: {{ template "iris-rest.name" . }}    release: {{ .Release.Name }}  ports:  {{- range $key, $value := .Values.service.ports }}    - name: {{ $key }}{{ toYaml $value | indent 6 }}  {{- end }}  type: {{ .Values.service.type }}  {{- if ne .Values.service.loadBalancerIP "" }}  loadBalancerIP: {{ .Values.service.loadBalancerIP }}  {{- end }}{{- end }}   $ cat templates/_helpers.tpl{{/* vim: set filetype=mustache: */}}{{/*Expand the name of the chart.*/}} {{- define "iris-rest.name" -}}{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}{{- end -}} {{/*Create chart name and version as used by the chart label.*/}}{{- define "iris-rest.chart" -}}{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}{{- end -}}   $ cat values.yamlnamespaceOverride: iris-rest replicaCount: 1 strategy: |  type: Recreate image:  repository: eu.gcr.io/iris-rest  tag: v1 webPort:  name: web  value: 52773 service:  enabled: true  name: iris-rest  type: LoadBalancer  loadBalancerIP: ""  ports:    web:      port: 52773      targetPort: 52773      protocol: TCP   Helm チャートを作成するには、Helm クライアントと kubectl コマンドラインユーティリティをインストールします。 $ helm versionversion.BuildInfo{Version:"v3.0.1", GitCommit:"7c22ef9ce89e0ebeb7125ba2ebf7d421f3e82ffa", GitTreeState:"clean", GoVersion:"go1.13.4"}   iris というネームスペースを作成します。 デプロイ中にこれが作成されていれば良かったのですが、現時点ではその動作は実装されていません。 まず、Terraform によって作成されたクラスターの資格情報を kube-config に追加します。 $ gcloud container clusters get-credentials <CLUSTER_NAME> --zone <LOCATION> --project <PROJECT_ID>$ kubectl create ns iris   Helm が Kubernetes で以下を作成することを(実際にデプロイを開始せずに)確認します。 $ cd <root_repo_dir>/helm$ helm upgrade iris-rest \  --install \  . \  --namespace iris \  --debug \  --dry-run   ここでは、出力(Kubernetes のマニフェスト)をスペースを確保するために省略しています。 特に問題がなければ、デプロイしましょう。 $ helm upgrade iris-rest --install . --namespace iris$ helm list -n iris --allIris-rest  iris  1  2019-12-14 15:24:19.292227564  +0200  EET  deployed    iris-rest-0.1.0  1.0.3   Helm がアプリケーションをデプロイしたことはわかりますが、Docker イメージ eu.gcr.io/iris-rest:v1 をまだ作成していないため、Kubernetes がそのイメージをプルすることはできません(ImagePullBackOff)。 $ kubectl -n iris get poNAME                                           READY  STATUS                       RESTARTS  AGEiris-rest-59b748c577-6cnrt 0/1         ImagePullBackOff  0                    10m   とりあえず、今はここで終わっておきましょう。 $ helm delete iris-rest -n iris   CircleCI 側 Terraform と Helm クライアントを試しましたので、それらを CircleCI 側のデプロイプロセスで使用できるようにしましょう。 $ cat <root_repo_dir>/.circleci/config.ymlversion: 2.1 orbs:  gcp-gcr: circleci/gcp-gcr@0.6.1 jobs:  terraform:    docker:    # Terraform image version should be the same as when    # you run terraform before from the local machine      - image: hashicorp/terraform:0.12.17    steps:      - checkout      - run:          name: Create Service Account key file from environment variable          working_directory: terraform          command: echo ${TF_SERVICE_ACCOUNT_KEY} > account.json      - run:          name: Show Terraform version          command: terraform version      - run:          name: Download required Terraform plugins          working_directory: terraform          command: terraform init      - run:          name: Validate Terraform configuration          working_directory: terraform          command: terraform validate      - run:          name: Create Terraform plan          working_directory: terraform          command: terraform plan -out /tmp/tf.plan      - run:          name: Run Terraform plan          working_directory: terraform          command: terraform apply /tmp/tf.plan  k8s_deploy:    docker:      - image: kiwigrid/gcloud-kubectl-helm:3.0.1-272.0.0-218    steps:      - checkout      - run:          name: Authorize gcloud on GKE          working_directory: helm          command: |            echo ${GCLOUD_SERVICE_KEY} > gcloud-service-key.json            gcloud auth activate-service-account --key-file=gcloud-service-key.json            gcloud container clusters get-credentials ${GKE_CLUSTER_NAME} --zone ${GOOGLE_COMPUTE_ZONE} --project ${GOOGLE_PROJECT_ID}      - run:          name: Wait a little until k8s worker nodes up          command: sleep 30 # It’s a place for improvement      - run:          name: Create IRIS namespace if it doesn't exist          command: kubectl get ns iris || kubectl create ns iris      - run:          name: Run Helm release deployment          working_directory: helm          command: |            helm upgrade iris-rest \              --install \              . \              --namespace iris \              --wait \              --timeout 300s \              --atomic \              --set image.repository=eu.gcr.io/${GOOGLE_PROJECT_ID}/iris-rest \              --set image.tag=${CIRCLE_SHA1}      - run:          name: Check Helm release status          command: helm list --all-namespaces --all      - run:          name: Check Kubernetes resources status          command: |            kubectl -n iris get pods            echo            kubectl -n iris get servicesworkflows:  main:    jobs:      - terraform      - gcp-gcr/build-and-push-image:          dockerfile: Dockerfile          gcloud-service-key: GCLOUD_SERVICE_KEY          google-compute-zone: GOOGLE_COMPUTE_ZONE          google-project-id: GOOGLE_PROJECT_ID          registry-url: eu.gcr.io          image: iris-rest          path: .          tag: ${CIRCLE_SHA1}      - k8s_deploy:          requires:            - terraform            - gcp-gcr/build-and-push-image   CircleCI 側のプロジェクトに次のようないくつかの環境変数 を追加する必要があります。 GCLOUD\_SERVICE\_KEY は CircleCI のサービスアカウントキーであり、TF\_SERVICE\_ACCOUNT_KEY は Terraform のサービスアカウントキーです。 サービスアカウントキーが _account.json_ ファイル全体の内容であることを思い出してください。 次に、変更をリポジトリにプッシュしましょう。 $ cd <root_repo_dir>$ git add .circleci/ helm/ terraform/ .gitignore$ git commit -m "Add Terraform and Helm"$ git push   CircleCI UI ダッシュボードには、次のようにすべてが正常であることが示されているはずです。     Terraform は冪等性のあるツールであり、GKE クラスターが存在する場合、「terraform」ジョブは何も実行しません。 クラスターが存在しない場合は、Kubernetes をデプロイする前に作成されます。最後に、IRIS の可用性を確認しましょう。 $ gcloud container clusters get-credentials <CLUSTER_NAME> --zone <LOCATION> --project <PROJECT_ID> $ kubectl -n iris get svcNAME        TYPE                     CLUSTER-IP     EXTERNAL-IP   PORT(S)                     AGEIris-rest    LoadBalancer  10.23.249.42    34.76.130.11    52773:31603/TCP   53s $ curl -XPOST -H "Content-Type: application/json" -u _system:SYS 34.76.130.11:52773/person/ -d '{"Name":"John Dou"}' $ curl -XGET -u _system:SYS 34.76.130.11:52773/person/all[{"Name":"John Dou"},]   まとめ Terraform と Helm は標準の DevOps ツールであり、IRIS のデプロイと緊密に統合する必要があります。 これらはある程度の学習を必要としますが、何度か実践した後は大幅に時間と労力を節約できるようになります。
記事
Hiroshi Sato · 2021年1月27日

クラス定義のプロパティ表示順と、プロパティに対応したグローバル変数の格納順

これは InterSystems FAQ サイトの記事です。 クラス定義のプロパティの表示順は、スタジオのプロパティウィザードを利用して登録した場合は、末尾に追記されます。 また、エディタ上の任意の場所でプロパティ定義文を記述する場合は、その場所に追記され、クラス定義が登録されます。 つまり、定義者が記述した順番に登録されます。 (スタジオが並び換えを行ったりはしません。) 作成したクラス定義が、PersistentやSerialのようにデータベースに格納する属性を持ったクラス定義である場合、”初回のコンパイル”で クラス定義に対応するグローバル変数の定義情報=ストレージ定義を作成します。 初回コンパイル以降に、プロパティ定義の追加が行われれば、そのプロパティに対応するグローバル変数のスロット番号を、末尾に追加し、ストレージ定義を更新します。 以下の例は、クラス定義に対応するストレージ定義の例です。 (初回コンパイル時の状態) Class Sample.Person Extends %Persistent{ Property Name As %String; /// 誕生日Property DOB As %Date; <storage name="Default"><data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="2"><value>Name</value></value> <value name="3"><value>DOB</value></value></data> <datalocation>^Sample.PersonD</datalocation><defaultdata>PersonDefaultData</defaultdata><extentsize>100000</extentsize><idlocation>^Sample.PersonD</idlocation><indexlocation>^Sample.PersonI</indexlocation><streamlocation>^Sample.PersonS</streamlocation><type>%Library.CacheStorage</type></storage> } <storage name="Default">から</storage>までの表示が、クラス定義の初回コンパイルで作成されるストレージ定義情報です。 作成したSample.Personクラスの格納先グローバル変数は、ストレージ定義の<datalocation> <indexlocation> <streamlocatoin> を参照するとわかります。 また、各プロパティ定義が、指定グローバル変数のどこに格納されるかは、<value name="2"> と <value name="3">を参照するとわかります。<value name="2"><value>Name</value>%lt;/value> <value name="3"><value>DOB</value></value> つまり、Nameプロパティは、^Sample.PersonDの $ListBuild()構造の2番目に格納され、DOBは3番目に格納されることがわかります。 ここで、Addressプロパティを、クラス定義の表示上、一番上に追加します。 Class Sample.Person Extends %Persistent{ Property Address As %String; Property Name As %String; /// 誕生日Property DOB As %Date; <storage name="Default"><data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="2"><value>Name</value></value> <value name="3"><value>DOB</value></value> <value name="4"><value>Address</value></value></data> <datalocation>^Sample.PersonD</datalocation><defaultdata>PersonDefaultData</defaultdata><extentsize>100000</extentsize><idlocation>^Sample.PersonD</idlocation><indexlocation>^Sample.PersonI</indexlocation><streamlocation>^Sample.PersonS</streamlocation><type>%Library.CacheStorage</type> } ストレージ定義情報を参照すると、クラス定義の表示上1番上に登録したAddressプロパティは、 <value name="4"><value>Address</value></value> $ListBuildの4番目に格納される定義として追加されています。 つまり、ストレージ定義は、クラス定義の表示上、どこに追記されても、後から追加されたプロパティについては、格納位置として、一番最後に追加していく仕組みがわかります。 ということで、クラス定義上の表示順と、ストレージ定義の格納順は必ずしも一致しない事があります。 なお、ストレージの格納順序を変更することはできます。 以下の例は、AddressとNameの格納順を、 ≪現在≫ Address → 4番目 Name → 2番目≪変更後≫ Address → 2番目 Name → 4番目に変更した状態のストレージ定義例です。(一部抜粋) <data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="4"><value>Name</value></value> <value name="3"><value>DOB</value></value> <value name="2"><value>Address</value></value></data> ご覧いただいた通り、AddressプロパティとNameプロパティのスロット番号を入れ替えるだけで、ストレージ定義の格納順の変更が行えます。 ただ、ストレージ定義情報の順番のみが入れ替わるだけであり、既存グローバル変数のデータが入れ替わる事はありません。 この点、ご注意ください。(修正したストレージ定義に合わせて、グローバル変数を入れ替えたい場合は、手動でグローバル変数の中身を変更する必要があります。)
記事
Megumi Kakechi · 2021年4月7日

ファイル入出力処理をスクリプトで記述する方法

これは InterSystems FAQ サイトの記事です。ファイル入出力処理を行うには、ライブラリクラスを利用する方法が便利です。 ライブラリクラスを使用する以外には、Open/Use/Close コマンドを使用する方法もあります。<※1> ファイル入出力処理には、%Library.Fileクラス、%Stream.FileCharacter/%Stream.FileBinary を使用します。 簡易例やプロパティ/メソッド詳細は、以下ドキュメントをご参照ください<※2>。クラスリファレンス【IRIS】クラスリファレンス 【補足】%Libraryパッケージは、クラス定義構築基盤として利用するクラスが多いためパッケージ名を省略することができます。(%Library.File は %File として利用できます) 【A】%Fileクラスを利用する方法 %Fileクラスには、ファイル入出力操作の他に、ディレクトリ作成(CreateDirectory()など)/存在チェック(Exists())/OS非依存でファイルパス取得(NormalizeFilename()など)が行える様々な便利メソッドを用意しています。 ファイル出力処理手順は以下の通りです。 (1) ファイル用オブジェクトを作成 ファイル名をフルパスで指定しながらファイル用オブジェクトを作成します。 set file=##class(%File).%New("c:\kit\text.txt") (2) ファイルを新規書き込みモードを指定しながらオープン   N:新規作成、W:書込み、S:ストリーム形式 set st=file.Open("NWS") 【ご参考】指定の文字コード(例えば UTF-8)でファイル入出力を行う場合は、Kパラメータを使用します。 set st=file.Open("NWSK\UTF8\") (3) ファイルへの書き込み Write()メソッド:現在の位置に引数で指定した値を出力します。WriteLine()メソッド:現在の位置に引数で指定した値を改行付きで出力します。 set st=file.WriteLine("1行目") set st=file.WriteLine("2行目") set st=file.WriteLine("3行目") (4) ファイルをクローズ do file.Close() (5) オブジェクト消去 kill file ファイル入力処理手順は以下の通りです。 (1) ファイルオブジェクトを作成 set file=##class(%File).%New("c:\kit\text.txt") (2) ファイルを読み込みモードを指定しながらオープン   R:読込、S:ストリーム形式 set st=file.Open("RS") (3) AtEndプロパティに 1 が設定されるまでファイルの読み込み    Read()メソッド:引数に指定した長さまで読み込み   ReadLine()メソッド:1行ずつ読み込み   ※ AtEndプロパティはファイルの最後(EOF)を検出すると 1 が設定されるプロパティです。  【補足】ストリームの先頭位置に戻るには Rewind()、後方への移動は MoveToEnd() を使用します。 while '(file.AtEnd){ write file.ReadLine(),! } (4) ファイルをクローズ do file.Close() (5) オブジェクト消去 kill file 【B】%Stream.FileCharacterを利用する方法 %Fileと異なる点は、ファイルオープン時にパラメータを使用しなくても利用できる点です。ファイル出力処理手順は以下の通りです。 (1) ファイルオブジェクトを作成 set file=##class(%Stream.FileCharacter).%New() (2) (1)で作成したオブジェクトのFilenameプロパティにオープン対象のファイルをフルパスで指定 set file.Filename="c:\kit\text.txt" 【ご参考】指定の文字コードでファイル入出力を行う場合は、TranslateTableプロパティを利用します。 set file.TranslateTable="UTF8" (3) ファイルへの書き込み %File()と同様にWrite()/WriteLine()を使用します。 【補足】書き込み時、ストリームの後方へ位置を移動すると追記書き、先頭へ移動すると新規書き込みとして動作します。   (先頭位置から書き込みを行うと、以前登録のあった内容はクリアされ新規書き込みとして扱われます。) 後方移動には MoveToEnd() を使用します。 先頭へ戻るには Rewind() を使用します。 (4) ファイルの保存 set st=file.%Save() (5) オブジェクト消去 kill file <※1>コマンドでファイル入出力処理を行う方法は、以下ドキュメントをご参照ください。シーケンシャルファイルの入出力について【IRIS】シーケンシャルファイルの入出力について <※2> クラスリファレンス(=クラスドキュメント)の開き方は以下の通りです。 [ランチャー] > [ドキュメント] > [クラスリファレンス]スタジオから開く方法は以下の通りです。スタジオの表示メニュー > [クラスドキュメントの表示] 以下のトピックもあわせてご覧ください。 文字コードを変換するときに利用できる変換テーブル名は何ですか?
記事
Toshihiko Minamoto · 2022年7月26日

Jupyter Notebooks に ObjectScript を追加する方法

![ObjectScript カーネルのロゴ](https://i.imgur.com/L4pi1da.jpg) [Jupyter Notebook](https://jupyter.org/) は、多数の異なるマークアップ言語とプログラミング言語でコードを実行できるセルで構成された対話型環境です。 Jupyter はこれを実現するために適切なカーネルに接続しなければなりませんが、 ObjectScript カーネルがなかったため、それを作成することにしました。 [こちら](objectscriptkernel.eastus.cloudapp.azure.com)から試すことができます。 結果を少し覗いてみましょう。 ![代替テキスト](https://i.imgur.com/DPQFFnd.gif "スニークピーク") ## Jupyter カーネルの基礎 [Jupyter カーネル](https://jupyter-client.readthedocs.io/en/stable/kernels.html)はいくつかの方法で作成できます。 ここでは、Python ラッパーカーネルを作成することにしましょう。 `ipykernel.kernelbase.Kernel` のサブクラスを作成して、特定の言語で実行されるコードを受け取る `do_execute` メソッドを実装する必要があります。 つまり、ある ObjectScript コードを取得して、何らかの方法で実行し、ノートブックにその結果を返すという概念です。 でも、実際にはどうすればよいのでしょうか。 では、その方法をさらに噛み砕いて説明しましょう。 ## ObjectScript コードを IRIS に送る まず初めに、コードを IRIS に送る必要があります。 ここで使用するのが、[Python 用の IRIS Native API](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_PYTHON_NATIVE) です。 ここでは、`irisnative` パッケージをインポートして、接続を確立するだけです。 ``` def get_iris_object(): # InterSystems IRIS への接続を作成する connection = irisnative.createConnection('iris', 51773, 'IRISAPP', '_SYSTEM', 'SYS') # iris オブジェクトを作成する return irisnative.createIris(connection) ``` その後で、この接続を使用して、IRIS データベースに格納されているクラスを呼び出すことができます。 ``` def execute_code(self, code): class_name = "JupyterKernel.CodeExecutor" return self.iris.classMethodValue(class_name, "CodeResult", code) ``` `CodeExecutor` クラスと `CodeResult` メソッドは何に使用されているのでしょうか。 ではそれを見てみましょう。 ## ObjectScript コードを実行する このクラスの目的は、1 行の ObjectScript コードを実行して、実行の結果を含む JSON オブジェクトを返すことです。 コードを `CodeResult` の `vstrCommand` 変数に渡します。 まず、IO を現在のルーチンにリダイレクトします。その後、渡されたコードを [XECUTE](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_cxecute) コマンドで実行し、IO を元の場所にリダイレクトして、結果を返します。 ``` Include %sySystem Class JupyterKernel.CodeExecutor { ClassMethod CodeResult(vstrCommand As %String) As %String [ ProcedureBlock = 0 ] { set tOldIORedirected = ##class(%Device).ReDirectIO() set tOldMnemonic = ##class(%Device).GetMnemonicRoutine() set tOldIO = $io try { set str="" set status = 1 //IO を現在のルーチンにリダイレクトする。以下に定義するラベルを利用します use $io::("^"_$ZNAME) //リダイレクトを有効にする do ##class(%Device).ReDirectIO(1) XECUTE (vstrCommand) } catch ex { set str = ex.DisplayString() set status = 0 } //元のリダイレクト/ニーモニックルーチン設定に戻す if (tOldMnemonic '= "") { use tOldIO::("^"_tOldMnemonic) } else { use tOldIO } do ##class(%Device).ReDirectIO(tOldIORedirected) quit {"status":(status), "out":(str)}.%ToJSON() rchr(c) quit rstr(sz,to) quit wchr(s) do output($char(s)) quit wff() do output($char(12)) quit wnl() do output($char(13,10)) quit wstr(s) do output(s) quit wtab(s) do output($char(9)) quit output(s) set str = str _ s quit } } ``` ## 結果を表示する ObjectScript コードを実行しましたが、次はどうすればよいでしょうか。 その結果を表示する必要があります。 例外がなければ、行単位で結果を表示するだけで済みます。 が、渡されたコードで例外が発生したのであれば、実行を停止し、失敗した行番号、行そのもの、そして発生した例外を表示しなければなりません。 ![代替テキスト](https://i.imgur.com/42zQpo7.gif "サンプルノートブックの実行") ## アプリを起動する このカーネルを自分で試してみましょう。以下のようにして試すことができます。 ## 前提条件 [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) と [Docker](https://www.docker.com/products/docker-desktop) がインストール済みであることを確認してください。 リポジトリを、以下のようにローカルディレクトリに Clone/git pull します。 ``` $ git clone https://github.com/Vekkby/objectsriptkernel.git ``` このディレクトリでターミナルを開き、以下を実行します。 ``` $ docker-compose up -d --build ``` ## 操作方法 ブラウザから以下を使用してノートブックサーバーにアクセスできます。 ``` localhost:8888 ``` 'work' ディレクトリ内に 'hello.ipynb' というサンプルノートブックがあります。 ![代替テキスト](https://i.imgur.com/ualyzhy.gif "サンプルノートブック") ### 投票をお願いします このアプリは、IRIS Native API コンテストに参加しています。 こちら からこのアプリに投票してください。
記事
Toshihiko Minamoto · 2022年12月16日

IRIS IntegratedML を使用した母体リスクを予測する Web アプリ

母体リスクは、医学界でよく知られているいくつかのパラメーターから測定できます。 この測定により、医学界とコンピューター化されたシステム(特に AI)を支援すべく、科学者である Yasir Hussein Shakir は、母体リスクの検出/予測における ML アルゴリズムをトレーニングするための非常に便利なデータセットを公開しました。 このデータセットは、ML の最大級のデータリポジトリとして最もよく知られている Kaggle に公開されています。 https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml ## データセットについて 妊娠中と出産後の母体のヘルスケアに関する情報の不足により、妊娠中の女性の多くは、妊娠に関わる問題で死亡しています。 これは、農村地域や新興国の下位中流家庭の間でより一般的に起きている問題です。 妊娠中は、状態を絶えず観察することで、胎児の適切な成長と安全な出産を保証する必要があります(出典: https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml)。 データは、IoT ベースのリスク監視システムを通じて、様々な病院、地域の診療所、妊産婦ヘルスケアから収集されています。 * Age: 女性が妊娠したときの年齢 * SystolicBP: 最高血圧(mmHg)。妊娠中に重要な属性の 1 つ。 * DiastolicBP: 最低血圧(mmHg)。妊娠中に重要な属性の 1 つ。 * BS: モル濃度(mmol/L)による血糖値。 * HeartRate: 1 分あたりの通常の安静時心拍数。 * Risk Level: 前の属性を考慮した妊娠中の予測リスク強度レベル。 ## Kaggle から母体リスクデータを取得する Kaggle の母体リスクデータは、Health-Dataset アプリケーション(https://openexchange.intersystems.com/package/Health-Dataset)を使って IRIS テーブルに読み込めます。 これを行うには、module.xml プロジェクトから依存関係(Health Dataset 用の ModuleReference)を設定します。 Health Dataset アプリケーションリファレンスを含む Module.xml <?xml version="1.0" encoding="UTF-8"?> <Export generator="Cache" version="25">   <Document name="predict-diseases.ZPM">     <Module>       <Name>predict-diseases</Name>       <Version>1.0.0</Version>       <Packaging>module</Packaging>       <SourcesRoot<src/iris</SourcesRoot>       <Resource Name="dc.predict.disease.PKG"/>       <Dependencies>         <ModuleReference>           <Name>swagger-ui</Name>           <Version>1.*.*</Version>         </ModuleReference>         <ModuleReference>           <Name>dataset-health</Name>           <Version>*</Version>         </ModuleReference>       </Dependencies>        <CSPApplication         Url="/predict-diseases"         DispatchClass="dc.predict.disease.PredictDiseaseRESTApp"         MatchRoles=":{$dbrole}"         PasswordAuthEnabled="1"         UnauthenticatedEnabled="1"         Recurse="1"         UseCookies="2"         CookiePath="/predict-diseases"        />        <CSPApplication         CookiePath="/disease-predictor/"         DefaultTimeout="900"         SourcePath="/src/csp"         DeployPath="${cspdir}/csp/${namespace}/"         MatchRoles=":{$dbrole}"         PasswordAuthEnabled="0"         Recurse="1"         ServeFiles="1"         ServeFilesTimeout="3600"         UnauthenticatedEnabled="1"         Url="/disease-predictor"         UseSessionCookie="2"       />     </Module>       </Document> </Export> ## 母体リスクを予測するための Web フロントエンドとバックエンドのアプリケーション Open Exchange アプリのリンク(https://openexchange.intersystems.com/package/Disease-Predictor)に移動し、以下の手順に従います。 * リポジトリを任意のローカルディレクトリに Clone/git pull します。 $ git clone https://github.com/yurimarx/predict-diseases.git * このディレクトリで Docker ターミナルを開き、以下を実行します。 $ docker-compose build * IRIS コンテナを実行します。 $ docker-compose up -d * AI モデルをトレーニングするための Execute Query into Management Portal(http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER)に移動します。 * トレーニングに使用するビューを作成します。 CREATE VIEW MaternalRiskTrain AS SELECT BS, BodyTemp, DiastolicBP, HeartRate, RiskLevel, SystolicBP, age FROM dc_data_health.MaternalHealthRisk * ビューを使用して AI モデルを作成します。 CREATE MODEL MaternalRiskModel PREDICTING (RiskLevel) FROM MaternalRiskTrain * モデルをトレーニングします。 TRAIN MODEL MaternalRiskModel * [http://localhost:52773/disease-predictor/index.html](http://localhost:52773/disease-predictor/index.html) に移動し、Disease Predictor フロントエンドを使用して、以下のように疾患を予測します。 ![母体リスク予測器](https://github.com/yurimarx/predict-diseases/raw/master/thirdscreen.png) ## 背後の処理 ### 母体リスク疾患を予測するためのバックエンドのクラスメソッド InterSystems IRIS では、前に作成されたモデルを使って、SELECT の実行により予測することができます。 母体リスク疾患を予測するためのバックエンドのクラスメソッド /// 母体リスクの予測 ClassMethod PredictMaternalRisk() As %Status {     Try {       Set data = {}.%FromJSON(%request.Content)       Set %response.Status = 200       Set %response.Headers("Access-Control-Allow-Origin")="*"             Set qry = "SELECT PREDICT(MaternalRiskModel) As PredictedMaternalRisk, "                   _"age, BS, BodyTemp, DiastolicBP, HeartRate, SystolicBP "                   _"FROM (SELECT "_data.BS_" AS BS, "                   _data.BodyTemp_" As BodyTemp, "                   _data.DiastolicBP_" AS DiastolicBP, "                   _data.HeartRate_" AS HeartRate, "                   _data.SystolicBP_" As SystolicBP, "                   _data.Age_" AS age)"       Set tStatement = ##class(%SQL.Statement).%New()       Set qStatus = tStatement.%Prepare(qry)       If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}       Set rset = tStatement.%Execute()       Do rset.%Next()         Set Response = {}       Set Response.PredictedMaternalRisk = rset.PredictedMaternalRisk       Set Response.Age = rset.Age       Set Response.SystolicBP = rset.SystolicBP       Set Response.DiastolicBP = rset.DiastolicBP       Set Response.BS = rset.BS       Set Response.BodyTemp = rset.BodyTemp       Set Response.HeartRate = rset.HeartRate         Write Response.%ToJSON()       Return 1           } Catch err {       write !, "Error name: ", ?20, err.Name,           !, "Error code: ", ?20, err.Code,           !, "Error location: ", ?20, err.Location,           !, "Additional data: ", ?20, err.Data, !       Return     } } これで、どの Web アプリケーションもこの予測を使用して、結果を表示できるようになりました。 predict-diseases アプリケーションのソースコードは、frontend フォルダをご覧ください。
記事
Megumi Kakechi · 2022年12月1日

ライセンスサーバでライセンスを一元管理する方法

複数のインスタンス間でライセンスを共有する際に、ライセンスサーバを立ててライセンスの使用量を管理します。IRISライセンスサーバには、ライセンスの使用量管理に加えて便利な新しい機能が追加されました。 -- ライセンスサーバでできること --1. マルチサーバライセンス(共有ライセンス)の統合管理(使用量の管理)2. 各インスタンスへのライセンスキーの配布・管理【New】 1 は従来からのライセンスサーバの機能で、関連記事 にて機能紹介をしております。 2 はIRIS以降使用できるようになった新しい機能です。 複数のインスタンスを構成している場合、中央管理しているディレクトリに格納されているライセンスキーファイル(*.key)を各インスタンスに配布・管理するようにライセンスサーバを構成することができます。その場合、個々のインスタンスにライセンスキー(iris.key)を配置する必要はありません。ライセンスキーはユニークな LicenseID (※1, ※2) によって識別され、各インスタンスの起動時にロード&有効化されます。 ※1 ライセンスキーをテキストエディタで開くと、[ConfigFile] セクションにて設定されている LicenseID を確認できます。※2 ライセンスキーに記載されたLicenseIDの値を各インスタンスで事前に設定します(方法詳細は後述)。 ライセンス反映時、ライセンスキーに記載されたLicenseIDと各インスタンスの設定値を照らし合わせ、一致した場合にキーが各インスタンスにロードされます。 【設定方法】1.ライセンスサーバを構成するインスタンスで KeyDirectory プロパティを設定します。 KeyDirectory で指定したディレクトリ内に、すべての ライセンスキーを配置します。 複数のライセンスキーを配置する場合は、LicenseID で区別するようにします(同じ LicenseID のキーは複数配置できません)。 KeyDirectoryを設定している場合、起動時にそのインスタンスで見つかった有効な *.key ファイルをすべて読み取り、ライセンスサーバに送信します。 ライセンスサーバの設定は、管理ポータルで行います。 管理ポータル: [システム管理] > [ライセンス] > [ライセンス・サーバ] 2.各インスタンスで LicenseID プロパティ(キーファイルの新しいプロパティ)を 設定します。 各インスタンスは LicenseID プロパティを使用して、起動時にライセンスサーバからライセンスキーを要求することができます。 LicenseID が設定されていると、インスタンス起動時に指定したLicenseID のライセンスキーがライセンスサーバーよりロードされて有効化されます。 インスタンスは定期的に、期限切れのキーまたはアップグレードされた新しいキーがあるかどうかを確認します。 LicenseID の設定は、管理ポータルで行います。 管理ポータル: [システム管理] > [構成] > [追加設定] > [開始]:LicenseID もしくは、構成ファイル(iris.cpf)で直接設定することも可能です 。CPF の [Startup] セクションの LicenseID を設定します。 ライセンスサーバはディレクトリからキーファイルを手動で再ロードしてライセンスを更新することも可能です(※3)。 ※3 %SYS ネームスペースで do ReloadKeys^%SYS.LICENSE を実行します。  ライセンスの種類によっては、各インスタンスでインスタンスの再起動が求められる場合があります。 ライセンスサーバーを起動するインスタンスは、LicenseIDが異なる複数のキーをロードすることができます。ライセンスキーは、一意の LicenseID により各インスタンス上で識別されることになります。 コンテナを使用している場合は、すべてのコンテナライセンスを外部ディレクトリ(KeyDirectory プロパティで設定)に配置することもできます。そうすることで、最終的にコンテナ (および /mgr) 内にライセンスファイルを置かないようにすることが可能となります。 詳細は以下のドキュメントをご覧ください。InterSystems IRIS ライセンスの管理 【関連】複数インスタンスでライセンスを共有する場合に必要な設定