クリアフィルター
お知らせ
Mihoko Iijima · 2021年6月8日
これは InterSystems FAQ サイトの記事です。
現時点のサポートバージョンについては、以下のドキュメントをご参照ください。
ミニマム・サポートバージョンについて
このリストに記述されていないバージョンに関しては、該当システムの保守契約があるという前提で、
メール、お電話等の通常のお問い合わせは受け付けます。
出来うる限りの対応を努力しますが、そのバージョンでの対応が出来ず、最新バージョンへのバージョンアップによる問題解決をご提案させていただく場合がございます。
インターシステムズは、以下の理由により、出来うる限り、最新バージョンへバージョンアップしていただくことをお勧めしております。
開発部門では、定期的に内部のコードレビュを実施しており、その結果、発見された機能不全等を適宜、最新バージョンに取り込んでいます。
過去にお客様先で発見された障害への対応は、順次新バージョンに取り込んでいきます。障害の性質によっては、非常に大規模な改造となり、旧バージョンへの取り込み(バックポート)が不可能なものもございます。
古いバージョンに対し、新バージョンに追加されたバグフィックスを取り込むこと(バックポート)は、非常に困難なケースが多く、新たな副作用を生む可能性が高くなります。また、バージョンが古くなればなるほどバックポートは困難になっていく可能性が高まります。
開発をより迅速に行うための便利な機能等、新機能の恩恵を受けることができます。
新しいバージョンは以前のバージョンよりも性能が同等または高くなければならない、というインターシステムズ製品開発部門のポリシーにより、バージョンが新しくなるにつれ、アプリケーションの性能向上が期待できます。
日々品質向上に向け努力を続けております。結果として最新バージョンの品質は以前のバージョンに比べて高いことが期待できます。
新しいハードウェア対応、それに伴うオペレーティングシステムや様々なソフトウェアの新バージョンへの対応は、最新のバージョンが優先されます。
セキュリティの脆弱性等への対応は最新バージョンが優先されます。
セキュリティの脆弱性等への対応は、大規模な広範囲にわたる変更の可能性が高く、古いバージョンへの対応が大変困難となります。
サポート対象バージョンに関しては、所定のリグレッションテストを実施後出荷しますが、応急パッチ等の非定型な対応では、リグレッションテストを実施しませんので、ソフトウェアの信頼性に大きな差があります。
お使いの IT 環境は、技術の進歩、セキュリティ意識の高まり等様々な要因で変化していく可能性が高い中、古いバージョンのソフトウェアを使い続けることには想定以上のリスクがあります。
関連記事・FAQトピック
セキュリティ脆弱性に関する対応について
バグフィックス対応について
古いバージョンの購入はできますか?
お知らせ
Toshihiko Minamoto · 2023年6月20日
先週の InterSystems Global Summit にて、今年の初めにリリースしました2023.1のエクスペリメンタル機能として、新たな 外部テーブル を発表しました。現在、 外部テーブルの Early Access Program にご参加いただきご評価いただくことで、この機能がお客さまのニーズに合っているか、次に向けてどの機能を優先するべきか、お知らせいただきたいと考えています。
外部テーブルって何なの?この素晴らしい概要ビデオを見る時間やポップコーンがない場合に備えて、外部テーブルは、ファイルやリモートデータベースなど、物理的に別の場所に保存されているデータをIRIS SQLとしてアクセスするのに役立つ機能です。外部テーブルは、通常のIRISテーブルとしてSQLに表示され、他の通常テーブルや外部テーブルとのJOINなど、あらゆるSQLステートメントで使用することができます。クエリを実行する際、外部テーブルから何を検索する必要があるのかを理解し、そのサーバーがリレーショナルデータベースの場合は、ネットワーク経由で取得するデータを最小限に抑えるようなクエリを出力しています。
なぜデータをロードするだけじゃないの?それはいい質問ですね!データをロードした後、IRISでクエリを実行すると、ソース・システムですでに変更されている可能性があります。コストのかかる同期メカニズムを構築するのではなく、外部テーブルとして投影することで、クエリ時に常に最新のデータを取得することができます。その他の使用例としては、ファイルベースの大きなデータで、IRISデータベースのストレージを消費してしまうような場合、または一般的に一度しか読まないようなデータなどがあります。ところで、外部テーブルの構文と機能は、2021.2 で紹介した LOAD DATA コマンドで知っているものと完全に一致しています。
既にリンクテーブルがあるのでは?その通りです。その機能はかなり以前からあり、私たちの顧客ではよく採用されています。しかし、JDBCとODBCの実装が異なる時期に行われたため、機能や制限が微妙に異なり、メンテナンスや機能拡張が複雑になっていました。当初の実装以降、IRISのSQL内部でいくつかの改良(Table Valued Functionsを含む)が行われ、外部とIRISをより明確に分離した、より実用的なアプローチが可能になりました。このため、新しい機能として Foreign Tables を実装し、公式の ANSI SQL 標準と一致するようにしました。
現在、外部テーブルをLinked Tablesの後継とするつもりです。現在、Foreign Tablesを使用している方は、その機能のどれがあなたのユースケースにとって重要かをぜひ教えてください。そうすれば、外部テーブルの次の開発フェーズに優先順位をつけ、迅速に多くのユーザーに参加してもらい、ラボ以外からフィードバックが得られるでしょう。
どこで申し込むの?intersystems.com/early-access-programs にお申込みいただければ、チュートリアルとデモのリポジトリから開始できます。定期的にご連絡を差し上げ、ご意見を伺い、新しい機能を追加していく予定です。
皆様からのご連絡をお待ちしております!
記事
Mihoko Iijima · 2021年8月23日
これは InterSystems FAQ サイトの記事です。
※データベースファイルとは、IRIS.DAT、および、CACHE.DATのことを指します。
Caché/Ensemble 2018.1.4、IRIS 2019.1.2/2020.1.0 以降のバージョンより、データベースファイル のサイズの縮小に、データベースの「圧縮」と「未使用領域の削除」機能をあわせて使用できます。
※注意※Caché/Ensemble 2018.1.4、IRIS 2019.1.2/2020.1.0 より前のバージョンでは、データベースの「圧縮」機能は使用できません。使用された場合、データベース破損が引き起こされる可能性があります(「未使用領域の削除」機能は利用できます)。
詳細情報は以下、弊社ウェブサイト内のページをご確認ください。
製品ニュースとアラート>警告: データベース圧縮またはデフラグ後のデータベース整合性の問題
データベースサイズ縮小手順は以下の通りです。
管理ポータル: [システムオペレーション] > [データベース] を開き、操作したいデータベース名をクリックします。
(または、画面左上の[空き容量]ボタンをクリックし、データベースの空き容量を表示します。)
① 「圧縮」ボタンにより、データベースファイルの使用していないブロックを後ろに集めます
<圧縮のイメージ> ●が使用中ブロック、〇が未使用ブロックだとします。
●●○●●○○●○○ ←のようにブロックが並んでいるデータベースファイル(*.DAT)を「圧縮」すると
●●●●●○○○○○ ←このようになります (左が前方)
②「未使用領域削除」ボタンにより、後方に集めた未使用ブロックを削除してデータベースファイルのサイズを縮小します。
<未使用領域削除のイメージ> ●が使用中ブロック、〇が未使用ブロックだとします。
●●●●●○○○○○ ←のようにブロックが並んでいるデータベースファイルに「未使用領域削除」を実行すると
●●●●● ←このようになります
圧縮について詳細は、ドキュメント(データベースの圧縮(IRIS))をご参照ください(2018.1以前のドキュメント:データベースの圧縮)。
管理ポータルの操作と同様の処理を、^DATABASE ユーティリティを使用して実行することもできます。
%SYS ネームスペースに移動してから実行します。
USER>zn "%SYS"
%SYS>do ^DATABASE
1) Create a database
2) Edit a database
3) List databases
4) Delete a database
5) Mount a database
6) Dismount a database
7) Compact globals in a database
8) Show free space for a database
9) Show details for a database
10) Recreate a database
11) Manage database encryption
12) Return unused space for a database // 「未使用領域削除」
13) Compact free space in a database // 「圧縮」
14) Defragment a database // 「DB の断片化解消」
15) Show background database tasks
Option?
※「圧縮」 = 13) Compact freespace in a database 「未使用領域削除」= 12) Return unused space for a database
^DATABASE ユーティリティの 14) Defragment globals in a database(「DBの断片化解消」)について補足します。
「DBの断片化解消」では、指定したデータベースファイル内のグローバル毎にブロックが再編成され、データベースファイル内で連続領域に再配置します。
「圧縮」をより強力にしたものですが、この再配置を行う為には、データベースファイル末尾に十分な空きブロックが必要です(一般的に見て、デフラグを定期的に実行する必要はありません)。
<「DBの断片化解消」のイメージ>
^a ♦ ^b ♠ ^c♥ 空き○
断片化解消前♦♦○♠♠○♠♠♦♦♠♥♥♥○♦♦♥♥♦○○○○○○
断片化解消後♦♦♦♦♦♦♦♠♠♠♠♠♥♥♥♥♥○○○○○○○○○
※注意※「断片化解消」機能についても、2015.1.3、2015.2.2 より前のバージョンでは使用できません。使用された場合、「圧縮」機能と同様に、データベース破損が引き起こされる可能性があります。
詳細は、ドキュメント(データベースのグローバルのデフラグ(IRIS))をご参照ください(2018.1以前のドキュメント:データベースのグローバルのデフラグ)。
記事
Shintaro Kaminaka · 2020年7月30日
この記事では、RESTFormsプロジェクト(モダンなWebアプリケーション用の汎用REST APIバックエンド)を紹介します。
プロジェクトの背後にあるアイデアは単純です。私はいくつかのREST APIを書いた後、REST APIが一般的に次の2つの部分で構成されていることに気付きました。
* 永続クラスの操作
* カスタムビジネスロジック
また、独自のカスタムビジネスロジックを書く必要はありますが、RESTFormsには永続クラスの操作に関連するすべての機能を提供しています。
**使用例**
* Cachéにすでにデータモデルがあり、REST API形式で情報の一部(またはすべて)を公開したい
* 新しいCachéアプリケーションを開発しており、REST APIを提供したい
**クライアントサイド**
このプロジェクトはWebアプリケーションのバックエンドとして開発されているため、JSだけで事足ります。 形式の変換は必要ありません。
**補足:CRUD**
オブジェクトまたはコレクションに対し、次の4つの操作を実行できます。
* Create(作成)
* Read(読み込み)
* Update(更新)
* Delete(削除)
**機能**
RESTFormsを使用して以下を実行できます。
* 公開されたクラスに対するCRUD - クラスのメタデータを取得し、クラスのプロパティを作成 / 更新 / 削除できます。
* オブジェクトに対するCRUD - オブジェクトを取得 / 作成 / 更新 / 削除できます。
* オブジェクトコレクションに対するRead(SQL経由) - SQLインジェクションから保護します。
* 自己検出 – 最初に使用可能なクラスのリストを取得し、その後でクラスのメタデータを取得し、そのメタデータを基にしてオブジェクトに対するCRUDを実行できます。
**パス**
以下の表には、主なパスとRESTFormsを使用して実行できる操作を掲載しています。
URL
説明
info
利用可能なすべてのクラスを一覧表示します
info/all
すべてのクラスのメタデータを取得します
info/:class
クラスのメタデータ
field/:class
プロパティをクラスに追加します
field/:class
クラスのプロパティを変更します
field/:class/:property
クラスのプロパティを削除します
object/:class/:id
オブジェクトを取得します
object/:class/:id/:property
オブジェクトの1つのプロパティを取得します
object/:class
オブジェクトを作成します
object/:class/:id
動的オブジェクトからオブジェクトを更新します
object/:class
オブジェクトからオブジェクトを更新します
object/:class/:id
オブジェクトを削除します
objects/:class/:query
(SQL)クエリでクラスのオブジェクトを取得します
objects/:class/custom/:query
(SQL)カスタムクエリでクラスのオブジェクトを取得します
**RESTFormsを使い始めるには?**
1. GitHubからプロジェクトをインポートします(お勧めの方法は独自リポジトリにサブモジュールとして追加する方法ですが、単にリリースをダウンロードしても良いです)。
2. RESTFormsを介して公開したい各クラスについて以下を実施します。
* アダプタクラスから継承する
* 権限を指定します(一部のクラスを読み取り専用として公開する場合などに実施)。
* オブジェクトの表示値として使用されるプロパティを指定します。
* 表示したいプロパティの表示名を指定します。
**セットアップ**
1. [リリースページ](https://github.com/intersystems-ru/RESTForms/releases/tag/v1.0)で最新リリースである20161.xml( Caché 2016.1用)または201162.xml(Caché 2016.2以降用)をダウンロードして任意のネームスペースにインポートします。
2. 新しいWebアプリケーション /forms をDispatchクラス Form.REST.Main を使用して作成します。
3. http://localhost:57772/forms/test?Debug をブラウザで開き、インストールを検証します({"Status": "OK"} が出力され、場合によってはパスワードの入力が求められます)。
4. テストデータが必要な場合は、次を呼び出します:
```do ##class(Form.Util.Init).populateTestForms()```
**例**
最初に、利用可能なクラスを知る必要があります。 この情報を取得するには、次を呼び出します。
http://localhost:57772/forms/form/info
次のような応答が返されます。
[
{ "name":"Company", "class":"Form.Test.Company" },
{ "name":"Person", "class":"Form.Test.Person" },
{ "name":"Simple form", "class":"Form.Test.Simple" }
]
現在3つのサンプルクラス(RESTFormで提供)があります。Person(Form.Test.Personクラス)のメタデータを見てみましょう。 この情報を取得するには、次を呼び出します。
http://localhost:57772/forms/form/info/Form.Test.Person
次のように、クラスのメタデータが応答として返されます。
{
"name":"Person",
"class":"Form.Test.Person",
"displayProperty":"name",
"objpermissions":"CRUD",
"fields":[
{ "name":"name", "type":"%Library.String", "collection":"", "displayName":"Name", "required":0, "category":"datatype" },
{ "name":"dob", "type":"%Library.Date", "collection":"", "displayName":"Date of Birth", "required":0, "category":"datatype" },
{ "name":"ts", "type":"%Library.TimeStamp", "collection":"", "displayName":"Timestamp", "required":0, "category":"datatype" },
{ "name":"num", "type":"%Library.Numeric", "collection":"", "displayName":"Number", "required":0, "category":"datatype" },
{ "name":"аge", "type":"%Library.Integer", "collection":"", "displayName":"Age", "required":0, "category":"datatype" },
{ "name":"relative", "type":"Form.Test.Person", "collection":"", "displayName":"Relative", "required":0, "category":"form" },
{ "name":"Home", "type":"Form.Test.Address", "collection":"", "displayName":"House", "required":0, "category":"serial" },
{ "name":"company", "type":"Form.Test.Company", "collection":"", "displayName":"Company", "required":0, "category":"form" }
]
}
これらの情報は次のような意味を持ちます。
クラスのメタデータ:
* name - クラスの表示名。
* class - 基本となる永続クラス。
* displayProperty - オブジェクトを表示するときに使用するオブジェクトのプロパティ。
* objpermissions - ユーザーがオブジェクトを使用して実行できる操作。 この例では、ユーザーは新しいオブジェクトを作成し、既存のオブジェクトを変更し、既存のオブジェクトを削除し、次を取得できます。
プロパティのメタデータ:
* name - プロパティ名 - クラスの定義と同じです。
type - プロパティのクラス。
* コレクション - リスト/配列のコレクションです。
* displayName - 表示プロパティ名。
* required - このプロパティが必須であるかどうか。
* category - プロパティのタイプクラスのカテゴリ。 RESTForms対応のすべてのクラスが「form」として表示されることを除き、通常のCachéクラスのカテゴリに従います。
クラス定義では次のようになります。
/// テストフォーム: Person
Class Form.Test.Person Extends (%Persistent, Form.Adaptor, %Populate)
{
/// フォーム名。グローバルキーではないため、何でもかまいません。
/// クラスをフォームとして持たないようにするには(ここのように)空の文字列に設定します。
Parameter FORMNAME = "Person";
/// デフォルトの権限
/// このフォームのオブジェクトは、作成、読み取り、更新、削除できます。
/// すべてのユーザーの権限を変更するには、このパラメーターを再定義します。
/// このクラスのcheckPermissionメソッドを再定義します(Form.Securityを参照してください)。
/// ユーザーやロールなどに基づいて独自のセキュリティを追加します。
Parameter OBJPERMISSIONS As %String = "CRUD";
/// オブジェクトの基本情報に使用されるプロパティ
/// デフォルトでは、getObjectDisplayNameメソッドはここから値を取得します。
Parameter DISPLAYPROPERTY As %String = "name";
/// このパラメーターの値をSQLでORDER BY句の値として使用します。
Parameter FORMORDERBY As %String = "dob";
/// Personの名前。
Property name As %String(COLLATION = "TRUNCATE(250)", DISPLAYNAME = "Name", MAXLEN = 2000);
/// Personの生年月日。
Property dob As %Date(DISPLAYNAME = "Date of Birth", POPSPEC = "Date()");
Property ts As %TimeStamp(DISPLAYNAME = "Timestamp") [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];
Property num As %Numeric(DISPLAYNAME = "Number") [ InitialExpression = "2.15" ];
/// Personの年齢。<br>
/// これは、 <property>DOB</property> から派生した値を持つ計算されたフィールドです。
Property аge As %Integer(DISPLAYNAME = "Age") [ Calculated, SqlComputeCode = { set {*}=##class(Form.Test.Person).currentAge({dob})}, SqlComputed, SqlComputeOnChange = dob ];
/// このクラスメソッドは、誕生日 <var>date</var> が与えられた場合に現在の年齢を計算します。
ClassMethod currentAge(date As %Date = "") As %Integer [ CodeMode = expression ]
{
$Select(date="":"",1:($ZD($H,8)-$ZD(date,8)\10000))
}
/// Personの配偶者。
/// これは別の永続オブジェクトへの参照です。
Property relative As Form.Test.Person(DISPLAYNAME = "Relative");
/// Personの自宅住所。 埋め込みオブジェクトを使用します。
Property Home As Form.Test.Address(DISPLAYNAME = "House");
/// このPersonが働いている会社。
Relationship company As Form.Test.Company(DISPLAYNAME = "Company") [ Cardinality = one, Inverse = employees ];
}
**クラスでRESTFormsを有効にする**
そして、このクラスでRESTFormsを有効にするため、通常の永続クラスから始めて次のことを行いました。
1. Form.Adaptor から拡張しました。
2. 値を含むパラメーター FORMNAME(クラス名)を追加しました。
3. OBJPERMISSIONS パラメーター(すべての権限のCRUD)を追加しました。
4. DISPLAYPROPERTY パラメーター(オブジェクト名の表示に使用されるプロパティ名)を追加しました。
5. FORMORDERBY パラメーター(RESTFormsを使用するクエリでソートするデフォルトのプロパティ)を追加しました。
6. メタデータで確認したいプロパティごとに DISPLAYNAME プロパティのパラメーターを追加しました。
以上です。 コンパイル後、RESTFormsを含むクラスを使用できるようになります。
いくつかのテストデータを生成しましたので(インストールのステップ4を参照)、IDが1のPersonを取得してみましょう。 オブジェクトを取得するには、次を呼び出します。
http://localhost:57772/forms/form/object/Form.Test.Person/1
その応答は以下のとおりです(生成されるデータは異なる場合があります)。
{
"_class":"Form.Test.Person",
"_id":1,
"name":"Klingman,Rhonda H.",
"dob":"1996-10-18",
"ts":"2016-09-20T10:51:31.375Z",
"num":2.15,
"аge":20,
"relative":null,
"Home":{
"_class":"Form.Test.Address",
"House":430,
"Street":"5337 Second Place",
"City":"Jackson"
},
"company":{
"_class":"Form.Test.Company",
"_id":60,
"name":"XenaSys.com",
"employees":[
null
]
}
}
オブジェクト(具体的にはnumプロパティ)を変更するには、次を呼び出します。
PUT http://localhost:57772/forms/form/object/Form.Test.Person
このボディを使用します。
{
"_class":"Form.Test.Person",
"_id":1,
"num":3.15
}
速度を上げるには、_class、_id、および変更対象のプロパティのみをリクエストのボディに含める必要があります。
では、新しいオブジェクトを作成しましょう。 以下を呼び出します。
POST http://localhost:57772/forms/form/object/Form.Test.Person
このボディを使用します。
{
"_class":"Form.Test.Person",
"name":"Test person",
"dob":"2000-01-18",
"ts":"2016-09-20T10:51:31.375Z",
"num":2.15,
"company":{ "_class":"Form.Test.Company", "_id":1 }
}
オブジェクトの作成が成功した場合、RESTFormsは以下のようにIDを返します。
{"Id": "101"}
成功しなかった場合、エラーがJSON形式で返されます。 すべての永続オブジェクトのプロパティは、 _class および _id プロパティによってのみ参照する必要があります。
そして最後に、新しいオブジェクトを削除しましょう。 以下を呼び出します。
DELETE http://localhost:57772/forms/form/object/Form.Test.Person/101
これがForm.Test.Personクラスに対する完全なCRUDです。
**デモ**
[現在デモ環境はお試しいただくことができません。]
こちらでRESTFormsをオンラインで試すことができます(ユーザー名:Demo、パスワード:Demo)。
また、RESTFormsUIアプリケーション(RESTFormsデータエディタ)もあります。こちらをご確認ください(ユーザー名:Demo、パスワード:Demo)。 クラスリストのスクリーンショットを以下に掲載しています。

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

## ブログとは?
これはWEB LOGの略名で、基本的にはユーザーが書いてページに投稿、公開できるプラットフォームです。
## マークダウンフォーマットとは?
マークダウンとは、フォーマットしたテキストを作成するマークアップ言語であり、HTMLより簡単で、多くのCMSプラットフォームで人気があります。
例:
```
# Header 1
## Header 2
### Header 3
```
結果:
# Header 1
## Header 2
### Header 3
***********
## IRISで作成するメリットとは?
IRIS Globalsを使用して各投稿のデータを永続化しながら、各データコンサルタントはキー・バリュー・データベースとして動作するIRISのスピードというメリットを得ます。すでに作業でIris Instanceを使用している場合は、このようなアプリケーションを作成するために別の技術を使用する必要はありません。
## そしてPythonとは?
Pythonは最も人気の高いプログラミング言語の一つです。チューリング完全言語であり、開発課題のほとんどを楽にしてくれるオープンソースライブラリがたくさんあります。
私は、PythonとIrisを統合するために、IRISNative APIを使用しました。
## データベースモデル キー値
私のエンジンは、これでもかというほどシンプルに動作します。
各投稿を永続化するために、サブスクリプトが「post」、その次のサブスクリプトが投稿IDのグローバル「^blog」を作成します。
このグローバルに投稿コンテンツを入れて、終わりです!
これだけで、テーブル、インデックスなどを作成する必要はありません。
```
^blog("post", "1") = "# post 1 content..."
^blog("post", "2") = "# post 2..."
^blog("post", "3") = "# post 3 markdown content..."
```
## HTMLでマークダウンをレンダリングするには?
これで、オープンソースモジュール使用可能なPythonのメリットを無限に得るこquoとができます。私の選択は、たった1行のコマンドで簡単にマークダウンをレンダリングできるDashライブラリでした(^_^)
```python
import dash_core_components as dcc
import dash
import irisnative
#creating a connection with an IRIS Instance
conn = irisnative.createConnection("host","port","namespace","username","password") obj_iris = irisnative.createIris(conn)
#getting the content of one post with id 1
content = obj_iris.get("blog", "post", "1")
#creating the dash application
app = dash.Dash(__name__)
#rendering a markdown value
rendered_markdown_in_html = dcc.Markdown(content)
#showing on the page the rendender markdown
app.layout = html.Div([rendered_markdown_in_html])
```
## すべての投稿を表示するには?
サブスクリプト「^blog("post",)」繰り返して、上記と同じ方法でレンダリングされたマークダウンをページにプリントできます。
データベースをモデル化し、これよりも速く簡単に動作するフォームを作ったことがありますか?回答はコメント欄に書いてください!
## 自分でビルドしたくないけど、動作する様子は見たい!
簡単です!ここをクリックして、*御覧ください。*
http://iris-multimodel-suite.eastus.cloudapp.azure.com/blog-post
(サイトは既に閉鎖されています。ご了承ください)
# この記事とアプリケーションをお楽しみいただけましたか?
ここではマルチモデルコンテストでの私のアプリケーションの一部をご説明しています。よろしければ、私のアプリに投票してください。
記事
Toshihiko Minamoto · 2023年1月24日
ZPM は、InterSystems IRIS データプラットフォーム用のアプリケーションやモジュールと連携するように設計されています。モジュールを管理するためのCLIであるZPN Clientと、モジュールやメタ情報のデータベースであるThe Registryの2つのコンポーネントで構成されています。ZPM を使用して、モジュールの検索、インストール、アップグレード、削除、公開を行うことができます。ZPMを使用すると、ObjectScriptクラス、フロントエンドアプリケーション、Interoperabilityプロダクション、IRIS BIソリューション、IRISデータセット、またはEmbedded Pythonホイールなどのあらゆるファイルをインストールできます。
今日、このクックブックは3つのセクションについて説明します。
1. ZPMのインストール
2. モジュールの作成
3. Registry内のモジュールの検索、インストール、公開
1. ZPMのインストール
* ZPM最新バージョンのダウンロード(1つのXMLファイルです)[ダウンロードリンク](https://pm.community.intersystems.com/packages/zpm/latest/installer)
* ダウンロードしたXMLをIRISにインポートし、IRISターミナルを開き、次のようにコマンドを入力するだけでIRISにデプロイされます。
_write $SYSTEM.OBJ.Load("C:\zpm.xml", "c")_
注意:"C:\zpm.xml"は、ダウンロードしたXMLファイルのパスです。このステップはしばらく時間がかかるかもしれません。
* インストールが完了したら、_zpm_ とタイプして Enter キーを押すと、zpm シェルが表示されます。

2. モジュールの作成
モジュールの作成を始める前に、ダウンロード用のファイルを格納したフォルダを用意する必要があります。そこで、zpm というフォルダをC ドライブに 作成しました。
コマンド _generate C:/zpm_ を実行します。
必要な項目をすべて指定すると、最初のモジュールが正常に生成され、次のような画面も表示されます。

注意:
1. モジュールのバージョン(module version)はセマンティックバージョニングを使用しています
2. モジュールソースフォルダ(module source folder)は、すべてのクラスファイルが格納されているフォルダです。
3. zpm にはウェブアプリケーション(Web application)と依存関係(Dependencies)を追加するオプションもありますが、この例では空白にしておきます。
ここで、ファイルエクスプローラを開くと、以下のスクリーンショットのように、「module.xml」というファイルが表示されるはずです。

コマンド "_load C:\ZPM__" をタイプします。タイプすると、モジュールが再ロードされ、検証され、コンパイルされ、アクティブ化されます。

3. Registry内のモジュールの検索、インストール、公開
最新のRegistryで利用可能なパッケージを検索します: _zpm:USER>search_
最新のRegistry からパッケージをインストールする 例として、zpmshowというモジュールをパブリックRegistryにインストールする方法を説明します: _zpm:USER>install zpmshow _(コマンドは、 "moduleName" をインストールします)。
ロード後、モジュールを公開します: _zpm:USER>publish myFirstZPMDemo_
_zpm:USER>search_ を使って公開を確認することができます。この場合、"myfirstzpmdemo 0.1.0 "が最新のRegistryにあることがわかります。

注意:モジュールを公開しているときに、次のようなエラーが発生した場合:_ "エラー! 発行モジュールにおいて、何か問題が発生しました(ERROR! Publishing module, something went wrong)" と表示される場合、_最新のRegistry のステータスが有効で利用可能であることを確認してください。
zpm:USER>repo -list, _で最新のRegistryの状態を確認することができます。

ビデオ:[こちらをクリックしてください](https://www.loom.com/share/0ca097f0dea4476ea294841295f972b2%C2%A0%C2%A0)
記事
Mihoko Iijima · 2023年3月5日
開発者の皆さん、こんにちは!
1つ前の記事では、VSCodeのObjectScriptエクステンションに追加されたRESTサービスのデバッグツールについてご紹介しましたが、この記事では、RESTクライアントなどからHTTP要求を実行し、処理中のIRIS内プロセスにアタッチしながらデバッグを行う方法についてご紹介します。
事前準備などは、前回の記事と一緒になりますのでこの記事では割愛します。
図解のクラスでお試しいただく場合は、こちら👉https://github.com/Intersystems-jp/RESTDebug-VSCode をご利用ください。
この方法では、HTTP要求時に実行しているIRIS内プロセスを特定する必要があります。
そのため、デバッグ対象のメソッドで処理を待機させるため、hang 20のようにhangコマンドをコード中に追加して試します。
手順は以下の通りです。
1) プロセスにアタッチしてデバッグを行うため、launch.jsonを作成する。
VSCodeのデバッグ用ファイル:launch.jsonファイルを作成し、アタッチ用の設定を行います。
作成方法詳細は、VSCode:プロセスにアタッチしてデバッグする方法の「手順1:launch.json の用意」をご参照ください。
例)
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "objectscript",
"request": "attach",
"name": "アタッチデバッグ練習",
"system": true,
"processId": "${command:PickProcess}"
}
]
}
2) デバッグ対象メソッドにHangコマンド追加します。
Hangを利用して処理を数秒待機させ、その間に実行中プロセスを特定します。ブレークポイントを置きたい行の前あたりにhangコマンドを追加してください(20~30秒待機するように記述します)。
hang 20
VSCodeでコードを編集するためには、ワークスペース内にクラス定義用ファイル(*.cls)を配置する必要があります。
サーバ側にあるコードに追記したい場合は、ワークスペースにエクスポートしてから追記します。
エクスポートは、サーバ側の対象コードを右クリックし「Export」を選択します。(下図参照)
Export実行後、ワークスペースに移動します。エクスポートファイルは、/src/パッケージ名/クラス名.clsの形式でワークスペースにエクスポートされます。
対象クラスを開き、コードを追記します。
コードの追記が終わったら、Ctrl+Sで保存します(保存+コンパイルが実施されます)。
3) 外部クライアントからHTTP要求を実行します。
例は、PostmanからPOST要求でクエリパラメータとBodyに文字列を指定しています。
Sendボタンを実行すると、2) で指定したHangコマンドが実行されるため、指定秒数待機します。
この間に、アタッチ対象プロセスを探します。
4) アタッチ対象プロセスにアタッチする。
3) の実行後すぐ、VSCodeのデバッグウィンドウを開き、launch.jsonで指定したデバッグ名(例では、「アタッチデバッグ練習」)をクリックし、プロセスリストを表示させます。
プロセスリストには「RESTディスパッチクラス名.*」(*には数字が入ります)の形式で処理中ルーチン名が表示されます。
対象プロセスを見つけたら行をクリックしプロセスにアタッチします。
注意:指定したhangの秒数の間にプロセスを見つけてアタッチしてください。
5) デバッグを実行する。
Hangコマンドの実行が終わると、ブレークマークのところでデバッグが一時停止します。
後は、デバッグ用アイコンを利用してデバッグを進めるだけです。
6) デバッグを終了する。
最後に、デバッグ停止のボタンを押して終了します。
メモ:
アタッチ用プロセス一覧に対象プロセスが表示されない場合は、一旦VSCodeを再起動して再度お試しください。
アタッチのデバッグを実行するために追加したhangコマンドはデバッグ終了時必ず削除/コメント化してください。
記事
Megumi Kakechi · 2020年9月16日
これはInterSystems FAQ サイトの記事です。
HTMLからRESTを使って画像ファイルをアップロードする方法をご紹介します。
1.はじめに、以下のようなhtmlとクラスを作成してください。
*UploadTest.html
<html lang="ja">
<head>
<title>Upload</title>
</head>
<body>
<input id="up" type="file" />
<button id="btn">Upload</button>
<div></div>
<script type="text/javascript">
const sendfile = function(e) {
let up = document.getElementById("up");
let file = up.files[0];
let fd = new FormData();
fd.append("imgfile", file);
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
var result = document.querySelector('div');
xmlhttp.onload = function () {
result.innerHTML = xmlhttp.responseText;
};
};
xmlhttp.open("POST", "http://127.0.0.1:52773/csp/user/isjtest/uploadimg", true);
xmlhttp.send(fd);
}
let btn = document.getElementById("btn");
btn.addEventListener("click", sendfile);
</script>
</body>
</html>
*User.MyREST.cls (IRIS/CachéサーバのUSERネームスペースに作成してください)
※こちらのサンプルでは、C:\tempフォルダにファイルをUploadしています。 適宜フォルダを作成して頂くか、任意のフォルダパスに変更して再コンパイルしてください。
Class User.MyREST Extends %CSP.REST
{
Parameter CONVERTINPUTSTREAM = 1;
Parameter HandleCorsRequest = 1;
XData UrlMap
{
<Routes>
<Route Url="/uploadimg" Method="POST" Call="readMimeData" />
</Routes>
}
ClassMethod readMimeData() As %Status
{
set upload=$g(%request.MimeData("imgfile", 1))
set fname=%request.MimeData("imgfile",1).FileName
set file=##class(%File).%New("c:\temp\"_fname)
do file.Open("NWUK\BIN\")
do file.CopyFrom(upload)
set st = file.%Save()
if st {
write fname_" アップロード完了!!"
} else {
write fname_" アップロード失敗"
}
do file.Close()
quit $$$OK
}
}
2. ウェブ・アプリケーション /csp/user/isjtest の定義を作成します。 管理ポータル:[システム管理]>[セキュリティ]>[アプリケーション]> [ウェブ・アプリケーション]>[新しいウェブ・アプリケーションを作成] RESTのディスパッチ・クラスに、1.で作成したUser.MyRESTクラスを指定します。(下記画像参照)
3. 必要に応じて UploadTest.html を編集し(※1)、Webサーバのドキュメントルート(※2)に配置します。 ※1. xmlhttp.openには、環境にあったIPアドレス・ポートを指定してお試しください。
xmlhttp.open("POST", "http://<サーバIP>:<IRIS/Cachéポート>/csp/user/isjtest/uploadimg", true);
※2. 例:C:\inetpub\wwwroot
4. クライアントブラウザより以下を実行し、任意のファイルをUploadします。 http://localhost/UploadTest.html
5. 指定したフォルダにファイルがUploadされたことをご確認ください。 この投稿を参考にさせて頂き、初めてRESTでIRISへの接続をするプログラムを作成してみました。何となく思った通りに動作しそうな雰囲気です。アプリケーション開発環境での組み込みができていませんので、まだ何が起こるか不安がよぎっていますが、現時点では満足しています。本日は力尽きました...
私の場合、すでに構築済みの手法(フレームワーク、と呼べるほど大層なものではなりません)があり、その手法に合わせるために、敢えてJSONを利用していません。そのため、多くのRESTのサンプルコードがJSON前提ということもあり参考になるものが見当たらず、苦しみました。(自業自得、ですが..)
ところで、上記のサンプルコード(UploadTest.html のJavaScript部分)についてです。
var result = document.querySelector('div');
のコードがありますが、「div」に対応するオブジェクト(タグ)が無いように思われます。その為?サーバーからのレスポンスが画面上に表示されないようです。ご確認頂ければ幸いです。 RESTでIRISへの接続をお試しいただきありがとうございます。また、サンプルコードへのご指摘ありがとうございます。確かに、タグが抜けておりました。大変失礼いたしました。サンプル(UploadTest.html)に足りないタグを追加しました。お手数をおかけしますが、ご確認をよろしくお願いいたします。 <button id="btn">Upload</button> <div></div> ←このタグを追加 ご確認とコードの改修、有難う御座いました。
重箱の隅をつつくような内容でしたがご対応頂き感謝いたします。
記事
Hiroshi Sato · 2020年7月2日
初めに
VisM.OCXはVisual Basicでクライアント・サーバー型のアプリケーション開発を支援するためにInterSystemsが提供してきたツールです。
誕生から既に20年以上が経過した非常に古いテクノロジーです。
OCX規格(ActiveXコンポーネント)は、マイクロソフト社が推進してきた規格ですが、やがてマイクロソフト社が後継となる.Net Frameworkをリリースし、その新しいフレームワークへの移行を強力に推進すると同時に、OCX規格は非推奨機能となっています。
一方で下位互換性のため、.Net Framework配下でOCXを動作可能とする仕組みが用意されており、結果としてOCXは、.Net Framework環境下で動作可能です。
従って、VisM.OCXも.Net対応のプログラミング言語C#やVB.NETからそのまま利用することができます。
しかしながら.Net Framework上で動作するとは言え、.Net Frameworkが用意する安全性の高い資源管理や強固なセキュリティ機能の恩恵を受けることができず、いわゆるマネージドコードとして動作できないという制約を抱えています。
上記の状況を整理した結果、InterSystems社は、IRISをリリースするに際し、ViSM.OCXは、標準製品では動作せずに、特別なキーを適用することで動作可能になるような措置(使用を制限する)を適用しました。
その結果、VisM.OCXを使用するためには、InterSystems社にお問い合わせいただき、その特別なキーを取得するための申請を行う必要があります。
また広く皆様にIRISを使って評価していただきたいと考えて提供している無償のCommunityエディションでも動作しません。
ここでIRISでVisM.OCXアプリケーションを動かすのはハードルが高いと感じた方に朗報です。
VisM.OCXを使用せずに、ViSM.OCX対応アプリケーションをIRIS上で動作可能にするための移行ツール(Caché Directエミュレーター)を用意しました。
このツールは、Open Exchangeから取得可能です。
Open Exchangeサイト
ここでは、このツールを使った移行作業を実際に簡単なVBアプリケーションを例として紹介していきたいと思います。
ここで説明しているADBKアプリケーションは、下記のGithubから入手可能です。
ADBKサンプル
ADBKアプリケーション
このサンプルアプリケーションは、20年以上前にVB6サンプルとして作成されました。
アドレス帳をCaché データベースに保存し、クライアントのVB6アプリケーションからVisM.OCXを使って、データの登録や検索を行う非常にシンプルなサンプルです。
VB6プロジェクトを.Netプロジェクトに変換する
このVB6のアプリケーションをまず.Netアプリケーションに変換しなければなりません。
この変換を行うためには、最新のVisual Studioではだめで、Visual Studio 2008を別途インストールする必要があります。
変換方法は以下のサイトをご参考ください。
VB6アプリケーションをVB.NETに変換する方法
VisM.OCXの削除
次にその変換された.net プロジェクトファイルを新しいVisual Studio(現時点では2019が最新)で読み込みます。
まずメインフォームの右下に配置されているVisM.OCX(青い立方体のアイコン)が見えると思います。
この部品を削除します。(選択した後に、右クリックで削除メニュー項目が表示される)
次に右側に表示されているソリューションエクスプローラーの参照の所を開いて、AxVISMLibを選択して右クリックし、削除を選びます。
必ず部品の削除を先に行ってください。
ここで更新したファイルの保存を行い、プロジェクトを閉じます。
必要に応じて、この状態一式を別ディレクトリーにバックアップします。
C#エミュレータークラスを使用可能にする
Caché DirectエミュレータをOpen Exchangeからダウンロードし、そのZipファイルを適当なディレクトリに展開します。
cacheDirectWapper.csを新規のC# クラスライブラリープロジェクトに取り込みます。
参照設定でIRISの.Netライブラリーを指定します。
c:\intersystems\IRIS\dev\dotnet\bin\v4.5InterSystems.Data.IRISClient.dll
プロジェクトメニューからxxxのプロパティをクリックします。
xxxはプロジェクト名です。左側のペインからアプリケーションを選びます。
アセンブリ名は適当な名前にします。(CacheDirectEmulatorなど)対象のフレームワークが.Net Framework 4.5でなければ、.Net Framework 4.5に変更します。
左側のペインからビルドを選びます。構成をアクティブな(Release)またはReleaseにします。
ビルドします。(dllが作成されます)
Caché Directエミュレーターの参照設定を追加する
VB.Netのプロジェクトを再度開き、プロジェクトメニューから参照の追加を選択します。
作成されたdllを参照設定します。(dllとアプリケーションプロジェクトの.Net Frameworkのバージョンを合わせる必要があります)
アプリケーション修正
それでは、VisM.OCX用に作られたアプリケーションをCaché Directエミュレータのインタフェースを使って動作するように変更しましょう。
まず、ADBKMain.vbを開きます。
まず、新しく追加したCaché Directエミュレータライブラリーを参照できるようにインポートする必要があります。
これは先頭にあるOption文の後ろに以下のように追加します。
Option Strict OffOption Explicit On'以下のインポートが必要Imports cdapp
続いてVisM.OCXの置き換えです。
このVBアプリは、直接VisM.OCXを使用するのではなく、それをラップするクラスを作成していました。従って、ここでは、VisM.OCXを直接置き換えるのではなく、そのラッパークラスを置き換える方式を採用します。
元々の定義は以下のようになっていました。
cVMClassというのが、VisMをラップしたクラスです。
Dim CacheDirect As New cVMClass
これをCaché Directエミュレータクラスを参照するように変更します。
長いので途中で折り返しています。
Public WithEvents CacheDirect As cacheDirectWapper = New cacheDirectWapper("Server = localhost; Port=1972; Namespace=USER; Password = SYS; User ID = _system;")
VisM.OCXの場合は、サーバーへの接続は、いくつかオプションがあるのですが、このエミュレーターはオブジェクト生成時にサーバー接続まで行う仕様になっています。
WithEvents句は、イベント処理が不要の場合は、指定しなくてもいいです。
これでCacheDirectを参照しているところは、ほとんど修正することなくそのまま使用できます。
但しフォームのロード処理にいくつか変更が必要でした。
Private Sub ADBKMain_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load CacheDirect.VisM = VisM1.GetOcx If Not Install("Address Book Demo Application", VisM1, "GLO", "^ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.gsa", "MAC", "ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.rsa") Then End End Sub
ここで元々のラッパークラスでは、VisM.OCXをプロパティとして設定する仕様だったのですが、これは今回必要なくなりました。
次にデータやルーチンが初期インストールされていない場合にそれらをロードする仕組みを用意していたのですが、これは今はちゃんとライブラリーが用意されていて、それを使うほうが簡単なので、書き換えることにします。
Private Sub ADBKMain_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load 'CacheDirect.VisM = VisM1.GetOcx 'If Not Install("Address Book Demo Application", VisM1, "GLO", "^ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.gsa", "MAC", "ADBK", "USER", "+'$D(^$ROUTINE(""ADBK""))", "adbk.rsa") Then End
CacheDirect.Execute("=$DATA(^$ROUTINE(""ADBK""))") If CacheDirect.VALUE = 0 Then CacheDirect.P0 = "c:\temp" CacheDirect.P1 = "ck-d" CacheDirect.Execute("set P2=$system.OBJ.ImportDir(P0,,P1,.P2)") If CacheDirect.P2.Substring(0, 1) <> 1 Then MsgBox("Loadでエラーが発生しました" & CacheDirect.P2)
End If
End If
End Sub
最後にオブジェクトの消滅に関してもより安全な方法を採用したために、書き換えが必要です。
Private Sub ADBKMain_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed 'UPGRADE_NOTE: オブジェクト CacheDirect をガベージ コレクトするまでこのオブジェクトを破棄することはできません。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6E35BFF6-CD74-4B09-9689-3E1A43DF8969"' をクリックしてください。 CacheDirect = Nothing End Sub
Caché Directエミュレータクラスのendメソッドを呼び出すように変更します。
Private Sub ADBKMain_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed 'UPGRADE_NOTE: オブジェクト CacheDirect をガベージ コレクトするまでこのオブジェクトを破棄することはできません。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6E35BFF6-CD74-4B09-9689-3E1A43DF8969"' をクリックしてください。 'CacheDirect = Nothing CacheDirect.end() End Sub
続いてFindByName.vbの変更点を見てみましょう。
ここでもCacheDirectのインスタンス化を実施していましたが、今回はADBKMainクラスのCaché Directエミュレータクラスを共有できるので、ここで宣言する必要がなくなります。
以下の記述をコメントアウトします。
'Dim CacheDirect As New cVMClass
次に検索ボタンが押された時の処理を変更していきます。
Private Sub CmdFind_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdFind.Click Dim i As Object 'Dim NOL As Short' 名前のリストを取得 ListLookupName.Items.Clear() '以下の機能は未実装 CacheDirect.Clear() CacheDirect.PDELIM = Chr(1) CacheDirect.P0 = "^ADBK(""XNAME"")" CacheDirect.P1 = TxtSNAME.Text CacheDirect.Execute("Do GetList^VISMUTIL(P0,P1,P1,"" "")") If CDbl(CacheDirect.Error_Renamed) <> 0 Then ' MsgBox (" Error " & CacheDirect.ErrorName) Exit Sub End If ' 取得した名前リストをListBoxに展開 For i = 1 To CacheDirect.NoOfPLISTItem 'UPGRADE_WARNING: オブジェクト i の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 ListLookupName.Items.Add(CacheDirect.PLISTItem(i)) Next iEnd Sub
ここでCacheDirect.Clear()はラッパークラスが独自に用意していたメソッドで、新しいエミュレータクラスには存在しません。
内容を確認した所、P0-P9,PLIST変数を初期化する処理のようで、処理を俯瞰する限り、ここで初期化する必要がないと判断し、コメントアウトすることにします。
ここでは、ADBKMainのエミュレータクラスを参照する必要があるため、すべてのCacheDirect参照の前にADBKMain.を付加する必要があります。
そして、VB6からVB.NETにコンバートする際に、Errorというキーワードは強制的に変換されるようで、Error_Renamedになっていたので、これを元のErrorに戻す必要がありました。
NoOfPLISTItemとPLISTItem(i)はラッパークラス独自の実装だったのですが、機能的には全く同じものがエミュレータークラスにあるので、そのメソッドに名前の変更を行いました。
Private Sub CmdFind_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdFind.Click Dim i As Object 'Dim NOL As Short ' 名前のリストを取得 ListLookupName.Items.Clear() '以下の機能は未実装 'CacheDirect.Clear() ADBKMain.CacheDirect.PDELIM = Chr(1) ADBKMain.CacheDirect.P0 = "^ADBK(""XNAME"")" ADBKMain.CacheDirect.P1 = TxtSNAME.Text ADBKMain.CacheDirect.Execute("Do GetList^VISMUTIL(P0,P1,P1,"" "")") 'If CDbl(CacheDirect.Error_Renamed) <> 0 Then If CDbl(ADBKMain.CacheDirect.Error) <> 0 Then MsgBox (" Error " & ADBKMain.CacheDirect.ErrorName) Exit Sub End If ' 取得した名前リストをListBoxに展開 'For i = 1 To CacheDirect.NoOfPLISTItem For i = 1 To ADBKMain.CacheDirect.getPLISTLength() 'UPGRADE_WARNING: オブジェクト i の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'ListLookupName.Items.Add(CacheDirect.PLISTItem(i)) ListLookupName.Items.Add(ADBKMain.CacheDirect.getPLIST(i)) Next iEnd Sub
次にイベント処理の変更です。
VisM.OCXでExecuteメソッドが実行されたことを補足するためのイベント処理が以下の様に定義されていました。
'Private Sub VisM1_Executed(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles VisM1.Executed Dim status As Object If VisM1.P9 <> "" Then 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("データエラー " & VisM1.P9, MsgBoxStyle.OKOnly, "データエラー") End If End Sub
Handle以下をエミュレーターのイベントに書き換える必要があります。ここではVisM.OCXを直接参照していたので、その部分をエミュレータークラスを参照する様に変更します。
Private Sub CacheDirect_Executed(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CacheDirect.ExecuteEvent Dim status As Object If CacheDirect.P9 <> "" Then ' 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("データエラー " & CacheDirect.P9, MsgBoxStyle.OkOnly, "データエラー") End If End Sub
Errorイベントに関しても同様の変更を行います。
'Private Sub VisM1_OnError(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles VisM1.OnError Dim status As Object 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("Cache' Direct Error " & VisM1.ErrorName, MsgBoxStyle.OKOnly, "Cache' Direct Error") 'エラーが発生した時点でのローカル変数をダンプする CacheDirect.PrintLocalVariable() End Sub
Private Sub CacheDirect_OnError(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CacheDirect.ErrorEvent Dim status As Object ' 'UPGRADE_WARNING: オブジェクト status の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 status = MsgBox("Cache' Direct Error " & CacheDirect.ErrorName, MsgBoxStyle.OkOnly, "Cache' Direct Error") ''エラーが発生した時点でのローカル変数をダンプする 'CacheDirect.PrintLocalVariable() MsgBox("エラーが発生しました" & CacheDirect.ErrorName, MsgBoxStyle.OkOnly) End Sub
次に検索した結果で検索するためのOKボタンが押された時の処理です
Private Sub CmdOK_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdOK.Click Dim result() As String CacheDirect.Clear() CacheDirect.P0 = VB6.GetItemString(ListLookupName, ListLookupName.SelectedIndex) '選択されている名前に対応するデータを検索する。 CacheDirect.Execute(("Do GetData^ADBK(P0)")) 'If CDbl(CacheDirect.Error_Renamed) <> 0 Then Exit Sub End If '検索フォームを非表示 Me.Hide() '取得データをフォームフィールドに展開する 'CType(ADBKMain.Controls("TxtNAME"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 1) 'CType(ADBKMain.Controls("TxtZIP"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 2) 'CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 3) 'CType(ADBKMain.Controls("TxtTELH"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 4) 'CType(ADBKMain.Controls("TxtTELO"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 5) 'CType(ADBKMain.Controls("TxtAGE"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 6) 'CType(ADBKMain.Controls("TxtDOB"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 7) End Sub
ここでもClearメソッドが呼ばれていますが、不要と判断し、コメントアウトしました。 先ほどと同様ErrorがError_Renameに強制的に変換されていました。 VB6.GetItemStringもVB6固有のメソッドになるので、書き換えが必要でした。 次にMPieceメソッドもラッパークラス固有の関数でしたが、内容を確認すると.NetのSplit関数を使うともっとシンプルに変換できることがわかりました。 先ほどと同様に先頭にADBKMain.を付加する必要があります。
Private Sub CmdOK_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles CmdOK.Click Dim result() As String 'CacheDirect.Clear() 'CacheDirect.P0 = VB6.GetItemString(ListLookupName, ListLookupName.SelectedIndex) If (ListLookupName.SelectedIndex >= 0) Then ADBKMain.CacheDirect.P0 = ListLookupName.Items(ListLookupName.SelectedIndex).ToString() '選択されている名前に対応するデータを検索する。 ADBKMain.CacheDirect.Execute(("Do GetData^ADBK(P0)")) 'If CDbl(CacheDirect.Error_Renamed) <> 0 Then Exit Sub End If '検索フォームを非表示 Me.Hide() '取得データをフォームフィールドに展開する 'CType(ADBKMain.Controls("TxtNAME"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 1) 'CType(ADBKMain.Controls("TxtZIP"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 2) 'CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 3) 'CType(ADBKMain.Controls("TxtTELH"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 4) 'CType(ADBKMain.Controls("TxtTELO"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 5) 'CType(ADBKMain.Controls("TxtAGE"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 6) 'CType(ADBKMain.Controls("TxtDOB"), Object).Text = CacheDirect.MPiece(CacheDirect.P1, Chr(2), 7) result = ADBKMain.CacheDirect.P1.ToString().Split(Chr(2)) CType(ADBKMain.Controls("TxtNAME"), Object).Text = result(0) CType(ADBKMain.Controls("TxtZIP"), Object).Text = result(1) CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = result(2) CType(ADBKMain.Controls("TxtTELH"), Object).Text = result(3) CType(ADBKMain.Controls("TxtTELO"), Object).Text = result(4) CType(ADBKMain.Controls("TxtAGE"), Object).Text = result(5) CType(ADBKMain.Controls("TxtDOB"), Object).Text = result(6) End If End Sub
次にロード処理ですが、このフォーム上では何もする必要がないので、VisMコントロールのロード処理をコメントアウトします。
Private Sub FindByName_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load 'CacheDirect.VisM = CType(Controls("VisM1"), Object) End Sub
フォームのクローズ処理では、エミュレータクラスの終了処理は不要なので、その処理をコメントアウトします。
Private Sub FindByName_FormClosed(ByVal eventSender As System.Object, ByVal eventArgs As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed 'UPGRADE_NOTE: オブジェクト CacheDirect をガベージ コレクトするまでこのオブジェクトを破棄することはできません。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6E35BFF6-CD74-4B09-9689-3E1A43DF8969"' をクリックしてください。 'CacheDirect = Nothing End Sub
これで修正は終了で、アプリケーションも動作するはずです。
autoinstall.basとVmclass.clsは移行の結果不必要になったので、プロジェクトから削除します。
アプリケーション実行
エミュレータクラスのロード
OpenExchangeのサイトからCacheDirect.Emulator.clsをダウンロードし、それをIRISにロード(USERネームスペース)します。
アプリケーションルーチン、グローバル、クラスのロード
ADBKサンプルのGitHubからUser/ADBK.cls,ADBK.mac,VISMUTIL.mac,adbkglb.xmlをダウンロードし、c:\tempにコピーします。
アプリケーションの実行を行うと最初に必要なグローバル、クラス、ルーチンのロードを行うはずです。
最後に
全く無修正というわけにはいきませんが、既存の資産を生かしつつ、VisM.OCXを使用したアプリケーションの移行が簡単にできるということをご理解いただき、VisMアプリケーションの移行にチャレンジしていただきたいと思います。
もう一つ、今回はサーバー側の処理は全く修正していない点も強調しておきたいと思います。
ついでに今回のサンプルでも所々実施したように古い処理をリファクタリングするのにもいい機会だと思います。 Delphiなど.Netをサポートしていない開発ツールでVisM.OCXを使っていた人のためにODBC版も作ってみました。https://github.com/wolfman0719/CacheDirectODBCEmulator
記事
Toshihiko Minamoto · 2021年8月25日
この記事では、Caché Webアプリケーション(主にREST)のテストとデバッグを外部ツールを用いて行うことについて説明します。 [パート2](https://community.intersystems.com/post/debugging-web-part-2)では、Cachéツールの使用について説明します。
サーバー側のコードを作成したのでクライアントからテストしたい、またはすでにWebアプリケーションが存在するが機能していない― そういったときに使用できるのがデバッグです。 この記事では、最も使いやすいツール(ブラウザ)から最も包括的なツール(パケットアナライザー)までを説明しますが、まずは、最も一般的なエラーとその解決方法について少し説明します。
### エラー
**401 Unauthorized**
これは、本番環境へのデプロイ中に最も頻繁に発生するエラーだと思います。 ローカル開発サーバーには通常、最小限のセキュリティ設定か、バニラを除く通常セキュリティ設定が構成されています。 一方の本番サーバーには、より制限的なスキームが適用されます。 つまり、以下を確認してください。
* ログイン済みであること
* アクセスするdatabase/table/procedure/row/columnへのアクセス権があること
* 許可されていないユーザーがOPTIONSリクエストを実行できること
**404 Not Found**
以下を確認してください。
* URLが正しいこと
* 新しいアプリケーションであり、外部Webサーバーを使用している場合は、Webサーバーをリロードしてください。
**アプリケーションエラー**
最も簡単に見つけるには、スタックトレースを使用できます。 解決策は、完全にアプリケーション固有です。
### デバッグツール
**Webブラウザ**
必ず最初に利用できるデバッグツールはWebブラウザです。Chromeが推奨されますが、Firefoxでも十分にデバッグできます。 GETリクエストについては、URLをアドレスバーに入力すればテストできますが、その他すべてのリクエストには、Webアプリケーションかjsコードの記述が必要です。 一般的には以下のように行います。
* F12キーを押して、デベロッパーツールを開きます。
* [Network]タブに移動します。
* [Preserve Log]チェックボックスがオンになっていない場合は、それをオンにします。
* XHRリクエストのみを表示します。
* Webアプリケーションでバグのあるアクションを実行します。

ここから、リクエストを調べて再送信できます。 Firefoxでは、リクエストを繰り返す前にリクエストを編集することも可能です。
メリット:
* いつでも利用可能
* 使いやすい(エンドユーザーは[Network]と[Console]タブのスクリーンショットを送信できます)
* エンドユーザー環境
デメリット:
* 部分送信/破損などのレスポンスを表示しない
* 大規模なレスポンスでは速度が低下する
* 大量のレスポンスでは速度が低下する
* すべて手動で行われる
**RESTクライアント**
RESTクライアントは、Webアプリケーションのテスト向けに特別に作成されたスタンドアロンのWebアプリケーションまたはWebブラウザアドオンです。 私は[Postman](https://www.getpostman.com/)を使用していますが、似たようなものはたくさん存在します。 Postmanでのデバッグは次のようになります。

Postmanはリクエストをコレクションにグループ化して処理します。 リクエストは環境に送信可能です。 環境は変数のコレクションです。 たとえば、私のCACHE@localhost環境ホスト変数は、localhostに設定されており、userは_SYSTEMに設定されています。 リクエストが送信される際は、変数は選択した環境の値に置き換えられてリクエストが送信されます。
MDX2JSONプロジェクトのサンプルの[collection](https://github.com/intersystems-ru/Cache-MDX2JSON/blob/master/MDX2JSON.postman_collection.json)と[environment](https://github.com/intersystems-ru/Cache-MDX2JSON/blob/master/CACHE.postman_environment.json)はこちらにあります。
メリット:
* 一度作成すれば、どこででも使用できる
* リクエストの制御に優れている
* レスポンスの「Pretty」表示
デメリット:
* 連鎖リクエスト(request1に対するレスポンスがrequest2またはrequest2Bを強制できる)のデバッグは依然として手動
* 部分送信/破損などのレスポンスに失敗することがある
**HTTPデバッグプロキシ**
HTTP(S) トラフィックをログに記録するスタンドアロンアプリケーション。 ログに記録されたリクエストを変更して再送信することができます。 私は[Charles](https://www.charlesproxy.com/)と[Fiddler](http://www.telerik.com/fiddler)を使用しています。

メリット:
* 部分送信/破損などのレスポンスを処理する
* レスポンスの「Pretty」表示
* HTTPSトラフィックのサポートに優れている(パケットアナライザーより)
* キャプチャセッションを保存できる
デメリット:
* リクエストを送信するために何か(Webアプリケーション/RESTクライアント/JSコード)が必要
パケットアナライザー
ネットワークを通過するトラフィックを傍受してログに記録できるコンピュータープログラム。 データストリームがネットワークを流れる際に、スニファーが各パケットをキャプチャし、必要に応じてパケットの生データをデコードします。 これが最も包括的なオプションですが、適切に動作させるには、ある程度のスキルも必要となります。 私は[WireShark](https://www.wireshark.org/)を使用しています。 以下にインストールと使用方法を簡単に説明します。
1. ローカルパケットをキャプチャする場合は、[ループバック](https://wiki.wireshark.org/CaptureSetup/Loopback)について読み、前提条件のソフトウェア(Windows用npcap)をインストールします。
2. WireSharkをインストールします。
3. [キャプチャフィルタ](https://wiki.wireshark.org/CaptureFilters)を構成します(たとえば57772のトラフィックのみをキャプチャするファイルはport 57772とします)。
4. キャプチャを開始します。
5. [表示フィルタ](https://wiki.wireshark.org/DisplayFilters)を構成します(たとえば特定のIPへのhttpトラフィックのみを表示する場合は、ip.addr == 1.2.3.4 && httpとします)。
以下は、ポート57772(キャプチャフィルタ)のhttpトラフィック(表示フィルタ)をキャプチャした例です。
メリット:
* 部分送信/破損などのレスポンスを処理する
* 大量のトラフィックをキャプチャできる
* 何でもキャプチャできる
* キャプチャセッションを保存できる
デメリット:
* リクエストを送信するために何か(Webアプリケーション/RESTクライアント/JSコード)が必要
どれを使用するか
それは目的によって異なります。 まず、リクエストをログに記録する(デバッグプロキシ、パケットアナライザー)かリクエストを生成(ブラウザ、RESTクライアント)することを目的とすることができます。
REST Web APIを開発している場合は、RESTクライアントを使用するのが、動作をテストする最速の方法です。
ただし、RESTクライアントからのリクエストが機能してもクライアントWebアプリケーションが機能しない場合は、httpデバッグプロキシとパケットアナライザーが必要となることがあります。
クライアントがあり、サーバー側APIを開発してそれを操作する場合は、httpデバッグプロキシかパケットアナライザーが必要となります。
4種類すべてのツールを理解し、使用中のものが作業に不十分となった場合に素早く切り替えられるようにしておくことをお勧めします。
適切なツールが明確である場合もあります。
私は最近、人気のあるhttp拡張プロトコル用のサーバー側APIを開発しましたが、その際の要件は次のとおりでした。
* クライアントがすでに作成済みであり、コードを変更できない。
* クライアントごとに動作が異なる。
* httpとhttpsでの動作が異なる。
* 認証タイプごとに動作が異なる。
* クライアントごとの1秒当たりのリクエスト数は最大100件である。
* 全員がRFCを無視する。
この要件で使用できるソリューションは1つしかありません。パケットアナライザーです。
または、JS消費用のREST APIを開発しているのであれば、テストに最適なツールはRESTクライアントです。
Webアプリケーションをデバッグする場合は、Webブラウザでデバッグを開始しましょう。
パート2では、Webデバッグに関してCaché側でできること(たくさんあります)について説明します。
皆さんは、クライアント側通信をデバッグする際にどのようなアプローチを採用していますか?
記事
Mihoko Iijima · 2021年6月23日
開発者のみなさん、こんにちは!
2023/2/21追記
チュートリアルページが新しくなり「Developer Hub」に変わりました!
Interoperability(相互運用性)チュートリアルについても新しくなりました。詳しくは「InterSystems Developer Hub:クリック1回で開始できるチュートリアル(4種)のご紹介」をご参照ください。
この記事では、GettingStarted ページの無料体験環境(Sandbox)で試せるチュートリアルの中から、「Connect Your Systems」(Interoperabilityのチュートリアル)の使い方をご紹介します。
GettingStarted ページでできることについては、こちらの記事でご紹介しています。
無料体験環境(Sandbox)の開始手続きについては、こちらの記事でご紹介しています。ぜひご参照ください。
Connect Your Systems では、IRIS の Interoperability メニューを利用してどのように異なるシステム同士を接続し、処理を連携させることができるのか、ビデオによる解説とチュートリアルをご用意しています。
(1) Interoperability 概要 : ビデオ(日本語字幕切り替え可)と概要説明
(オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/)
(2) Combat Fraud with Transactions and AI: ビデオのみ
(オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/integration-fraud/)
このページのビデオでは、InterSystems IRIS が複数のデータ・ストリームと機械学習モデルを統合して、レストランの POS で不正取引を検出する仕組みのデモをご紹介しています。
(3) Interoperability QuickStart: ビデオ(日本語字幕切り替え可)とチュートリアル
(オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/)
※ ビデオは (1) Interoperability 概要 のビデオと共通です。
(4) Build a Smart Ticketing System(チュートリアル)
(オリジナルページは👉https://gettingstarted.intersystems.com/interoperability/red-lights-part-2/)
以下、(1) (3) (4) について画面やチュートリアルの進め方をご紹介しています。ぜひご参照ください!
(1) Interoperability 概要
オリジナルページ 👉https://gettingstarted.intersystems.com/interoperability/ に 3 分36 秒の概要解説ビデオがあります。日本語字幕を追加する方法は、図解をご参照ください。
IRIS の Interoperability(相互運用性)メニューを利用することで、異なるシステム同士のデータ共有がかつてないほど簡単になります!
ビデオにもあるように、異なるシステム同士でデータを共有しようとすると、複数のシステムからデータを取り込む必要があったり、定義したロジックに従ってメッセージをルーティングしたり、共通プロトコルでアクセスできたり、異なるプロトコルだったり、複数の言語を使用したアクセスが必要になってくるなど、要件は様々です。
InterSystems IRIS® data platform の Interoperability メニューを利用することで以下の内容を実現できます。
メタデータやメッセージ・コンテンツに基づいてメッセージをルーティングできます。
ローコード UI を使用してビジネス・ロジックの作成や更新が行えます。
接続対象システムが使用しているフォーマットに合わせてデータを変更するようなデータ変換ロジックを作成できます。
すぐに使えるコンポーネントを使用して素早く統合処理を記述したり、独自のコードを作成してさらに柔軟性を高めることができます。
システムを流れるデータに機械学習アルゴリズムを適用することもできます。
統合されたデータを 1 つのリポジトリに保存し、保存されたデータを利用して分析を行うことができます。
トラブルシューティングや監査のためにシステムを流れるメッセージをトレースできます。
(3) Interoperability QuickStart
(オリジナルページ👉https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/)
Interoperability クイックスタートチュートリアルのテーマは以下の通りです。
信号機にカメラが設置されていて、赤信号の違反を自動的に識別できるようになった、と仮定して以下の内容を実施しようとしています。
違反を識別したデータを後で分析できるようにデータベースに保存しておきたい。
違反した車が緊急車両でない場合、違反チケットを発行するため別のアプリケーションに違反者情報のデータを送信する。
このチュートリアルの中では、自動的に識別された違反データの中から、どの情報が違反チケットを発行する対象データであるかを判断するビジネス・ルールを作成し、データのルーティングを行います。
また、判断結果を送信する接続先システムが必要としているデータのみを送付できるように、データの変換も行います。
ビジネス・ルールやデータ変換は、InterSystems IRIS の管理ポータルを使用して開発できます。開発の他にも、Interoperability メニューが提供する機能を利用したメッセージのトレースなども確認していく予定です。
テーマに合わせた一連の連携の流れや接続先情報を設定保存するための定義として「プロダクション」があります。
プロダクションは、ビジネス・サービス、ビジネス・プロセス、ビジネス・オペレーションの3種類のコンポーネントを組み合わせ定義することができます。
それぞれのコンポーネント概要は以下の通りです。
ビジネス・サービス:外部システムと接続し、外部システムからの情報を受信します(プロダクションの中で一番最初に動くコンポーネントです)。
ビジネス・プロセス:ルーティングやメッセージ変換などのビジネスロジックを定義するコンポーネントでプロダクション内の処理の調整役になります。
ビジネス・オペレーション:外部システムに処理を依頼する役割があります(プロダクション内で使用していたデータなどを渡すことができます)。
メモ:このチュートリアルの流れでは、違反情報をデータベースに登録する流れの作成は含まれていません。
① Set up
InterSystems IRIS を開始してプロダクションを参照してみよう!
(1) Sandbox を開始し、IDE を開きます。
https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/#set-up のページを開き、Sandboxを開始します。
下図のように、LOGINボタンが表示されている場合は、LOGINボタンをクリックし、ログインしてください。
LOGIN後、Sandbox の情報が表示されず「PROVISION SANDBOX」ボタンが表示されていたら「PROVISION SANDBOX」ボタンをクリックし、Sandbox を作成してください。
ユーザ登録がまだの方は、こちらの記事をご覧いただき、ユーザ登録を行ってください。
(2) 「Sandbox IDE」のリンクから IDE を開きます。また「Management Portal」のリンクから管理ポータルを開きます。
管理ポータル起動時、ログイン画面が表示されたら、ユーザ名: tech パスワード:demo を入力してください。
(3) 管理ポータルの以下メニューを開きます。
Interoperability > 一覧 > プロダクション > Demo.RedLights の行を選択し「開く」ボタンをクリックします。
(4) プロダクション構成画面で と表示されている場合は、ボタンをクリックし、プロダクションを開始します。
プロダクション開始時「このプロダクションを開始してもよろしいですか?」のポップアップメッセージが表示されるので「OK」をクリックします。
プロダクション開始中の状態確認画面が表示され「完了しました」が表示されたら「OK」ボタンを押し画面を閉じて開始処理を完了します。
② Monitor message transport
データ送受信時のメッセージをトレース画面を利用して確認します。
① SetUp でプロダクションを開始すると、プロダクションに定義している監視対象ディレクトリに用意されていたサンプルファイルが読み込まれ、処理されます。
ここでは、処理されたファイルの情報をメッセージのビジュアルトレース画面を使用して確認してみます。
1、メッセージを参照する
(1) 管理ポータルの Interoperability > 表示 > メッセージ に移動します。
表示されているメッセージから、ソースの列に「RealTimeRedLightViolation」と表示されている行の一番先頭にあるメッセージを選択します。
(2) 画面右側にある「トレース」タブを選択し「完全トレースを表示」のリンクをクリックします。
(3) 開いた画面は「ビジュアルトレース」と呼びます。
この画面では、IRIS のプロダクションに設定されているコンポーネント間でどのようなメッセージが送受信されているかを確認できます。
(4) 全メッセージはデータベースに格納され、開発時やテスト時の送受信データの確認や、運用期間中にトラブルが発生した際のメッセージの確認など、様々な場面で利用できる情報です。
(5) ビジネス・サービス:RealTimeRedLightViolationでは、外部から入力された情報を元に、メッセージが作成されました。
以下の情報を確認してみましょう。
そのメッセージは、どのビジネス・プロセスに送信されたでしょうか。
ヒント:ビジネス・サービス:RealTimeRedLightViolationの列に表示されている の送付先がどこであるか確認します。
答え:Demo.TicketBPL
ビジネス・オペレーション:To_TicketApplication へ送信されていない情報はどれでしょうか。
ヒント: をクリックし画面右側で「ボディ」タブを選択します。
をクリックし同様に、画面右側の「ボディ」タブを選択し、[1] と [3] のメッセージの違いを確認します。
答え:CARTYPE
2、他のメッセージを送信してみる
(1) Sandbox の IDE を開きます。
https://gettingstarted.intersystems.com/interoperability/quickstart-interop-2/#set-up を開き「Sandbox IDE」のリンクをクリックします。
IDE を開き左画面で shared > Samples-Integration-RedLights > data > SampleFiles を開きます。
LocalRedLightViolation1-copy.csv を Ctrl + c でコピーし、shared/Samples-Integration-RedLights/data/In に Ctrl +v でペーストします。
再度、(1) の手順を利用してメッセージ・ビューワを確認すると、読み込んだ CSV からメッセージが作成されていることを確認できます。
③ Consume CSV files using the built-in Record Mapper
管理ポータルの「レコード・マップ」は、CSV などの区切りマークで区切られたデータが含まれるファイルに対する入出力処理用コードの自動生成とプロダクションで使用するメッセージ定義を簡単に生成させることができる機能です。
チュートリアルで使用するレコードマップ定義は既に用意されていますが、サンプルの CSV ファイルを使用してCSVファイルのデータがどのように解析されるか、レコードマップ定義のサンプルファイル読み込みオプションを利用して確認します。
この他、CSV ウィザードもあり、CSV のヘッダを読み込み、対応するレコードマップを自動的に作成するメニューも用意されています。
(1) 直前に開いたメッセージ・ビューワ画面があれば、画面左上部の表示されている [Interoperability] メニューをクリックします。
次に、Interoperability > 構築 > レコード・マップ > 開くボタンクリック > Demo > RedLightViolationsRecordMap を選択します。
(2) をクリックし、テストに使用したCSV ファイル /home/project/shared/Samples-Integration-RedLights/data/SampleFiles/LocalRedLightViolation1-copy.csv を選択します(ファイルの種類で「カンマ区切りファイル(*.csv)」か「すべてのファイル(*)」を選択しすると表示されます)。
サンプルファイル選択後の画面は以下の通りです。
サンプルファイルを表示させるユーティリティを利用して、意図した通りに情報が区切れるかを確認しながらレコード・マップ定義を作成できます。
サンプルファイルが目的通りの表示になる=レコードマップ定義を利用して正しく解析できることを意味しています。
画面表示中に cross-origin エラーが発生する場合は、管理ポータルの URL を https:// から http:// に変更してお試しください(s を付けずに表示してみてください)。
Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、サンプルファイルのアップロードを行う場合にこの対応が必要になることがあります。
(3) 画面上部の ホーム をクリックします。
次に、Interoperability > 構成 > プロダクション を選択します。
ビジネス・サービス:RealTimeRedLightViolation を選択し、画面右側の「設定」タブを参照し、RecordMap 定義を探します。
RecordMap 定義に先ほど参照した Demo.RedLightViolationsRecordMap が設定されていることをご確認ください。
プロダクションに含まれるコンポーネント(ビジネス・サービス/プロセス/オペレーション)は、プロダクション開始中でも設定変更が行え、変更を行ったコンポーネントのみに影響を与えることができます。
確認したレコードマップを使用しているビジネス・サービスは、予め構築済のレコードマップ用ビジネス・サービスを利用しているため、コードの記述は不要です。
区切りマーク付きファイルを処理する場合、レコード・マップ定義を利用することで、プロダクションに素早く新しい処理を追加することができます。
④ Route messages, apply business logic and transformations
信号機から受信したデータは、チケットアプリケーションに送付される予定で、さらに分析用にデータベースに保存する予定です。
しかし、受信した全データをチケットアプリケーションに送付するのではなく、赤信号を無視した車両がパトカーや消防車などの許可された車両ではない場合に限り送信します。
チュートリアルのサンプルでは、受信したデータを正しく振り分けるための適切なルーティング・ロジックが作成されています。また、データベース保存用データとして必要な情報のみを送付するように設定されています。
メモ:このチュートリアルの流れでは、抽出データをデータベースへ保存するコンポーネントの作成は含まれていません。
以下の手順で、サンプルコードを確認しましょう。
(1) プロダクション構成でビジネス・プロセス:Demo.TicketBPL を選択し、右画面の「設定」タブを選択します。
情報を提供する設定 を展開し、クラス名: Demo.TicketBPL の右側に表示されている 虫眼鏡のアイコンをクリックしビジネス・プロセス・エディタを開きます。
サンプルコードは、ローコードエディタである、ビジネス・プロセス・エディタを使用しています。
この中では、許可された車種以外の違反情報を他のアプリケーションに送信するため、call アクティビティを使用して「ビジネス・オペレーション」を呼び出しています。
メモ: や をアクティビティと呼びます。
(2) ルールアクティビティ を選択します。右画面に表示される設定画面から ボタンをクリックします。
このルール画面を使用して、メッセージの処理方法を変更することができます。
サンプルでは、CarType に含まれる文字列が、許可された車種であるかをチェックしています。例では、ambulance、police、firetruck が来た時はホワイトリストとして違反チケット用アプリケーションに送付しない流れが設定されています。
ルール画面は、特定のユーザのみが修正できるようにセキュリティを追加することもできます。
ルール画面を通して、適切なユーザが処理の流れに影響のある条件値を変更するなど、ローコードで設定することができます。
チャレンジ問題:CarType が SWAT の場合も、許可された車種となるように新しいルールを追加してください。
(3) ビジネス・プロセス:Demo.TicketBPL のビジネス・プロセス・エディタの画面に戻ります(チュートリアルの手順では、別タブに開いています)。
もし、画面を開いていない場合は、ホーム > Interoperability > 構築 > ビジネス・プロセス から開いてください。
をクリックし、アクティビティの設定画面から ボタンをクリックします(アクティビティ選択後、右画面に表示される設定画面を少しスクロールすると「リクエスト・ビルダ」ボタンが見つかります)。
データベースに保存したい情報の抽出をこの画面で行っています。
左半分に表示されている情報は、ビジネス・プロセスに送付される情報です。右半分に表示されている情報は、呼び出し対象として設定しているビジネス・オペレーションに送付する情報です。
チュートリアルテーマのように簡単なデータ変換の場合は(例では、情報の一部をカットしているだけ)、ビジネス・プロセス内で情報抽出が行えます。
より複雑なデータ変換が必要なケースもあります。その場合は、上図と同様に線を引きながら設定できるデータ変換画面を使用して、アプリケーション全体から呼び出し可能なデータ変換定義を作成することもできます。
このチュートリアルの中では、ビジネス・サービスとビジネス・オペレーションに対してコードを記述していません(予め構築済のビジネス・サービス、ビジネス・オペレーション用コードを利用しているためコードの記述は不要でした)。
もちろん、ビジネス・サービス、ビジネス・オペレーションをコードを記述しながら開発することもできます。
例えば、TCP、FTP、SQL、RESTなど接続周りの処理を簡単に記述できるアダプタを使用して、接続先のシステムから送受信する情報に合わせてカスタムコードを記述しながら開発していく方法もあります。
(4) Build a Smart Ticketing System
このチュートリアルでは、1つ前の (3) Interoperability QuickStart で確認した Red Light Violationアプリケーションをベースにしているため、説明の流れが (3) Interoperability QuickStart チュートリアルを完了した状態を想定して手順が記述されています。
このチュートリアルのテーマは
シカゴの交通システムのデータに基づいて危険な交差点を特定するための処理を追加します。
です。以下の内容を作成します。
ファイル入力によりデータを受信し、判断結果をファイル出力するシンプルなインターフェースを使用します。
赤信号違反の数に基づいてハイリスクと判断された交差点のみをリストアップするロジックを追加します。
公開されている REST API を使用して人口密度に関するデータを取得する処理を追加します。
システムに適したフォーマットにデータを修正する処理を追加します。
① Get set up
(1) Sandbox の IDE を開きます。
https://gettingstarted.intersystems.com/interoperability/red-lights-part-2/#get-set-up のページを開き、アクセス情報を確認します。
LOGIN ボタンが表示される場合は、LOGIN ボタンをクリックして下さい。「PROVISION SANDBOX」のボタンが表示される場合は「PROVISION SANDBOX」ボタンをクリックします。
(2) IDE のターミナルウィンドウを利用して、以下のチュートリアルで使用するリポジトリをクローンします。
cd /home/project/shared
git clone https://github.com/intersystems/Samples-Integration-SmartTicketing
(3) IDE のメニュー InterSystems > Management Portal から管理ポータルを開きます。
(4) サンプルコードをインポートします。
管理ポータルのメニュー システムエクスプローラ > クラス を開き、 ボタンをクリックします。
/home/project/shared/Samples-Integration-SmartTicketing/Setup/CustomCensusComponents.xml をインポートファイルに指定し、インポートを行います。
インポートの操作で cross-origin エラーが発生する場合は、管理ポータルの URL を https:// から http:// に変更し、再度実行してみてください。
Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、ファイルのアップロードを成功させるにはこの方法が必要となる場合があります。
② Add a new interface
新しいインターフェースを追加してみましょう!
最初に、レコードマップを使用してファイル入力を行うシンプルなビジネス・サービスを追加します。
チュートリアルを進めていくうちに、より複雑な内容も登場しますが、現時点では、レコードマップを使用することで入力したファイルから好みのフォーマットにマッピングする処理をとても簡単に作成できることを確認します。
IRIS には、多くの構築済コンポーネントがあります。レコードマップも構築済コンポーネントを使用するため、コーディングの必要はありません。
(1) 次の手順で、シカゴの交差点に関する情報を入力するための新しいレコードマップを使用するビジネス・サービスを作成します。
A) 管理ポータルの Interoperability > 構築 > レコードマップ を開きます。
B) ボタンをクリックし、ファイルに /home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv を指定します。残りの設定項目は以下の通りです。
サンプルファイル:/home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv
レコード・マップ名:Demo.CameraViolationsMap
セパレータ:, (変更なし)
レコード終端文字:LF
文字エンコード:UTF-8 (変更なし)
3か所のチェックボックスをすべてチェック(「サンプルがヘッダ行を持つ」をチェックすることで、CSV ファイルの1行目をヘッダ行と認識します。)
設定が完了したら ボタンをクリックすると、以下の画面が開きます。
CSVファイルを指定してレコードマップを作成する操作で cross-origin エラーが発生する場合は、管理ポータルの URL を https:// から http:// に変更し、再度実行してみてください。
Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、ファイルのアップロードを成功させるにはこの方法が必要となる場合があります。
C) 以下のフィールドが正しく設定されているか確認します。足りない場合は をクリックして追加してください。
D) ボタンをクリックし、レコードマップ定義と関連するメッセージクラスを生成します。
E) 管理ポータル > Interoperability > 構成 > プロダクション からプロダクション構成ページを開きます。
新しいビジネス・サービスを追加します。 赤枠のボタンをクリックします。
サービスクラスに EnsLib.RecordMap.Service.FileService を指定します(プルダウンで表示される名称には、似た名前が多いので気を付けて選んでください)。
サービス名に From_RedLightArchive を指定し、OK ボタンをクリックして追加します。
(現時点で、設定は完了していません)
プロダクションが開始していない場合は、「開始する」ボタンをクリックし、開始してください。
F) 追加したビジネス・サービスの設定を完成させます。
コンポーネント名をクリックします(From_RedLightArchive の字の部分をクリックします)。
画面右側に設定欄が表示されます。以下の項目を設定してください。
設定完了後、「適用」のボタンを押し忘れないようにご注意ください!
有効:チェックします。
ファイル・パス:/home/project/shared/Samples-Integration-RedLights/data/In/
ファイル・スペック:Red_Light_Camera_Violations*.csv
アーカイブ・パス:/home/project/shared/Samples-Integration-RedLights/data/SampleFiles/
RecordMap:Demo.CameraViolationsMap
(2) 続いて、危険な交差点のリストを出力するビジネス・オペレーションを作成します。
A) 再度、CSVウィザードを利用してレコードマップ定義を作成します。
今回のレコードマップ名は Demo.HighRiskArchiveMap とします。定義内容も前回と同じで、レコードマップ名だけ異なります。ご注意ください。
CSV ウィザードは以下メニューからも移動できます。
管理ポータル > Interoperability > 構築 > CSVレコードウィザード
上記画面の「生成」ボタンを押し忘れないようにしてください。
B) 管理ポータル > Interoperability > 構成 > プロダクション からプロダクション構成ページを開きます。
新しいビジネス・オペレーションを追加します。 赤枠のボタンをクリックします。
オペレーションクラスに EnsLib.RecordMap.Operation.FileOperationを指定します(プルダウンに表示される名称には似た名前が多いので、気を付けて選んでください)。
オペレーション名に To_HighRiskArchiveを指定し、OK ボタンをクリックして追加します。
(現時点で、設定は完了していません)
ビジネス・オペレーション:To_HighRiskArchive を選択します(コンポーネント名をクリックします)。
画面右側に設定欄が表示されます。以下の項目を設定してください。
設定完了後、「適用」のボタンを押し忘れないようにご注意ください!
有効:チェックします。
ファイル・パス:/home/project/shared/Samples-Integration-RedLights/data/Out/
ファイル名:%Q_HighRisk.txt
RecordMap:Demo.HighRiskArchiveMap
(3) ビジネス・サービス:From_RedLightArchive を選択します(コンポーネント名をクリックします)。
画面右側の設定タブの「ターゲット構成名」に作成したビジネス・オペレーション:To_HighRiskArchive を設定し、適用ボタンを押してください。
(4) プロダクションが実行中であることを確認します。
もし、停止している場合は「開始する」ボタンをクリックしてください。
(5) これでシンプルなビジネス・サービス、ビジネス・オペレーションの追加は終わりです。早速テストしてみましょう!
A) Sandbox の IDE の左画面から /home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv ファイルを Ctrl + c でコピーし、/home/project/shared/Samples-Integration-RedLights/data/In/ のフォルダに Ctrl + v でペーストします。
B) 管理ポータルに戻りメッセージビューワを開きます。
Menu > メッセージ・ビューワ を選択します。
C) 直近で処理されたメッセージを選択し、画面右でトレースタブを選択し「完全トレースを表示」をクリックすると、トレース画面が開きます。
ここまでの動作が正常に終了している場合、ビジネス・オペレーション:To_HighRiskArchive で設定したファイル・パスのディレクトリ(/home/project/shared/Samples-Integration-RedLights/data/Out/)にファイルが出力されている予定です。
もしエラーが発生している場合は、ビジュアルトレース画面で の表示されます。その場合は、この記事の一番下に掲載している「トラブルシューティングガイド」をご参照ください。
③ Add business orchestration logic
次に、どの交差点がハイリスクであるかを判断し、対象データのみをファイルに出力するルールセットを追加します。
ビジネス・プロセス・エディタを使い、条件のチェックやルールセットの適用などのロジックを追加し、対象となるデータを1つ前の演習で作成したビジネス・オペレーション:To_HighRiskArchive に送信し、ファイルが指定されたディレクトリに出力されるか確認します。
(1) ビジネス・プロセス・エディタを開きます。
管理ポータルの Interoperability > 構築 > ビジネス・プロセス を開きます。
(2) ボタンをクリックし、新しいビジネス・プロセスを以下の名称で作成します。
パッケージ名: Demo
名前:HighRiskIntersectionsBPL
(3) 次に、ビジネス・プロセスの設定を追加します。
ビジネス・プロセスが受信する基本要求(リクエスト・クラス)と、呼び出し元に返送する基本応答(レスポンス・クラス)、このビジネス・プロセス内で使用できるコンテキストプロパティを定義します。
画面右の「コンテキスト」タブを選択し、以下の値を設定します。
リクエスト・クラス: Demo.CameraViolationsMap.Record
レスポンス・クラス: Ens.Response(デフォルト値のまま)
コンテキストプロパティ: IntersectionRisk データタイプに %String(MAXLEN=50) を設定
コンテキストプロパティ: VIOLATIONS データタイプに %Integer を設定
設定が完了したら、 ボタンをクリックし、保存してください(アクティビティの設定が途中でも保存できます。保存時エラーメッセージが出ますが気にせず保存してください)。
(4) 次に、Assign アクティビティを追加します。
アクティビティの追加は、アクティビティのプルダウン > Assing をクリックします。
追加後、<assign> アクティビティを <start> の下に接続します。
<start> の下についているアイコン からドラッグし <assign> アクティビティの上についているアイコン までドラッグし続け、線がくっついたところでドロップします。
次に <assign> アクティビティの設定を追加します。アクティビティを選択し、右画面で以下設定してください。
アクション: set
プロパティ: context.VIOLATIONS
値: request.VIOLATIONS
メモ:ビジネス・プロセスの「コンテキスト」タブで設定したプロパティは、ビジネス・プロセス内で使用できるコンテキストオブジェクトのプロパティで 変数 context で操作できます。request はビジネス・プロセスの起動のきっかけとなったメッセージのインスタンスを操作できる変数です。
設定が完了したら、 ボタンをクリックし、保存してください(アクティビティの設定が途中でも保存できます。保存時エラーメッセージが出ますが気にせず保存してください)。
(5) <rule> アクティビティを追加します。
どの交差点がハイリスクであるかを判断する流れを、ビジネス・プロセス内で直接書くこともできますが、ルールエディタで判断基準を変更できるように、ルール(<rule> アクティビティ)を組み込みことができます。
ルールエディタを利用する利点としては、ビジネス・プロセスのコードを変更せずに、判断基準となる値のみを変更できる点です。
追加する <rule> アクティビティでは、1日の交差点通過時の違反回数から very high、hight、low のいずれかの文字列が設定される予定です。
以下の手順で追加します。
A) <rule> アクティビティを追加します。
B) キャプションを設定します。
C) <rule> アクティビティから呼び出されるルールを作成します。
<rule> アクティビティを選択し ボタンをクリックし、出力される子画面で以下入力します。
パッケージ名: Demo
名前: IntersectionRiskRule
コンテキスト・クラス: Demo.HighRiskIntersectionsBPL.Context
設定した「コンテキスト・クラス」の指定により、ビジネス・プロセスで使用してる特定の交差点の違反数を登録するコンテキスト・プロパティの値をルールに渡すことができます。
D) ルール・エディタに rule を追加します。
を選択し、 をクリックしてから を選択します。
E) 追加した rule に when を追加します。
を選択し、 をクリックしてから を選択します。
D) 1つの目の の をダブルクリックし、VIOLATIONS > 15 を条件式として設定します。
E) 1つ目の を選択し、 をクリックしてから を選択し、“very high” を value に設定します。
H) 同様の手順で、以下の図と同じ設定になるように定義してください。
I) 最後に、保存ボタンをクリックし、設定を保存します。
(6) ルールエディタは、新しいタブで開いています。
一旦ルールエディタのタブを閉じ、ビジネス・プロセス・エディタに戻ります。
ビジネス・プロセス・エディタの <rule> アクティビティの設定で、ルール名に Demo.IntersectionRiskRule を設定し、Result Location に context.IntersectionRisk を設定します。
設定が完了したら、 ボタンをクリックし、保存してください(アクティビティの設定が途中でも保存できます。保存時エラーメッセージが出ますが気にせず保存してください)。
(7) <rule> アクティビティを選択した状態で、<if> アクティビティを追加します。
メモ:接続したいアクティビティを選択した状態で新しいアクティビティを追加すると、予め線が結ばれた状態で追加されます。
<if> アクティビティのブロックの終わりを示す と <if> アクティビティがつながった状態で追加されます。
(8) <if> アクティビティを設定します。
名前: ハイリスクかどう
条件: (context.IntersectionRisk = "high") || (context.IntersectionRisk = "very high")
(IntersectionRisk の値が "high" か "very high" の場合に、その情報をファイル出力するようにこの後の問題で作成します)
(9) ハイリスクの交差点だと特定された場合、その情報をファイル出力します。
<if> アクティビティが真である場合の true の線を選択した状態で <call> アクティビティを追加します。
<call> アクティビティの設定は以下の通りです。
名前: HighRiskArchiveの呼び出し
ターゲット: オペレーションを選択し、To_HighRiskArchive を設定
リクエストメッセージクラス: Demo.HighRiskArchiveMap.Record
レスポンスメッセージクラス: Ens.Response (デフォルト設定のまま)
(10) <call> アクティビティの ボタンをクリックします。
(11) ビジネス・プロセスの起動のきっかけとなった要求メッセージ(基本要求)の request の %Source プロパティ を <call> アクティビティで設定したターゲット構成へ送付する要求メッセージの callrequest の %Source プロパティにコピーします(線を結びます)。
(線を引くとき、色が変わるまでドロップしないようにしてください)
同様に、request.INTERSECTION から context.INTERSECTION へ線を引きます。
(12) 線を引いた後、OK ボタンをクリックします。
要求メッセージ・クラスの要求アクションに と設定されます。
(13) <if> アクティビティの設定を変更します。
ブロックの終わりを示す に false の線をつなぎます。
(14) 最後に、<if> アクティビティのブロックの終わりを示すアイコンから、<end> まで線を引き完成です。
(15) 忘れずに、コンパイルを実行してください。
コンパイル結果が子画面に表示されます。「コンパイルが正常に終了ました」と表示されるまでお待ちください。
(16) 作成したビジネス・プロセスをプロダクションに追加します。
管理ポータル > interoperability > 構成 > プロダクション を開きます。
(17) ビジネス・プロセスを追加します。 をクリックします。
(18) ビジネス・プロセス・クラスに作成した Demo.HighRiskIntersectionsBPL を設定し、有効にするのチェックをいれ、OKボタンをクリックします。
(19) ビジネス・サービス:From_RedLightArchive のターゲット構成名を変更します。
Demo.HighRiskInteractionsBPL に変更し適用ボタンをクリックします。
(20) テストを実行します。
先ほどのテストと同じファイル(/home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv)をCtrl + c でコピーし、/home/project/shared/Samples-Integration-RedLights/data/In に Ctrl + v でペーストしてテストを実行してください。
メッセージビューワを利用して、いくつかのメッセージがビジネス・プロセスで止まっていることを確認してください。
管理ポータル > Interoperability > 表示 > メッセージ
ビジネス・プロセスで停止しているトレース例は以下の通りです。
をクリックすると、画面右側にルールのどの条件を通過したか、確認できる画面が表示されます。
のリンクをクリックすると、ルールエディタが開き、通過した場所を表示します。
以下のトレース例は、オペレーションまでメッセージが流れいてる例です(high のパタン)。
④ Add routing
先ほどまでの流れでは、危険な交差点であることが判明した場合、その情報をファイル出力していました。
ここに、各交差点の人口密度に関する情報を取得し、危険な交差点情報の追加情報としてファイル出力に加えることで、危険な交差点に対してどのような対応がとれるかより良い方法を探ることができるようになります。
作成中のビジネス・プロセス:Demo.HighRiskIntersectionsBPL に、REST を使用して US Census が提供するサービスの呼び出しを追加し、交差点の緯度と経度から GeoJSON コードを取得するように変更します。
次に、人口情報を取得するため米国国勢調査が提供する別のサービスの呼び出しを行い、取得した人口密度情報をハイリスクの交差点情報に追加し、ファイル出力を行います。
(1) プロダクション構成ページにビジネス・オペレーションを追加します。
チュートリアルのサンプルとして用意のある Demo.ToCensusGeoServiceBO クラスを使用して以下の設定で新しいビジネス・オペレーションを追加します。
オペレーションクラス: Demo.ToCensusGeoServiceBO
有効にする: チェックします
(設定タブ)HTTPサーバ: tigerweb.geo.census.gov
(設定タブ)URL: /arcgis/rest/services/TIGERweb/Tracts_Blocks/MapServer/5/
(設定タブ)SSL 設定: RESTSSL
SSL 設定は作成していないため、以下の手順で追加します。
管理ポータル > システム管理 > セキュリティ > SSL/TLS 構成 > 新規構成の作成ボタンクリック > 構成名に RESTSSL を設定し保存ボタンクリック
(2) プロダクションの設定を変更します。
管理ポータル > Interoperability > (USERネームスペースを選択)> 構成 > プロダクション を開き画面右側の「設定」タブの下の方に配置されている [開発とデバッグ]を展開し「テスト使用可能」をチェックし適用ボタンクリックします。
(3) ビジネス・オペレーション:Demo.ToCensusGeoServiceBO をテストします。
をクリック(コンポーネント名をクリック)し、画面右側の「アクション」タブを選択し ボタンをクリックします。
テスト画面が表示されるので以下入力し をクリックします。
Latitude: 41.95402905
Longtitude: -87.70807749
(4) もう1つ、ビジネス・オペレーションを追加します。
クラス名: Demo.ToCensusDS
有効にする: チェックします。
(設定タブ)HTTPサーバ: api.census.gov
(設定タブ)URL: /data/2014/acs/acs5
(設定タブ)SSL設定: RESTSSL
(5) ビジネス・プロセス: HighRiskIntersectionsBPL をビジネス・プロセス・エディタで開きます。
プロダクション構成画面で を選択し(コンポーネント名を選択し)、画面右側の設定タブの「情報を提供する設定」を展開し、クラス名の右側に表示されている虫眼鏡アイコン をクリックします。
(6) 以下の修正を行います。
Geo サービス(ビジネス・オペレーション:Demo.ToCensusGeoServiceBO)から取得した GeoJSON 情報を Census サービス(ビジネス・オペレーション:Demo.ToCensusDS)に渡すために、追加でコンテキスト・プロパテを設定します。
[コンテキスト] タブに移動し、Demo.GeoResponse タイプの GeoResponse プロパティと、%String(MAXLEN=50) タイプの Population プロパティを追加します。
(7) すべての交差点情報に緯度と経度のデータがあるとは限らないためファイル出力情報に追加できない場合もあります。
経度と緯度の情報がある場合、ない場合で処理を分けられるように新しい <if> アクティビティを追加します。
追加場所は、現在の <if> アクティビティと HighRiskArchiveオペレーション を呼び出す<call> アクティビティの間に追加します。
true の線を選択し、<if>アクティビティを追加します。
<if> アクティビティの設定内容は以下の通りです。
名前: Geo情報の有無
条件: $length(request.LATITUDE)>0
メモ:$length() は InterSystems ObjectScript の関数で、引数に指定した文字数を返します。
(8) (7)で追加した<if> アクティビティ(Geo情報の有無)で、経度と緯度の情報が取得できた場合、2 つの <call> アクティビティを追加します。
1 つ目の <call> アクティビティの設定は以下の通りです。
名前: Geo情報取得
ターゲット: Demo.ToCensusGeoServiceBO
非同期: チェックを外します(同期処理に変更します)
要求メッセージクラス: Demo.GeoRequest
要求アクション:
応答メッセージクラス: Demo.GeoResponse
応答アクション:
要求メッセージクラスの設定では、永続クラス > Demo > GeoRequest の順に選択します。
応答メッセージクラスの設定では、永続 クラス> Demo > GeoResponse の順に選択します。
要求アクションの設定は、 ボタンをクリックしても設定できます。
応答アクションの設定は、 ボタンからも設定できます。
2つ目の <call> アクティビティの設定は以下の通りです。
名前: 人口密度情報取得
ターゲット: Demo.ToCensusDS
非同期: チェックを外します(同期処理に変更します)
要求メッセージクラス: Demo.PopulationRequest
要求アクション:
応答メッセージクラス: Demo.PopulationResponse
応答アクション:
要求メッセージクラスの設定では、永続クラス > Demo > PopulationRequest の順に選択します。
応答メッセージクラスの設定では、永続クラス > Demo > PopulationResponse の順に選択します。
要求アクションの設定は、 ボタンをクリックしても設定できます。
State プロパティと同様に、Country、Tract、BackGroup も設定した例は以下の通りです。
応答アクションの設定は、 ボタンからも設定できます。
(9) Geo情報(経度と緯度の情報)がない場合、人口密度情報を取得できないため、人口密度情報を含まないハイリスク用のファイルを作成する必要があります。
Geo情報有無 の <if> アクティビティで false の場合は何も処理せず、ブロックの終わりに移動するように線を引きます。
(10) ビジネス・プロセスをコンパイルします。
(11) ハイリスクの交差点の情報を出力するファイルに人口密度情報を追加するため、レコードマップ:Demo.HighRiskArchiveMap を修正します。
管理ポータルの ホーム > Interoperability > 構築 > レコード・マップ > 開く > Demo.HighRiskArchiveMap で定義を開きます。
(レコードマップの画面は直近で操作した定義が表示されます。手順通りに進めていると Demo.HighRiskArchiveMap が開いた状態で画面が開きます。)
PopulationDensity をカラムを追加し、データタイプを %String に設定します。
をクリックし新しいカラムを追加します。
修正が完了したら、忘れずに ボタンをクリックします。ボタンを押した後の画面は以下ご参照ください。
(12) ビジネス・プロセス:Demo.HighRiskIntersectionsBPL のエディタに戻ります。
を選択し、を利用して値の割り当てを追加します。
context.Population を callrequest.PopulationDensity に割り当てを追加します(callrequest.PopulationDensity はレコードマップで追加したカラム名です)。
修正が完了したらコンパイルを行います。
(13) テストを行います。
Sandbox の IDE の左画面から /home/project/shared/Samples-Integration-SmartTicketing/SampleFiles/Red_Light_Camera_Violations_Test.csv ファイルを Ctrl + c でコピーし、/home/project/shared/Samples-Integration-RedLights/data/In/ のフォルダに Ctrl + v でペーストします。
メッセージビューワで確認します(管理ポータルの ホーム > Interoperability > 表示 > メッセージ)。
画面右側の「トレース」タブを参照しながら、追加した REST サービスを呼び出すオペレーションへの経路があるトレースを開き、流れを確認してください。
⑤ Explore custom business components
InterSystems IRIS には、コーディングをほとんど必要としないすぐに使えるコンポーネントが多く用意されています。
構築済コンポーネント以外では、InterSystems ObjectScript で作成するカスタム・コンポーネントもあり、カスタム・コンポーネントの利点としては、処理に必要なことなら何でもコードで追記することができます。
チュートリアルに登場したビジネス・オペレーションの Demo.ToCensusGeoServiceBO と Demo.ToCensusDS は、カスタム・コンポーネントとして作成しています。
以降の紹介では、InterSystems ObjectScript で記述されたカスタムコンポーネントのコードについてご説明します。
Sandbox の IDE を開きます(https://gettingstarted.intersystems.com/interoperability/red-lights-part-2/#get-set-up からアクセス情報をご確認ください)。
左画面で、shared/Samples-Integration-SmartTicketing/Setup/src/cls に移動し、Demo.ToCensusDS クラスを開きます。
このクラスの中のコードでは、REST エンドポイントを呼び出し、情報を取得して、呼び出したビジネス・プロセスにデータを返送している処理を記述しています。
以下、コードで登場する変数やメソッドについての補足です。
変数 tURL は、プロダクション構成の で設定した情報を ..Adapter.URL で取得し、ここに REST サービスを呼び出す時に必要となるクエリパラメータを追記しています。
GetURL()メソッドは、 と組み立てた URL(=変数 tURL)を利用して、GET 要求を実行し、HTTP 応答を受信しています。HTTP 応答は、参照渡しで指定された第2引数 tHttpResponse に登録されます。
..JSONStreamToObject() メソッドは、ビジネス・オペレーションが使用している REST 用のアウトバウンドアダプタ(ビジネス・オペレーションに追加できるアダプタ)から提供されるメソッドで、GET 要求から得られた HTTP 応答のストリームに含まれる JSON データを IRIS のダイナミックオブジェクト(=IRIS 内で JSON を操作するときに使用するインスタンス)に変換しています。
Demo.GeoResponse のインスタンスを新規生成(set pResponse = ##class(Demo.GeoResponse).%New())している流れでは、HTTP 応答から変換した JSON のダイナミックオブジェクトを使用して、返送データを応答メッセージのプロパティに設定しています。インスタンスを設定している変数 pResponse は、メソッド:GetGeoInfo() の第2引数に参照渡しとして設定されている引数で、この変数に情報を設定することで、ビジネス・オペレーションの応答メッセージとして呼び出し元に返送されます。
/// Retrieves GeoJSON data from the USCensus using longitude and latitude.
Method GetGeoInfo(pRequest As Demo.GeoRequest, Output pResponse As Demo.GeoResponse) As %Status
{
try {
// Prepare and log the call
// Append the city to the URL configured for adapter
set tURL= ..Adapter.URL _ "query?geometry=" _ pRequest.Longitude _ ","
_ pRequest.Latitude _ "&geometryType=esriGeometryPoint&outfields=*"
_ "&returnGeometry=true&returnIdsOnly=false&inSR=4326&f=geojson"
// Execute the call
$$$TRACE("Executing call")
set tSC=..Adapter.GetURL(tURL,.tHttpResponse)
// Return the response
if $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)
&&tHttpResponse.Data.Size {
set tSC=
$$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
}
quit:$$$ISERR(tSC)
if $IsObject(tHttpResponse) {
// Instantiate the response object
set pResponse = ##class(Demo.GeoResponse).%New()
// Convert JSON into a Proxy Object
set tSC = ..JSONStreamToObject(tHttpResponse.Data, .tProxy)
if (tSC){
// Set response properties from the Proxy Object
set pResponse.State = tProxy.features.GetAt(1).properties.STATE
set pResponse.County = tProxy.features.GetAt(1).properties.COUNTY
set pResponse.Tract = tProxy.features.GetAt(1).properties.TRACT
set pResponse.BlockGroup = tProxy.features.GetAt(1).properties.BLKGRP
$$$TRACE("Object created with " _ pResponse.State)
}
}
}catch{
set tSC=$$$SystemError
}
quit tSC
}
Troubleshooting Guide
問題
対策
Sandbox が起動しない、ページが表示されない
この記事のコメント欄でお知らせください
メッセージビューワにメッセージが表示されない
ビジネス・サービス:From_RedLightArchiveの設定タブにある「ターゲット構成名」が正しく設定されているかご確認ください。
チュートリアル前半では、ターゲット構成名には To_HighRiskArchive が設定されています。
チュートリアル公判では、Demo.HighRiskIntersectionsBPL が設定されています。
cross-originエラーが発生する
管理ポータルの URL を https:// から http:// に変更し、再度実行してみてください。
Sandbox は、コンテナ間でコンテンツを共有するように設定されているため、共有フォルダ内のファイルに正常にアクセスするために必要になる場合があります。
ビジネス・オペレーション:Demo.ToCensusGeoServiceBO テスト時にエラーが発生
テスト時に入力したデータ(longitude、latitude)をご確認ください。(例に記載の文字列をご利用ください)
テストボタンが無効になっていて押せない
プロダクションの設定を確認します。設定タブの下の方にある「開発とデバッグ」を展開し、テスト使用可能のチェックを入れ 適用ボタンをクリックしてから再度お試しください。
メッセージビューワに表示されるメッセージ一覧に「Ens.ScheduleService」が表示されていて、チュートリアルの流れには登場していません。
このメッセージはバックグラウンド処理に関連したメッセージで無視いただけます。
メッセージ一覧にチュートリアルで使用しているメッセージが表示されていれば大丈夫です。
記事
Tomohiro Iwamoto · 2020年10月22日
リモートや在宅での勤務が一般化しつつあります。
そのため、今までの集中型、オンサイトの開発体制を見直し、分散型の開発体制への移行を進めておられるユーザさんも多いのではないかと思います。
VSCodeを使用したIRISアプリケーションの開発が、コミュニティーを中心に広まり始めて久しいですが、Gitとの相性が良いこの開発ツールが今後さらに浸透していくことは間違いありません。あちらこちらで、その使いまわし方が語られていますが、ここでは、ソースコントロールとの関連を中心にご紹介したいと思います。
ObjectScript Extensionの使い方の基本については、こちらやこちらをご覧ください。
VSCode InterSystems ObjectScript Extensionのプロダクションリリース(V1.0.x)の配布が始まりました。
これに合わせて、今までのコミュニティーサポートに加え、InterSystemsによる公式サポートもアナウンスされています。よりいっそう安心してご利用いただけるようになりました。
目的
メインの開発ツールとしてVSCode+ObjectScript Extensionを使用している環境でのソースコード管理について、その流れを解説します。また、現時点ではVSCodeでビジュアル編集ができないInteroperability機能の編集内容(スタジオ、管理ポータルを使用します)をソースコード管理に加える方法を例示します。
前提
IRISのソースコード管理がその期待通りの動作をするためには、いくつか前提が存在します。
各利用者が専用のワークディレクトリ、ローカルレポジトリを持つ
これは、Gitの利用環境ではごく普通のことです。これを共有してしまうと、コミット内容にあわせて編集対象をステージングするというGitの基本的な操作が出来なくなります。
一時的にデバッグ用途で作成したルーチン(test1, deb2)などを誤ってソースコード管理に追加するのは避けたいものです。
VSCodeとスタジオで同一のワークディレクトリ、ローカルレポジトリを使用する
これを別にしてしまうと、同じファイルを別のローカルレポジトリにコミットするという危険を排除出来なくなります。
また、VSCode,スタジオ双方を使用した一連の修正をコミットするという操作が出来なくなります。
各利用者が編集時の接続先となる専用のIRIS環境を持つ
これを共有してしまうと、利用者どうしの修正が逐次干渉しあい、またコミット前の修正を"ダーティリード"することになるため、分散開発のメリットが損なわれます。
環境例
これら前提を満たす、2種類の環境を例示します。
1. 全てローカル
各利用者がIRISインスタンス自体をローカルPC上にインストールして個人環境として使用しながら開発を進めます。
Gitのような分散型のソースコード管理と相性が良く、スタジオのソースコントロールとの共存も容易なため、お勧めです。
2. IRIS環境を共有
全利用者が共有のIRIS環境を使用します。前提を満たすためにネームスペースとそこに紐づけるデータベース、ワークディレクトリを、各利用者ごとに用意します。この場合、分散開発は実現しません。また、後述の手間がかかるため、ソースコード管理を徹底活用したいという向きにはあまりお勧めはしません。
利用者
ネームスペース
データベース
用途
UserA
MYAPP_USERA
myapp_usera/IRIS.DAT
ソースコード
common/IRIS.DAT
共通ソースコード
data_usera/IRIS.DAT
データ
UserB
MYAPP_USERB
myapp_userb/IRIS.DAT
ソースコード
common/IRIS.DAT
共通ソースコード
data_userb/IRIS.DAT
データ
通常、VSCodeのワークディレクトリはローカルPC上ですが、スタジオや管理ポータルを併用する場合(ソースコントロールフックを使用する場合)、VSCodeのワークディレクトリもリモート上に用意し、Remote Development extensionでSSH接続します。
これはLinux向けの機能ですが、WindowsであってもSSHとWSLを導入すればVSCodeのリモート接続対象になれます。
1,2共にIRISをDockerで稼働させることで、後述するブランチの切り替え時操作も非常に楽になります。魅力的な選択子なのですが実行環境がLinuxに限定されます。
使用するソースコード管理機能
VSCodeでは標準のソースコントロール機能(Git)を使用します。
スタジオ/管理ポータルでは、編集内容を保存時にワークディレクトリに出力するこちらのソースコード管理フックを使用します。
使用方法の例
上述の「全てローカル」の場合を例にとり使用方法の流れを俯瞰します。
全体の流れは、Gitを使用した共有リポジトリパターンの典型的な開発フローと何ら変わりありませんので、利用環境に合わせて適用いただくための参考としてご覧ください。
1. リモートリポジトリを作成
開発プロジェクトProject1用に、管理者がレポジトリ名:Project1, ブランチb1を作成。
cd \var\git
git clone https://github.com/IRISMeister/Project1.git
cd Project1
git checkout -b b1
git push --set-upstream origin b1
2. フォルダ構成の決定
決まりはありません。ここでは、下記のようなフォルダ構成とします。
フォルダ名: Project1 通常レポジトリ名と一致する。
C:\var\git\Project1 ローカルレポジトリ(.gitフォルダ)が存在
C:\var\git\Project1\... IRISと直接関係のないファイル(他プログラミング言語、リソース、Docker関連など)を配置
C:\var\git\Project1\src IRIS関連ファイル(cls, mac, incなど)を配置
3. 参加各位のローカルフォルダにgit clone
ブランチをb1に切り替えて作業開始
cd \var\git
git clone https://github.com/IRISMeister/Project1.git
cd Project1
git checkout b1
4. ツールの初期設定
VSCodeでの設定
単にそのワークディレクトリを開くだけです。
cd \var\git\Project1
code .
コンパイル・テスト実行環境として使用するための環境として、ローカルIRISのMYAPP(任意です)ネームスペースを接続先に指定します。
VSCodeでは、.VSCode/settings.jsonに下記のような接続情報を設定します。
"objectscript.conn": {
"active": true,
"host": "localhost",
"port": 52773,
"ns": "MYAPP",
"username": "SuperUser",
"password": "SYS"
}
よりセキュアなInterSystems Server Managerを使用いただきたいところですが本題から逸れてしまうので割愛いたします
スタジオでの設定
BPM,DTL,Ruleの編集内容をソースコントロールに含める必要がある場合、スタジオになんらかのソースコントロールフックの導入が必要です。前述のソースコントロールフック(%ZScc.Basic)を導入・有効化し、ファイル出力先として、ワークディレクトリを指定します。
Set $NAMESPACE="MYAPP"
Set ^ZScc("Basic","LocalWorkspaceRoot")="c:\var\git\Project1\"
Set ^ZScc("Basic","Src")="src"
その後、通常の接続操作でMYAPPネームスペースに接続します。これで当該ネームスペース上にてソースコントロールが有効化します。また、この設定は、管理ポータルでのBPM,DTL,Rule編集画面でも有効になります。
5. 開発作業
git pull - コンフリクト解消 - ローカルIRISへのImport - 開発作業 - git add/commit - git push
この繰り返しになります。
主な編集作業はVSCodeで行います。Interoperabilityのプロダクション構成,BPL,DTL,Ruleだけは、スタジオもしくは管理ポータルで編集作業を行います。
プロダクション構成,BPL,DTL,Ruleもビジュアルな編集ではなく、ソースコードとしての編集であればVSCodeで可能です
スタジオでのソースコード管理コマンドの実行方法はメニュー操作になります。
また、ソースコントロールフックの有効化を行うと、管理ポータルのBPL,DTL,Rule編集画面などにソースコード管理ボタンが提供されます。
左のアイコンでメニュー操作、右のアイコンで出力の確認を行います。
ただし、本例で使用する%ZScc.Basicのフックは、ソースコード保存時に、自動的にワークディレクトリに対象をエクスポートするだけですのでメニュー操作はほぼ不要です。
Git用のソースコントロールフック(%ZScc.Git)を選んだ場合、各Gitコマンドの発行が可能になりますが、本稿の対象外です。これとは別に、スタジオ単体利用時により特化したソースコード管理フックも公開されています。
開発作業のイメージ
他の利用者による修正を反映、コンフリクトがあれば解消します。修正内容をローカルIRISに反映します。画像(2)
ローカルIRISを使用しながら開発・単体テストを実行します。画像(3)
(必要に応じて)スタジオや管理ポータルでBMP,DTL,Ruleを新規作成・編集します。画像(3,4)
これらの変更はVSCode上で、未ステージング状態の変更要素として認識されます。
適宜ローカルのレポジトリにコミットします。
VSCode(お勧め)、コマンドラインで実行します。画像(5)
適宜リモートレポジトリにプッシュします。
VSCode(お勧め)、コマンドラインで実行します。画像(6)
6. 自動化テスト
継続的にテスト実施するような環境を使用して、リモートリポジトリのb1ブランチのソースコードをテストします。 画像(7)
Dockerを使える(プロダクション環境がLinux環境)と楽です。画像(8,9)
こちらにGitHub Actionを使用して、IRISベースのDockerイメージを作成する例があります。
プロダクション環境がWindowsの場合はチャレンジングな項目になります。可能性としては、WindowsでDOSやPowerShellを起動可能なCI/CDツールとIRISの無人インストール機能を組み合わせるなどして実現していく事になると思います。画像(10)
7. リリース作業
管理者がb1ブランチをmasterブランチにマージします。画像(11)
8. 新規リリースに向け、b2ブランチを作成。以降、繰り返し。
ブランチとネームスペースについて
シンプルな構造(ビルドに必要な全てのユーザ作成のソースコードが単一のデータベース上に存在、ブランチはmasterのみ)の採用が可能な小規模での開発が可能な場合は、これらを気にする必要はありません。
ネームスペースが指し示すソースコード保存用のデータベース(ルーチンのデフォルトデータベース)に関して配慮が必要です。
ネームスペースとソースコードの保存場所は、1対1とは限りません。パッケージマッピングなどにより複数のデータベースに跨っている可能性があります(共通関数などを別個のデータベースに配置している場合など)。これら共通関数は、使用するプロジェクトとは切り離してソースコード管理される可能性もあります。
そのことを考慮すると、ブランチ切り替えの際に、単純にネームスペースで認識できる既存のソースコードを「全部削除」して、切り替わったソースコードと置き換える、というオペレーションでは、どうしても、削除もれや削除し過ぎといったミスや無駄な再コンパイル発生を排除できません。
少々手間ではありますが、各ブランチに対応するソースコード保存専用のデータベースを個別に用意しておいて、ブランチを切り替える際には、ネームスペースのメインのソースコードの保存場所も切り替えるのが最も安全だと思います。
IRIS環境を共有する場合は、さらにこれらが各利用者ごとに分かれますので、ブランチ数×利用者数の数だけデータベースを作成することになり、かなりの手間となります。
ブランチ
ネームスペース
データベース
用途
master
MYAPP
myapp/master/IRIS.DAT
ソースコード
CommonPackage/IRIS.DAT
共通ソースコード
DATA/IRIS.DAT
データ
b1
MYAPP
myapp/b1/IRIS.DAT
ソースコード
同上
共通ソースコード
同上
データ
b2
MYAPP
myapp/b2/IRIS.DAT
ソースコード
同上
共通ソースコード
同上
データ
一方、開発時のIRISの稼働環境として、リモートレポジトリを使用して焼いたDockerイメージを使用すれば、切り替え操作は使用するコンテナイメージの選択という簡単な操作で済む(利用者から隠ぺいされる)ため、手間(なによりもオペミス)が大幅に削減できます。
IRIS DockerイメージはLinux(Ubuntu)ベースですので、プロダクション環境もLinuxである必要があります
ブランチ
イメージ名
ネームスペース
データベース
用途
データベース保存場所
master
MYIRIS:master
MYAPP
myapp/IRIS.DAT
ソースコード
イメージ内
CommonPackage/IRIS.DAT
共通ソースコード
イメージ内
DATA/IRIS.DAT
データ
外部データベース
b1
MYIRIS:b1
MYAPP
同上
ソースコード
イメージ内
同上
共通ソースコード
イメージ内
同上
データ
外部データベース
b2
MYIRIS:b2
MYAPP
同上
ソースコード
イメージ内
同上
共通ソースコード
イメージ内
同上
データ
外部データベース
IRIS環境を共有した際の注意点
同一のネームスペースを複数の利用者が共有した場合、誰かの修正を他の人がVSCode経由で上書きしようとすると、ObjectScript Extensionから警告を受け、そのコンフリクト解消のための選択を促されます。
小規模開発であれば、これを使用して開発を進めることも可能です。VSCodeには、スタジオの操作環境に近い、サーバサイドを直接編集するモードもありますので、その利用を検討されるのも良いかもしれません。
このモードではワークディレクトリが使用されないためVSCodeのGitでIRISのソースコード管理をすることが出来ません。
記事
Toshihiko Minamoto · 2020年11月26日
[前回](https://community.intersystems.com/post/deploying-intersystems-iris-solution-gcp-kubernetes-cluster-gke-using-circleci)は GKE サービスを使用して IRIS アプリケーションを Google Cloud 上で起動しました。
また、クラスターを手動で(または [gcloud](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-cluster) を介して)作成するのは簡単ですが、最新の [Infrastructure-as-Code(IaC)手法](https://martinfowler.com/bliki/InfrastructureAsCode.html)では、Kubernetesクラスターの説明もコードとしてリポジトリに格納する必要があります。 このコードの記述方法は、IaC に使用されるツールによって決まります。
Google Cloud の場合は複数のオプションが存在し、その中には Deployment Manager と Terraform があります。 どちらが優れているかにつては意見が分かれています。詳細を知りたい場合は、この Reddit のスレッド「Opinions on Terraform vs. Deployment Manager?」と Medium の記事「Comparing GCP Deployment Manager and Terraform」を参照してください。
この記事では特定のベンダーとの結びつきが少なく、さまざまなクラウドプロバイダーで IaC を使用できる Terraform を選択します。
ここでは過去の記事を読み、Googleアカウントを作成し、前回の記事と同様に「開発」という名前のプロジェクトを作成しているものと仮定します。 この記事ではその ID は <PROJECT_ID> として表示されます。 以下の例では、それを自分のプロジェクトの IDに変更してください。
Google には無料枠がありますが、無料ではないことに注意してください。 必ず出費をコントロールするようにしてください。
また、ここではすでに元のリポジトリをフォークしていることを前提にしています。 この記事全体を通してこのフォークを「my-objectscript-rest-docker-template」と呼び、そのルートディレクトリを「<root_repo_dir>」として参照します。
コピーと貼り付けを簡単にするため、すべてのコードサンプルをこのリポジトリに格納しています。
次の図では、デプロイプロセス全体を 1 つの図で表しています。
では、次のように執筆時点での Terraform の最新バージョンをインストールしましょう。
$ terraform versionTerraform v0.12.17
インターネット上の多くの例では旧バージョンが使用されており、0.12 では多くの変更が加えられているため、ここではバージョンが重要になります。
ここでは GCP アカウントで Terraform に特定のアクションを実行(特定の API を使用)させたいと考えています。 これを可能にするには「terraform」という名前のサービスアカウントを作成し、Kubernetes Engine API を有効にしてください。 その実施方法についてはご心配なく。この記事を読み進めるだけで、あなたの疑問は解消します。
Web Console を使用することもできますが、ここでは gcloud ユーティリティを使った例を試してみましょう。
次の例では、数種類のコマンドを使用します。 これらのコマンドや機能の詳細については、次のドキュメントのトピックを参照指定してください。
gcloud iam service-accounts create
特定のリソースのサービスアカウントへの役割の付与
gcloud iam service-accounts keys create
Google Cloud プロジェクトでの API の有効化
それでは、例を見ていきましょう。
$ gcloud init
前回の記事で gcloud を取り上げましたので、ここではセットアップの詳細は説明しません。 この例では、次のコマンドを実行します。
$ cd <root_repo_dir>$ mkdir terraform; cd terraform$ gcloud iam service-accounts create terraform --description "Terraform" --display-name "terraform"
次に、「Kubernetes Engine Admin」(container.admin)の他にいくつかのロールを terraform サービスアカウントに追加しましょう。 これらのロールは今後役に立つことでしょう。
$ gcloud projects add-iam-policy-binding <PROJECT_ID> \ --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \ --role roles/container.admin
$ gcloud projects add-iam-policy-binding <PROJECT_ID> \ --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \ --role roles/iam.serviceAccountUser
$ gcloud projects add-iam-policy-binding <PROJECT_ID> \ --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \ --role roles/compute.viewer
$ gcloud projects add-iam-policy-binding <PROJECT_ID> \ --member serviceAccount:terraform@<PROJECT_ID>.iam.gserviceaccount.com \ --role roles/storage.admin
$ gcloud iam service-accounts keys create account.json \--iam-account terraform@<PROJECT_ID>.iam.gserviceaccount.com
最後の入力では、account.json ファイルを作成していることに注意してください。 このファイルは必ず秘密にしてください。
$ gcloud projects list$ gcloud config set project <PROJECT_ID>$ gcloud services list --available | grep 'Kubernetes Engine'$ gcloud services enable container.googleapis.com$ gcloud services list --enabled | grep 'Kubernetes Engine'container.googleapis.com Kubernetes Engine API
次に、Terraform の HCL 言語で GKE クラスターを記述しましょう。 ここではいくつかのプレースホルダーを使用していますが、これらは実際の値に置き換えてください。
プレースホルダー
意味
例
<PROJECT_ID>
GCP のプロジェクト ID
possible-symbol-254507
<BUCKET_NAME>
Terraform のステート/ロック用のストレージ(一意である必要があります)
circleci-gke-terraform-demo
<REGION>
リソースが作成されるリージョン
europe-west1
<LOCATION>
リソースが作成されるゾーン
europe-west1-b
<CLUSTER_NAME>
GKE クラスター名
dev-cluster
<NODES_POOL_NAME>
GKE ワーカーノードのプール名
dev-cluster-node-pool
以下に実際のクラスターの HCL 構成を示します。
$ cat main.tfterraform { required_version = "~> 0.12" backend "gcs" { bucket = "<BUCKET_NAME>" prefix = "terraform/state" credentials = "account.json" }}
provider "google" { credentials = file("account.json") project = "<PROJECT_ID>" region = "<REGION>"}
resource "google_container_cluster" "gke-cluster" { name = "<CLUSTER_NAME>" location = "<LOCATION>" remove_default_node_pool = true # In regional cluster (location is region, not zone) # this is a number of nodes per zone initial_node_count = 1}
resource "google_container_node_pool" "preemptible_node_pool" { name = "<NODES_POOL_NAME>" location = "<LOCATION>" cluster = google_container_cluster.gke-cluster.name # In regional cluster (location is region, not zone) # this is a number of nodes per zone node_count = 1
node_config { preemptible = true machine_type = "n1-standard-1" oauth_scopes = [ "storage-ro", "logging-write", "monitoring" ] }}
HCL コードを適切に整形できるよう、Terraform には次の便利な整形コマンドが用意されています。
$ terraform fmt
上記のコードスニペットは、作成されたリソースが Google によって提供され、リソース自体は google_container_cluster と google_container_node_pool であることを示しています。また、ここではコスト削減のために preemptible を指定しています。 また、デフォルトの代わりに独自のプールを作成しています。
次の設定を簡単に説明します。
terraform { required_version = "~> 0.12" backend "gcs" { Bucket = "<BUCKET_NAME>" Prefix = "terraform/state" credentials = "account.json" }}
Terraform はすべての実行結果をステータスファイルに書き込み、このファイルを他の作業に使用します。 このファイルは共有しやすいように、離れた場所に保存することをお勧めします。 一般的には Google バケットに保存されます。
このバケットを作成しましょう。 プレースホルダー <BUCKET_NAME> の代わりに自分のバケットの名前を使用してください。 バケットを作成する前に、<BUCKET_NAME> が使用できるかどうかを次のコマンドで確認してください。すべての GCP で一意である必要があるためです。
$ gsutil acl get gs://<BUCKET_NAME>
期待する応答:
BucketNotFoundException: 404 gs://<BUCKET_NAME> bucket does not exist
「Busy」という応答があった場合、別の名前を選択する必要があります。
AccessDeniedException: 403 <YOUR_ACCOUNT> does not have storage.buckets.get access to <BUCKET_NAME>
Terraform の推奨どおりにバージョン管理も有効にしましょう。
$ gsutil mb -l EU gs://<BUCKET_NAME>
$ gsutil versioning get gs://<BUCKET_NAME>gs://<BUCKET_NAME>: Suspended
$ gsutil versioning set on gs://<BUCKET_NAME>
$ gsutil versioning get gs://<BUCKET_NAME>gs://<BUCKET_NAME>: Enabled
Terraform はモジュール方式であり、GCP で何かを作成するにはGoogle Provider プラグインを追加する必要があります。 これを行うには、次のコマンドを使用します。
$ terraform init
Terraform が GKE クラスターを作成する際の実行計画を見てみましょう。
$ terraform plan -out dev-cluster.plan
コマンドの出力には、計画の詳細が含まれています。 特に問題なければ、次のコマンドでこの計画を実行しましょう。
$ terraform apply dev-cluster.plan
ちなみに、Terraform によって作成されたリソースを削除するには、このコマンドを <root_repo_dir>/terraform/ ディレクトリから実行してください。
$ terraform destroy -auto-approve
しばらくクラスターから離れて先に進みましょう。 ただし、何もかもリポジトリにプッシュされないように、先にいくつかのファイルを例外に追加しましょう。
$ cat <root_repo_dir>/.gitignore.DS_Storeterraform/.terraform/terraform/*.planterraform/*.json
Helm の使用
前回の記事では、Kubernetes のマニフェストを yaml ファイルとして <root_repo_dir>/k8s/ ディレクトリに保存し、それを「kubectl apply」コマンドを使用してクラスターに送信しました。
今回は別の手法を試してみましょう。最近バージョン 3にアップデートされた Kubernetes のパッケージマネージャーである Helm を使用します。 バージョン 2 には Kubernetes 側のセキュリティの問題があったため、バージョン 3 以降を使用してください(詳細については、Running Helm in production: Security best practices を参照してください)。 まず、Kubernetes のマニフェストを k8s/ ディレクトリからチャートとして知られる Helm パッケージにまとめます。 Kubernetes にインストールされている Helm チャートはリリースと呼ばれます。 最小構成では、チャートは次のような複数のファイルで構成されます。
$ mkdir <root_repo_dir>/helm; cd <root_repo_dir>/helm$ tree <root_repo_dir>/helm/helm/├── Chart.yaml├── templates│ ├── deployment.yaml│ ├── _helpers.tpl│ └── service.yaml└── values.yaml
これらのファイルの目的は、公式サイトで詳細に説明されています。 独自チャートを作成するためのベストプラクティスは、Helm ドキュメントの The Chart Best Practices Guide に記載されています。
次にファイルの内容を示します。
$ cat Chart.yamlapiVersion: v2name: iris-restversion: 0.1.0appVersion: 1.0.3description: Helm for ObjectScript-REST-Docker-template applicationsources:- https://github.com/intersystems-community/objectscript-rest-docker-template- https://github.com/intersystems-community/gke-terraform-circleci-objectscript-rest-docker-template
$ cat templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: {{ template "iris-rest.name" . }} labels: app: {{ template "iris-rest.name" . }} chart: {{ template "iris-rest.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }}spec: replicas: {{ .Values.replicaCount }} strategy: {{- .Values.strategy | nindent 4 }} selector: matchLabels: app: {{ template "iris-rest.name" . }} release: {{ .Release.Name }} template: metadata: labels: app: {{ template "iris-rest.name" . }} release: {{ .Release.Name }} spec: containers: - image: {{ .Values.image.repository }}:{{ .Values.image.tag }} name: {{ template "iris-rest.name" . }} ports: - containerPort: {{ .Values.webPort.value }} name: {{ .Values.webPort.name }}
$ cat templates/service.yaml{{- if .Values.service.enabled }}apiVersion: v1kind: Servicemetadata: name: {{ .Values.service.name }} labels: app: {{ template "iris-rest.name" . }} chart: {{ template "iris-rest.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }}spec: selector: app: {{ template "iris-rest.name" . }} release: {{ .Release.Name }} ports: {{- range $key, $value := .Values.service.ports }} - name: {{ $key }}{{ toYaml $value | indent 6 }} {{- end }} type: {{ .Values.service.type }} {{- if ne .Values.service.loadBalancerIP "" }} loadBalancerIP: {{ .Values.service.loadBalancerIP }} {{- end }}{{- end }}
$ cat templates/_helpers.tpl{{/* vim: set filetype=mustache: */}}{{/*Expand the name of the chart.*/}}
{{- define "iris-rest.name" -}}{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}{{- end -}}
{{/*Create chart name and version as used by the chart label.*/}}{{- define "iris-rest.chart" -}}{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}{{- end -}}
$ cat values.yamlnamespaceOverride: iris-rest
replicaCount: 1
strategy: | type: Recreate
image: repository: eu.gcr.io/iris-rest tag: v1
webPort: name: web value: 52773
service: enabled: true name: iris-rest type: LoadBalancer loadBalancerIP: "" ports: web: port: 52773 targetPort: 52773 protocol: TCP
Helm チャートを作成するには、Helm クライアントと kubectl コマンドラインユーティリティをインストールします。
$ helm versionversion.BuildInfo{Version:"v3.0.1", GitCommit:"7c22ef9ce89e0ebeb7125ba2ebf7d421f3e82ffa", GitTreeState:"clean", GoVersion:"go1.13.4"}
iris というネームスペースを作成します。 デプロイ中にこれが作成されていれば良かったのですが、現時点ではその動作は実装されていません。
まず、Terraform によって作成されたクラスターの資格情報を kube-config に追加します。
$ gcloud container clusters get-credentials <CLUSTER_NAME> --zone <LOCATION> --project <PROJECT_ID>$ kubectl create ns iris
Helm が Kubernetes で以下を作成することを(実際にデプロイを開始せずに)確認します。
$ cd <root_repo_dir>/helm$ helm upgrade iris-rest \ --install \ . \ --namespace iris \ --debug \ --dry-run
ここでは、出力(Kubernetes のマニフェスト)をスペースを確保するために省略しています。 特に問題がなければ、デプロイしましょう。
$ helm upgrade iris-rest --install . --namespace iris$ helm list -n iris --allIris-rest iris 1 2019-12-14 15:24:19.292227564 +0200 EET deployed iris-rest-0.1.0 1.0.3
Helm がアプリケーションをデプロイしたことはわかりますが、Docker イメージ eu.gcr.io/iris-rest:v1 をまだ作成していないため、Kubernetes がそのイメージをプルすることはできません(ImagePullBackOff)。
$ kubectl -n iris get poNAME READY STATUS RESTARTS AGEiris-rest-59b748c577-6cnrt 0/1 ImagePullBackOff 0 10m
とりあえず、今はここで終わっておきましょう。
$ helm delete iris-rest -n iris
CircleCI 側
Terraform と Helm クライアントを試しましたので、それらを CircleCI 側のデプロイプロセスで使用できるようにしましょう。
$ cat <root_repo_dir>/.circleci/config.ymlversion: 2.1
orbs: gcp-gcr: circleci/gcp-gcr@0.6.1
jobs: terraform: docker: # Terraform image version should be the same as when # you run terraform before from the local machine - image: hashicorp/terraform:0.12.17 steps: - checkout - run: name: Create Service Account key file from environment variable working_directory: terraform command: echo ${TF_SERVICE_ACCOUNT_KEY} > account.json - run: name: Show Terraform version command: terraform version - run: name: Download required Terraform plugins working_directory: terraform command: terraform init - run: name: Validate Terraform configuration working_directory: terraform command: terraform validate - run: name: Create Terraform plan working_directory: terraform command: terraform plan -out /tmp/tf.plan - run: name: Run Terraform plan working_directory: terraform command: terraform apply /tmp/tf.plan k8s_deploy: docker: - image: kiwigrid/gcloud-kubectl-helm:3.0.1-272.0.0-218 steps: - checkout - run: name: Authorize gcloud on GKE working_directory: helm command: | echo ${GCLOUD_SERVICE_KEY} > gcloud-service-key.json gcloud auth activate-service-account --key-file=gcloud-service-key.json gcloud container clusters get-credentials ${GKE_CLUSTER_NAME} --zone ${GOOGLE_COMPUTE_ZONE} --project ${GOOGLE_PROJECT_ID} - run: name: Wait a little until k8s worker nodes up command: sleep 30 # It’s a place for improvement - run: name: Create IRIS namespace if it doesn't exist command: kubectl get ns iris || kubectl create ns iris - run: name: Run Helm release deployment working_directory: helm command: | helm upgrade iris-rest \ --install \ . \ --namespace iris \ --wait \ --timeout 300s \ --atomic \ --set image.repository=eu.gcr.io/${GOOGLE_PROJECT_ID}/iris-rest \ --set image.tag=${CIRCLE_SHA1} - run: name: Check Helm release status command: helm list --all-namespaces --all - run: name: Check Kubernetes resources status command: | kubectl -n iris get pods echo kubectl -n iris get servicesworkflows: main: jobs: - terraform - gcp-gcr/build-and-push-image: dockerfile: Dockerfile gcloud-service-key: GCLOUD_SERVICE_KEY google-compute-zone: GOOGLE_COMPUTE_ZONE google-project-id: GOOGLE_PROJECT_ID registry-url: eu.gcr.io image: iris-rest path: . tag: ${CIRCLE_SHA1} - k8s_deploy: requires: - terraform - gcp-gcr/build-and-push-image
CircleCI 側のプロジェクトに次のようないくつかの環境変数 を追加する必要があります。
GCLOUD\_SERVICE\_KEY は CircleCI のサービスアカウントキーであり、TF\_SERVICE\_ACCOUNT_KEY は Terraform のサービスアカウントキーです。 サービスアカウントキーが _account.json_ ファイル全体の内容であることを思い出してください。
次に、変更をリポジトリにプッシュしましょう。
$ cd <root_repo_dir>$ git add .circleci/ helm/ terraform/ .gitignore$ git commit -m "Add Terraform and Helm"$ git push
CircleCI UI ダッシュボードには、次のようにすべてが正常であることが示されているはずです。
Terraform は冪等性のあるツールであり、GKE クラスターが存在する場合、「terraform」ジョブは何も実行しません。 クラスターが存在しない場合は、Kubernetes をデプロイする前に作成されます。最後に、IRIS の可用性を確認しましょう。
$ gcloud container clusters get-credentials <CLUSTER_NAME> --zone <LOCATION> --project <PROJECT_ID>
$ kubectl -n iris get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEIris-rest LoadBalancer 10.23.249.42 34.76.130.11 52773:31603/TCP 53s
$ curl -XPOST -H "Content-Type: application/json" -u _system:SYS 34.76.130.11:52773/person/ -d '{"Name":"John Dou"}'
$ curl -XGET -u _system:SYS 34.76.130.11:52773/person/all[{"Name":"John Dou"},]
まとめ
Terraform と Helm は標準の DevOps ツールであり、IRIS のデプロイと緊密に統合する必要があります。
これらはある程度の学習を必要としますが、何度か実践した後は大幅に時間と労力を節約できるようになります。
記事
Hiroshi Sato · 2021年1月27日
これは InterSystems FAQ サイトの記事です。
クラス定義のプロパティの表示順は、スタジオのプロパティウィザードを利用して登録した場合は、末尾に追記されます。
また、エディタ上の任意の場所でプロパティ定義文を記述する場合は、その場所に追記され、クラス定義が登録されます。
つまり、定義者が記述した順番に登録されます。
(スタジオが並び換えを行ったりはしません。)
作成したクラス定義が、PersistentやSerialのようにデータベースに格納する属性を持ったクラス定義である場合、”初回のコンパイル”で クラス定義に対応するグローバル変数の定義情報=ストレージ定義を作成します。
初回コンパイル以降に、プロパティ定義の追加が行われれば、そのプロパティに対応するグローバル変数のスロット番号を、末尾に追加し、ストレージ定義を更新します。
以下の例は、クラス定義に対応するストレージ定義の例です。
(初回コンパイル時の状態)
Class Sample.Person Extends %Persistent{ Property Name As %String; /// 誕生日Property DOB As %Date; <storage name="Default"><data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="2"><value>Name</value></value> <value name="3"><value>DOB</value></value></data> <datalocation>^Sample.PersonD</datalocation><defaultdata>PersonDefaultData</defaultdata><extentsize>100000</extentsize><idlocation>^Sample.PersonD</idlocation><indexlocation>^Sample.PersonI</indexlocation><streamlocation>^Sample.PersonS</streamlocation><type>%Library.CacheStorage</type></storage> }
<storage name="Default">から</storage>までの表示が、クラス定義の初回コンパイルで作成されるストレージ定義情報です。
作成したSample.Personクラスの格納先グローバル変数は、ストレージ定義の<datalocation> <indexlocation> <streamlocatoin> を参照するとわかります。
また、各プロパティ定義が、指定グローバル変数のどこに格納されるかは、<value name="2"> と <value name="3">を参照するとわかります。<value name="2"><value>Name</value>%lt;/value> <value name="3"><value>DOB</value></value>
つまり、Nameプロパティは、^Sample.PersonDの $ListBuild()構造の2番目に格納され、DOBは3番目に格納されることがわかります。
ここで、Addressプロパティを、クラス定義の表示上、一番上に追加します。
Class Sample.Person Extends %Persistent{ Property Address As %String; Property Name As %String; /// 誕生日Property DOB As %Date; <storage name="Default"><data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="2"><value>Name</value></value> <value name="3"><value>DOB</value></value> <value name="4"><value>Address</value></value></data> <datalocation>^Sample.PersonD</datalocation><defaultdata>PersonDefaultData</defaultdata><extentsize>100000</extentsize><idlocation>^Sample.PersonD</idlocation><indexlocation>^Sample.PersonI</indexlocation><streamlocation>^Sample.PersonS</streamlocation><type>%Library.CacheStorage</type> }
ストレージ定義情報を参照すると、クラス定義の表示上1番上に登録したAddressプロパティは、
<value name="4"><value>Address</value></value>
$ListBuildの4番目に格納される定義として追加されています。
つまり、ストレージ定義は、クラス定義の表示上、どこに追記されても、後から追加されたプロパティについては、格納位置として、一番最後に追加していく仕組みがわかります。
ということで、クラス定義上の表示順と、ストレージ定義の格納順は必ずしも一致しない事があります。
なお、ストレージの格納順序を変更することはできます。
以下の例は、AddressとNameの格納順を、
≪現在≫ Address → 4番目 Name → 2番目≪変更後≫ Address → 2番目 Name → 4番目に変更した状態のストレージ定義例です。(一部抜粋)
<data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="4"><value>Name</value></value> <value name="3"><value>DOB</value></value> <value name="2"><value>Address</value></value></data>
ご覧いただいた通り、AddressプロパティとNameプロパティのスロット番号を入れ替えるだけで、ストレージ定義の格納順の変更が行えます。
ただ、ストレージ定義情報の順番のみが入れ替わるだけであり、既存グローバル変数のデータが入れ替わる事はありません。
この点、ご注意ください。(修正したストレージ定義に合わせて、グローバル変数を入れ替えたい場合は、手動でグローバル変数の中身を変更する必要があります。)