検索

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

Java Business Host から PEX への移行

# Java Business Host から PEX への移行 InterSystems IRIS 2020.1 および InterSystems IRIS for Health 2020.1 で [PEX](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EPEX) がリリースされ、Java Business Host を使うよりも優れたかたちで Java プログラムをプロダクション環境に取り込めるようになりました。 PEX は、相互運用性のコンポーネントを構築するための API をすべて提供するほか、Java と .NET の両方で使用できます。 Java Business Host は非推奨となり、今後のリリースで廃止される予定です。 PEX を使うメリット * デベロッパーはプロダクションのすべてのコンポーネントを、Java と .Net のどちらでも構築できる * コンポーネント間で一層複雑なメッセージ構造の受け渡しができる * シンプルな設定 * 開発のワークフローがシンプルな上に、ObjectScript を使う必要がない ここからは、既存の Java Business Host のコードを PEX に移行する方法に注目します。 ## 概要 PEX で使用されるクラスとインターフェースは、Java Business Host (JBH) のものとは異なります。 本記事では、その相違点を要約して解説しますが、詳細は[完全なドキュメント](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EPEX_apiref)をご覧ください。 * [ビジネスサービスの変換](#Converting-a-Business-Service-from-Java-Business-Host-to-PEX) * [ビジネスオペレーションの変換](#Converting-a-Business-Operation-from-Java-Business-Host-to-PEX) * [設定](#Settings) * [メッセージ](#Messages) * [ログ](#Logging) ## ビジネスサービスを Java Business Host から PEX に変換する PEX のビジネスサービスを構築するには、`com.intersystems.gateway.bh.BusinessService` の代わりに、`com.intersystems.enslib.pex.BusinessService` を実装する必要があります。 PEX で使用されるビジネスサービスの設計パターンは、サービスがスレッドを開始してメッセージを作成するというものから、サービスが定期的に呼び出される関数を実装してメッセージを作成するというものに一変しました。 JBH では、以下のようなコードが使われています。 ```java @Override public boolean OnInit(Production p) throws Exception { production = p; if (messageThread == null) { Messager messager = new Messager(); messageThread = new Thread(messager); messageThread.start(); } return true; } ``` 一方の PEX では、関数を 3 つ実装するだけで OK です。 ```java public void OnInit() throws Exception { // 初期化 return; } public Object OnProcessInput(Object messageInput) throws Exception { // ここで SendMessage() か SendMessageAsync() を呼び出します return null; } public void OnTearDown() throws Exception { // シャットダウン return; } ``` また、設定はどのように使用され、メッセージはどのように配信され、ログはどのように記録されるのかも変更する必要があります。 詳しくは後ほど解説します。 ## ビジネスオペレーションを Java Business Host から PEX に変換する PEX のビジネスオペレーションを構築するには、`com.intersystems.gateway.bh.BusinessOperation` の代わりに `com.intersystems.enslib.pex.BusinessOperation` を実装する必要があります。 ビジネスオペレーションの設計パターンは、JBH も PEX も構造的には同じですが、2 つのメインエントリーポイントへのパラメーターが変更されています。 ### OnInit() の変更点 PEX の `OnInit()` はパラメーターを受け取りません。 ### OnMessage() の変更点 PEX の場合、`OnMessage()` には、JBH で使用される `String` の代わりに、ジェネリック型の `Object` が与えられます。 これにより、プロダクションの作成者は好きなメッセージを渡すことができます。 JBH では、アプリケーションに以下のようなコードが使われていたのではないでしょうか ```java public boolean OnMessage(String message) throws Exception { // ビジネスロジックを実行 return true; } ``` PEX では、パラメーターにジェネリック型の Java Object が使用されます。適切にキャストする必要がありますが、String を使った場合よりも一層複雑なメッセージを送信できます。 以下は、ファイルストリームであるリクエストを抽出する方法を示した例です。 ```java public Object OnMessage(Object request) throws Exception { com.intersystems.jdbc.IRISObject streamContainer = (com.intersystems.jdbc.IRISObject)request; com.intersystems.jdbc.IRISObject str = (com.intersystems.jdbc.IRISObject)streamContainer.get("Stream"); String originalFilename = (String)streamContainer.get("OriginalFilename"); Long contentSize = (Long)str.get("Size"); String content = (String)str.invoke("Read", contentSize); // ビジネスロジックを実行 return null; } ``` また、設定が使用される方法、メッセージが配信される方法、ログが記録される方法も変更する必要があります。 詳しくは後ほど解説します。 ## 設定 設定の宣言が簡単になりました。 JBH では、設定は `SETTINGS` 文字列を使って宣言され、以下のようなコードで取り込まれていました。 ```java String setting = production.GetSetting("Min"); if (!setting.isEmpty()) { min = Integer.parseInt(setting); } ``` PEX の場合、設定は単純に public メンバフィールドとなります。 これらは、クラスがインスタンス化されるときに自動的に設定されます。 ```java public int Min = 0; ``` public メンバーフィールドは、Java の基本データ型 (String や int など) であれば、何でもプロダクション環境で設定できます。 ## メッセージ メッセージはよりパワフルに送信できます。 JBH では、メッセージは文字列として送信されます。 一方の PEX を使うと、メッセージは、 ObjectScript で定義されるオブジェクトの場合であれは、オブジェクト (IRISObject) として送信され、Java で定義されているクラスの場合であれば、`com.intersystems.enslib.pex.Message` のサブクラスとして送信されます。 JBH の場合は、以下のようなコードが使われます ```java production.SendRequest(value.toString()); ``` PEX の場合だと、以下のようなコードが使われます ```java MyExampleMessageClass req = new MyExampleMessageClass("message to send"); SendRequestAsync(Target, req); ``` ## ログ ログ機能はすべて似たようなものですが、名前だけが違います。 PEX で情報メッセージをログするときは、`LOGINFO()` を使います。 ```java LOGINFO("Received message"); ``` ## オブジェクトのゲートウェイ Java Business Host では、専用のゲートウェイが必要でしたが、 PEX では、Java ゲートウェイ 1 つで、Java のすべてのニーズに対応できます。 また、ゲートウェイはたくさん使うこともできます。 これはあなた次第です。 こちらの [Java ゲートウェイの手引き](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EJVG_intro) がおすすめです。 ## 結論とフィードバック まだ PEX を試していないという方は、ぜひぜひお試しください。 PEX を使えば、少ないコードで解決できるビジネスの問題の幅をぐっと広げることができます。また、今はすべての作業を .NET で実行できるようにもなりました。 JBH のアプリケーションを PEX へ移行させることに関するご質問や問題は、私か WRC までご連絡ください。
記事
Mihoko Iijima · 2021年10月12日

いつも使用しているユーザで IRIS や Caché にアクセスできなくなった時の原因の探り方(監査の使い方)

開発者の皆さん、こんにちは。 いつも使用しているユーザでアプリケーションや InterSystems 製品(IRIS/Caché/Ensemble/HealthShare) にアクセスしたとき、セキュリティ設定変更などの影響で急にアクセスできなくなった!という場合に、調査に便利な監査ログの参照方法をご紹介します。 ここでは、%Allロールを持つシステム管理ユーザ( _system や SuperUser )で管理ポータルにアクセスできる状態での確認方法をご紹介します。 監査ログですが、まずはシステムで監査が取られる設定になっているかご確認ください(通常無効化されている場合は、調査の時だけ有効に変更してください)。 管理ポータル > システム管理 > セキュリティ > 監査 > 監査を有効に 次に、アクセスできなくなった原因を探るため、以下のシステムイベントの監査を取得できるように変更します。 管理ポータル > システム管理 > セキュリティ > 監査 > システムイベントを構成以下のイベントの「状態変更」をクリックし、 Enabled に はい と表示されるようにします。 %System/%Login/LoginFailure %System/%Security/Protect この状態で、アクセスできない操作をもう1度試し、試した後で「監査データベースの閲覧」ページでエラーの内容を確認します。 管理ポータル > システム管理 > セキュリティ > 監査データベースの閲覧 以下例では、ターミナルにログインした時の監査ログの記録をご紹介します。 ログインに使用するユーザ名は test、 パスワード test、ロールに %Operator が設定されていて、管理ポータルの [システムオペレーション] メニューが利用できるユーザとします。 1) パスワードが異なるとき ターミナルにアクセスしたときの表示は以下の通りです。 ユーザ名:test パスワード:**** アクセスが拒否されました。 監査データベースの閲覧画面を再表示した時の一覧には、「プログラマモード ログイン失敗」と表示されています。 詳細を確認するため、「詳細」のリンクをクリックします。 「イベントデータ」の行にエラーメッセージが記録されています。 エラーメッセージ: エラー #798: パスワード 認証が失敗しました エラー #952: パスワードが不正です 2) ユーザが存在しない時 存在しないユーザ(abc)でログインした場合は、以下のエラーが記録されます(この時も「プログラマモード ログイン失敗」と一覧に表示されます)。 詳細のリンクから「イベントデータ」を確認すると、以下のエラーメッセージが記録されています。 エラーメッセージ: エラー #798: パスワード 認証が失敗しました エラー #838: ユーザ abc が存在しません 3) ユーザ名とパスワードはあっているのにターミナルにアクセスできない時(アクセス拒否 と出るとき) ユーザ名、パスワードの指定はあっていそうなのに、ターミナルにアクセスできないエラーが出ているときの状態です。 以下のエラーが記録されます(この時も「プログラマモード ログイン失敗」と一覧に表示されます)。 詳細を確認します。 今回は、「エラーメッセージ: エラー #836: プログラマーアクセスの権限が不十分です」を出ています。 テストに使用しているユーザは %Operatorロールを持ちますが、ターミナルのアクセスに必要な %Developerロールを持っていません。そのため、アクセス権限不十分とエラーが出ています。 この他、使用しているユーザが「無効」になっている場合もアクセス拒否となり「プログラマーモード ログイン失敗」と表示され、詳細には以下のエラーメッセージが表示されます。 4) ターミナルにアクセスできるけど、特定のネームスペースにアクセスできない状態 ユーザ test の役割が変わり、開発者としてターミナルにアクセスできるユーザに変更する必要があるとします。 ここで、管理者がユーザ test から %Operator ロールを削除し、%Developer ロールを付与したとします。 管理ポータル > システム管理 > セキュリティ > ユーザ > testを選択 > ロール > %Developer付与 ユーザ名:test パスワード:**** USER> やっとターミナルにアクセスできました! 管理ポータルの [システムオペレーション] メニューの操作をルーチンで試そうと %SYS ネームスペースに移動します。 USER>set $namespace="%SYS" SET $NAMESPACE="%SYS" ^ <PROTECT> *c:\intersystems\irishealth3\mgr\ USER> 残念・・。エラーです。 エラーの原因を監査ログを参照して確認します。 Protect のイベントが記録され「Attempt to access a protected resource」と表示されています。 詳細を確認します。 この記録は、mgr以下にある IRIS.dat(= IRISSYSデータベース)に対する <PROTECT> エラーが発生したことを意味します。 これは、ユーザ test から %Operator ロールを削除することで、IRISSYS データベースに対する READ と WRITE の許可がなくなったことが原因です。 %Developer ロールだけでは、アクセスしたいデータベースに対する許可が不足するため、追加でユーザ test に適切なデータベースの許可を付与する必要があります。 例のように、%SYS ネームスペースにアクセスしたい場合は、IRISSYSのデータベースロール(%DB_IRISSYS)を付与することでデータベースに対してREAD/WRITE の許可が追加できます。 再度、ターミナルにユーザ test でログインし直してから %SYS ネームスペースに移動し、試しにユーティリティを実行してみます。 ユーザ名:test パスワード:**** USER>set $namespace="%SYS" %SYS>do ^TASKMGR 1) タスク作成 2) タスク編集 3) タスク一覧 4) タスク削除 5) タスク一時停止 6) タスク再開 7) タスク実行 8) タスクリポート 9) タスクマネージャオプション 10) 終了 オプション? うまく行きました。 いかがでしたでしょうか。 今まで使用していたユーザで急にアクセスできない!という状況になった時、セキュリティ設定に変更がなかったかどうかご確認ください。 もし変更した後アクセスできなくなった場合は、この記事で試したように、監査を使用してどんなエラーが発生しているか確認することができます。 監査についてのドキュメントもあります。ぜひご参照ください。 最後に、普段監査を使用されていない環境は、調査が終わったら「無効化」することをお忘れなく!
記事
Toshihiko Minamoto · 2021年2月2日

Caché データベースブロックの内部構造、パート 2

この記事は Caché データベースの内部構造を説明したこちらの[記事](https://jp.community.intersystems.com/node/485976)の続編です。 そちらの記事では、様々なブロックタイプ、それぞれのつながりやグローバルとの関係について説明しました。 純粋に理論を述べた記事でした。 ブロックツリーを視覚化するのに役立つ[プロジェクト](https://github.com/daimor/CacheBlocksExplorer)を作成しましたので、この記事ではその仕組みを詳しく説明します。 [![](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png)](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png) デモを行うために、新しいデータベースを作成しましたが、Caché のデフォルト機能としてすべての新しいデータベースで初期化されるグローバルは消去しています。 それでは、シンプルなグローバルを作成しましょう。 set ^colors(1)="red" set ^colors(2)="blue" set ^colors(3)="green" ​ set ^colors(4)="yellow" ![](https://lh3.googleusercontent.com/uTlHS8bdRsFrQ1v0kfywcJp-6kQeq6uGas7NOLAuV24e7sSZx2EnFb0WQRPZRYE-0mVBB08Fzf-0lRBCLVbWmTGblVlMzQkydztrDJTNiqLhw4ja1PfHGbaTVFApmL_-Gl-fxb8o) 作成されたグローバルのブロックを表す画像をご覧ください。 これはシンプルなものなので、その説明は Type 9 のブロック (グローバルカタログのブロック) に記載されています。 次にくるのが、「上位ポインタと下位ポインタ」のブロック (Type 70) です (グローバルツリーはまだ浅いため)。ここでは、まだ 8KB の単一のブロックに収まるデータブロックへのポインタを使用できます。 それでは、単一のブロックには収まりきらないほどの数の値を別のグローバルに書き込んでみます。そして、最初のブロックに収まらなかった新しいデータブロックにポイントするポインタブロックの中に新しいノードが表示されます。 それでは、それぞれ 1000 文字を持つ値を 50 個書き込んでみましょう。 このデータベースのブロックサイズは 8192 バイトであることを覚えておいてください。 set str="" for i=1:1:1000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str } ​ quit 下の画像をご覧ください。 ![](https://lh3.googleusercontent.com/tm6axITzAxWACgl5d2V53xHfdWw5MFCRqm92stdMlPjVSSnGvXZH2cRNNoStJQfZUxyCtPk-x9ClbT8xl-lMGJ3qht0zyfs5MPXLjyPt7lSR6_dltexWP1TgbRj2SL4Ot11CQuDY) ポインタブロックレベルでデータブロックにポイントするノードがいくつかあります。 各データブロックには、次のブロックをポイントするポインタがあります (「適切なリンク」)。 Offset は、このデータブロック内で占有されているバイト数をポイントしています。 それでは、ブロックの分割をシミュレートしてみましょう。 ブロックの合計サイズが 8KB をオーバーしてしまうほどの数の値をブロックに追加しましょう。それにより、ブロックは半分に分割されます。 サンプルコード set str="" for i=1:1:1000 { set str=str_"1" } set ^test(3,1)=str set ^test(3,2)=str ​ set ^test(3,3)=str 結果は以下の通りです。 ![](https://lh6.googleusercontent.com/bk_JK4MZaZxevS1Sd3egpWJD9kgfcP7lo1ueN_1t9uvcrI6KhJxtcr9J2mqGfAgKWHuCQ-t-QE20CWR0WcifhKxv8huKCrSYYhYxppViIA2Q80nFRSKZry_UAcUfln6vxYpzOzwY)   ブロック #50 が分割され、新しいデータが入っているのが分かります。 ブロック #50 から取り出された値はブロック #58 に置かれ、このブロックにポイントするポインタがポインタブロックに表示されているのが分かります。 他のブロックに変化はありません。 **長い文字列を使った例** 8KB (データブロックのサイズ) よりも長い文字列を使うと、「長いデータ」で構成されるブロックができます。 そのような状況は、例えば、文字列を 10000 バイトとして書き込んでシミュレートします。 サンプルコード set str="" for i=1:1:10000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str ​ } 結果を見てみましょう。 ![](https://lh3.googleusercontent.com/pPRJxZ_lRjQz6LXzmvPKBNU76XwiAO0xlenWDKR9fYrjmgovPz4dhtbbMx-qiBOZzeNQ49IoaXVf9f3ANsWf_xmZrhZzcFY4Af9VqoChDCXTHKLfcRqtYFGan2ak40dhQ-XPVzLY) 結果としては、新しいグローバルノードは加えずに、値を変更しただけなので、画像に表示されているブロック構造に変更はありません。 しかし、すべてのブロックで、Offset の値 (占有されているバイト数) に変化がありました。 例えば、ブロック #51 の Offset の値は、7088 から 172 に変わっています。 新しい値がブロックに収まらない場合は、データの最後のバイトへのポインタが変更されるということが分かりました。しかし、データはどこに行ったのでしょう? 現時点では、「大きなブロック」に関する情報を示す技術的な可能性はありません。 それでは、^REPAIR ツールを使って、ブロック #51 の新しいデータに関する情報を取得してみましょう。 ![](https://lh5.googleusercontent.com/jnJfjd1eF2lwoM_Zk9N4TUL7XithZu92Z05o-lMrLBVmJfXtmZmHkDqIpdE6MxWBCNWVCXUdJQoyXgnXSQdjKZSJmQc5a3SR0ymXckZQxtGMxd9bcCfCX7EdcXPuFj4oYBYz15c0) このツールの仕組みを詳しく説明いたします。 右側のブロック #52 へのポインタがあり、同じ番号が次のノードの親ポインタブロックで指定されているのが分かります。 グローバルの照合順序は Type 5 に設定されています。 長い文字列を持つノードの数は 7 個です。 場合によっては、1 つのブロックの中に、いくつかのノードのデータ値と別のノードの長い文字列の両方が含まれる場合があります。 また、次のブロックの先頭で予測できる次のポインタ参照も表示されています。 長い文字列のブロックについて: キーワード「BIG」がグローバルの値をとして指定されているのが分かります。 それは、データが実際には「大きなブロック」に保管されていることを意味します。 同じ行には、含まれている文字列の長さの合計とこの値を保管するブロックの一覧が表示されています。 それでは、ブロック #73 (長い文字列のブロック) を見てみましょう。 ![](https://lh4.googleusercontent.com/J8ywd3WylH74L_xk0rQQ7z227FHQmvq9-MPI8L1fjN1ZUy8g2XQ8AdvHMErFisYBxHYzC-2y0ho9k_ZgrUayyxNMtl3r7uO7D4SiWpl_6uTTnf1VY7S9VJ0a093MegSSbznmwjnr) 残念ながら、このブロックはエンコードされた状態で表示されています。 しかし、ブロックヘッダーのサービス情報 (長さは常に 28 バイト) に続いて、私たちのデータが表示されているのが分かります。 データ型が分かっていると、ヘッダーの内容をとても簡単にデコードできます。 位置 値 説明 コメント 0-3 E4 1F 00 00 データの最後をポイントする Offset 8164 バイトとヘッダーの 28 バイトを合わせて合計 8192 バイトあり、ブロックは満タンです。 4 18 ブロックタイプ 記憶にあるかと思いますが、24 は長い文字列の型指定子です。 5 05 照合順序 照合順序 5 は「標準の Caché」を意味します 8-11 4A 00 00 00 適切なリンク ここは 74 になっています。記憶にあるかと思いますが、値はブロック #73 と #74 に保管されます。 ブロック #51 のデータはわずか 172 バイトしか占有していないことを覚えていますか? 大きな値を保存したにも関わらずです。 つまり、有効なデータがわずか 172 バイトとなり、ブロックはほぼ空になったように思えますが、それでも 8KB を占有しているのです! そのような場合、空きスペースには新しい値が入力されることが分かりましたが、Caché ではそのようなグローバルを圧縮することもできます。 [%Library.GlobalEdit](http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Library.GlobalEdit) クラスに CompactGlobal メソッドがあるのはそのためです。 このメソッドの効率を確認するために、サンプルコードを使って大規模のデータを作成してみましょう。例えば、ノードを 500 個作成します。 こちらのコードを実行します。 kill ^test for l=1000,10000 { set str="" for i=1:1:l { set str=str_"1" } for i=1:1:500 { set ^test(i)=str } } quit すべてのブロックを表示するのは控えますが、要点は理解していただけると思います。 データブロックはたくさんありますが、ノードの数は少なくなっています。 ![](https://lh5.googleusercontent.com/98r7ANOIJcjjh1inIP3OB-j_MxqS1eIqIr0SoDKaEMP896Wd5VimDtVwCMEu35wIL8HDojM11hgL2xU_s5Iudjki3VN-yZpfZ-aMwnseuWknFL-dEWwfUzED0cYMfxQ81J6_61nr) 以下のように CompactGlobal メソッドを実行します write ##class(%GlobalEdit).CompactGlobal("test","c:\intersystems\ensemble\mgr\test") 結果を見てみましょう。 ポインタブロックにはノードが 2 個しかありません。つまり、最初はポインタブロックにノードが 72 個もあったのに対し、実際はすべての値が 2 個のノードに移動されているのです。 従い、70 個ものノードを取り除いたことになり、ブロックの読み取り操作を行う回数が減ったため、グローバルをイテレーションしてデータにアクセスする時間が短縮されました。 ![](https://lh5.googleusercontent.com/Q44fAWxJ7NNuUIoL7Gibf-dnNf3GS34_CmspfoXNzy3uKV4Z24Ge2uwgfByFxK15AnUIUHtvJtsYBL4wRbEHUMnoqYACJzL2nEC0miX3uNadIRC2_sVeDtt8Vg_2qnmuqLX2WZV7) CompactGlobal には、グローバルの名前やデータベース、ターゲットとするフィル値 (デフォルトは 90%) など、様々なパラメーターを渡すことができます。 そして、Offset (占有されているバイト数) の値は 7360 となり、デフォルトのフィル値 90% に近くなったことが分かります。 関数には、処理されたメガバイト数や圧縮後のメガバイト数など、複数の出力パラメーターがあります。 以前、グローバルは、今や廃止ツールとされる ^GCOMPACT を使って圧縮されていました。 ちなみに、ブロックが部分的に満たされた状態で変化しないというのはいたって普通のことです。 また、グローバルを圧縮するのは好ましくないと考えられる場合もあります。 ですが、例えば、ほぼ読み取るだけで、変更することが滅多にないというグローバルは、圧縮すると良いかもしれません。 但し、グローバルがしょっちゅう変更される場合なら、データブロックの密度が低いと頻繁にブロックを分割する手間が省けるほか、新しいデータもより素早く保存できます。 本記事のまたさらに次の続編では、InterSystems School 2015 で初の開催となった InterSystems [ハッカソン (hackathon)](http://writeimagejournal.com/?p=1912) の最中に導入された、私自作のプロジェクトのまた別の機能「データベースブロックの分布状況を表すマップ」とその実用的な活用方法について解説いたします。
記事
Mihoko Iijima · 2021年6月17日

【GettingStarted with IRIS】無料体験環境:Sandbox 開始手順について

開発者の皆さん、こんにちは! 2023/2/21追記 チュートリアルページが新しくなり「Developer Hub」に変わりました!(ユーザ登録不要です) チュートリアルの種類や使い方については、「InterSystems Developer Hub:クリック1回で開始できるチュートリアル(4種)のご紹介」をご参照ください。 この記事では、GettingStarted ページのチュートリアルを試す環境として利用できる、無料体験環境 Sandbox の開始手順についてご紹介します。 GettingStarted ページで何ができるのか?については、前の記事でご紹介しています。ぜひご参照ください。 どのチュートリアルからでも Sandbox の作成やアクセス情報を確認することができます。 この記事では、Full Stack Tutorial を試す流れで Sandbox を作成する手順をご紹介します。 1) REGISTERボタンがある画面へ移動 画面左目メニューで、Full Stack Tutorial > Part1:Creating databases with SQL をクリックし、画面右側の「REGISTER」ボタンをクリックすると、ユーザ情報登録画面に移動します。 2) ユーザ情報登録 Sandbox 作成時、以下図の情報をご記入いただきます(* の付いた項目は必須です)。 実際の画面は縦長の画面ですが、図解用に左右に画面を貼り付けています。赤い四角で囲われた項目の入力とチェック、ご記入が終わったら「Continue」ボタンをクリックしてください。 この後、ご登録時にご記入いただいたメールアドレスへトークンを送付します。メールをご確認ください。 3) トークンの入力とパスワードの設定 登録したメールアドレスにトークンが届きます。テキストボックスにトークンを記入し、パスワード設定画面に移動します。 パスワードの設定完了でユーザ登録も完了します。 最後に、「Return to Login」のリンクをクリックします。 4) Sandbox へログインと環境作成 LOGINボタンを押すとユーザ登録情報を使用して自動的にログインが行われます。 (ユーザ名、パスワードの入力画面が出た場合は、ユーザ登録時のメールアドレスと設定されたパスワードでログインを行ってください) LOGIN後、環境作成用の「PROVISION SANDBOX」ボタンが表示されるので、クリックします。 環境構築には数分かかるので、しばらくそのままでお待ちください。 Sandbox の作成が完了すると、Sandbox 専用 IDE へのリンクと、IRIS の管理ポータルへのリンクが表示されます。 Sandbox へのアクセス情報は、GettingStarted ページのどのチュートリアルからでも参照できます。 作成した Sandbox は72時間後に消去されますので、消去された場合は「PROVISION SANDBOX」ボタンを押すことで新環境を作成できます。 72時間経過で自動削除される場合、それまで操作されていた内容も消去されます。予めご了承ください。 以上で、Sandbox の作成方法は終了です。 次は、いよいよチュートリアルの開始方法をご紹介します!
記事
Megumi Kakechi · 2023年2月12日

%TimeStamp型プロパティを使用した範囲指定のクエリが遅い場合の対処方法

これは InterSystems FAQ サイトの記事です。 日時検索で、TimeStamp型のクエリのパフォーマンスが出ない場合の対処法をご紹介します。 %TimeStamp データ型形式 (yyyy-mm-dd hh:mm:ss.ffff)は、人が読めることを目的とした ODBC 日付形式の文字列として格納されます。そのため、どうしてもデータサイズが大きくなりクエリの実行に時間がかかってしまいます。%TimeStamp型のプロパティにインデックスを作成している場合にも、クエリオプティマイザはそのインデックスを優先して最適化するようにはなっておりません。 IRISでは、POSIX 時刻(※)をサポートしているため、TimeStamp値を表すのに %Library.PosixTime データ型形式を使用できます。こちらは、Integer型で保存され、%Timestampの高性能な代替法となります。 ※POSIX 時間は、協定世界時 (UTC) 1970年1月1日 00:00:00(UNIXエポック)からの経過秒数として表されます。 1970-01-01 00:00:00より前の日付は、負の論理値で表されます。 %PosixTime データ型形式(エンコードされた 64 ビットの符号付き整数)は、%TimeStamp データ型よりも少ないディスク容量とメモリ使用で済むため、%TimeStamp よりも優れたパフォーマンスが期待されます。%PosixTime でサポートされる最も古い日時は、0001-01-01 00:00:00 で、論理値は -6979664624441081856 です。そして、サポートされる最後の日時は 9999-12-31 23:59:59.999999 で、論理値は 1406323805406846975 です。 現在日時を%PosixTime型で出力したり、%TimeStamp データ型形式にしたい場合は以下のように行えます。 USER>set ptime = ##Class(%Library.PosixTime).CurrentTimeStamp() USER>write ptime 1154596073773251031 USER>write ##Class(%Library.PosixTime).LogicalToTimeStamp(ptime) 2023-01-24 14:06:06.404055 USER>write ##Class(%Library.PosixTime).TimeStampToLogical("2023-01-24 14:06:06.404055") 1154596073773251031 詳細はクラスリファレンスをご覧ください。%Library.PosixTime *** Cachéをお使いのお客様は、残念ながら%PosixTime データ型形式は使用できません。「それでも何とか TimeStamp型のクエリのパフォーマンスを向上させたい!」場合は、代替案として新しい計算フィールド(Calculated/SqlComputed)をテーブルに追加し、それらにインデックスを作成する方法があります。 こちらを使用する場合、タイムスタンプを $H 形式の文字列で保存するため、インデックスのデータサイズが小さくなった分だけのパフォーマンスが向上します。 例:以下のような計算プロパティを追加します。 Property DataTS As %TimeStamp; Property DataTS2 As %String [ Calculated, SqlComputeCode = { Set {*}=$ZDTH({DataTS},3)}, SqlComputed ]; // これを追加 インデックスは、"2022-09-21 11:31:00" の代わりに "66373,41452" で作成するようになります。 この場合、アプリで使用しているクエリ自体も変更する必要があります。日付の比較は以下のように行います。 SELECT * FROM TEST.TABLE1 WHERE tochar(DataTS2, 'YYYY-MM-DD')||' '||tochar(substring(DataTS2,7,5), 'HH24:MI:SS') BETWEEN '2000-01-01 00:00:00' and '2022-12-31 23:59:59' /// $H 日付データを 'YYYY-MM-DD HH24:MI:SS' 形式に変更する /// tochar(DataTS2, 'YYYY-MM-DD')||' '||tochar(substring(DataTS2,7,5), 'HH24:MI:SS') 【関連】TIMESTAMP型のフォーマットについて日付範囲クエリのSQLパフォーマンスを改善する TIMESTAMP型の項目に対して、TO_CHAR() や TO_DATE() を用いた SELECT を実行するとエラーになります
記事
Mihoko Iijima · 2025年6月25日

管理ポータルの再下位メニューに対してアクセスを制限する方法

これは InterSystems FAQ サイトの記事です。 管理ポータルのシステム管理やシステムエクスプローラーなど最上位のメニューについては、事前定義ロールをユーザに付与することでアクセスを制限することができます。 最下位のメニュー、例えば [システム管理] > [構成] > [システム構成] > [ネームスペース] などは、事前定義ロールの %Manager を付与されたユーザであれば、全てのユーザが利用できてしまいます。 このページに対して、「%Manager ロールを持っているが、あるユーザは使用でき あるユーザは使用できない」のようにユーザ毎のアクセス制限を追加したい場合は、カスタムリソースを作成し再下位メニューに対して作成したカスタムリソースを付与することで制限を追加することができます。 手順は以下の通りです。 カスタムリソースを任意名で作成する。この時パブリック許可は設定しない。 管理ポータルの任意の再下位メニューに 1 で作成したカスタムリソースを設定する。 新規でロールを作成し、1で作成したカスタムリソースに対する USE 許可を設定する。 メニューを利用できるユーザを 3 で作成したロールのメンバーに設定する。 具体的な設定の流れは以下の通りです。 1. カスタムリソースを任意名で作成する。この時パブリック許可は設定しない。 管理ポータル > [システム管理] > [セキュリティ] > [リソース] の画面で新規リソースを作成します。 図例では、Restrict リソースをパブリック許可なしで作成しています。 2、管理ポータルの任意の再下位メニューに 1 で作成したカスタムリソースを設定する。 図では、管理ポータル > [システム管理] > [構成] > [システム構成] > [ネームスペース] の画面に対してアクセス制限を加えたいため、このメニューに 1 で作成したリソースを設定します。 デフォルトの状態は以下の通りです。 リソースを追加した後は以下の通りです。 3、新規でロールを作成し、1で作成したカスタムリソースに対する USE 許可を設定する。 次は、ユーザ毎にアクセスを変えたいため、作成したカスタムリソースの USE 許可を持つロールを作成します。 RestrictedRole ロール作成の流れは以下の通りです。 4、メニューを利用できるユーザを 3 で作成したロールのメンバーに設定する。 予め、TestM1、TestM2 ユーザを作成しそれぞれに %Manager ロールを付与している状態を作っています。 さらに、3 で作成したロールのメンバーに TestM1 ユーザを追加しています。 最後に、設定後の画面表示を確認します。 TestM1 ユーザでログインした場合、[ネームスペース]メニューは利用できますが、TestM2 ユーザは追加設定したカスタムリソースに対する USE 許可を持たないため、メニューにアクセスできないことが確認できました。
記事
Toshihiko Minamoto · 2021年11月9日

別のIRISメッセージビューワを作成する

IRISインターオペラビリティのメッセージビューワで何かを変更できるとしたら、何を変更しますか? 「Dashboard IRIS History Monitor」の記事を公開したところ、素晴らしいフィードバックやリクエストをいただきました。 中には、メッセージビューワの拡張に関するリクエストがありました。   まだプロジェクトを確認していない方は、ぜひご覧ください。絶対に見る価値がありますし、[2019年の最高のInterSystems Open Exchange開発者およびアプリケーション](https://community.intersystems.com/post/best-intersystems-open-exchange-developers-and-applications-2019)の1つとしてブロンズ賞を受賞しました。 「新しい」メッセージビューワに含めようと思う機能についてのアイデアを書き留め始めましたが、これらのリソースをどのようにすれば素早く簡単に見せることができるのでしょうか。 まずは、 一般的に、相互運用性の本番環境をセットアップし、[ドキュメント](https://docs.intersystems.com/iris20191j/csp/docbook/Doc.View.cls?KEY=EGDV_deploying)の指示のとおりに、ターゲットシステムにエクスポートしてデプロイすることから始めます。 これは私があまり好まないプロセスです。 特に何か悪いというわけではありませんが、 コードを使ってすべてを行う考えがあるためです。 誰かがこういったプロジェクトを実行するたびに、次のように開始することを期待しています。 `$ docker-compose build` `$ docker-compose up -d` いかがでしょうか!!! たったこれだけのステップを思い浮かべながら、InterSystemsコミュニティを調べ始めると、いくつかのヒントが見つかりました。 ある投稿では、私が自問していた質問が挙げられていました。「[ルーチンを使って本番環境を作成するにはどうすればよいのか。](https://community.intersystems.com/post/how-create-productions-routine)」 その投稿の中で、@Eduard.Lebedyuk が、コードを使って本番環境を作成する方法を次のように回答しています。 「本番クラスを自動的に作成するには、次を行う必要があります。 1. テストプロダクション用の%Dictionary.ClassDefinitionオブジェクトを作成します。 2. Ens.Config.Productionオブジェクトを作成します。 3. %Dictionary.XDataDefinitionを作成します。 4. (2) を (3) にシリアル化します。 5. XData (3) を (1) に挿入します。 6. (1) を保存してコンパイルします。」 @Jenny Amesのコメントにも、次のように書かれていました。  「私たちがよくお勧めしているベストプラクティスは、逆方向に構築することです。 ビジネスオペレーションを先に構築してから、ビジネスプロセス、そしてビジネスサービスを構築していく方法です...」 というわけで、早速やってみましょう! ## リクエスト、ビジネスオペレーション、およびビジネスサービス クラス**diashenrique.messageviewer.util.InstallerProduction.cls**は、名前から想像できるように、プロダクションのインストールを担当するクラスです。 インストーラのマニフェストは、そのクラスからClassMethod **Install**を呼び出します。 /// 拡張ビューワの表示機能にプロダクションをインストールするヘルパー ClassMethod Install() As %Status { Set sc = $$$OK Try { Set sc = $$$ADDSC(sc,..InstallProduction()) quit:$$$ISERR(sc) Set sc = $$$ADDSC(sc,..GenerateMessages()) quit:$$$ISERR(sc) Set sc = $$$ADDSC(sc,..GenerateUsingEnsDirector()) quit:$$$ISERR(sc) } Catch (err) { Set sc = $$$ADDSC(sc,err.AsStatus()) } Return sc } クラスメソッド**InstallProduction**は、次を作成することで、プロダクションを作成するためのメインの構造をまとめます。 * リクエスト * ビジネスオペレーション * ビジネスサービス * 相互運用性プロダクション コードを使用して相互運用性プロダクションを作成しようと考えているため、完全なコーディングモードに移行して、リクエスト、ビジネスオペレーション、およびビジネスサービスの全クラスを作成しましょう。 これを行うには、いくつかのInterSystemsライブラリパッケージを広範に使用します。 * %Dictionary.ClassDefinition * %Dictionary.PropertyDefinition * %Dictionary.XDataDefinition * %Dictionary.MethodDefinition * %Dictionary.ParameterDefinition クラスメソッド**InstallProduction**は、次のコードを使用して、**Ens.Request**を継承した2つのクラスを作成します。   Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.SimpleRequest","Message")) quit:$$$ISERR(sc) Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.AnotherRequest","Something")) quit:$$$ISERR(sc) ClassMethod CreateRequest(classname As %String, prop As %String) As %Status [ Private ] { New $Namespace Set $Namespace = ..#NAMESPACE Set sc = $$$OK Try { Set class = ##class(%Dictionary.ClassDefinition).%New(classname) Set class.GeneratedBy = $ClassName() Set class.Super = "Ens.Request" Set class.ProcedureBlock = 1 Set class.Inheritance = "left" Set sc = $$$ADDSC(sc,class.%Save()) #; create adapter Set property = ##class(%Dictionary.PropertyDefinition).%New(classname) Set property.Name = prop Set property.Type = "%String" Set sc = $$$ADDSC(sc,property.%Save()) Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv")) } Catch (err) { Set sc = $$$ADDSC(sc,err.AsStatus()) } Return sc }   では、**Ens.BusinessOperation**を継承したビジネスオペレーションのクラスを作成しましょう。 Set sc = $$$ADDSC(sc,..CreateOperation()) quit:$$$ISERR(sc) このクラスを作成するほかに、MessageMapとメソッドConsumeを作成します。 ClassMethod CreateOperation() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         Set classname = "diashenrique.messageviewer.Operation.Consumer"         Set class = ##class(%Dictionary.ClassDefinition).%New(classname)         Set class.GeneratedBy = $ClassName()         Set class.Super = "Ens.BusinessOperation"         Set class.ProcedureBlock = 1         Set class.Inheritance = "left"           Set xdata = ##class(%Dictionary.XDataDefinition).%New()         Set xdata.Name = "MessageMap"         Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("Consume")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("Consume")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")               Do class.XDatas.Insert(xdata)         Set sc = $$$ADDSC(sc,class.%Save())           Set method = ##class(%Dictionary.MethodDefinition).%New(classname)         Set method.Name = "Consume"         Set method.ClassMethod = 0         Set method.ReturnType = "%Status"         Set method.FormalSpec = "input:diashenrique.messageviewer.Message.SimpleRequest,&output:Ens.Response"         Set stream = ##class(%Stream.TmpCharacter).%New()         Do stream.WriteLine("   set sc = $$$OK")         Do stream.WriteLine("   $$$TRACE(input.Message)")         Do stream.WriteLine("   return sc")         Set method.Implementation = stream         Set sc = $$$ADDSC(sc,method.%Save())           Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc } 相互運用性プロダクションを作成する直前のステップでは、ビジネスサービスクラスを作成しましょう。 Set sc = $$$ADDSC(sc,..CreateRESTService()) quit:$$$ISERR(sc) このクラスにはHttpリクエストを受信するためのUrlMapとRoutesがあります。 ClassMethod CreateRESTService() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         Set classname = "diashenrique.messageviewer.Service.REST"         Set class = ##class(%Dictionary.ClassDefinition).%New(classname)         Set class.GeneratedBy = $ClassName()         Set class.Super = "EnsLib.REST.Service, Ens.BusinessService"         Set class.ProcedureBlock = 1         Set class.Inheritance = "left"           Set xdata = ##class(%Dictionary.XDataDefinition).%New()         Set xdata.Name = "UrlMap"         Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do class.XDatas.Insert(xdata)         Set sc = $$$ADDSC(sc,class.%Save())           #; create adapter         Set adapter = ##class(%Dictionary.ParameterDefinition).%New(classname)         Set class.GeneratedBy = $ClassName()         Set adapter.Name = "ADAPTER"         Set adapter.SequenceNumber = 1         Set adapter.Default = "EnsLib.HTTP.InboundAdapter"         Set sc = $$$ADDSC(sc,adapter.%Save())           #; add prefix         Set prefix = ##class(%Dictionary.ParameterDefinition).%New(classname)         Set prefix.Name = "EnsServicePrefix"         Set prefix.SequenceNumber = 2         Set prefix.Default = "|demoiris"         Set sc = $$$ADDSC(sc,prefix.%Save())           Set method = ##class(%Dictionary.MethodDefinition).%New(classname)         Set method.Name = "SendMessage"         Set method.ClassMethod = 0         Set method.ReturnType = "%Status"         Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"         Set stream = ##class(%Stream.TmpCharacter).%New()         Do stream.WriteLine("   set sc = $$$OK")         Do stream.WriteLine("   set request = ##class(diashenrique.messageviewer.Message.SimpleRequest).%New()")         Do stream.WriteLine("   set data = {}.%FromJSON(input)")         Do stream.WriteLine("   set request.Message = data.Message")         Do stream.WriteLine("   set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")         Do stream.WriteLine("   return sc")         Set method.Implementation = stream         Set sc = $$$ADDSC(sc,method.%Save())           Set method = ##class(%Dictionary.MethodDefinition).%New(classname)         Set method.Name = "SendSomething"         Set method.ClassMethod = 0         Set method.ReturnType = "%Status"         Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"         Set stream = ##class(%Stream.TmpCharacter).%New()         Do stream.WriteLine("   set sc = $$$OK")         Do stream.WriteLine("   set request = ##class(diashenrique.messageviewer.Message.AnotherRequest).%New()")         Do stream.WriteLine("   set data = {}.%FromJSON(input)")         Do stream.WriteLine("   set request.Something = data.Something")         Do stream.WriteLine("   set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")         Do stream.WriteLine("   return sc")         Set method.Implementation = stream         Set sc = $$$ADDSC(sc,method.%Save())           Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc }   ## Visual Studioコードの使用 %Dictionaryパッケージを使用してクラスを作成するのは困難な場合があり、読みにくくもありますが、非常に便利です。 コードの可読性を良くしてアプローチをもう少しわかりやすくするために、Visual Studioコードを使用して新しいリクエスト、ビジネスサービス、およびビジネスオペレーションクラスを作成することにします。 * diashenrique.messageviewer.Message.SimpleMessage.cls * diashenrique.messageviewer.Operation.ConsumeMessageClass.cls * diashenrique.messageviewer.Service.SendMessage.cls Class diashenrique.messageviewer.Message.SimpleMessage Extends Ens.Request [ Inheritance = left, ProcedureBlock ] { Property ClassMessage As %String; }   Class diashenrique.messageviewer.Operation.ConsumeMessageClass Extends Ens.BusinessOperation [ Inheritance = left, ProcedureBlock ] { Method Consume(input As diashenrique.messageviewer.Message.SimpleMessage, ByRef output As Ens.Response) As %Status {      Set sc = $$$OK     $$$TRACE(pRequest.ClassMessage)     Return sc } XData MessageMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] {              Consume        }   } Class diashenrique.messageviewer.Service.SendMessage Extends Ens.BusinessService [ ProcedureBlock ] {  Method OnProcessInput(input As %Library.AbstractStream, ByRef output As %Stream.Object) As %Status {     Set tSC = $$$OK     // リクエストメッセージを作成     Set request = ##class(diashenrique.messageviewer.Message.SimpleMessage).%New()     // リクエストメッセージプロパティに値をセット     Set request.ClassMessage = input     // ビジネスプロセスに同期呼び出しを行い、レスポンスメッセージをレスポンスとして使用      Set tSC = ..SendRequestSync("diashenrique.messageviewer.Operation.ConsumeMessageClass",request,.output)     Quit tSC } } コードの可読性の観点では、大きな差があります!  ## 相互運用性プロダクションの作成 相互運用性プロダクションを仕上げましょう。 これを行うには、プロダクションクラスを作成してから、それをビジネスオペレーションとサービスクラスに関連付けます。 Set sc = $$$ADDSC(sc,..CreateProduction()) quit:$$$ISERR(sc) ClassMethod CreateProduction(purge As %Boolean = 0) As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {          #; create new production         Set class = ##class(%Dictionary.ClassDefinition).%New(..#PRODUCTION)         Set class.ProcedureBlock = 1         Set class.Super = "Ens.Production"         Set class.GeneratedBy = $ClassName()         Set xdata = ##class(%Dictionary.XDataDefinition).%New()         Set xdata.Name = "ProductionDefinition"         Do xdata.Data.Write("")           Do class.XDatas.Insert(xdata)         Set sc = $$$ADDSC(sc,class.%Save())         Set sc = $$$ADDSC(sc,$System.OBJ.Compile(..#PRODUCTION,"fck-dv"))         Set production = ##class(Ens.Config.Production).%OpenId(..#PRODUCTION)         Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Service.REST"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())         Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Operation.Consumer"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())             Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Service.SendMessage"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())             Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Operation.ConsumeMessageClass"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())         }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc } プロダクションクラスをビジネスオペレーションとサービスクラスに関連付けるために、クラス**Ens.Config.Item**を使用します。 これは、クラスの作成に%Dictionaryパッケージを使用したのか、VS Code、Studio、またはAtelierを使用したかに関係なく使用できます。 いずれにしても、達成できました! コードを使用して相互運用性プロダクションを作成できました。 ただし、このコードの元の目的を忘れてはいけません。拡張メッセージビューワの機能を示すプロダクションとメッセージを作成するという目的です。 以降のクラスメソッドを使用して、両方のビジネスサービスを実行し、メッセージを生成します。  ### %Net.HttpRequestを使用たメッセージの生成: ClassMethod GenerateMessages() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         Set action(0) = "/demoiris/send/message"         Set action(1) = "/demoiris/send/something"         For i=1:1:..#LIMIT {             Set content = { }             Set content.Message = "Hi, I'm just a random message named "_$Random(30000)             Set content.Something = "Hi, I'm just a random something named "_$Random(30000)             Set httprequest = ##class(%Net.HttpRequest).%New()             Set httprequest.SSLCheckServerIdentity = 0             Set httprequest.SSLConfiguration = ""             Set httprequest.Https = 0             Set httprequest.Server = "localhost"             Set httprequest.Port = 9980             Set serverUrl = action($Random(2))             Do httprequest.EntityBody.Write(content.%ToJSON())             Set sc = httprequest.Post(serverUrl)              Quit:$$$ISERR(sc)         }     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc } ### EnsDirectorを使用したメッセージの生成: ClassMethod GenerateUsingEnsDirector() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         For i=1:1:..#LIMIT {             Set tSC = ##class(Ens.Director).CreateBusinessService("diashenrique.messageviewer.Service.SendMessage",.tService)             Set message = "Message Generated By CreateBusinessService "_$Random(1000)             Set tSC = tService.ProcessInput(message,.output)             Quit:$$$ISERR(sc)         }     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc }   } コードは以上です。 完全なプロジェクトは、[https://github.com/diashenrique/iris-message-viewer](https://github.com/diashenrique/iris-message-viewer.)をご覧ください。 ## プロジェクトの実行 では、プロジェクトの実際の動作を確認しましょう。 まず、git cloneまたはgit pullで、任意のローカルディレクトリにリポジトリを作成します。 `git clone https://github.com/diashenrique/iris-message-viewer.git` 次に、このディレクトリでターミナルを開き、次を実行します。 `docker-compose build` 最後に、プロジェクトでIRISコンテナを実行します。 `docker-compose up -d` さらに、を使用して管理ポータルにアクセスします。 次の画像のように、相互運用性のネームスペースMSGVIEWERが表示されます。 ![](/sites/default/files/inline/images/images/iris_managementportal.png) そしてこれが、私たちの愛らしいプロダクションです。2つのビジネスサービスと2つのビジネスオペレーションがあります。 ![](/sites/default/files/inline/images/images/iris_production.png) 非常にたくさんのメッセージがあります。 ![](/sites/default/files/inline/images/images/standardmessageviewer.png) カスタムメッセージビューワですべてが稼働しているので、その機能を見てみましょう。 ## 拡張メッセージビューワ 相互運用性プロダクションに有効になっているネームスペースのみが表示されることに注意してください。 [![Interoperability Message Viewer](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/InteroperabilityNamespace.png)](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/InteroperabilityNamespace.png)   拡張メッセージビューワには、さまざまなフィルタの作成、nレベルへの列のグループ化、Excelへのエクスポートなどを行える機能と柔軟性が備わっています。 ![Interoperability Message Viewer](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/MessageViewer.png) さまざまなフィルタを使用して、必要な結果を得ることができます。 また、Shiftキーを押しながら列のヘッダーをクリックすると、複数の並べ替えを使用することも可能です。 データグリッドをExcelにエクスポートすることもできるのです! ![](/sites/default/files/inline/images/images/filteroption.gif) ![](/sites/default/files/inline/images/images/filterpanel.gif) さらに、フィルタビルダーオプションを使用して、複雑なフィルタを作成することができます。 使用できる任意の列に対してデータをグループ化し、必要なnレベルを使用して情報をまとめることができます。 デフォルトでは、このグループはDate Created(作成日)フィールドを使用して作成されます。 ![データのグループ化](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/GroupByFunction.gif)   また、列を選択できる機能があります。 次のページには、Ens.MessageHeaderのすべての列があります。デフォルトの列のみが初期ビューに表示されていますが、 「Column Chooser」(列選択)ボタンを使って、ほかの列を選択することができます。 ![列セレクター](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/ColumnChooser.gif) すべてのグループはワンクリックで折りたたみと展開が可能です。 ![](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/Collapse_Expand.gif) SessionId(セッションID)フィールドの情報には、ビジュアルトレース機能へのリンクがあります。 ![ビジュアルトレース](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/MessageVisualTrace.png) 必要に応じて、メッセージを再送することができます。 必要なメッセージを選択し、Resend をクリックするだけで再送信は完了です。 この機能には、次のクラスメソッドが使用されています。 `##class(Ens.MessageHeader).ResendDuplicatedMessage(id)` ![](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/ResendMessage.gif) 最後に、前述のように、データグリッドをExcelにエクスポートすることができます。 ![](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/ExportToExcel.png) Excelの結果には、キャッシュサーバーページ(CSP)に定義されているものと同じフォーマット、コンテンツ、およびグループが表示されます。 追伸: この問題への取り組みで大いに助けてくれた@Renan.Lourencoに、特に深くお礼申し上げます。  
記事
Mihoko Iijima · 2021年11月4日

【GettingStarted with IRIS】MQTT を使った遠隔モニタリング(IRIS の MQTT アダプタを試してみよう!)

開発者の皆さん、こんにちは! この記事では、【GettingStarted with IRIS】シリーズの MQTT アダプタを簡単に試せるサンプルの利用方法についてご紹介します! (MQTTブローカーはインターネット上に公開されているテスト用ブローカーを利用しています)​​ サンプルは、こちら👉https://github.com/Intersystems-jp/Samples-MQTT-EKG-Devices (コンテナで動作します) IRIS/IRIS for Health のバージョン2020.1から、IoT の世界でよく利用される MQTT プロトコルに対応できる MQTT アダプタが追加されました。 MQTTインバウンドアダプタでは、メッセージの Subscribe が行え、MQTTアウトバンドアダプタでは、メッセージの Publish が行えます。 サンプルでは、MQTT を使った遠隔モニタリングをテーマに、患者さんに装着した心電図から心拍数(BPM)をリアルタイムに近い状態で取得し、モニタ画面に患者さん毎の心拍数を表示します(IRIS の MQTT インバウンドアダプタを利用したメッセージの Subscribe をご体験いただけます)。 Publish されるトピックについて サンプルでは、演習環境毎にユニークになるようにコンテナ開始時に以下の形式でトピックを作成しています(末尾の # はワイルドカードの指定です)。 /Student_4629/acmeHospital/EKG/# 実際に Publish されるトピックは患者さんに装着した心電図のデータになるので、# の部分は、Patient-1 や Patient-2 などのように患者さんを特定できる文字列が入ります。 サンプルのシナリオは、1つの医療機関の患者情報を取得する流れにしています(本来であれば複数の医療機関の患者情報をモニタできるようにしたほうが良いのですがシンプルに試すため、1つの医療機関の患者情報を取得する流れにしています)。 トピックの流れ サンプルには1つHTMLファイルが用意されています。このファイルをブラウザで開くと MQTT ブローカーに接続し、データ(心拍数)を1秒ごとにブローカーへ Publish します。 IRIS は、MQTTブローカーから指定のトピックを Subscribe します。 ​​​ MQTT ブローカーを Subscribe する設定を行った IRIS のサービスは、ブローカーからトピックを取得します。 実際の設定は以下の通りです。 受信したトピックは IRIS の中では​​​メッセージ(EnsLib.MQTT.Message)として扱われ、次のコンポーネントであるプロセス(図では Process_MQTT_Request)に渡します。 プロセスでは、受信した MQTT 用メッセージからモニタ表示に利用するデータ(Solution.HeartRate)に変換するため、データ変換を呼び出します。 プロセスエディタの開き方と、中で行われているデータ変換の呼び出しの設定を確認する方法は以下の通りです。 患者ごとの測定値を参照する流れ MQTT ブローカーから Subscribe した心拍数をリアルタイムに近い状態で画面表示するため、データ変換で作成された Solution.HeartRate から1秒ごとに患者ごとの心拍数を収集し、画面に表示しています。 この表示を行うため、1秒間隔で Solution.HeartRate に対するSELECT文が実行されています。 このクエリを実行しているのが、メトリックと呼ぶクラスです。プロダクションでは以下の場所に設定されています。 メトリックでは、指定の呼び出し間隔で Solution.HeartRate から患者ごとの BPM を収集しています。詳細はソースコードをご参照ください。 ということで、実際にサンプルを動かして動作を確認してみましょう!手順は以下の通りです。 (1) git clone (2) コンテナビルド&開始 (3) 演習環境毎のトピック作成 (4) プロダクション開始 (5) サンプルHTMLをブラウザで開いて Publish 開始! (6) モニタ画面で状況確認 (1) git clone git clone https://github.com/intersystems/Samples-MQTT-EKG-Devices (2) コンテナの開始 git clone で作成されるディレクトリに移動します。 cd Samples-MQTT-EKG-Devices Windows 以外でお試しいただいている場合は、setup.sh を実行します。 ./setup.sh コンテナを開始します。 docker-compose up -d (3) 演習環境毎のトピック作成 docker-compose exec iris iris session iris -U %SYS "##class(App.Installer).InitializeDocker()" この実行で演習環境で使用するトピックを指定しています。 注意:appp.htmlで使用するJavaScripとプロダクション設定に演習環境用のトピックを追記しています。この実行を行わないとサンプルは動作しません。 IRISの起動が完了していないと、上記メソッド実行後、以下のメッセージが出力されます。 Sign-on inhibited: Startup or Installation in progress このメッセージが表示される場合は、しばらく待ってから再度実行してください。 正常に実行できると、以下のように演習環境用のトピックが表示されます。 You have successfully initiated the MQTT exercise Please take note of your topic top-level string: /Student_4908/acmeHospital/EKG/# press enter to continue Enterで元の画面に戻ります。 (4) プロダクション開始 ​​​​​​管理ポータルを開きます。👉 http://localhost:52773/csp/sys/UtilHome.csp ユーザ名: SuperUser パスワード(大文字で設定されています): SYS Interoperability > INTEROPネームスペース選択 > 一覧 > プロダクション (5) サンプルHTMLをブラウザで開いて Publish 開始! app.html を開きます👉 http://localhost/app.html サーバ名はご利用の環境に合わせてご変更ください。 IRIS が​ MQTT ブローカーから Subscribe したトピックは、メッセージを利用して確認できます。 開いたプロダクション画面で、サービス:From_EKG_MQTT をクリックし、画面右の「メッセージ」タブを選択します。ヘッダの列にある番号をクリックするとトレース画面が開き、受信したトピックとその値が確認できます。 ​​​​​(6) モニタ画面で状況確認 モニタ画面を開き、患者ごとの心拍数を確認します。 管理ポータル > Analytics > ユーザポータル > SolutionEKG ※管理ポータル 👉 http://localhost:52773/csp/sys/UtilHome.csp 初期状態では3名の患者データが表示されます。 app.html にある 「追加」ボタンで心電図を付けた患者を増やすことができます。 app.html で患者を増やすと、モニタ用画面にも表示が増えます。 また、各患者のバーをスライドさせ、心拍数を変化させると、グラフもそれに合わせて変化し、MQTT ブローカーから Subscribe できていることがわかります。 以上でサンプルの動作確認は終了です。 app.html を閉じると MQTT ブローカーへの Publish が終了します。 最後に、コンテナを停止します。 docker-compose stop コンテナを破棄する場合は、以下のコマンドを実行してください。 docker-compose down サンプルには、完成形の Solution パッケージの他に、Demo パッケージが用意されいてプロダクションの定義やプロセスの作成を試すこともできます。 作成方法については、次の記事でご紹介する予定です!
記事
Toshihiko Minamoto · 2021年8月11日

OAuth 認証と InterSystes IRIS: トラストプロトコルを使いこなす

### 不在時に、セキュリティとプライバシーを維持しながら、コンピューターを相互に信頼させるにはどうすればよいでしょうか? ![](/sites/default/files/inline/images/images/c07270055f2e7600834b6fde7c64fcda.jpg) 「ドライマルティーニを」と彼は言った。 「1 杯。 深いシャンパングラスで。」 「承知いたしました。」 「気が変わった。 ゴードンを 3、ヴォッカを 1、キナリレを半量。 キンキンに冷えるまでよくシェイクしてから、大きめの薄いレモンピールを 1 つ加えてくれ。 わかったかい?」 「お承りいたしました。」 バーテンダーはその考えが気に入ったようだった。 イアン・フレミング著『カジノ・ロワイヤル』(1953 年)より OAuth は、ユーザーログイン情報を伴うサービスを「運用中」のデータベースから、物理的にも地理的にも分離する上で役立ちます。 このように分離すると、ID データの保護が強化され、必要であれば、諸国のデータ保護法の要件に準拠しやすくしてくれます。 OAuth を使用すると、ユーザーは、最小限の個人データをさまざまなサービスやアプリケーションに「公開」しながら、一度に複数のデバイスから安全に作業することができるようになります。 また、サービスのユーザーに関する「過剰な」データを操作しなくてよくなります(データはパーソナル化されていない形態で処理することができます)。 InterSystems IRIS を使用する場合、OAuth と OIDC サービスを自律的かつサードパーティのソフトウェア製品と連携してテストし、デプロイするための既成の完全なツールセットを利用できます。 ### OAuth 2.0 と Open ID Connect OAuth と Open ID Connect(OIDC または OpenID)は、アクセスと識別をデリゲートするためにオープンプロトコルを汎用的に組み合わせたもので、21 世紀現在、人気を得ているようです。 大規模な使用において、これより優れたオプションはまだ誰も思いついていません。 HTTP(S) プロトコル上にとどまり、[JWT(JSON Web Token)コンテナ](https://en.wikipedia.org/wiki/JSON_Web_Token)を使用するため、特にフロントエンドのエンジニアに人気があります。 OpenID は OAuth を使用して機能しています。実際、OpenID は OAuth のラッパーです。 OpenID を電子識別システムの認証と作成に使用するオープンスタンダードとして使用することは、開発者にとって目新しい事ではありません。 2019 年には、公開から 14 周年を迎えました(バージョン 3)。 Webとモバイル開発、そしてエンタープライズシステムで人気があります。 そのパートナーである OAuth オープンスタンダードはアクセスをデリゲートする役割を担っており、12 年目を迎えています。関連する RFC 5849 標準が登場してからは 9 年です。 この記事の目的により、プロトコルの最新バージョンである OAuth 2.0 と最新の [RFC 6749](https://tools.ietf.org/html/rfc6749) を使用することにしましょう。 (OAuth 2.0 は、その前身の OAuth 1.0 とは互換していません。) 厳密に言えば、OAuth はプロトコルではなく、ソフトウェアシステムにアクセス権制限アーキテクチャを実装する際に、ユーザー識別操作を分離して別のトラステッドサーバーに転送するための一連のルール(スキーム)です。 OAuth は特定のユーザーについて何も言及できないことに注意してください! ユーザーが誰であるか、ユーザーがどこにいるのか、またユーザーが現在コンピューターを使用しているかどうかさえも、知ることはできません。 ただし、OAuth を使用すれば、事前に発行されたアクセストークンを使用して、ユーザーが参加することなくシステムと対話することが可能であり、 これは重要なポイントです(詳細は、OAuth サイトにある「[User Authentication with OAuth 2.0](https://oauth.net/articles/authentication/)」をご覧ください)。 [User-Managed Access(UMA)](https://tools.ietf.org/html/draft-hardjono-oauth-umacore-14)プロトコルも OAuth に基づくプロトコルです。 OAuth、OIDC、および UMA を合わせて使用することで、次のような分野で保護された ID とアクセス管理(IdM、IAM)システムを実装することができます。 * 医療分野における患者の [HEART(Health Relationship Trust)](https://openid.net/wg/heart/)個人データプロファイルの使用。 * 製造会社および貿易会社向けの顧客 ID&アクセス管理(CIAM)プラットフォーム。 * [OAuth 2.0 Internet of Things (IoT) Client Credentials Grant](https://tools.ietf.org/html/draft-tschofenig-ace-oauth-iot-00) による、IoT(モノのインターネット)システムにおけるスマートデバイス向けデジタル証明書のパーソナル化。 API エコノミーの新しいアクセス制御ベン図 何よりも、個人データをシステムのほかの部分と同じ場所に保存してはいけません。 認証と認可は物理的に分離する必要があります。 そして、ID と認証を各個人に与えることが理想と言えます。 自分で保管せずに、 所有者のデバイスを信頼するのです。 ### 信頼と認証 ユーザーの個人データを自分のアプリや作業データベースと組み合わさったストレージ場所に保存するのはベストプラクティスではありません。 言い換えれば、このサービスを提供できる信頼のある人を選ぶようにする必要があります。 このサービスは、次の項目で構成されます。 * ユーザー * クライアントアプリ * 識別サービス * リソースサーバー アクションは、ユーザーのコンピューターの Web ブラウザで実行されます。 ユーザーには識別サービスが備わったアカウントがあり、 クライアントアプリは、識別サービスと相互インターフェースとの契約に署名済みです。 リソースサーバーは、識別サービスを信頼して、識別できた人にアクセスキーを発行します。 ユーザーはクライアント Web アプリを実行して、リソースを要求します。 クライアントアプリは、アクセス権が必要なそのリソースへのキーを提示する必要があります。 ユーザーにキーがない場合、クライアントアプリはリソースサーバーへのキーを発行するために契約している識別サービスに接続します(ユーザーを識別サービスに転送します)。 識別サービスは、どのようなキーが必要かを問い合わせます。 ユーザーは、リソースにアクセスするためのパスワードを入力します。 この時点でユーザー認証が行われ、ユーザーの身元が確認されると、リソースへのキーが提供され(ユーザーをクライアントアプリに戻します)、ユーザーがリソースを利用できるようになります。 ### 認可サービスの実装 InterSystems IRIS プラットフォームでは、必要に応じてさまざまなプラットフォームからのサービスをアセンブルできます。 次はその例です。 1. デモクライアントが登録された OAuth サーバーを構成して起動します。 2. デモ OAuth クライアントを OAuth サーバーと Web リソースに関連付けて構成します。 3. OAuth を使用できるクライアントアプリを開発します。 Java、Python、C#、または Node JS を使用できます。 以下の方に、ObjectScript でのアプリケーションコードの例を示しています。 OAuth にはさまざまな設定があるため、チェックリストが役立ちます。 例を見ていきましょう。 IRIS 管理ポータルに移動し、[システム管理]>[セキュリティ]>[OAuth 2.0]>[サーバー]の順に選択します。 各項目には設定行の名前とコロン、そして必要であればその後に例または説明が含まれます。 別の方法として、Daniel Kutac の 3 部構成になっている「[InterSystems IRIS Open Authorization Framework (OAuth 2.0)の実装 - パート1](https://jp.community.intersystems.com/node/478821)」、[パート2](https://jp.community.intersystems.com/node/480201)、そして[パート3](https://jp.community.intersystems.com/node/480196) に記載されているスクリーンショットのヒントを参考にしてください。 次のスクリーンショットはすべて、例として提示されています。 独自のアプリケーションを作成する際は、別のオプションを選択する必要があるでしょう。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_12_54.png) [一般設定]タブで、次のように設定してください。 * 説明: 構成の説明を入力します。「認証サーバー」など。 * ジェネレーターのエンドポイント(以降「EPG」)のホスト名: サーバーの DNS 名。 * サポートされている許可の種類(少なくとも 1 つを選択): * 認可コード * 暗黙 * アカウントの詳細: リソース、所有者、パスワード * クライアントアカウントの詳細 * SSL/TLS 構成: oauthserver [スコープ]タブで、次を設定します。 * サポートされているスコープを追加: この例では「scope1」です。 [間隔]タブで、次を設定します。 * アクセスキー間隔: 3600 * 認可コードの間隔: 60 * キー更新の間隔: 86400 * セッション中断間隔: 86400 * クライアントキー(クライアントシークレット)の有効期間: 0 [JWT 設定]タブで、次を設定します。 * 入力アルゴリズム: RS512 * キー管理アルゴリズム: RSA-OAEP * コンテンツ暗号化アルゴリズム: A256CBC-HS512 [カスタマイズ]タブで、次を設定します。 * 識別クラス: %OAuth2.Server.Authenticate * ユーザークラスの確認: %OAuth2.Server.Validate * セッションサービスクラス: OAuth2.Server.Session * キーの生成クラス: %OAuth2.Server.JWT * カスタムネームスペース: %SYS * カスタマイズロール(少なくとも 1 つ選択): %DB_IRISSYS および %Manager では、変更内容を保存します。   次のステップでは、OAuth サーバーにクライアントを登録します。 [顧客の説明]ボタンをクリックして、[顧客の説明を作成]をクリックします。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_13_15.png) [一般設定]タブで、次の情報を入力します。 * 名前: OAuthClient * 説明: 簡単な説明を入力します。 * クライアントタイプ: 機密 * リダイレクト URL: oauthclient から識別した後に、アプリに戻るポイントのアドレス。 * サポートされている付与の種類: * 認可コード: はい * 暗黙 * アカウントの詳細: リソース、所有者、パスワード * クライアントアカウントの詳細 * JWT 認可 * サポートされているレスポンスタイプ: 次のすべてを選択してください。 * コード * id_token * id_token キー * トークン * 認可タイプ: シンプル [クライアントアカウントの詳細]タブは自動的に入力されますが、クライアントの正しい情報であるかを確認してください。 [クライアント情報] タブには次の項目があります。 * 認可画面: * クライアント名 * ロゴの URL * クライアントのホームページ URL * ポリシーの URL * 利用規約の URL   では、[システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]の順に移動して、OAuth サーバークライアントにバインディングを構成します。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_13_52.png) サーバーの説明の作成: * ジェネレーターのエンドポイント: 一般的なサーバーのパラメーターから取得されます(上記を参照)。 * SSL/TLS 構成: 事前構成済みのリストから選択します。 * 認可サーバー: * 認可エンドポイント: EPG + /authorize * キーエンドポイント: EPG + /token * ユーザーエンドポイント: EPG + /userinfo * キーのセルフテストエンドポイント: EPG + /revocation * キーの終了エンドポイント: EPG + /introspection * JSON Web Token(JWT)設定: * 動的登録以外のほかのソース: URL から JWKS を選択します。 * URL: EPG + /jwks ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_14_00.png) このリストから、たとえばサーバーが OAuth-client にユーザーに関するさまざまな情報を提供できることがわかります(scopes\_supported および claims\_supported)。 また、アプリケーションを実装するときは、共有する準備ができているデータが何であるかをユーザーに尋ねても何の価値もありません。 以下の例では、scope1 の許可のみを要求します。 では、構成を保存しましょう。   SSL 構成に関するエラーがある場合は、[設定]>[システム管理]>[セキュリティ]>[SSL/TSL 構成]に移動して、構成を削除してください。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_15_30.png) これで OAuth クライアントをセットアップする準備が整いました。 [システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]>[クライアント構成]>[クライアント構成を作成]に移動します。 [一般]タブで、次を設定します。 * アプリケーション名: OAuthClient * クライアント名: OAuthClient * 説明: 説明を入力します。 * 有効: はい * クライアントタイプ: 機密 * SSL/TCL 構成: oauthclient を選択します。 * クライアントリダイレクト URL: サーバーの DNS 名 * 必要な許可の種類: * 認可コード: はい * 暗黙 * アカウントの詳細: リソース、所有者、パスワード * クライアントアカウントの詳細 * JWT 認可 * 認可タイプ: シンプル [クライアント情報]タブで、次を設定します。 * 認可画面: * ロゴの URL * クライアントのホームページ URL * ポリシーの URL * 利用規約の URL * デフォルトのボリューム: サーバーに以前に指定したものが取得されます(scope1 など)。 * 連絡先メールアドレス: カンマ区切りでアドレスを入力します。 * デフォルトの最大経過時間(分): 最大認可経過時間または省略できます。 [JWT 設定]タブで、次を設定します。 * JSON Web Token(JWT)設定 * X509 アカウントの詳細から JWT 設定を作成する * IDToken アルゴリズム: * 署名: RS256 * 暗号化: A256CBC * キー: RSA-OAEP * Userinfo アルゴリズム * アクセストークンアルゴリズム * クエリアルゴリズム [クライアントログイン情報]タブで、次を設定します。 * クライアント ID: クライアントがサーバーに登録された際に発行された ID(上記を参照)。 * 発効されたクライアント ID: 入力されません * クライアントシークレット: クライアントがサーバーに登録された際に発行されたシークレット(上記を参照)。 * クライアントシークレットの有効期限: 入力されません * クライアント登録 URI: 入力されません 構成を保存しましょう。 ### OAuth 認可を使用した Web アプリ OAuth は、インタラクション参加体(サーバー、クライアント、Web アプリケーション、ユーザーのブラウザ、リソースサーバー)間の通信チャネルが何らかの形で保護されていることに依存しています。 この役割は SSL/TLS プロトコルが果たしているのがほとんどですが、 OAuth は、保護されていないチャネルでも機能します。 そのため、たとえばサーバー Keycloak はデフォルトで HTTP プロトコルを使用して、保護なしで実行します。 調整と調整時のデバッグが単純化されます。 サービスを実際に使用する際、OAuth のチャネル保護は、厳重な要件に含まれるべきであり、Keycloak ドキュメントに記述されている必要があります。 InterSystems IRIS の開発者は、OAuth に関するより厳密なアプローチに従っており、SSL/TSL の使用を要件としています。 単純化できる唯一の方法は、自己署名証明書を使用するか、組み込みの IRIS サービス PKI([システム管理]>>[セキュリティ]>>[公開鍵システム]を利用することです。 ユーザーの認可の検証は、UAuth サーバーに登録されているアプリケーションの名前と OAuth クライアントスコープの 2 つのパラメータを明示的に示すことで行えます。 Parameter OAUTH2APPNAME = "OAuthClient"; set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized( ..#OAUTH2APPNAME, .sessionId, "scope1", .accessToken, .idtoken, .responseProperties, .error) 認可がない場合に備え、ユーザー ID をリクエストして、アプリケーションを操作する許可を取得するためのリンクを準備しておきます。 ここでは、OAuth サーバーに登録されているアプリケーションの名前を指定して、OAuth クライアントと要求されるボリューム(スコープ)を入力するだけでなく、ユーザーを返す Web アプリケーションのポイントへのバックリンクも指定する必要があります。 Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/" set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, "scope1", ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc)   IRIS を使用して、ユーザーを IRIS OAuth サーバーに登録しましょう。 たとえば、ユーザーに名前とパスワードを設定するだけで十分です。 受信した参照の下でユーザーを転送すると、サーバーはユーザーを識別する手続きを実行し、Web アプリケーションのアカウントデータによって、操作許可が照会されます。また、%SYS フィールドのグローバル OAuth2.Server.Session で自身に結果を保持します。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_08_47.png)![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_10_02.png)   3. 認可されたユーザーのデータを示します。 手続きが正常に完了したら、アクセストークンなどがあります。 それを取得しましょう。 set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT( .#OAUTH2APPNAME, accessToken, "scope1", .aud, .JWTJsonObject, .securityParameters, .sc )   以下に、完全に動作する OAuth の例のコードを示します。 Class OAuthClient.REST Extends %CSP.REST { Parameter OAUTH2APPNAME = "OAuthClient"; Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/"; // to keep sessionId Parameter UseSession As Integer = 1; XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Method="GET" Url = "/" Call = "Do" /> </Routes> } ClassMethod Do() As %Status { // Check for accessToken set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized( ..#OAUTH2APPNAME, .sessionId, "scope1", .accessToken, .idtoken, .responseProperties, .error) // to show accessToken if isAuthorized { set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT( ..#OAUTH2APPNAME, accessToken, "scope1", .aud, .JWTJsonObject, .securityParameters, .sc ) &html< Hello!<br> > w "You access token = ", JWTJsonObject.%ToJSON() &html< </html> > quit $$$OK } // perform the process of user and client identification and get accessToken set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, "scope1", ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc) if $$$ISERR(sc) { w "error handling here" quit $$$OK } // url magic correction: change slashes in the query parameter to its code set urlBase = $PIECE(url, "?") set urlQuery = $PIECE(url, "?", 2) set urlQuery = $REPLACE(urlQuery, "/", "%2F") set url = urlBase _ "?" _ urlQuery &html< <html> <h1>Authorization in IRIS via OAuth2</h1> <a href = "#(url)#">Authorization in <b>IRIS</b></a> </html> > quit $$$OK } } コードの作業コピーは、InterSystems GitHub リポジトリ()にもあります。 必要に応じて、OAuth サーバーと OAuth クライアントに高度なデバッグメッセージモードを有効にしてください。これらは、%SYS エリアの ISCLOG グローバルに記述されます。 set ^%ISCLOG = 5 set ^%ISCLOG("Category", "OAuth2") = 5 set ^%ISCLOG("Category", "OAuth2Server") = 5 詳細については、「[IRIS、OAuth 2.0 と OpenID Connect の使用](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH)」ドキュメントをご覧ください。 ### まとめ これまで見てきたように、すべての OAuth 機能には簡単にアクセスでき、完全に使用できる状態になっています。 必要に応じて、ハンドラークラスとユーザーインターフェースを独自のものに置き換えることができます。 OAuth サーバーとクライアントの設定は、管理ポータルを使う代わりに、構成ファイルで構成することも可能です。
記事
Mihoko Iijima · 2021年6月17日

【GettingStarted with IRIS】チュートリアルを始めよう!その1:Full Stack チュートリアル

開発者のみなさん、こんにちは! 2023/2/21追記 チュートリアルページが新しくなり「Developer Hub」に変わりました! Full Stackチュートリアルの開始方法や他のチュートリアルについて詳しくは、「InterSystems Developer Hub:クリック1回で開始できるチュートリアル(4種)のご紹介」をご参照ください。 この記事では、GettingStarted ページの無料体験環境(Sandbox)で試せるチュートリアルの中から、「Full Stack Tutorial」の使い方をご紹介します。 GettingStarted ページでできることについては、こちらの記事でご紹介しています。 無料体験環境(Sandbox)の開始手続きについては、こちらの記事でご紹介しています。ぜひご参照ください。 この記事では、チュートリアルを GettingStarted ページの Sandbox でご体験いただく流れを記載しています。Full Stack Tutorial のパート1 を開き、ログインいただくと Sandbox へのアクセス情報が表示されますので、Sandbox 用 IDE のリンクなどはお手元の環境でご確認ください。 また、チュートリアルの流れの中で、IRIS への接続情報を Sandbox 用 IDE で修正する内容があります。Full Stack Tutorial の対象ページを開き、お使いの Sandbox の情報をご確認ください。 Full Stack Tutorial 概要 (オリジナルはこちらから👉 https://gettingstarted.intersystems.com/full-stack/) InterSystems IRIS data platform は、マルチモデル(SQL、NoSQL)や、マルチワークロード(トランザクションとリアルタイム分析)が行える DBMS 機能、システム統合、変換、API管理、ビジネスロジックを操作できるプラットフォームで、組み込みの統合、分析機能、BI、機械学習、自然言語処理を含む強力で様々な機能を提供しています。これら機能を利用するために、別製品を追加することも、データを別の構成に移動することも不要で、全て 1 つのプラットフォームで操作いただけます。 フルスタックチュートリアルでは、小さな製造会社(焙煎したてのコーヒーを販売する会社)の基本的な情報管理インフラを作成していくテーマを用意しています。 この会社では、焙煎したばかりのおいしいコーヒー豆を焙煎、包装、販売しています。 このチュートリアルを通して、InterSystems IRIS data platform が IT アーキテクチャのバックボーンとしてどのように機能するかを学習いただけます。 このチュートリアルは、3つのパートに分かれています。 コーヒーメーカーとして、生豆の在庫管理からオンラインポータルでの販売までを設定するためのプロセスをご紹介します。 パート1 架空会社「IRIS コーヒーカンパニー」で使用する、簡単な在庫処理システムを作成します。 IRIS が他の多くのデータベースと同様に、テーブルの作成やデータの読み込みに標準的な SQL を使用できることをチュートリアルを通して学習します。また、Python を使用して JSON 形式で送付されてくる注文情報を処理します。 パート1の終わりには、新しいコーヒー豆の納品を会社の在庫として処理できるようになります。 パート2 在庫管理、焙煎、販売など、ビジネスのさまざまな処理を RESTful サービスを介して通信できるようにします。 コーヒーの焙煎所は在庫から豆を要求し、焙煎と包装の後、REST サービスを使って最終製品をカタログに載せ、オンラインで販売します。 これらの処理は全て、チュートリアルで作成する RESTful サービスを利用して実行されます。 パート3 人気のある JavaScript フレームワーク Vue.js を使用して、焙煎職人が作ったコーヒー豆を販売するオンラインストアを作成します。 1) パート1:SQLを使用してデータベースを作成する このチュートリアルを完成させるためには、無料体験環境の Sandbox の準備が必要です。 こちらのページにアクセスし👉https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/)まだログインされていない場合は、ログインを行ってください。 ユーザ登録がまだの場合は、こちらの記事をご覧いただき、ユーザ登録後、ログインを行ってください。 ログイン後、 ボタンをクリックします。既にクリックされている場合は、アクセス情報が以下のように表示されます。 情報表示の一番上に「Sandbox IDE」と書かれたリンクがあります。こちらをクリックするとブラウザでアクセスできる Sandbox 専用 IDE が開きます(下図)。 チュートリアルは、この IDE を使用します。 準備ができたら、フルスタックチュートリアル用ソースコードを git clone するため、画面中央下のターミナルウィンドウで以下実行します。 git clone https://github.com/intersystems/quickstarts-full-stack.git IDE の画面左側に quickstarts-full-stack フォルダが参照できれば、git clone 成功です。 この IDE は Theia で、Visual Studio Code とよく似た機能を持っています。左側にファイルエクスプローラーがあります。右側にはコード編集パネルがあり、編集パネルの下にはターミナル・ウィンドウがあります。 ✨いよいよチュートリアルの開始です!✨ データベースを作成します。 IRIS コーヒーカンパニーは、3つの部門に分かれています。 倉庫にはコーヒーの生豆の在庫が保管されています。テーブル名:ICO.inventory にデータが格納されます。 焙煎所は、コーヒー豆を焙煎する部門で、データを保存する必要がない部門です。 Storefront は、焙煎したコーヒーを販売する店舗です。販売データはテーブル名:ICO.catalog に格納されます。 IRIS ターミナルをSQL用モードに切り替え、CREATE文を使って2つのテーブルを作成します。 手順は以下の通りです(IDEからアクセスできます)。 (1) Sandbox の IDE を開きます(パート1:https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#database-creation を開き、ログインしていると以下のように IDE へのリンクが表示されます)。 (2) IDE のメニューから、InterSystems > Web Terminal を選択します。別タブで Web ターミナルが開きます。 (3) ログイン画面が出てきたら、ユーザ名:tech  パスワード:demo を入力してログインします。 (4) プロンプトに USER> と表示される画面が開きます。 (5) Web ターミナルのモードを SQL 実行モードに変えるため、/sql を入力します。 (Web ターミナル専用コマンドです)。 (6) 以下のSQL文をコピーして、Web ターミナルに貼り付けます(Ctrl +v でペーストできます)。 CREATE TABLE ICO.inventory ( vendor_id VARCHAR(128), vendor_product_code VARCHAR(128), quantity_kg DECIMAL(10,2), date_arrival DATE ) プロンプトが戻ってくるまでお待ちください。 テーブルが作成できたか確認のため、SELECT * from ICO.inventory を実行してみます(まだレコードがないので、「Nothing to display」と表示されます)。 SELECT * from ICO.inventory 7) 次に、ICO.catalog テーブルを作成するため、以下 SQL をコピーし、Web ターミナルに貼り付け実行します。 CREATE TABLE ICO.catalog ( catalog_id BIGINT IDENTITY, product_code VARCHAR(128), quantity INTEGER, price DECIMAL(10,2), time_roasted DATETIME, roasting_notes VARCHAR(2048), img VARCHAR(2048) ) (8) 作成が完了したら、データはありませんが、SELECT * from ICO.catalog を実行します(これも、Nothing to display と表示されれば成功です)。 SELECT * from ICO.catalog ここまでで、テーブル定義の作成は終了です。 (9) Web ターミナルで、 /sql を実行すると、元のターミナルプロンプトに戻ります(=IRIS の ObjectScript が実行できるターミナルに戻ります)。 Python を利用してデータをロードします。 チュートリアルでは、Python でデータをロードする処理を作成しています。 大まかな処理の流れは以下の通りです。 世界中のベンダーから生豆の出荷依頼が来ると想定し、生豆の注文をデータベースに入力できるようにします。出荷情報は JSON 形式とし、単一の注文マニフェストファイルで送付される仕様としています。 注文マニフェストの例は、quickstarts-full-stack/setup/order_manifest.json にあります。IDE の左画面を利用してファイルを開いてみましょう。 データロードには、Python のプログラムを使用します。プログラム内では、注文マニフェストの JSON ファイルを解析し、データベースに接続し INSERT を実行しています(データ登録時に SQL が使用されますが、今回はWeb ターミナルではなく、Python のプログラム内で実行しています)。 注文マニフェストをデータベースにインポートする処理を作成します。 Python の標準ライブラリを使用して、指定ディレクトリから JSON ファイルを読み込みます。その後 ODBC 経由でデータベースに接続し、SQL の INSERT をし湯押して、JSON データを IRIS に登録します。 次の Python コードの解説が不要な場合は、サンプルスクリプトの実行に移動してください。 Python コードの中身解説 スクリプトは setup/manifest_importer.py にあります。 以下、データロード用スクリプトで行っている重要な要素について解説します。 main() 関数では、JSON 注文マニフェストファイルをインポートし、検証を行い、inventory テーブルに登録するために必要な構造をチェックしています。 def main(): with open('./order_manifest.json') as f: data = json.load(f) data, status, exp = validate_manifest(data) 次に、connection.config ファイルにあるデータベースの接続情報を読み込んでいます。 connection_detail = get_connection_info("connection.config") ip = connection_detail["ip"] port = int(connection_detail["port"]) namespace = connection_detail["namespace"] username = connection_detail["username"] password = connection_detail["password"] driver = "{InterSystems ODBC}" ODBCドライバーを使ってデータベースへの接続を設定します。 connection_string = 'DRIVER={};SERVER={};PORT={};DATABASE={};UID={};PWD={}' \ .format(driver, ip, port, namespace, username, password) connection = pyodbc.connect(connection_string) JSON データ(data)とデータベース接続オブジェクト(connection)を使って load_manifest() 関数を呼び出します。 msg = load_manifest(data, connection) load_manifest() 関数では、JSON ファイル内のすべてのアイテムを繰り返し取得しながら INSERT 文を組み立て、前の手順で作成した ICO.inventory テーブルに各アイテムを挿入します。 方法は以下の通りです。最初に、INSERT 文を作成しています。 fieldnamesql = "INSERT INTO ICO.inventory (vendor_id, vendor_product_code, quantity_kg, date_arrival)" 次の2行では、現在の日付を使用して「2021-06-16」の形式の日付文字列を作成します。この日付は、製品が倉庫に到着した日付に使用します。 today = date.today() mydate = today.strftime("%Y-%m-%d") このコードは、JSON ファイル内の各アイテムのオブジェクトをループし、取得したデータを使用して INSERT 文の VALUES の部分を作成しています。 その結果、次のような INSERT 文が完成します。 INSERT INTO ICO.inventory (vendor_id, vendor_product_code, quantity_kg, date_arrival) VALUES (ETRADER, ETHIOPIA32, 200, ‘2021-06-16’) 次に、ここまでの解説で作成した SQL 文(変数 valsql)を実行します。 load_manifest() 関数の1行目でデータベースカーソルを定義しているので、カーソルに SQL 文を入力して execute() を実行しています。 cursor.execute(valsql) Pythonのデータローダスクリプトを実行する コードの解説が終了しましたので、プログラムを実行してみましょう。 最初に、ODBCドライバのインストールを行います。 Sandbox の IDE を開きます。IDEを開く URL は https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#jsondataimport を開き、「Run the Python import」近くに表示されます。表示されない場合は、ログインを行ってください。 IDE を開いたら、ターミナルウィンドウで、以下実行してください。 cd /home/project/quickstarts-full-stack/setup sudo odbcinst -i -d -f pyodbc_wheel/linux/odbcinst.ini 次に、データベースの接続情報を確認します。 (1) Sandbox の IDE で /home/project/quickstarts-full-stack/setup/connection.config を開きます。 (2) IP アドレスに記載されている文字列を、修正します。 https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#jsondataimport を開き 「Database connection settings for your sandbox」の近くに変更対象の IP アドレスが表示されます。 図例だと、ip の設定に 34.71.28.209 を指定します。 port の設定も、27404 に変更します。 (3) 変更後、ファイルを保存します(Ctrl + s)。 接続情報変更後、Sandbox の IDE のターミナルウィンドウで以下実行します。 python manifest_importer.py 図例にあるような、INSERT 文の実行ログを確認できると思います。 IRIS は、Python だけでなく、Java、.NET、Node.js からもアクセスできます。言語別のアクセス方法の体験については、QuickStart をご参照ください。 SQLでデータを確認 Python から登録したデータが正しくテーブルに登録できているかを、Web ターミナルを使用して確認します。手順は以下の通りです。 (1) Sandbox 用 IDE 開きます。 https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#database-query を開き「SQL database queries」の近くに下図のように情報が表示されます。 (2)  InterSystems > Web Terminal を開きます。 (3) ログイン画面が表示されたら、ユーザ名:tech  パスワード:demo でログインしてください。 (4) /sql を入力し、SQL 実行モードに切り替えます。 以下実行します。 select * from ICO.inventory 詳細情報を参照するため、いくつかのクエリを実行してみましょう。 100 kg 以上の大型商品を検索するクエリ SELECT * FROM ICO.inventory WHERE quantity_kg > 100 特定のベンダー(例では、DKEの文字から始まるベンダー名を抽出)のすべての在庫を確認するクエリ SELECT * FROM ICO.inventory WHERE vendor_id LIKE 'DKE' 独自の在庫を追加してみましょう! 最後に、在庫を増やしてみましょう。vendor id、verdor_product_code、quantity_kg に独自の値を登録した JSON マニフェストファイルを作成します。 (1) Sandbox の IDE の左画面を利用して、order_manifest.json を開き、File > Save As... メニューを利用して、別名保存します(order_manifest-original.json)。 注意:データロード用スクリプトでは、JSON マニフェストファイルに含まれるコメント行の対応を行っていません。コメントは使用しないでください。 (2) Sandbox の IDE で order_manifest.json ファイルを開きます。 (3) 好きな値を登録します(英数字でご記入ください)。 (4) python manifest_importer.py を実行します。 パート1で確認できたこと IRIS をリレーショナルデータベースとして使用できることが確認できました。 また、Python から SQL 文を実行できることを確認できました。 この後は、パート2 に進み、会社全体で使用する REST サービスを作成します。 パート2:ObjectScript を使用した REST サービスの開発 (オリジナルページはこちら👉 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/) 適切に設計されたシステムでは、ビジネスアプリケーションをデータベース上で直接操作することはありません。その代わりに、サービスを介したアクセスを提供し、実行されるアクションを制御/監視できるようにします。 パート2では、ビジネスを機能させるために必要な RESTful Web サービスを作成します。 ほとんどのデータベースでは、Java Spring、Python Flask、Node.js Express などのミドルウェアフレームワークを使用して、SQLでデータレイヤーとやり取りしています。IRIS でもその方法を利用することはできますが、より簡単で高性能な別の選択肢があります。 ObjectScriptでコードを記述する:ストアドプロシージャのパフォーマンスと、プログラミング言語の柔軟性、パワー、使いやすさを手に入れることができます。 ミドルウェアが不要です:ミドルウェア層が組み込まれています。 コツさえつかめば、ObjectScript は Web アプリケーションのバックエンドを構築するための最速の方法と言えます! ObjectScript を使用したデータの操作 パート1 では、Python と SQL を使ってデータベースにアクセスする方法をご紹介しました。 パート2では、ObjectScript によるアクセスがいかに簡単か、特に主キーを使ってレコードを取得したい場合について見ていきたいと思います。 (1) Sandbox の IDE を開きます。 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#query-cos を開き、「ObjectScript database query」の近くに IDE へのリンクが表示されます。 IDE などのアクセス情報の表示が、 のように表示されていたら、ピンク色のリンクをクリックしてください。 (2)  IDE のメニューから InterSystems > Web Terminal を選択します。 (3) ログイン画面が表示されたらユーザ名:tech パスワード:demo でログインしてください。 (4) 以下の ObjectScript のコマンドをコピーし、ターミナルに貼り付けて実行してください。 以下のコードは 主キーが 1 である ICO.Inventory のレコードを取得しています。 set item = ##class(ICO.inventory).%OpenId(1) zwrite item set item.quantitykg = 300 zwrite item do item.%Save() 1行ずつコードを解説します。 1行目:データベースからレコードを 1 件ロードしています。 2行目:レコードデータをターミナルに表示しています。 3行目:在庫数が設定されている quantitykg の値を 300(kg) に変更しています。 4行目:変更を確認するため、画面に再度表示しています。 5行目:データベースに変更データを保存しています。 InterSystems Web Terminal は、通常のコマンド・ライン・ターミナルと同様に動作しますが、ObjectScript コマンドも実行できます。 ObjectScript と SQL によるデータベースの操作 それでは、ObjectScript を使用して SQL を使ったより複雑なクエリを実行してみましょう。 Web Terminal に戻って、次のように入力してください。 set sqlquery = "SELECT * FROM ICO.inventory" set resultset = ##class(%SQL.Statement).%ExecDirect(,sqlquery) while resultset.%Next() { Write !, resultset.%Get("vendor_id") } 1行ずつコードを解説します。 1行目:実行したい SELECT 文を変数に設定しています。 2行目:%SQL.Statement クラスを使用して、1 行目で設定した変数に設定した SQL 文を実行し、変数 resultset に検索結果のインスタンスを設定しています。 3行目:resultset 変数を使用して、検索結果を繰り返し行移動(%Next())しながら vendor_id プロパティの値を画面表示しています。 ObjectScript のクラスを書いてみる 今まで試した Web Terminal のスクリプトは、1行に沢山のコードを指定しているので、見やすいとは言えません。 今度は、ObjectScript のコードをファイルにまとめてみましょう。 (1) Sandbox の IDE を開きます (2) 画面左のフォルダから、quickstarts-full-stack > services > cls > ICO を開きます。 (3) ICO フォルダを右クリックし、New File メニューを選択します。 (4) ファイル名に Test.cls を指定し、OK ボタンをクリックします。 (5) ファイルに以下の文字列を記述します(ICO.Test クラスを作成しています)。 Class ICO.Test{/// DescriptionClassMethod QueryDB() As %Status{ set sqlquery = "SELECT * FROM ICO.inventory" set resultset = ##class(%SQL.Statement).%ExecDirect(,sqlquery) while resultset.%Next() { Write !, resultset.%Get("vendor_id") }}} 記述後、ファイルを保存し(Ctrl + s)Web Terminal で以下実行します。 do ##class(ICO.Test).QueryDB() SQL と ObjectScript の使い分けや、マルチモデル機能については、開発者コミュニティの記事で詳しく紹介しています。 Web サービスの作成 ここまでの内容は、ObjectScript のプログラミング入門編でした。 ここからは、確認した内容を活かして、コーヒー焙煎ビジネスに必要な Web サービスを構築してみましょう。 作成概要は以下の通りです。 (1) 事前に作成されたコードをコピーしてコンパイルします。 (2) JSON の操作が行えるようにテーブル定義を変更します(JSONアダプタクラスの継承を追加します)。 (3) RESTful API がどのように構築されているか確認します。 (4) curl とブラウザを使い、Web サービスのデプロイとテストを行います。 (5) Web サービスを利用して、コーヒーの在庫を店舗に移動させます。 (6) 販売情報を記録します。 パート1では、標準的な SQL を使用してデータベースのテーブルを作成しました。 実は、その作成の裏では、対応する ObjectScript クラスも作成されていることをご説明していませんでした。 テーブルを作成するとクラス定義(永続クラス)も用意される仕組みにより、開発者は目的に応じて、SQL とコードの記述が簡単に行えるようになっています。 SQL で取得したデータを JSON としても出力できるようにするためには、データベースのテーブル定義に ObjectScript のコードに少し加える必要があります。その前に、データベースに登録されているコードを見てみましょう。 (1) Sandbox の IDE を開き、InterSystems のアイコン  をクリックします。 (2) IDE の画面左側で Classes > ICO を展開します。catalog.cls と inventory.cls の名称のファイルが確認できます。 パート1 で SQL 文を使ってデータベースにテーブルを作成した際に、それに対応する ObjectScript クラスも同時に作成されていることが確認できます。 (3) catalog.cls をクリックします(上図)。カラムの定義は Property 定義として表示されています。また、データ型や文字長の設定は見覚えのある値が登録されているはずです。同様に、inventory.cls をご参照ください。 (4) 開いたファイルはすべて閉じておいてください。 JSON の操作が行えるテーブル定義に変更する JSON の入出力を可能するため、確認したクラスを少し変更する必要があります。以下の手順で変更しましょう。 (1) Sandbox の IDE の ファイルのアイコンをクリックします。 (2) quickstarts-full-stack > services > cls > ICO を開きます。 (3) ICO フォルダの上で右クリック > New File を選択し、catalog.cls の名称で保存します。 (4) ICO フォルダの上で右クリック > New File を選択し、inventory.cls の名称で保存します。 (5) quickstarts-full-stack > services > cls_Sample > ICO を開きます。 (6) catlog.cls を開き、ファイル内を全てコピーし、quickstarts-full-stack > services > cls > ICO > catalog.cls に貼り付けます。 (7) quickstarts-full-stack > services > cls_Sample > ICO > inventory.cls を開き、ファイル内を全てコピーし、quickstarts-full-stack > services > cls > ICO > inventory.cls に貼り付けます。 (8) 変更したファイル(quickstarts-full-stack > services > cls > ICO > catalog.cls と inventory.cls )を保存します。 保存時、画面右下に選択欄が表示されるので "Overwrite on Server" を選択します。この操作により、%JSON.Adapter と プロパティのパラメータ:%JSONFIELDNAME を追加したクラスの新しいバージョンがサーバにアップロードされ、コンパイルされます。 (9) 2つのファイルに以下の操作を行いました。 A:%JSON.Adapter を継承し、テーブルのデータに対して自動的に JSON 出力が行えるように設定しました。 B:プロパティのパラメータ:%JSONFIELDNAME を追加し、JSON 出力時に使用するプロパティ名を変更しました。 ここまでの流れで、データベース用意したテーブル定義が、ObjectScript のクラス定義としても表現され、SQL と ObjectScript を使ってデータベースに対する操作ができることを確認できました。 これで、Web API を構築する準備が整いました! RESTful サービスを作ってみよう! テーブル定義(クラス定義)へ JSON アダプタを継承することで JSON の操作が行えるようになり、REST サービスを作成する準備が整いました! 先ほどと同様の手順で、クラス定義を追加します。 quickstarts-full-stack > services > cls > ICO 以下に Handler.cls を新規に作成します。手順は以下の通りです。 (1) Sandbox の IDE の ファイルのアイコンをクリックします。 (2) quickstarts-full-stack > services > cls > ICO フォルダを開きます。 (3) ICO フォルダを右クリックし New File を選択し、クラス名に Handler.cls を設定します。 (4) quickstarts-full-stack > services > cls_Samples > ICO フォルダを開きます。 (5) quickstarts-full-stack > services > cls_Samples > ICO> Handler.cls クラスを開き、クラス定義の中身を全部コピーし、(3) で作成した quickstarts-full-stack > services > cls > ICO > Handler.cls に貼り付けます。 (6) ファイルを保存します(今回は "Overwrite on Serve" のオプション表示は出てきません)。 Handler.cls の作成により、ミドルウェアとなる REST API が作成できました。 サービスを起動させるための最後の手順として、Web に公開するための設定を追加します。 IRIS では、Node.js の Express や Python の Flask と同じように、Web リクエストを ObjectScript コードにルーティングするツールを提供しています。 管理ポータルを使用して作成した ICO.Handler クラスを Web リクエスト時に呼び出されるように設定してみましょう。 管理ポータルを開きます。 Sandbox の IDE のメニューから InterSystems > Management Portal を選択します。 管理ポータルが開いたら、以下メニューにアクセスします。 システム管理 > セキュリティ > アプリケーション > ウェブ・アプリケーション 以下の手順で、REST のエンドポイントのパスを作成します。 (1)  ボタンをクリックします。 (2) 名前 に /api/coffeeco を設定します。 (3) ネームスペースに USER を設定します。 (4) アプリケーション有効 にチェックが入っていることを確認します。 (5) REST にチェックします。 (6) ディスパッチクラス に ICO.Handler を設定します。 (7) 許可された認証方法では、「認証なし」と「パスワード」にチェックを入れます。 (8) 保存ボタンをクリックし、設定を保存します。 保存後、「アプリケーション・ロール」タブが表示されるので、クリックし、「アプリケーションロール」に %All を設定します。 設定には、「利用可能」の一覧から %All を選択し、 をクリックし、画面右側の「選択済み」に移動させます。 その後、ボタンをクリックすると、「アプリケーションロール」に %All が追加されます。 REST インターフェースでは、ここで紹介しているように手動でコーディングすることもできますし、Open API v2.0 の仕様を作成して自動的にコードを生成する、API ファーストのアプローチをとることもできます。詳しくはドキュメントをご覧ください。 作成したサービスのテストをしましょう ここまでの設定で、REST 用エンドポイントの作成が完了しました。エンドポイントは https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#json-enable-the-data-tables を開き「Test the service」の近くに表示されます(Sandbox 毎に情報は異なります)。 https://Sandbox で利用できるWebサーバアドレス/api/coffeeco/inventory/listbeans 上記 URL(Webサーバアドレスはお試しいただいている環境に合わせてご変更ください)をコピーし、ブラウザのアドレスバーに入力し、REST サービスをテストすると、以下のような結果が返ります。 見易い表示に変えると、以下のような結果が返ります。 { "rowcount": 5, "items": [{ "id": "1", "vendor_product_code": "ETHIOPA32", "date_arrival": "65541", "quantity_kg": 400 }, { "id": "2", "vendor_product_code": "BRAZILPREM", "date_arrival": "65541", "quantity_kg": 200 }, { "id": "3", "vendor_product_code": "GUATEMALAALT30", "date_arrival": "65559", "quantity_kg": 200 }, { "id": "4", "vendor_product_code": "SUMATRA2", "date_arrival": "65559", "quantity_kg": 200 }, { "id": "5", "vendor_product_code": "SUMATRA3", "date_arrival": "65559", "quantity_kg": 400 }] } コーヒー豆を焙煎します 現在、ブラウザからのリクエストでレスポンスが返ってくることを確認できたので、サービスが動作していることがわかりました。ここからは、コーヒービジネスを操作するためのサービスを作成します。 最初に、コーヒーの生豆を在庫から取り出し、焙煎します。この動作には、/api/coffeeco/inventory/getbeans の URL を使用します。 Handler.cls を開き、ファイル末尾に定義された XData UrlMap の <Routes> のタグ内部をご参照ください。 URL /api/coffeeco/inventory/getbeans に対して、クラスメソッド GetRawBeans() が呼び出されるように定義されていることがわかります。 URL で値を渡すための URL に含めるパラメータの記載方法にも注目してください。 クラス定義内で GetRawBeans()メソッドを参照します。 レコードの主キーとなる値が URL の :id で渡されます。GetRawBeans() メソッドが実行されるとき、メソッドの第 1 引数に :id の情報が渡されます。 この値を %OpenId() メソッドの引数に指定することで、ObjectScript を使用してデータベースから対象のデータをロードすることができます(とても簡単な方法です)。 残りのコードは以下の通りです。 (1) quantity に設定されている値が十分な量であるかの確認 (2) 在庫から要求された量を減らす処理 (3) 要求元に新しい在庫量を返す処理 在庫から豆を取り出し、焙煎を行うためのリクエストを実行してみます。 今回のリクエストは、ブラウザでテストすることはできません(ブラウザで、POST 要求を送信することができないためです)。 Sandbox の IDE のターミナルウィンドウでは、curl コマンドを実行できるので、以下のように入力してみください。 注意:リクエストに使用する Web サーバアドレスは Sandbox の利用環境により異なります。お使いの環境のサーバ名に修正して実行してください。 curl -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/inventory/getbeans/1/2.4 お店にコーヒーを並べる このチュートリアルではシンプルな例を利用しているため、リクエストされた生豆の量はどこにも記録されていません。 コーヒーを焙煎し、袋詰めし、店頭に並べて販売する処理についてはリクエストする Web アプリケーション側で処理することとします。 その処理を追加するため、quickstarts-full-stack > services > samples ディレクトリに 2 つのスクリプトが用意されています。 createproducts.sh 5 つのサンプルコーヒー製品の情報を作成するシェルです。最初の 3 つは本日焙煎されたもので、最後の 2 つは 6 日前に焙煎されたものです。 このシェルの実行で、鮮度の古い6日前の焙煎豆情報を作成しています。オンラインショップでは、鮮度が古い焙煎豆を割引して販売したいので、割引対象データとして準備しています。 loadproducts.sh curl コマンドを実行して、対象ディレクトリ内のすべての JSON ファイルを繰り返し参照し、用意した REST サービスを使用して JSON ファイル内のデータを ICO.catalog にロードします。 それではシェルを実行してみましょう。手順は以下の通りです。 (1) Sandbox の IDE のターミナルウィンドウで以下実行します。 cd /home/project/quickstarts-full-stack/services/samples sh createproducts.sh (2) IDE で、quickstarts-full-stack > services > samples ディレクトリに 5 つの JSON ファイルが作成できたことを確認してください。 (3) loadproducts.sh を IDE で開きます。 (4) 環境変数 IRISDB の値を修正します(Sandbox のウェブサーバアドレスに修正します)。 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#coffee-to-store を開き「Put coffee in the store」の近くに表示される情報をご利用ください。 (5) loadproducts.sh を保存します。 (6) Sandbox の IDE のターミナルウィンドウで以下実行します。 cd /home/project/quickstarts-full-stack/services/samples sh loadproducts.sh シェルを実行したくない場合は、curl コマンドを利用してデータを読み込むこともできます。 コマンド実行例は以下の通りです(Webサーバアドレスは Sandbox ごとに異なります。実行環境に合わせて実行してください。)。 curl -d "@product_brazil_dark.json" -H "Content-Type: application/json" -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/catalogproduct この時点で、生豆を出荷し在庫に入れ、一部は焙煎してパッケージ化し ICO.catalog テーブルに保存したため、オンラインショップで販売できるようになりました。 コーヒーカタログを提供する ここからは、フロントエンドのオンラインストアフロントに必要なサービスを考えていきます。Web 開発者は、以下の用途で利用できる一連のサービスを必要としています。 (1) 焙煎したばかりの新鮮なコーヒーを販売してほしい (2) 値引きして販売する必要のある鮮度の古いコーヒーバック情報を入手したい (3) コーヒーバックの販売記録を作りたい 最初の2項目は非常に簡単です。読み取り専用のサービスとなるので、GET要求を使用すれば取得できます。 両方の GET 要求を 1 つの GetProducts()メソッドで処理できるように、新鮮な在庫と古い鮮度の在庫のどちらを返すか指定できる入力引数を用意することもできます。 Handler.cls (quickstarts-full-stack > services > cls > ICO > Handler.cls)の下の方に定義されている UrlMap 定義をご参照ください。 /catalog/getproducts は、全て新しい鮮度のコーヒーを返すUrlです。 /catalog/getproducts/:fresh は、/catalog/getproducts に似ていますが、鮮度の古いコーヒーを取得できる追加のパラメータを用意しています(fresh が 0 の場合に鮮度が古い情報を返します)。 /catalog/sellproduct/:id/:quantity は、クライアントが特定の商品のバッグを販売したことを記録する処理の Url です。 XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Url="/inventory/listbeans" Method="GET" Call="ListRawBeans" /> <Route Url="/inventory/getbeans/:id/:quantity" Method="POST" Call="GetRawBeans" /> <Route Url="/catalog/catalogproduct" Method="POST" Call="CatalogProduct" /> <Route Url="/catalog/getproducts" Method="GET" Call="GetProducts" /> <Route Url="/catalog/getproducts/:fresh" Method="GET" Call="GetProducts" /> <Route Url="/catalog/sellproduct/:id/:quantity" Method="POST" Call="SellProduct" /> </Routes> } GetProducts() メソッドでは、SQL を使用してクエリを実行し、返されたレコードを繰り返し処理で取得し、取得できた情報を JSON オブジェクトに設定し、 JSON 配列に追加し、最後に呼び出し元に作成した JSON データを返送しています。 以上の流れで、Web 開発者が販売中の商品を紹介する素敵なサイトを構築するために必要な情報がすべて揃いました! コーヒーを販売する 販売したコーヒーを記録するサービスの SellProduct() は、プロダクト ID と販売されるバッグの数量が引数として指定されます。 ここでは、エラーチェックや特別な支払い処理、発送などは行わず、非常にシンプルな処理を行っていて、カタログのコーヒーバッグの数量を減少させるだけの処理としています。 また、お客様が複数の商品を購入した場合、クライアントはそれぞれの商品に対して SellProduct リクエストを送信するものとします。 GetRawBeans() メソッドの処理と同様に、レコード ID がわかっている場合にレコードを照会する ObjectScript の便利なメソッド %ExistsId()、%OpenId()、%Save() を利用します。このメソッドは GetRawBeans() と非常によく似ているため、新たに追加する説明はありません。 サービスを試してみます。 実行に使用する URL の Web サーバアドレスは Sandbox 毎に異なります。 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#catalog-services を開き、「Try out the services」の近くの表示をご確認ください。 新鮮なコーヒー豆を問い合わせるサービス curl https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/getproducts セール商品を問い合わせるサービス(末尾のパラメータに 0 を指定しています) curl https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/getproducts/0 商品を販売します curl -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/sellproduct/1/2 この実行結果の例は以下の通りです。 {"catalog_id":1,"product_code":"BRAZILDARK","quantity":38,"price":13.99,"time_roasted":"2021-02-03T09:00:00Z","roasting_notes":"Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.","img":"brazil_dark.jpg"} パート3:コーヒーストア用 Web アプリの構築 オリジナルページはこちら👉 https://gettingstarted.intersystems.com/full-stack/part-three-front-end/ Introduction このチュートリアルの最後のパート 3 では、あなたが運営するコーヒーショップのオンライン storefront を作成します。 アプリケーションでは、Vue.js という JavaScript フレームワークを使用して、シングルページウェブアプリケーション(SPA)を作成しています。 Vue.js についての説明はこのチュートリアルの範囲外ですが、Vue.js を使用してどのように Web アプリケーションが構築されるか、また、パート2 で作成した REST サービスがこのアプリでどのように使用されているかを確認することができます。 コードをカスタマイズする Web アプリケーション用コードは全て準備済です。実際に動かして動作を確認してみましょう。 まず、必要なパッケージのインストールを行います。インストールには数分かかります。また、インストール後にいくつかの編集を行います。 Sandbox の IDE のターミナルウィンドウで以下のコマンドを入力してください。 IDE へのリンク情報は、https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#customize-code を開き、ログインすると以下図のように表示されます。 IDE などのアクセス情報の表示が、 のように表示されていたら、ピンク色のリンクをクリックしてください。 IDE のターミナルウィンドウで以下実行してください。インストールには数分時間がかかります。 cd /home/project/quickstarts-full-stack/frontend npm install npm install yarn インストールが完了したら、お使いの IRIS サーバのアドレスをソースコードに指定します。 (1) IDE で quickstarts-full-stack > frontend > src > views > Home.vue を開きます。 (2) localhost:52773 と記載されている部分(url の設定)を、お使いの IRIS 用アドレスに書き換えます(例:52773-1-4e734fe2.try.learning.intersystems.com)。 https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#customize-code を開き、「Customize the code」の近くにアクセス情報が表示されます。ご確認ください。 (3) IDE で quickstarts-full-stack > frontend > src > views > Sale.vue を開き、(2)と同様に、IRIS の接続情報を書き換えてください。 (4) IDE で quickstarts-full-stack > frontend > src > components > ProductCard.vue を開き、orderurl の設定を (2) と同様に、IRIS の接続情報を書き換えてください。 では、このアプリのテスト用の組み込み Web サーバで実行してみましょう。 (1) Sandbox の IDE のターミナルウィンドウで以下実行します(実行には時間がかかります)。 cd /home/project/quickstarts-full-stack/frontend yarn serve (2) ブラウザに、パート3の画面で指定された URL にアクセスしてください。 URL は https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#store-view を開き、「View the storefront」の近くに表示されます。 URL にアクセスすると、以下の画面が表示されます。 Vue.js の中で、どのように実行されているか少し解説します。 React や Angular などの多くのフレームワークと同様に、URLはコンポーネントに「ルーティング」されます。 IDE で、quickstarts-full-stack > frontend > src > router > index.js を参照すると、デフォルトの URL パスである「/」が Home コンポーネントにルーティングされています。 Webアプリケーションで「/」 が指定されると、quickstarts-full-stack > frontend > src > views > Home.vue が表示され、/catalog/getproducts/1 を GET 要求で実行しています。 IRIS では、quickstarts-full-stack > services > cls > ICO > Handler.cls の GetProducts() メソッドが引数に 1 を指定された状態で実行され(=新鮮な豆を取得)過去5日間に焙煎されたすべての販売商品のリストをJSONで返します。 URL のパラメータに 1 を渡すか、指定しない場合、焙煎されたばかりのバッグのみが要求されます GET https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/getproducts/1 実行結果例は以下の通りです。 { "rowcount": 5, "products": [{ "catalog_id": "1", "product_code": "BRAZILDARK", "quantity": 38, "time_roasted": "2021-02-09 09:00:00", "roasting_notes": "Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.", "img": "brazil_dark.jpg", "price": 13.99 }, { "catalog_id": "2", "product_code": "ETHIOPIAMEDIUM", "quantity": 40, "time_roasted": "2021-02-08 09:00:00", "roasting_notes": "Sweet floral notes, followed by the potent citrus notes, perfectly married into bergamot.", "img": "ethiopia_medium.jpg", "price": 14.99 }, { "catalog_id": "3", "product_code": "GUATEMALALIGHT", "quantity": 120, "time_roasted": "2021-02-09 17:30:00", "roasting_notes": "Full body and a rich chocolatey-cocoa flavor, and a toffee-like sweetness.", "img": "guatemala_light.jpg", "price": 11.99 }, { "catalog_id": "4", "product_code": "SUMATRADARK", "quantity": 80, "time_roasted": "2021-02-07 13:01:30", "roasting_notes": "Smooth and chocolaty with a sweet edge and minimal earthiness.", "img": "sumatra_dark.jpg", "price": 12.99 }, { "catalog_id": "5", "product_code": "SUMATRALIGHT", "quantity": 40, "time_roasted": "2021-02-07 09:00:00", "roasting_notes": "This rich and juicy Sumatra carries sustained notes of cherry and citrus.", "img": "sumatra_light.jpg", "price": 12.99 }] } REST サービスの処理詳細については、quickstarts-full-stack > services > cls > ICO > Handler.cls の GetProducts() メソッドをご参照ください。 Home.vue は、JSON オブジェクトを繰り返し処理して、JSON 内の各アイテムに対して ProductCard (quickstarts-full-stack > frontend > src > components > ProductCard.vue) を作成します。 ProductCard.vue は、単一の商品を表示し、それを注文するための UI を作成する方法を知っているだけです。 では、Web ページの「Last chance」のリンクをクリックしてみましょう。 製品の短いリストが表示されるはずです(表示されない場合は、ICO.catalog テーブルの time_roasted の値をいくつか変更して、5日以上前の値にする必要があります)。この表示は、同じバックエンドのサービスを呼び出していますが、5日以上前の焙煎珈琲を返すようにパラメータを指定しています(1 以外の任意の数字を渡すことで指定できます)。 REST の呼び出しは以下の通りです。 GET /api/coffeeco/catalog/getproducts/2 応答結果例は、以下の通りです。 { "rowcount": 1, "products": [{ "catalog_id": "10", "product_code": "SUMATRALIGHT", "quantity": 40, "time_roasted": "2021-02-02 09:00:00", "roasting_notes": "This rich and juicy Sumatra carries sustained notes of cherry and citrus.", "img": "sumatra_light.jpg", "price": 12.99 }] } Home.vue と同様に、Sale.vue でも ProductCard コンポーネントを使用して商品を表示していますが、fetchProducts() 関数の実行結果(=RESTの応答)から ProductCard にデータを渡す前に価格を 3 ドル分値引きしています。 これは、コードを単純化するためにコンポーネントを再利用する簡単な例であり、価格設定と在庫管理を分離していることを示しています。 購入処理 いよいよチュートリアルの最後の項目です!コーヒーを買ってみましょう! 商品の数量を変更し「Place Order」ボタンをクリックします。注文が完了したことを示すポップアップが表示されるはずです(チュートリアルは架空サンプルです。実際のアプリであれば、ショッピングカートに商品を入れて、顧客が支払いを済ませるまで注文は行われないでしょう)。 チュートリアルは簡単な例としているため、ProductCard が RESTサービスの SellProduct() メソッドを呼び出し、注文されたコーヒー豆をカタログから取り出す様子を示しています。 REST 呼び出しは以下の通りです。 POST /api/coffeeco/catalog/sellproduct/商品のカタログID/販売したコーヒーバッグ数 POST要求なので、ブラウザからは実行できません。 IDE のターミナルを新規に開きます(Terminal > New Terminal)。 以下コマンドを新規ターミナルで実行してください。 コマンド実行例は、https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#purchaseを開き、「Making a purchase」近くの表示をご確認ください。 curl -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/sellproduct/1/2 応答結果の例は、以下の通りです。 { "catalog_id": 1, "product_code": "BRAZILDARK", "quantity": 36, "price": 13.99, "time_roasted": "2021-02-09T09:00:00Z", "roasting_notes": "Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.", "img": "brazil_dark.jpg" } ここでは、もう少しアプリケーションを使って遊んでいただく方法をご紹介します。 (1) 在庫が足りなくなるまで、コーヒーバッグを注文し続けます。最終的にはStorefront から商品を消滅させることができる予定です。 (2) Web 開発の経験がある方は、quickstarts-full-stack > frontend > src > components >ProductCard.vue コンポーネントの CSS を変更してみてください (ファイルの最後の <style> セクションにあります)。 (3) Vue.js の経験があれば、ProductCard.vue の processOrder() 関数で使われている基本的な JavaScript の警告メッセージを、もっと面白いものに変更してみましょう。また、独自のコンポーネントを作成して、ポップアップ・アラートの代わりにこの情報を表示することもできます。 最後に、Webアプリケーションの終了方法をご案内します。 Sandbox の IDE のターミナルウィンドウがプロンプトが戻っていない状態になっています。Web アプリケーションを終了して良い場合は、Ctrl + C を実行し、元のプロンプトに戻してください。 Next Steps InterSystems IRIS の基本的な機能をご紹介しましたが、いかがでしたでしょうか。 このチュートリアルで学習したことを振り返ります。 パート1では、テーブル作成やデータの読み込みに SQL を使用し、直接ターミナルから SQL を入力したり、Python プログラムから実行する方法を確認しました。 パート2では、高速で柔軟なデータベースプログラミング言語である ObjectScript のご紹介と、ObjectScript を使用して RESTサービスを構築する方法をご紹介します。 パート3では、人気の高い JavaScript フレームワークを使って、顧客向けのフロントエンド Web アプリを構築する方法を学習しました。 以上でフルスタックチュートリアルは終了です! 最後までお付き合いいただきありがとうございました! https://gettingstarted.intersystems.com/ には、まだまだ他のチュートリアルをご用意しています。ぜひご体験ください!
記事
Mihoko Iijima · 2020年12月28日

データベースの暗号化手順について

これは InterSystems FAQ サイトの記事です。 データベース暗号化は、ディスクヘの書き込みまたはディスクからの読み取りで暗号化と復号が実行されるため、アプリケーションのロジックに手を加える必要はありません。 この機能のドキュメントについては、以下ご参照ください。 マネージド・キー暗号化【IRIS】 マネージド・キー暗号化 暗号化データベース作成までの流れは、以下の通りです。 (1) 暗号化キーの作成 (a) 管理者 ユーザ名/パスワード (b) 暗号化キーファイル (2) 暗号化キーの有効化 (3) 暗号化されたデータベースの作成 暗号化データベース作成後の運用のための設定は以下の通りです。 〇 データベース暗号化の起動設定(暗号化キーの有効化をどのように行うか) 暗号化されたデータベースは、"暗号化キーの有効" が行われてアクセスできるようになります。既定の設定では、"暗号化キーの有効"を行いませんので、以下3種類の方法から選択します。 ① キーを有効化しない起動の構成 既定の設定のまま、インスタンス起動時に "暗号化キーの有効" が行われません。暗号化されたデータベースをマウントする前に管理ポータルなどから "暗号化キーの有効" を行う必要があります。 以下の場合、この運用は適応できません。 起動時に暗号化データベースのマウントが必要な場合 暗号化されたジャーナル・ファイルを利用している場合 監査ログが暗号化されている場合 ② インタラクティブ(対話式)にキーを有効化する起動の構成 インスタンス起動時に、インタラクティブに "暗号化キーの有効" を行います。 ③ 無人でキーを有効化する起動の構成 インスタンス起動時に、自動的に "暗号化キーの有効" を行います。 セキュリティの強度については、①および②の運用の方が、③よりも強度が高くなります。詳細は、下記ドキュメントページをご確認ください。 データベース暗号化の起動設定の構成【IRIS】データベース暗号化の起動設定の構成 ○暗号化キーファイル および 管理者とパスワードの管理方法(管理をどうするか) 暗号化キーの有効化するには以下の 2 つの情報が必要です。  (a) 管理者 ユーザ名/パスワード (b) 暗号化キーファイル これらに関して、ファイルの損失、管理者のユーザ名/パスワードの失念や漏えい等から防ぐ方法については、以下のドキュメントページをご確認ください。  暗号化データのアクセスにおける偶発的な損失からの保護【IRIS】 暗号化データのアクセスにおける偶発的な損失からの保護 ○ 緊急事態(緊急事態の対処方法) 緊急事態として以下の場合の対処については下記ページをご確認ください。  緊急事態【IRIS】  有効なキーが保存されているファイルが損傷したり紛失した場合【IRIS】  起動時に必要なデータベース暗号化キー・ファイルが存在しない場合【IRIS】  緊急事態  有効なキーが保存されているファイルが損傷したり紛失した場合  起動時に必要なデータベース暗号化キー・ファイルが存在しない場合
記事
Mihoko Iijima · 2021年4月22日

ソースプログラムを隠蔽化する方法

これは InterSystems FAQ サイトの記事です。 ルーチン(*.mac)の場合 ソースプログラムのコンパイル後に生成される *.obj のみをエクスポート/インポートすることでソースの隠蔽化を実現できます。 コマンド実行例は、EX1Sample.mac と EX2Sample.mac のコンパイルで生成される EX1Sample.obj と EX2Sample.obj をエクスポート対象に指定し、第2引数のファイルにエクスポートしています。 別ネームスペースに移動したあと、エクスポートした XML ファイルを利用してインポートを実行しています。 USER>do $system.OBJ.Export("EX1Sample.obj,EX2Sample.obj","/opt/app/routine.xml") XMLエクスポートの開始 04/22/2021 18:18:32 オブジェクトコードをエクスポート中: EX1Sample.obj オブジェクトコードをエクスポート中: EX2Sample.obj エクスポートが正常に完了しました。 USER>zn "test" // ネームスペース移動 TEST>do $system.OBJ.Load("/opt/app/routine.xml") ロード開始 04/22/2021 18:18:51 ファイル /opt/app/routine.xml を xml としてロード中 インポートしたオブジェクトコード: EX1Sample インポートしたオブジェクトコード: EX2Sample ロードが正常に完了しました。 TEST> クラス(*.cls)の場合 クラスの場合は、XMLで *.cls をエクスポート/インポートしたあとに、サーバで MakeClassDeployed() を実行します。 ただし、比較的新しいバージョンでは MakeClassDeployed() 実行後、ソースファイル(*.cls)は配置モードに設定されるのみで(編集はできなくなります)参照のみ行える仕様になっています。 参照も不可にしたい場合は、MakeClassDeployed() 実行後、クラスの Hidden プロパティを設定します(プロパティの属性 Hidden=True に設定)。 コマンド実行例は以下の通りです。 USER>do $system.OBJ.Export("GPS.REST.cls,GPS.DriveData.cls","/opt/app/test.xml") XMLエクスポートの開始 04/22/2021 18:05:13 クラスをエクスポート中: GPS.DriveData クラスをエクスポート中: GPS.REST エクスポートが正常に完了しました。 USER>zn "test" // testネームスペースに移動 TEST>do $system.OBJ.Load("/opt/app/test.xml","ck") ロード開始 04/22/2021 18:07:21 ファイル /opt/app/test.xml を xml としてロード中 インポートしたクラス: GPS.DriveData インポートしたクラス: GPS.REST , 2 クラスをコンパイル中, 個のワーカー・ジョブを使用 クラスのコンパイル中 GPS.DriveData クラスのコンパイル中 GPS.REST テーブルのコンパイル中 GPS.DriveData ルーチンのコンパイル中 GPS.REST.1 ルーチンのコンパイル中 GPS.DriveData.1 ロードが正常に完了しました。 TEST>do $system.OBJ.MakeClassDeployed("GPS.DriveData") TEST> CSP(*.csp)の場合 CSPファイルについては、*.cspをコピーし、配置先の CSP フォルダに貼付ます。 サーバでコンパイル後、CSPの設定で自動コンパイル OFF にしたあと、*.csp の削除と MakeClassDeployed() の実行を行います 実行例は以下の通りです。 1)CSPファイルのコピー後、サーバの CSP ディレクトリに貼付&コンパイル TEST>do $SYSTEM.CSP.LoadPageDir("/csp/test") 2)ウェブアプリケーションパスの設定で「自動コンパイル」をいいえに設定 【バージョン2013.1以降】 [管理ポータル] > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション] > 該当するアプリケーション名のリンク 【バージョン201.1~バージョン2012.2】 [管理ポータル] > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション] > 該当するアプリケーション名の[編集] 【バージョン2010.2以前】 [システム管理ポータル] > [システム] > [セキュリティ管理] > [CSPアプリケーション] > 該当するアプリケーション名の[編集] 3)MakeClassDeployed() の実行  ※cspsample.csp をコピーした場合の例 TEST>do $system.OBJ.MakeClassDeployed("csp.cspsample")
記事
Megumi Kakechi · 2022年10月6日

メッセージログ(messages.log) のログ深刻度が 2 以上でメールを送るようにする方法

Caché/Ensemble 時代からご使用のお客様にはなじみの機能だと思いますが、IRISには「システムがインスタンスのメッセージログ/messages.log(Cachéの場合は コンソールログ/cconsole.log) を監視し、ログ・レベル2(重大なエラー) 以上 のアラートを受け取るとメールを送信する」ログ・モニター機能があります。この機能を使用すると、アラートログ (alerts.log)へのログ書き込み管理のほかに、メールを送信することもできます。 メール送信の設定は、^MONMGR ユーティリティを使用して簡単に行えます。 以下に、サンプルをご案内します。 USER>zn "%SYS" %SYS>do ^MONMGR 1) Start/Stop/Update Log Monitor 2) Manage Log Monitor Options 3) Exit Option? 2 <-- ログモニター管理の設定を行います 1) Set Monitor Interval 2) Set Alert Level 3) Manage Email Options 4) Exit Option? 3 <-- Emailオプションの設定を行います 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 1 <-- Email設定をにONにします ​​​​​ Email is currently OFF <-- 現在の設定は OFF Change Email setting? No => yes <-- y または yes を入力します 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 2 <-- 送信元の設定をします Sender? aaa@bbb.com <-- 送信元のメールアドレスを設定します 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 3 <-- メールサーバの設定を行います Mail server? mail.bbb.com <-- メールサーバ Mail server port? 25 => <-- メールサーバポート Mail server SSLConfiguration? <-- (必要に応じて設定) Mail server UseSTARTTLS? 0 => <-- (必要に応じて設定) 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 4 <-- 送信先(受信者)情報の設定 1) List Recipients 2) Add Recipient 3) Remove Recipient 4) Exit Option? 2 <-- 送信先メールアドレスの設定 Email Address? test@abcde.com <-- 送信先メールアドレス 1) List Recipients 2) Add Recipient 3) Remove Recipient 4) Exit Option? <-- <Enter> 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 6 <-- 送信テスト Sending email on Mail Server mail.bbb.com From: aaa@bbb.com To: test@abcde.com 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? <-- <Enter> 1) Set Monitor Interval 2) Set Alert Level 3) Manage Email Options 4) Exit Option? <-- <Enter> Update running Monitor? Yes => Yes <-- Y でモニター情報を更新 Updating Log Monitor... Log Monitor updated 1) Start/Stop/Update Log Monitor 2) Manage Log Monitor Options 3) Exit Option? %SYS> 上記テスト送信で、「%Monitor Email Test」の件名でメールが送られます。これで設定は完了です。 設定が完了したら、メッセージログにログレベルが 2(重大なエラー) 以上のログを書き込み、メールが送られるか確認してみましょう。 次のコマンドで、メッセージログ/コンソールログに任意のメッセージを書き込むことができます。 // 第3引数がログレベル。0 (情報), 1 (警告), 2 (重大なエラー), 3 (致命的なエラー) write ##class(%SYS.System).WriteToConsoleLog("Test log message",0,2) 上記コマンドを実行すると、メッセージログに以下のようなログが書き込まれ、 10/05/22-15:18:23:537 (3688) 2 [Utility.Event] Test log message [InterSystems IRIS SEVERE ERROR xxxxx:IRIS] [Utility.Event] Test log message のような件名でメールが送られます。 詳細は、以下のドキュメントをご覧ください。ログ・モニタの使用 IRISには便利なユーティリティが数多くあります。以下の記事で紹介しておりますので、ぜひお試しください。【ご参考】IRISで使用できるユーティリティ一覧
記事
Mihoko Iijima · 2023年4月10日

テーブル定義のデータが格納されるグローバル変数名について

これは InterSystems FAQ サイトの記事です。 バージョン2017.2以降から、CREATE TABLE文で作成したテーブル定義のデータを格納するグローバル変数の命名ルールが変わり ^EPgS.D8T6.1 のようなハッシュ化したグローバル変数名が設定されます。(この変更はパフォーマンス向上のために追加されました。) ※ バージョン2017.1以前については、永続クラス定義のルールと同一です。詳細は関連記事「永続クラス定義のデータが格納されるグローバル変数名について」をご参照ください。 以下のテーブル定義を作成すると、同名の永続クラス定義が作成されます。 CREATE TABLE Test.Product( ProductID VARCHAR(10) PRIMARY KEY, ProductName VARCHAR(50), Price INTEGER ) 永続クラス:Test.Productの定義は以下の通りです。(パラメータ:USEEXTENTSETに1が設定されます) Class Test.Product Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {SuperUser}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = Product ]{Property ProductID As %Library.String(MAXLEN = 10) [ SqlColumnNumber = 2 ];Property ProductName As %Library.String(MAXLEN = 50) [ SqlColumnNumber = 3 ];Property Price As %Library.Integer(MAXVAL = 2147483647, MINVAL = -2147483648) [ SqlColumnNumber = 4 ];Parameter USEEXTENTSET = 1;/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement. Do not edit the SqlName of this index.Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];/// DDL Primary Key SpecificationIndex PRODUCTPKEY1 On ProductID [ PrimaryKey, SqlName = PRODUCT_PKEY1, Type = index, Unique ];Storage Default{<Data name="ProductDefaultData"><Value name="1"><Value>ProductID</Value></Value><Value name="2"><Value>ProductName</Value></Value><Value name="3"><Value>Price</Value></Value></Data><DataLocation>^CCar.Wt3i.1</DataLocation><DefaultData>ProductDefaultData</DefaultData><ExtentLocation>^CCar.Wt3i</ExtentLocation><ExtentSize>0</ExtentSize><IdFunction>sequence</IdFunction><IdLocation>^CCar.Wt3i.1</IdLocation><Index name="DDLBEIndex"><Location>^CCar.Wt3i.2</Location></Index><Index name="IDKEY"><Location>^CCar.Wt3i.1</Location></Index><Index name="PRODUCTPKEY1"><Location>^CCar.Wt3i.3</Location></Index><IndexLocation>^CCar.Wt3i.I</IndexLocation><StreamLocation>^CCar.Wt3i.S</StreamLocation><Type>%Storage.Persistent</Type>}} ExtentLocation:このクラスのグローバル名の生成に使用されるハッシュ値 DataLocation:レコードデータが登録されるグローバル変数名です。 Location:各インデックス固有のグローバル変数名が指定されます。 IndexLocation:この定義では、多くの場合使用されません。 StreamLocation:ストリームプロパティのデータが格納される変数です。 ストレージ定義に表示される情報について詳細は、ドキュメント「ハッシュ化したグローバル名」をご参照ください。 2017.1以前と同様の命名ルール(^スキーマ名.テーブル名D、I、S のグローバル変数名)を使用する場合は、CREATE TABLE文実行時に以下のクラスパラメータを指定します。 WITH %CLASSPARAMETER USEEXTENTSET = 0 CREATE TABLE Test2.Product( ProductID VARCHAR(10) PRIMARY KEY, ProductName VARCHAR(50), Price INTEGER ) WITH %CLASSPARAMETER USEEXTENTSET = 0 永続クラス定義:Test2.Productのパラメータ:USEEXTENTSETは以下のように定義されます。 Parameter USEEXTENTSET = 0; 永続クラス定義:Test2.Productのストレージ定義は以下の通りです。 Storage Default{<Data name="ProductDefaultData"><Value name="1"><Value>ProductID</Value></Value><Value name="2"><Value>ProductName</Value></Value><Value name="3"><Value>Price</Value></Value></Data><DataLocation>^Test2.ProductD</DataLocation><DefaultData>ProductDefaultData</DefaultData><IdFunction>sequence</IdFunction><IdLocation>^Test2.ProductD</IdLocation><IndexLocation>^Test2.ProductI</IndexLocation><StreamLocation>^Test2.ProductS</StreamLocation><Type>%Storage.Persistent</Type>} WITHで指定したテーブルのオプションについては詳しくは、ドキュメント「テーブルのオプション」をご参照ください。 関連記事:永続クラス定義のデータが格納されるグローバル変数名について
記事
Megumi Kakechi · 2023年4月11日

TIMESTAMP型の項目に対して、TO_CHAR() や TO_DATE() を用いた SELECT を実行するとエラーになります

これは InterSystems FAQ サイトの記事です。 Question: TIMESTAMP型の項目に対して、TO_CHAR() や TO_DATE() を用いた SELECT を実行すると以下のエラーになります。 実行SQL: select TO_CHAR(xxxDateTime,'YYYY-MM-DD') from Test エラー: [SQLCODE: <-400>:<深刻なエラーが発生しました>] [%msg: <Unexpected error occurred: <ZCHAR>IllegalValuePassedToTOCHAR^%qarfunc>] エラーの原因を教えてください。 Answer: こちらは、IRIS2022.1以降のバージョンで CREATE TABLE (DDL) の TIMESTAMP 型が IRIS側クラスで %Library.PosixTime にマッピングするように変更されているためです。(アップグレードした環境の場合は、従来のままの %Library.TimeStamp にマッピングされています) %TimeStamp は、データを人が読める文字列(yyyy-mm-dd hh:mm:ss.ffff)として保存します。対して、%PosixTime は64bitの整数で保持するため、ディスクやメモリ上のデータサイズを削減し、比較演算処理等のパフォーマンスが向上します。SQLクエリを高速化したい場合は、%PosixTime を使用されることをお勧めします。 TIMESTAMP型を、従来と同じ %TimeStamp にマッピングするDDLデータ型としては TIMESTAMP2 が用意されています。CREATE TABLE文で、TIMESTAMP型にしたいフィールドの箇所を TIMESTAMP2 とすることで %TimeStamp とすることができます。 例: create table Test2 (xxxDateTime TIMESTAMP, xxxDateTime2 TIMESTAMP2) ※ xxxDateTime は %PosixTime 、xxxDateTime2 は %TimeStamp となる。全体の設定変更で対応する場合は、 管理ポータル:   [システム管理] > [構成] > [SQLとオブジェクトの設定] > [システムDDLマッピング]より TIMESTAMP 行の 「編集」をクリックしてデータタイプを %Library.PosixTime から %Library.TimeStamp に変更します。  ↓ この設定変更後に実行される CREATE TABLEより、この変更が反映されるようになります。管理ポータルで現在のテーブルのフィールドがどちらのデータタイプになっているかを確認できます。 管理ポータル: [システムエクスプローラ] > [SQL] Tips: %PosixTime のフィールドから YYYY-MM-DD の日付部分のみを取得したい場合は、次のように一旦TIMESTAMPにCAST()してから TO_CHAR() を使用します。 select xxxDateTime, to_char(cast(xxxDateTime as timestamp),'YYYY-MM-DD') from Test %PosixTime のデータの Insert は %TimeStamp と同じように行えます。 USER>do $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------------- The command prefix is currently set to: <<nothing>>. Enter <command>, 'q' to quit, '?' for help. [SQL]USER>>set selectmode=odbc // ODBCモードでテスト selectmode = odbc [SQL]USER>>insert into Test2 (xxxDateTime, xxxDateTime2) values ('2023-02-20 13:45:00','2023-02-20 13:45:00') 6. insert into Test2 (xxxDateTime, xxxDateTime2) values ('2023-02-20 13:45:00','2023-02-20 13:45:00') 1 Row Affected statement prepare time(s)/globals/cmds/disk: 0.0010s/32/4,002/0ms execute time(s)/globals/cmds/disk: 0.0002s/3/183/0ms cached query class: %sqlcq.XXX.cls12 --------------------------------------------------------------------------- [SQL]USER>>select * from Test2 // そのままSelect(※ xxxDateTime :%PosixTime , xxxDateTime2 :%TimeStamp ) 7. select * from Test2 xxxDateTime xxxDateTime2 2023-02-20 13:45:00 2023-02-20 13:45:00 1 Rows(s) Affected statement prepare time(s)/globals/cmds/disk: 0.0004s/26/978/0ms execute time(s)/globals/cmds/disk: 0.0001s/2/528/0ms cached query class: %sqlcq.XXX.cls13 --------------------------------------------------------------------------- [SQL]USER>>q USER>zw ^poCN.Dqym.1 // %PosixTime型で実際に格納されているデータは64bit整数 ^poCN.Dqym.1=1 ^poCN.Dqym.1(1)=$lb(1154598405306846976,"2023-02-20 13:45:00") 現在日時を%PosixTime型で出力したり、%TimeStamp ⇔ %PosixTime 変換したい場合は以下のように行えます。 USER>write ##Class(%Library.PosixTime).CurrentTimeStamp() 1154596073773251031 USER>write ##Class(%Library.PosixTime).LogicalToTimeStamp(ptime) 2023-01-24 14:06:06.404055 USER>write ##Class(%Library.PosixTime).TimeStampToLogical("2023-01-24 14:06:06.404055") 1154596073773251031 詳細は以下のドキュメントをご覧ください日付、時刻、PosixTime、およびタイムスタンプのデータ型【ご参考】%TimeStamp型プロパティを使用した範囲指定のクエリが遅い場合の対処方法SQLベースのベンチマークを行う際に、実施していただきたい5つの項目