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

## Jupyter カーネルの基礎
[Jupyter カーネル](https://jupyter-client.readthedocs.io/en/stable/kernels.html)はいくつかの方法で作成できます。 ここでは、Python ラッパーカーネルを作成することにしましょう。
`ipykernel.kernelbase.Kernel` のサブクラスを作成して、特定の言語で実行されるコードを受け取る `do_execute` メソッドを実装する必要があります。
つまり、ある ObjectScript コードを取得して、何らかの方法で実行し、ノートブックにその結果を返すという概念です。
でも、実際にはどうすればよいのでしょうか。 では、その方法をさらに噛み砕いて説明しましょう。
## ObjectScript コードを IRIS に送る
まず初めに、コードを IRIS に送る必要があります。 ここで使用するのが、[Python 用の IRIS Native API](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_PYTHON_NATIVE) です。
ここでは、`irisnative` パッケージをインポートして、接続を確立するだけです。
```
def get_iris_object():
# InterSystems IRIS への接続を作成する
connection = irisnative.createConnection('iris', 51773, 'IRISAPP', '_SYSTEM', 'SYS')
# iris オブジェクトを作成する
return irisnative.createIris(connection)
```
その後で、この接続を使用して、IRIS データベースに格納されているクラスを呼び出すことができます。
```
def execute_code(self, code):
class_name = "JupyterKernel.CodeExecutor"
return self.iris.classMethodValue(class_name, "CodeResult", code)
```
`CodeExecutor` クラスと `CodeResult` メソッドは何に使用されているのでしょうか。
ではそれを見てみましょう。
## ObjectScript コードを実行する
このクラスの目的は、1 行の ObjectScript コードを実行して、実行の結果を含む JSON オブジェクトを返すことです。 コードを `CodeResult` の `vstrCommand` 変数に渡します。
まず、IO を現在のルーチンにリダイレクトします。その後、渡されたコードを [XECUTE](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_cxecute) コマンドで実行し、IO を元の場所にリダイレクトして、結果を返します。
```
Include %sySystem
Class JupyterKernel.CodeExecutor
{
ClassMethod CodeResult(vstrCommand As %String) As %String [ ProcedureBlock = 0 ]
{
set tOldIORedirected = ##class(%Device).ReDirectIO()
set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
set tOldIO = $io
try {
set str=""
set status = 1
//IO を現在のルーチンにリダイレクトする。以下に定義するラベルを利用します
use $io::("^"_$ZNAME)
//リダイレクトを有効にする
do ##class(%Device).ReDirectIO(1)
XECUTE (vstrCommand)
} catch ex {
set str = ex.DisplayString()
set status = 0
}
//元のリダイレクト/ニーモニックルーチン設定に戻す
if (tOldMnemonic '= "") {
use tOldIO::("^"_tOldMnemonic)
} else {
use tOldIO
}
do ##class(%Device).ReDirectIO(tOldIORedirected)
quit {"status":(status), "out":(str)}.%ToJSON()
rchr(c)
quit
rstr(sz,to)
quit
wchr(s)
do output($char(s))
quit
wff()
do output($char(12))
quit
wnl()
do output($char(13,10))
quit
wstr(s)
do output(s)
quit
wtab(s)
do output($char(9))
quit
output(s)
set str = str _ s
quit
}
}
```
## 結果を表示する
ObjectScript コードを実行しましたが、次はどうすればよいでしょうか。 その結果を表示する必要があります。
例外がなければ、行単位で結果を表示するだけで済みます。
が、渡されたコードで例外が発生したのであれば、実行を停止し、失敗した行番号、行そのもの、そして発生した例外を表示しなければなりません。

## アプリを起動する
このカーネルを自分で試してみましょう。以下のようにして試すことができます。
## 前提条件
[git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) と [Docker](https://www.docker.com/products/docker-desktop) がインストール済みであることを確認してください。
リポジトリを、以下のようにローカルディレクトリに Clone/git pull します。
```
$ git clone https://github.com/Vekkby/objectsriptkernel.git
```
このディレクトリでターミナルを開き、以下を実行します。
```
$ docker-compose up -d --build
```
## 操作方法
ブラウザから以下を使用してノートブックサーバーにアクセスできます。
```
localhost:8888
```
'work' ディレクトリ内に 'hello.ipynb' というサンプルノートブックがあります。 
### 投票をお願いします
このアプリは、IRIS Native API コンテストに参加しています。 こちら からこのアプリに投票してください。
記事
Toshihiko Minamoto · 2022年12月16日
母体リスクは、医学界でよく知られているいくつかのパラメーターから測定できます。 この測定により、医学界とコンピューター化されたシステム(特に AI)を支援すべく、科学者である Yasir Hussein Shakir は、母体リスクの検出/予測における ML アルゴリズムをトレーニングするための非常に便利なデータセットを公開しました。 このデータセットは、ML の最大級のデータリポジトリとして最もよく知られている Kaggle に公開されています。
https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml
## データセットについて
妊娠中と出産後の母体のヘルスケアに関する情報の不足により、妊娠中の女性の多くは、妊娠に関わる問題で死亡しています。 これは、農村地域や新興国の下位中流家庭の間でより一般的に起きている問題です。 妊娠中は、状態を絶えず観察することで、胎児の適切な成長と安全な出産を保証する必要があります(出典: https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml)。
データは、IoT ベースのリスク監視システムを通じて、様々な病院、地域の診療所、妊産婦ヘルスケアから収集されています。
* Age: 女性が妊娠したときの年齢
* SystolicBP: 最高血圧(mmHg)。妊娠中に重要な属性の 1 つ。
* DiastolicBP: 最低血圧(mmHg)。妊娠中に重要な属性の 1 つ。
* BS: モル濃度(mmol/L)による血糖値。
* HeartRate: 1 分あたりの通常の安静時心拍数。
* Risk Level: 前の属性を考慮した妊娠中の予測リスク強度レベル。
## Kaggle から母体リスクデータを取得する
Kaggle の母体リスクデータは、Health-Dataset アプリケーション(https://openexchange.intersystems.com/package/Health-Dataset)を使って IRIS テーブルに読み込めます。 これを行うには、module.xml プロジェクトから依存関係(Health Dataset 用の ModuleReference)を設定します。
Health Dataset アプリケーションリファレンスを含む Module.xml
<?xml version="1.0" encoding="UTF-8"?>
<Export
generator="Cache" version="25">
<Document name="predict-diseases.ZPM">
<Module>
<Name>predict-diseases</Name>
<Version>1.0.0</Version>
<Packaging>module</Packaging>
<SourcesRoot<src/iris</SourcesRoot>
<Resource Name="dc.predict.disease.PKG"/>
<Dependencies>
<ModuleReference>
<Name>swagger-ui</Name>
<Version>1.*.*</Version>
</ModuleReference>
<ModuleReference>
<Name>dataset-health</Name>
<Version>*</Version>
</ModuleReference>
</Dependencies>
<CSPApplication
Url="/predict-diseases"
DispatchClass="dc.predict.disease.PredictDiseaseRESTApp"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="1"
UnauthenticatedEnabled="1"
Recurse="1"
UseCookies="2"
CookiePath="/predict-diseases"
/>
<CSPApplication
CookiePath="/disease-predictor/"
DefaultTimeout="900"
SourcePath="/src/csp"
DeployPath="${cspdir}/csp/${namespace}/"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="0"
Recurse="1"
ServeFiles="1"
ServeFilesTimeout="3600"
UnauthenticatedEnabled="1"
Url="/disease-predictor"
UseSessionCookie="2"
/>
</Module>
</Document>
</Export>
## 母体リスクを予測するための Web フロントエンドとバックエンドのアプリケーション
Open Exchange アプリのリンク(https://openexchange.intersystems.com/package/Disease-Predictor)に移動し、以下の手順に従います。
* リポジトリを任意のローカルディレクトリに Clone/git pull します。
$ git clone https://github.com/yurimarx/predict-diseases.git
* このディレクトリで Docker ターミナルを開き、以下を実行します。
$ docker-compose build
* IRIS コンテナを実行します。
$ docker-compose up -d
* AI モデルをトレーニングするための Execute Query into Management Portal(http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER)に移動します。
* トレーニングに使用するビューを作成します。
CREATE VIEW MaternalRiskTrain AS SELECT BS, BodyTemp, DiastolicBP, HeartRate, RiskLevel, SystolicBP, age FROM dc_data_health.MaternalHealthRisk
* ビューを使用して AI モデルを作成します。
CREATE MODEL MaternalRiskModel PREDICTING (RiskLevel) FROM MaternalRiskTrain
* モデルをトレーニングします。
TRAIN MODEL MaternalRiskModel
* [http://localhost:52773/disease-predictor/index.html](http://localhost:52773/disease-predictor/index.html) に移動し、Disease Predictor フロントエンドを使用して、以下のように疾患を予測します。

## 背後の処理
### 母体リスク疾患を予測するためのバックエンドのクラスメソッド
InterSystems IRIS では、前に作成されたモデルを使って、SELECT の実行により予測することができます。
母体リスク疾患を予測するためのバックエンドのクラスメソッド
/// 母体リスクの予測
ClassMethod PredictMaternalRisk() As %Status
{
Try {
Set data = {}.%FromJSON(%request.Content)
Set %response.Status = 200
Set %response.Headers("Access-Control-Allow-Origin")="*"
Set qry = "SELECT PREDICT(MaternalRiskModel) As PredictedMaternalRisk, "
_"age, BS, BodyTemp, DiastolicBP, HeartRate, SystolicBP "
_"FROM (SELECT "_data.BS_" AS BS, "
_data.BodyTemp_" As BodyTemp, "
_data.DiastolicBP_" AS DiastolicBP, "
_data.HeartRate_" AS HeartRate, "
_data.SystolicBP_" As SystolicBP, "
_data.Age_" AS age)"
Set tStatement = ##class(%SQL.Statement).%New()
Set qStatus = tStatement.%Prepare(qry)
If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
Set rset = tStatement.%Execute()
Do rset.%Next()
Set Response = {}
Set Response.PredictedMaternalRisk = rset.PredictedMaternalRisk
Set Response.Age = rset.Age
Set Response.SystolicBP = rset.SystolicBP
Set Response.DiastolicBP = rset.DiastolicBP
Set Response.BS = rset.BS
Set Response.BodyTemp = rset.BodyTemp
Set Response.HeartRate = rset.HeartRate
Write Response.%ToJSON()
Return 1
} Catch err {
write !, "Error name: ", ?20, err.Name,
!, "Error code: ", ?20, err.Code,
!, "Error location: ", ?20, err.Location,
!, "Additional data: ", ?20, err.Data, !
Return
}
}
これで、どの Web アプリケーションもこの予測を使用して、結果を表示できるようになりました。 predict-diseases アプリケーションのソースコードは、frontend フォルダをご覧ください。
記事
Megumi Kakechi · 2022年12月1日
複数のインスタンス間でライセンスを共有する際に、ライセンスサーバを立ててライセンスの使用量を管理します。IRISライセンスサーバには、ライセンスの使用量管理に加えて便利な新しい機能が追加されました。
-- ライセンスサーバでできること --1. マルチサーバライセンス(共有ライセンス)の統合管理(使用量の管理)2. 各インスタンスへのライセンスキーの配布・管理【New】
1 は従来からのライセンスサーバの機能で、関連記事 にて機能紹介をしております。
2 はIRIS以降使用できるようになった新しい機能です。
複数のインスタンスを構成している場合、中央管理しているディレクトリに格納されているライセンスキーファイル(*.key)を各インスタンスに配布・管理するようにライセンスサーバを構成することができます。その場合、個々のインスタンスにライセンスキー(iris.key)を配置する必要はありません。ライセンスキーはユニークな LicenseID (※1, ※2) によって識別され、各インスタンスの起動時にロード&有効化されます。
※1 ライセンスキーをテキストエディタで開くと、[ConfigFile] セクションにて設定されている LicenseID を確認できます。※2 ライセンスキーに記載されたLicenseIDの値を各インスタンスで事前に設定します(方法詳細は後述)。 ライセンス反映時、ライセンスキーに記載されたLicenseIDと各インスタンスの設定値を照らし合わせ、一致した場合にキーが各インスタンスにロードされます。
【設定方法】1.ライセンスサーバを構成するインスタンスで KeyDirectory プロパティを設定します。
KeyDirectory で指定したディレクトリ内に、すべての ライセンスキーを配置します。 複数のライセンスキーを配置する場合は、LicenseID で区別するようにします(同じ LicenseID のキーは複数配置できません)。 KeyDirectoryを設定している場合、起動時にそのインスタンスで見つかった有効な *.key ファイルをすべて読み取り、ライセンスサーバに送信します。 ライセンスサーバの設定は、管理ポータルで行います。
管理ポータル: [システム管理] > [ライセンス] > [ライセンス・サーバ]
2.各インスタンスで LicenseID プロパティ(キーファイルの新しいプロパティ)を 設定します。
各インスタンスは LicenseID プロパティを使用して、起動時にライセンスサーバからライセンスキーを要求することができます。 LicenseID が設定されていると、インスタンス起動時に指定したLicenseID のライセンスキーがライセンスサーバーよりロードされて有効化されます。 インスタンスは定期的に、期限切れのキーまたはアップグレードされた新しいキーがあるかどうかを確認します。 LicenseID の設定は、管理ポータルで行います。
管理ポータル: [システム管理] > [構成] > [追加設定] > [開始]:LicenseID
もしくは、構成ファイル(iris.cpf)で直接設定することも可能です 。CPF の [Startup] セクションの LicenseID を設定します。
ライセンスサーバはディレクトリからキーファイルを手動で再ロードしてライセンスを更新することも可能です(※3)。
※3 %SYS ネームスペースで do ReloadKeys^%SYS.LICENSE を実行します。 ライセンスの種類によっては、各インスタンスでインスタンスの再起動が求められる場合があります。
ライセンスサーバーを起動するインスタンスは、LicenseIDが異なる複数のキーをロードすることができます。ライセンスキーは、一意の LicenseID により各インスタンス上で識別されることになります。
コンテナを使用している場合は、すべてのコンテナライセンスを外部ディレクトリ(KeyDirectory プロパティで設定)に配置することもできます。そうすることで、最終的にコンテナ (および /mgr) 内にライセンスファイルを置かないようにすることが可能となります。
詳細は以下のドキュメントをご覧ください。InterSystems IRIS ライセンスの管理
【関連】複数インスタンスでライセンスを共有する場合に必要な設定
記事
Tomoko Furuzono · 2020年9月15日
Cachéデータベースのオブジェクトおよびリレーショナルデータモデルは、標準、ビットマップ 、ビットスライスの3種類のインデックスをサポートします。 これら3つのネイティブタイプに加えて、開発者は独自のカスタムタイプのインデックスを宣言し、バージョン2013.1以降の任意のクラスで使用できます。 たとえば、iFindテキストインデックスは、そのメカニズムを使用しています。
カスタムインデックスタイプは、挿入、更新、削除を実行するための%Library.FunctionalIndexインターフェースのメソッドを実装するクラスです。 新しいインデックスを宣言するときに、そのようなクラスをインデックスタイプとして指定できます。
例:
Property A As %String;Property B As %String;Index someind On (A,B) As CustomPackage.CustomIndex;
CustomPackage.CustomIndex クラスは、カスタムインデックスを実装するまさにそのクラスです。
たとえば、ハッカソン中に私たちのチーム(Andrey Rechitsky 、 Aleksander Pogrebnikov、そして私)が開発した空間データのクワッドツリーベースのインデックスの小さなプロトタイプを分析してみましょう。 (ハッカソンは、InterSystems Russia Innovation School Training(インターシステムズ・ロシア・イノベーション・スクール・トレーニング)で毎年開催されます。ハッカソンの主な閃きとなったTimur Safin氏に感謝いたします。)
この記事では、クワッドツリーとそれらの操作方法については説明しません。 代わりに、既存のクワッドツリーアルゴリズム実装のための%Library.FunctionalIndexインターフェースを実装する新しいクラスを作成する方法を検討してみましょう。 私たちのチームでは、このタスクはAndreyに割り当てられました。 Andeyは、次の2つの方法で SpatialIndex.Indexerクラスを作成しました。
Insert(x, y, id)
Delete(x, y, id)
SpatialIndex.Indexerの新しいインスタンスを作成するとき、インデックスデータを格納するためのグローバルノード名を定義する必要がありました。
InsertIndex、 UpdateIndex 、DeleteIndex 、 PurgeIndex メソッドを使って SpatialIndex.Indexのクラスを作成するだけで済みました。 最初の3つのメソッドは、変更する文字列のIdを受け入れ、インデックス付きの値は、対応するクラス内のインデックス宣言で定義されているのとまったく同じ順序です。 この例では、入力引数はpArg(1) — A and pArg(2) — Bです。
Spoiler
Class SpatialIndex.Index Extends %Library.FunctionalIndex [ System = 3 ]{ClassMethod InsertIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"set indexer = ##class(SpatialIndex.Indexer).%New($Name("_IndexGlobal_"))") $$$GENERATE($C(9)_"do indexer.Insert(pArg(1),pArg(2),pID)") } }ClassMethod UpdateIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"set indexer = ##class(SpatialIndex.Indexer).%New($Name("_IndexGlobal_"))") $$$GENERATE($C(9)_"do indexer.Delete(pArg(3),pArg(4),pID)") $$$GENERATE($C(9)_"do indexer.Insert(pArg(1),pArg(2),pID)") } }ClassMethod DeleteIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"set indexer = ##class(SpatialIndex.Indexer).%New($Name("_IndexGlobal_"))") $$$GENERATE($C(9)_"do indexer.Delete(pArg(1),pArg(2),pID)") } }ClassMethod PurgeIndex() [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"kill " _ IndexGlobal) } }ClassMethod IndexLocation(className As %String, indexName As %String) As %String { set storage = ##class(%Dictionary.ClassDefinition).%OpenId(className).Storages.GetAt(1).IndexLocation quit $Name(@storage@(indexName)) }}
IndexLocation は、インデックス値が保存されているグローバル内のノードの名前を返す補足メソッドです。
ここで、 SpatialIndex.Indexタイプのインデックスが使われているテストクラスを分析しましょう。
Class SpatialIndex.Test Extends %Persistent{Property Name As %String(MAXLEN = 300);Property Latitude As %String;Property Longitude As %String; Index coord On (Latitude, Longitude) As SpatialIndex.Index;}
SpatialIndex.Testクラスがコンパイルされている場合、システムは、SpatialIndex.Indexの各インデックスのINTコードで次のメソッドを生成します。
zcoordInsertIndex(pID,pArg...) public {set indexer = ##class(SpatialIndex.Indexer).%New($Name(^SpatialIndex.TestI("coord")))do indexer.Insert(pArg(1),pArg(2),pID) }zcoordPurgeIndex() public {kill ^SpatialIndex.TestI("coord") }zcoordSegmentInsert(pIndexBuffer,pID,pArg...) public {do ..coordInsertIndex(pID, pArg...) }zcoordUpdateIndex(pID,pArg...) public {set indexer = ##class(SpatialIndex.Indexer).%New($Name(^SpatialIndex.TestI("coord")))do indexer.Delete(pArg(3),pArg(4),pID)do indexer.Insert(pArg(1),pArg(2),pID) }
%SaveData 、%DeleteData 、%SQLInsert 、%SQLUpdate および%SQLDelete メソッドはインデックス内のメソッドを呼び出します。 たとえば、次のコードは%SaveDataメソッドの一部です。
if insert { ... do ..coordInsertIndex(id,i%Latitude,i%Longitude,"") ... } else { ... do ..coordUpdateIndex(id,i%Latitude,i%Longitude,zzc27v3,zzc27v2,"") ... }
実際の例は常に理論よりも優れているため、次のリポジトリからファイルをダウンロードできます: https://github.com/intersystems-ru/spatialindex/tree/no-web-interface 。 これは、Web UIのないブランチへのリンクです。 このコードを使用するには、以下を行います。
クラスをインポートする
RuCut.zipを解凍する
次の呼び出しを使用してデータをインポートする:
do $system.OBJ.LoadDir("c:\temp\spatialindex","ck")do ##class(SpatialIndex.Test).load("c:\temp\rucut.txt")
rucut.txtファイルには、ロシアの10万の都市や町に関するデータとその名前と座標が含まれています。 Loadメソッドは各ファイル文字列を読み取り、それをSpatialIndex.Testクラスの個別のインスタンスとして保存します。 Loadメソッドが実行されると、グローバル^ SpatialIndex.TestI( 「coord」)には、緯度と経度の座標を持つクワッドツリーが含まれます。
では、クエリを実行しましょう!
インデックスの構築は、一番おもしろい部分ではありません。 さまざまなクエリでインデックスを使用します。 Cachéには、非標準インデックスの標準構文があります。
SELECT *FROM SpatialIndex.TestWHERE %ID %FIND search_index(coord, 'window', 'minx=56,miny=56,maxx=57,maxy=57')
%ID %FIND search_index は構文の固定部分です。 次に、インデックス名coordがあります。引用符は必要ありません。 他のすべてのパラメーター( 'window'、 'minx = 56、miny = 56、maxx = 57、maxy = 57')は Findメソッドに渡されます。これもインデックスタイプのクラスで定義する必要があります(この例では、 SpatialIndex.Indexです )。
ClassMethod Find(queryType As %Binary, queryParams As %String) As %Library.Binary [ CodeMode = generator, ServerOnly = 1, SqlProc ]{ if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) set IndexGlobalQ = $$$QUOTE(IndexGlobal) $$$GENERATE($C(9)_"set result = ##class(SpatialIndex.SQLResult).%New()") $$$GENERATE($C(9)_"do result.PrepareFind($Name("_IndexGlobal_"), queryType, queryParams)") $$$GENERATE($C(9)_"quit result") }}
このコードサンプルでは、パラメーターは2つしかありません。 queryType と queryParams ですが、必要な数のパラメータを自由に追加できます。
SpatialIndex.Indexが使われているクラスをコンパイルする場合、 Find メソッドは、 z <IndexName> Findと呼ばれる補足メソッドを生成しますが 、これはSQLクエリの実行に使用されます。
zcoordFind(queryType,queryParams) public { s:'$isobject($g(%sqlcontext)) %sqlcontext=##class(%Library.ProcedureContext).%New()set result = ##class(SpatialIndex.SQLResult).%New()do result.PrepareFind($Name(^SpatialIndex.TestI("coord")), queryType, queryParams)quit result }
Findメソッドは、%SQL.AbstractFindインターフェースを実装するクラスのインスタンスを返す必要があります。 このインターフェイスのメソッド、 NextChunk および PreviousChunk は、それぞれ64,000ビットのチャンクでビット文字列を返します。 特定のIDのレコードが選択基準を満たし、対応するビット(chunk_number * 64000 + position_number_within_chunk)が1に設定されます。
Spoiler
Class SpatialIndex.SQLResult Extends %SQL.AbstractFind{Property ResultBits [ MultiDimensional, Private ];Method %OnNew() As %Status [ Private, ServerOnly = 1 ] { kill i%ResultBits kill qHandle quit $$$OK }Method PrepareFind(indexGlobal As %String, queryType As %String, queryParams As %Binary) As %Status { if queryType = "window" { for i = 1:1:4 { set item = $Piece(queryParams, ",", i) set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"kill " _ IndexGlobal) set param = $Piece(item, "=", 1) set value = $Piece(item, "=" ,2) set arg(param) = value } set qHandle("indexGlobal") = indexGlobal do ##class(SpatialIndex.QueryExecutor).InternalFindWindow(.qHandle,arg("minx"),arg("miny"),arg("maxx"),arg("maxy")) set id = "" for { set id = $O(qHandle("data", id),1,idd) quit:id="" set tChunk = (idd\64000)+1, tPos=(idd#64000)+1 set $BIT(i%ResultBits(tChunk),tPos) = 1 } } quit $$$OK } Method ContainsItem(pItem As %String) As %Boolean{ set tChunk = (pItem\64000)+1, tPos=(pItem#64000)+1 quit $bit($get(i%ResultBits(tChunk)),tPos)}Method GetChunk(pChunk As %Integer) As %Binary { quit $get(i%ResultBits(pChunk)) }Method NextChunk(ByRef pChunk As %Integer = "") As %Binary { set pChunk = $order(i%ResultBits(pChunk),1,tBits) quit:pChunk="" "" quit tBits }Method PreviousChunk(ByRef pChunk As %Integer = "") As %Binary { set pChunk = $order(i%ResultBits(pChunk),-1,tBits) quit:pChunk="" "" quit tBits }}
Class SpatialIndex.SQLResult Extends %SQL.AbstractFind{Property ResultBits [ MultiDimensional, Private ];Method %OnNew() As %Status [ Private, ServerOnly = 1 ]{ kill i%ResultBits kill qHandle quit $$$OK}Method PrepareFind(indexGlobal As %String, queryType As %String, queryParams As %Binary) As %Status{ if queryType = "window" { for i = 1:1:4 { set item = $Piece(queryParams, ",", i) set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"kill " _ IndexGlobal) set param = $Piece(item, "=", 1) set value = $Piece(item, "=" ,2) set arg(param) = value } set qHandle("indexGlobal") = indexGlobal do ##class(SpatialIndex.QueryExecutor).InternalFindWindow(.qHandle,arg("minx"),arg("miny"),arg("maxx"),arg("maxy")) set id = "" for { set id = $O(qHandle("data", id),1,idd) quit:id="" set tChunk = (idd\64000)+1, tPos=(idd#64000)+1 set $BIT(i%ResultBits(tChunk),tPos) = 1 } } quit $$$OK }Method ContainsItem(pItem As %String) As %Boolean{ set tChunk = (pItem\64000)+1, tPos=(pItem#64000)+1 quit $bit($get(i%ResultBits(tChunk)),tPos)}Method GetChunk(pChunk As %Integer) As %Binary{ quit $get(i%ResultBits(pChunk))}Method NextChunk(ByRef pChunk As %Integer = "") As %Binary{ set pChunk = $order(i%ResultBits(pChunk),1,tBits) quit:pChunk="" "" quit tBits}Method PreviousChunk(ByRef pChunk As %Integer = "") As %Binary{ set pChunk = $order(i%ResultBits(pChunk),-1,tBits) quit:pChunk="" "" quit tBits}}
上記のコードサンプルに示すように、 SpatialIndex.QueryExecutorクラスの InternalFindWindow メソッドは、指定された長方形内にあるポイントを検索します。 次に、一致する行のIDがFORループのビットセットに書き込まれます。
私たちのハッカソンプロジェクトでは、Andreyは楕円の検索機能も実装しました。
SELECT *FROM SpatialIndex.TestWHERE %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2')and name %StartsWith 'Z'
%FINDに関する補足情報
%FIND 述語には追加パラメーター SIZEがあり、これは、SQLエンジンが一致する行の数を推定するのに役立ちます。 SQLエンジンは、このパラメーターに基づいて%FIND述語で指定されたインデックスを使用するかどうかを決定します。
たとえば、次のインデックスをSpatialIndex.Testクラスに追加してみましょう。
Index ByName on Name;
次に、クラスを再コンパイルして、このインデックスを作成します。
write ##class(SpatialIndex.Test).%BuildIndices($LB("ByName"))
最後に、TuneTableを実行します。
do $system.SQL.TuneTable("SpatialIndex.Test", 1)
クエリプランは次のとおりです。
SELECT *FROM SpatialIndex.TestWHERE name %startswith 'za'and %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2') size ((10))
coordインデックスは数行を返す可能性が高いため、SQLエンジンは Nameプロパティのインデックスを使用しません。
次のクエリには別のプランがあります。
SELECT *FROM SpatialIndex.TestWHERE name %startswith 'za'and %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2') size ((1000))
SQLエンジンは、両方のインデックスを使用してこのクエリを実行します。
そして最後の例として、coordインデックスはおそらく約10万行を返すため、ほとんど使用できないので、 Nameフィールドのインデックスのみを使用するリクエストを作成しましょう。
SELECT *FROM SpatialIndex.TestWHERE name %startswith 'za'and %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2') size ((100000))
この記事を読んでくださった方、または少なくとも最後までスクロールしてくださったすべての方に感謝いたします。
参考:
%FIND
search_index
%SQL.AbstractFind
%Library.FunctionalIndex
記事
Toshihiko Minamoto · 2021年7月1日
クラスのコンパイル時に、定義済みのプロパティ、クエリ、またはインデックスごとに対応する複数のメソッドが自動的に生成されます。 これらのメソッドは非常に便利です。 この記事では、その一部について説明します。
プロパティ
「Property」というプロパティを定義したとしましょう。 次のメソッドが自動的に利用できるようになります(太字のPropertyは変化する部分でプロパティ名になります)。
ClassMethod PropertyGetStored(id)
このメソッドは、データ型プロパティの場合は論理値を返し、オブジェクトプロパティの場合はIDを返します。 これはクラスのデータグローバルへの、ラップされたグローバル参照であり、特異なプロパティ値を取得する上でもっとも手早い方法です。 このメソッドは、保存されたプロパティでのみ利用できます。
Method PropertyGet()
プロパティgetterである。 再定義可能です。
Method PropertySet(val) As %Status
プロパティsetterである。 再定義可能です。
オブジェクトプロパティ
オブジェクトプロパティの場合、IDとOIDアクセスに関連するいくつかの追加メソッドを利用できるようになります。
Method PropertySetObjectId(id)
このメソッドはIDでプロパティ値を設定するため、オブジェクトを開いてプロパティ値として設定する必要はありません。
Method PropertyGetObjectId()
このメソッドは、プロパティ値のIDを返します。
Method PropertySetObject(oid)
このメソッドは、OIDでプロパティ値を設定します。
Method PropertyGetObject()
このメソッドは、プロパティ値のOIDを返します。
データ型プロパティ
データ型プロパティの場合、異なる形式間で変換するためのほかのメソッドがいくつか利用できるようになります。
ClassMethod PropertyDisplayToLogical(val)
ClassMethod PropertyLogicalToDisplay(val)
ClassMethod PropertyOdbcToLogical(val)
ClassMethod PropertyLogicalToOdbc(val)
ClassMethod PropertyXSDToLogical(val)
ClassMethod PropertyLogicalToXSD(val)
ClassMethod PropertyIsValid(val) As %Status
valが有効なプロパティ値であるかどうかをチェックします。
ClassMethod PropertyNormalize(val)
正規化された論理値を返します。
**注意事項**
* 関係はプロパティであり、これらのメソッドで取得/設定できます。
入力値(val)は、形式変換メソッドを除き、必ず論理値です。
インデックス
「Index」というインデックスの場合、次のメソッドを自動的に利用できるようになります。
ClassMethod IndexExists(val) As %Boolean
このvalを持つオブジェクトが存在するかどうかに応じて、1または0を返します。valはインデックス付きのプロパティの論理値です。
一意のインデックス
一意のインデックスの場合、追加メソッドを利用できるようになります。
ClassMethod IndexExists(val, Output id) As %Boolean
このvalを持つオブジェクトが存在するかどうかに応じて、1または0を返します。valはインデックス付きのプロパティの論理値です。 また、オブジェクトIDがある場合は、第2引数として返します。
ClassMethod IndexDelete(val, concurrency = -1) As %Status
インデックスの値がvalのエントリを削除します。
ClassMethod IndexOpen(val, concurrency, sc As %Status)
インデックスの値がvalの既存のオブジェクトを返します。
**注意事項:**
a)インデックスは複数のプロパティに基づいている可能性があるため、メソッドシグネチャは、入力として複数の値を持つように変更されます。例として、次のインデックスを見てみましょう。
Index MyIndex On (Prop1, Prop2);
この場合、IndexExistsメソッドには次のシグネチャがあります。
ClassMethod IndexExists(val1, val2) As %Boolean
val1はProp1値に対応し、val2はProp2値に対応します。 その他のメソッドも同じロジックに従います。
b)Cachéは、IDフィールド(RowID)にインデックスを作成するIDKEYインデックスを生成します。 ユーザーが再定義することが可能で、複数のプロパティを含むこともできます。 たとえば、クラスにプロパティが定義されているかをチェックするには、次を実行します。
Write ##class(%Dictionary.PropertyDefinition).IDKEYExists(class, property)
c)すべてのインデックスメソッドは、論理値をチェックします。
d) ドキュメント
クエリ
「Query」というクエリの場合(単純なSQLクエリまたはカスタムクラスクエリのどちらでも構いません。これに関する[記事](https://community.intersystems.com/post/class-queries-intersystems-cache%CC%81)をご覧ください)、Funcメソッドが生成されます。
ClassMethod QueryFunc(Arg1, Arg2) As %SQL.StatementResult
このメソッドは、クエリの反復処理に使用される%SQL.StatementResultを返します。 たとえば、SamplesネームスペースのSample.Personクラスには、1つのパラメーターを受け入れるByNameクエリがあり、 次のコードを使って、オブジェクトコンテキストから呼び出すことができます。
Set ResultSet=##class(Sample.Person).ByNameFunc("A")
While ResultSet.%Next() { Write ResultSet.Name,! }
また、[GitHubには、これらのメソッドを実演するデモクラス](https://github.com/eduard93/Utils/blob/master/Utils/GeneratedMethods.cls.xml)があります。
お知らせ
Shintaro Kaminaka · 2021年7月8日
開発者の皆さん、こんにちは!
HL7v2メッセージをFHIR(Fast Healthcare Interoperability Resources)に変換するニーズがあり、その変換プロセスが複雑で分かりにくいと感じたことはありませんか?インターシステムズは、HL7v2メッセージをFHIR(Fast Healthcare Interoperability Resources)に変換するプロセスを簡単にする、HealthShareメッセージ変換サービス(HealthShare Message Transformation Services)と呼ばれる新しいクラウドベースのSaaSサービスを展開しています。 この新しいサービスの早期アクセス・プレビュー・プログラムを発表できることを嬉しく思います。 必要なのは、無料のAWSアカウントと、HL7v2メッセージをドロップするためのS3バケット、そしてFHIR出力を得るための別のS3バケットだけです。
この機能の簡単なデモをご覧ください。
インターシステムズ社のラーニングサイトでは、AWS の無料アカウントとインターシステムズ社のクラウド・ポータルの無料アカウントにサインアップして、変換サービスの強力な機能を利用するための簡単な[ステップ・バイ・ステップ・ガイド](https://learning.intersystems.com/course/view.php?name=HMTSExerciseS3)を提供しています。 完全なドキュメントは、[インターシステムズ社のドキュメント](http://www.intersystems.com/hmts)でご覧いただけます。
このサービスは、7月下旬に正式に開始される予定です。プレビューが終了しても、最初の100万件の変換を無料で利用することができます。
### インターシステムズ社のこの新しいオファーの詳細は以下の内容です:
HealthShareメッセージ変換サービスの紹介。
医療IT業界では、FHIR® (Fast Healthcare Interoperability Resources)が、ヘルスケアデータを交換するための最新のデータ標準として採用されています。 オンデマンドのHealthShareメッセージ変換サービスを利用することで、医療機関、保険会社、製薬会社は、既存のデータフォーマットをFHIR規格に変換し、データから最大限の価値を引き出すことができます。 インターシステムズは、最新のFHIR規格だけでなく、HL7v2、X12、CDA、C-CDA、DICOMを含むすべての主要なヘルスケア規格を実装しており、ヘルスケアの相互運用性におけるリーダー的存在です。
HealthShareメッセージ変換サービスは、これらの以前の標準からFHIR R4へのメッセージ変換を簡単に行えるように設計されており、初期リリースではHL7 v2メッセージのFHIR R4への変換をサポートしています。 FHIRメッセージは、AWS S3バケットまたはAmazon HealthLake (Preview)に送ることができ、将来的には他のFHIRリポジトリオプションも追加される予定です。
HealthShareメッセージ変換サービスは、HL7v2メッセージをFHIRに変換することを簡単にします。 変換ロジックを気にする必要がないので、メッセージ変換の複雑な作業はインターシステムズに任せて、優れたヘルスケア・アプリケーションの構築に集中することができます。このサービスが提供するのは
- AWS上での簡単なプロビジョニングと起動
- インバウンドS3バケットのHL7v2メッセージのチェック
- HL7コンテンツのバリデーション
- FHIR R4へのメッセージ変換
- 変換されたメッセージを、アウトバウンドの S3 バケット、[InterSystems FHIR Accelerator (Preview) サービス](https://community.intersystems.com/post/new-video-what-fhir-accelerator-service)、または [Amazon HealthLake (Preview)](https://aws.amazon.com/jp/healthlake/) リポジトリにルーティングする
- 変換パイプラインのステータスと統計情報のモニタリング
さらに、このサービスはAWSのインフラ上に構築されているため、ISO 27001:2013およびHIPAAをサポートするHITRUST認証を取得しています。インターシステムズは、このサービスの運用、監視、バックアップを管理しています。
そして何より、このサービスが商業的に開始されると、最初の100万回の変換が無料で提供され、その後は使用した分だけを支払うことになり、変換されたメッセージあたりのコストは非常に低く抑えられます。 このサービスを利用するための長期契約はなく、いつでも解約できます。
皆様からのフィードバックをお待ちしております。 この記事のコメント欄にご意見をお寄せいただくか、 HMTS@InterSystems.com まで直接お問い合わせください。
HL7 ® と FHIR ® は Health Level Seven International の登録商標であり、これらの商標の使用は HL7 による推奨を意味するものではありません。FHIR商標の使用は、HL7によるHealthShare Message Transformation ServicesまたはHealthShare Message Transformation Servicesの推奨を意味するものではありません。
記事
Megumi Kakechi · 2021年8月25日
これは InterSystems FAQ サイトの記事です。
リレーションシップが設定されており 1対n の n が多量の場合、そのリレーションシップの順次処理などで大量のメモリ消費となるケースがあります。
プログラムの中で多側オブジェクトを参照し内部的にスウィズル処理した後には、そのOREFを含む変数の解放(削除、他の値の設定など)だけでは、その多側オブジェクトとリレーションシップオブジェクトが解放されないことが原因です。
それらを完全にメモリから解放するためには、OREF変数の解放とRelationshipオブジェクトの%UnSwizzleAt<%Library.RelationshipObject >メソッドの実行による明示的なメモリ解放が必要です。
- 使用例 -
Do {
Set employee = company.Employees.GetNext(.key)
If (employee '= "") {
Write employee.Name,!
// remove employee from memory
Do company.Employees.%UnSwizzleAt(key)
}
} While (key '= "")
多側オブジェクトが数個しかない場合は特に問題はありませんが、紐づいている数が大量な場合に、ループ文で連続アクセスした場合、その分大量にメモリにオブジェクトが展開され続けることになり、メモリー圧迫要因のひとつになります。
その様な状況を避けるために明示的な開放処理を入れる必要があります。
Kakechi さん
貴重な情報を公開いただきありがとうございます!Relationshipオブジェクトの理解が浅く申し訳ないのですが、一点だけ教えてください。
今回の場合、Relationshipオブジェクトをループで回した際にのみ事象が発生するという理解なのですが、Interoperabilityで利用可能なRecordMap利用時に生成される、Batchクラス内のRecordsプロパティも上記に該当するのでしょうか?
今私が携わるシステムではRecordMapを多用しており、数万件のデータをこの仕組みを用いて取り込みをおこなっているため、上記の事象が発生するのではないかと懸念しております。 Ohata様
こんにちは、ご質問ありがとうございます。Interoperabilityの場合、RecordMapのBatchサービスでは内部で対応しているため明示的に%UnSwizzleAt() する必要はありません。また、オペレーションについても UnSwizzleRecordsプロパティ(デフォルトはTrue:チェックあり)により、処理後にメモリから解放されるため、こちらについても明示的に %UnSwizzleAt() する必要はありません。UnSwizzleRecordsプロパティを 0 (False:チェックなし) に指定し大量のデータを処理する場合には、正しく開放処理を行わないと <STORE> エラーになる場合がありますのでご注意ください。
Kakechi さん
回答いただきありがとうございます!BSもBOも標準のまま利用するのであれば開放処理がシステム内で行われると理解しました。
私が作成しているシステムでは、BS/BP/BOをそれぞれ標準を継承してカスタマイズしたり、Batch.RecordをObjectScript内でループさせて利用するような処理もあるのですが、この場合には%UnSwizzleAt()が必要との理解でよろしいでしょうか?
例)BSでファイル検知を行い、RecordMapの仕組みを利用してファイルからBatchクラスを生成。↓BOに連携し、BatchクラスをObjectScript内のクラスに譲与。↓取得したBatch.Recordの内容をループで回しながら処理を実行。
このループの中では%UnSwizzleAt()が必要?もしくはInteroperabilityのプロセスで実行する処理の中では、BatchクラスのRecordの内容は勝手に開放される? Ohata様
継承しているBOのクラスが、EnsLib.RecordMap.Operation.Batch* のクラスである場合は、 UnSwizzleRecords プロパティをオーバーライドして変更していない限り、デフォルトの True(=1) となるため、明示的に %UnSwizzleAt() する必要はありません。
「BatchクラスをObjectScript内のクラスに譲与」というのが、例えば %Persistent などのユーザ作成クラスに対してである場合は、別途 %UnSwizzleAt() していただく必要があります。
どのような構成にしているかによって変わってくるところはありますので、詳細についてのご相談があれば弊社サポートセンター(jpnsup@intersystems.com)までお気軽にお問い合わせください。 Kakechi さん
回答いただきありがとうございます。内容理解できました!私のケースでは対応必要そうです。
相談に乗っていただきありがとうございました! Kakechi さん
回答いただきありがとうございます。内容理解できました!私のケースでは対応必要そうです。
相談に乗っていただきありがとうございました!
記事
Toshihiko Minamoto · 2022年3月8日
InterSystemsを使用してExcelファイルを生成する方法はたくさんあります。ZENレポートやIRISレポート(Logiレポートまたは正式にはJReportsと呼ばれるレポート)のほか、サードパーティのJavaライブラリを使用するなど、可能性はほぼ無限です。
_しかし、Caché ObjectScriptだけで単純なスプレッドシートを作成したい場合はどうでしょうか。 (サードパーティアプリケーションを使用せずに、です)_
私の場合、大量の生データを含むレポート(金融関係の人たちが好むレポート)を生成する必要がありますが、私のZEN/IRISでは対応できません。私が呼ぶところの「ゼロバイトファイル」が生成され、基本的にJavaのメモリ不足となり、レポーティングサーバーに大きな負荷を生じてしまいます。
これは、Office Open XML(OOXML)を使って実現できます。 Office Open XML形式は、多数のXMLファイルで構成されるZIPパケージです。 つまり基本的には、これらのXMLファイルを生成してZIP圧縮し、.xslxに名前を変更すればよいのです。 それくらい単純です。
ファイルは、Open Packaging Conventionsという単純な命名規則に従っています。 パーツのコンテンツタイプを宣言し、消費するアプリケーションにどこから開始するかを指示する必要があります。
単純なスプレッドシートを作成するには、少なくとも5つのファイルが必要です。
* workbook.xml
* worksheet.xml
* [Content_Types].xml
* styles.xml
* _rels
* .rels
* workbook.xml.rels
**workbook.xml**
workbookは、様々なワークシートをまとめるコンテナーです。 workbookでは、スタイルパーツ、共有文字列テーブル、およびスプレッドシートファイル全体に適用するその他の情報を参照できます。
ClassMethod GenerateWorkbookXML(){
set status =$$$OK
set xmlfile = tempDirectoryPath_"workbook.xml"
try{
set stream = ##class(%Stream.FileCharacter).%New()
set sc=stream.LinkToFile(xmlfile)
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine(" ")
do stream.WriteLine(" ")
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
return status
}
**_rels/workbook.xml.rels**
workbook.xmlパーツからの参照に一致するように、rId1というIDを持つリレーションを作成する必要があります。
ClassMethod CreateRelsXML(){
set status =$$$OK
set isunix=$zcvt($p($zv," ",3,$l($p($zv," (")," ")),"U")["UNIX"
if isunix {
set ext="/"
}else{
set ext="\"
}
set xmlfile = fileDirectory_"_rels"_ext_"workbook.xml.rels"
set stream = ##class(%Stream.FileCharacter).%New()
set sc=stream.LinkToFile(xmlfile)
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
try{
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
set xmlfile = fileDirectory_"_rels"_ext_".rels"
set stream = ##class(%Stream.FileCharacter).%New()
set sc=stream.LinkToFile(xmlfile)
do stream.WriteLine("
")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
try{
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
return status
}
**[Content_Types].xml**
静的ファイル(現時点では、ワークシートの数に応じた動的ファイル)はworkbookのワークシートとスタイルを紐づけます。Office Open XMLファイルごとに、ZIPパッケージ使用されるコンテンツタイプを宣言する必要があります。 これは、[Content_Types].xmlファイルで行います。
ClassMethod GenerateConntentTypesXML(){
set status =$$$OK
set xmlfile = tempDirectoryPath_"[Content_Types].xml"
set stream = ##class(%Stream.FileCharacter).%New()
set sc=stream.LinkToFile(xmlfile)
try{
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
return status
}
**styles.xml**
すべてのフォーマットがこのファイルに含まれます。現時点では、静的スタイルが追加されています(より動的なworkbook固有のスタイルに変換する予定です)。
Excelスタイル
ID
スタイル
Excelフォーマット
1
デフォルト
テキスト
2
#;[Red]-#
数値
3
#.##;[Red]-#.##
数値
4
yyyy/mm/dd
日付
5
hh:mm
日付
6
ヘッダーと中央揃え
テキスト
7
ヘッダー2左寄せ
テキスト
8
良い(緑ハイライト)
全般
9
悪い(赤ハイライト)
全般
10
どちらでもない(オレンジハイライト)
全般
11
yyyy/mm/dd hh:mm
日付
ClassMethod CreateStylesXML(){
set status =$$$OK
set xmlfile = tempDirectoryPath_"styles.xml"
try{
set stream = ##class(%Stream.FileCharacter).%New()
set sc=stream.LinkToFile(xmlfile)
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine(" ")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
return status
}
**worksheet.xml**
このファイルに日付が含まれます。 シートの最初の行は列のタイトルです。 次の行には、最初の列にのみデータが含まれます。
デフォルトで列が自動調整されない場合は、ここで各列の列幅を定義します。
サンプルworksheet.xml
Name
Amount
Jhon Smith
1000.74
Tracy A
6001.74
サンプルExcel
.png)
_worksheet内の数式は、関数 タグ_を使って表現できます。
B2*0.08
B2+C2
そして最後にそれらをzip圧縮し、名前を.xlsxに変更します(unix zipを使用)。
set cmd ="cd "_fileDirectory_" && find . -type f | xargs zip .."_ext_xlsxFile
#### Excelドキュメントを生成します。
以下のサンプルコードは、Excelドキュメントを生成します。
set file = "/temp/test.xlsx"
set excelObj = ##class(XLSX.writer).%New(file)
do excelObj.SetWorksheetName("test1")
set status = excelObj.BeginWorksheet()
set row = 0
set row = row+1
;----------- excelObj.Cells(rowNumber,columnNumber,style,content)
set status = excelObj.Cells(row,1,1,"Header1")
set row = row+1
set status = excelObj.Cells(row,1,2,"Content 1")
set status = excelObj.EndWorksheet()
W !,excelObj.fileName
ExcelのWriterクラスは、こちらの[xlsx.writer.xml.zip](/sites/default/files/inline/files/xlsx.writer.xml.zip)にあります。
記事
Toshihiko Minamoto · 2023年3月10日
糖尿病は、医学会でよく知られるいくつかのパラメーターから発見することが可能です。 この測定により、医学界とコンピューター化されたシステム(特に AI)を支援すべく、(米)国立糖尿病・消化器・腎疾病研究所(NIDDK)は、糖尿病の検出/予測における ML アルゴリズムをトレーニングするための非常に便利なデータセットを公開しました。 このデータセットは、ML の最大級のデータリポジトリとして最もよく知られている Kaggle に公開されています: https://www.kaggle.com/datasets/mathchi/diabetes-data-set。
糖尿病データセットには、以下のメタデータ情報が含まれています(出典: https://www.kaggle.com/datasets/mathchi/diabetes-data-set):
* Pregnancies: 妊娠回数
* Glucose: 経口ブドウ糖負荷試験における 2 時間後の血漿グルコース濃度
* BloodPressure: 拡張期血圧(mm Hg)
* SkinThickness: 上腕三頭筋皮下脂肪厚(mm)
* Insulin: 2 時間血清インスリン(mu U/ml)
* BMI: ボディマス指数(体重 kg/(身長 m)^2)
* DiabetesPedigreeFunction: 糖尿病血統要因(親族の糖尿病歴、およびこれらの親族と患者の遺伝的関係に関するいくつかのデータが提供されました。 この遺伝的影響の測定により、真性糖尿病の発症に伴う遺伝的リスクについての考えが得られました - 出典: https://machinelearningmastery.com/case-study-predicting-the-onset-of-diabetes-within-five-years-part-1-of-3/)
* Age: 年齢
* Outcome: クラス変数(0 または 1)
#### インスタンス数: 768
#### 属性数: 8 + class
#### 各属性について:(すべて数値)
1. 妊娠回数
2. 経口ブドウ糖負荷試験における 2 時間後の血漿グルコース濃度
3. 拡張期血圧(mm Hg)
4. 上腕三頭筋皮下脂肪厚(mm)
5. 2 時間血清インスリン(mu U/ml)
6. ボディマス指数(体重 kg/(身長 m)^2)
7. 糖尿病血統要因
8. 年齢
9. クラス変数(0 または 1)
#### 属性値の欠落: あり
#### クラス分布:(クラス値 1 は「糖尿病の検査で陽性」として解釈)
## Kaggle から糖尿病データを取得する
Kaggle の糖尿病データは、Health-Dataset アプリケーション(https://openexchange.intersystems.com/package/Health-Dataset)を使って IRIS テーブルに読み込めます。 これを行うには、module.xml プロジェクトから依存関係(Health Dataset 用の ModuleReference)を設定します。
Health Dataset アプリケーションリファレンスを含む Module.xml
<?
xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="predict-diseases.ZPM">
<Module>
<Name>predict-diseases</Name>
<Version>1.0.0</Version>
<Packaging>module</Packaging>
<SourcesRoot>src/iris</SourcesRoot>
<Resource Name="dc.predict.disease.PKG"/>
<Dependencies>
<ModuleReference>
<Name>swagger-ui</Name>
<Version>1.*.*</Version>
</ModuleReference>
<ModuleReference>
<Name>dataset-health</Name>
<Version>*</Version>
</ModuleReference>
</Dependencies>
<CSPApplication
Url="/predict-diseases"
DispatchClass="dc.predict.disease.PredictDiseaseRESTApp"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="1"
UnauthenticatedEnabled="1"
Recurse="1"
UseCookies="2"
CookiePath="/predict-diseases"
/>
<CSPApplication
CookiePath="/disease-predictor/"
DefaultTimeout="900"
SourcePath="/src/csp"
DeployPath="${cspdir}/csp/${namespace}/"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="0"
Recurse="1"
ServeFiles="1"
ServeFilesTimeout="3600"
UnauthenticatedEnabled="1"
Url="/disease-predictor"
UseSessionCookie="2"
/>
</Module>
</Document>
</Export>
## 糖尿病を予測するための Web フロントエンドとバックエンドのアプリケーション
Open Exchange アプリのリンク(https://openexchange.intersystems.com/package/Disease-Predictor)に移動し、以下の手順に従います。
リポジトリを任意のローカルディレクトリに Git pull します。
$ git clone https://github.com/yurimarx/predict-diseases.git
このディレクトリで Docker ターミナルを開き、以下を実行します。
$ docker-compose build
IRIS コンテナを実行します。
$ docker-compose up -d
AI モデルをトレーニングするための Execute Query into Management Portal(http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER)に移動します。
トレーニングに使用するビューを作成します。
CREATE VIEW DiabetesTrain AS SELECT Outcome, age, bloodpressure, bmi, diabetespedigree, glucose, insulin, pregnancies, skinthickness FROM dc_data_health.Diabetes
ビューを使用して AI モデルを作成します。
CREATE MODEL DiabetesModel PREDICTING (Outcome) FROM DiabetesTrain
モデルをトレーニングします。
TRAIN MODEL DiabetesModel
http://localhost:52773/disease-predictor/index.html に移動し、Disease Predictor フロントエンドを使用して、以下のように疾患を予測します。
## 背後の処理
### 糖尿病を予測するためのバックエンドのクラスメソッド
InterSystems IRIS では、前に作成されたモデルを使って予測するSELECT を実行することができます。
糖尿病を予測するためのバックエンドのクラスメソッド
/// Predict Diabetes
ClassMethod PredictDiabetes() As %Status
{
Try {
Set data = {}.%FromJSON(%request.Content)
Set qry = "SELECT PREDICT(DiabetesModel) As PredictedDiabetes, "
_"age, bloodpressure, bmi, diabetespedigree, glucose, insulin, "
_"pregnancies, skinthickness "
_"FROM (SELECT "_data.age_" AS age, "
_data.bloodpressure_" As bloodpressure, "
_data.bmi_" AS bmi, "
_data.diabetespedigree_" AS diabetespedigree, "
_data.glucose_" As glucose, "
_data.insulin_" AS insulin, "
_data.pregnancies_" As pregnancies, "
_data.skinthickness_" AS skinthickness)"
Set tStatement = ##class(%SQL.Statement).%New()
Set qStatus = tStatement.%Prepare(qry)
If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
Set rset = tStatement.%Execute()
Do rset.%Next()
Set Response = {}
Set Response.PredictedDiabetes = rset.PredictedDiabetes
Set Response.age = rset.age
Set Response.bloodpressure = rset.bloodpressure
Set Response.bmi = rset.bmi
Set Response.diabetespedigree = rset.diabetespedigree
Set Response.glucose = rset.glucose
Set Response.insulin = rset.insulin
Set Response.pregnancies = rset.pregnancies
Set Response.skinthickness = rset.skinthickness
Set %response.Status = 200
Set %response.Headers("Access-Control-Allow-Origin")="*"
Write Response.%ToJSON()
Return 1
} Catch err {
write !, "Error name: ", ?20, err.Name,
!, "Error code: ", ?20, err.Code,
!, "Error location: ", ?20, err.Location,
!, "Additional data: ", ?20, err.Data, !
Return
}
}
これで、どの Web アプリケーションもこの予測を使用して、結果を表示できるようになりました。 predict-diseases アプリケーションのソースコードは、frontend フォルダをご覧ください。
記事
Mihoko Iijima · 2023年6月6日
これは InterSystems FAQ サイトの記事です。
ターミナルでルーチンやクラスのコンパイルを行う際、コンパイル結果が画面に表示されるのでエラーが発生した場合でも確認しやすいですが、一括コンパイルの場合は、大量のコンパイル結果の中にエラー情報が含まれてしまうためエラー情報だけを取得したい場合には少し工夫が必要です。
以下、ルーチン/クラスの一括コンパイル時の結果からエラー情報を取得する方法をご紹介します。
ルーチンの場合
ネームスペースにあるルーチンをターミナルで一括コンパイルするには、%Library.Routine クラスの CompileAll() メソッドを使用します。
以下実行例は、USERネームスペースにあるルーチンを一括コンパイルした結果です。TestRoutine1でコンパイルエラーが発生しています。
USER>do ##class(%Routine).CompileAll()
Compiling in namespace USER at 11:50:47
Routine1.MAC TestRoutine1.MAC
TestRoutine1.MAC - Errors compiling routine
TestRoutine1.INT(3) : Error 8: <NOLINE> : ' do sub3()'
2 routines compiled with 1 errors at 11:50:47 in 00:00:00.030
USER>
大量にルーチンがある場合、出力結果が流れて画面から消えてしまうため、カレントデバイスに出力されている内容をファイル保存し、保存したファイルの中からエラー情報を取得するようにします。
1) コンパイル結果をファイルに保存する
CompileAll() メソッドの第2引数にファイル名をフルパスで指定します。
このメソッドは、第2引数に指定したデバイスがオープンされている場合、そのデバイスにログを書き込みます。
そのため、一旦ファイルを新規書き込みモードでオープンします(OPENコマンドを使用します)。
// ログファイルのフルパスを変数に設定します
set log="C:\temp\result.log"
//ファイルを新規書き込みモードでオープンします
open log:"NWS"
//第2引数にログ出力するファイル名を指定し、一括コンパイルを実行します。
do ##class(%Routine).CompileAll(,log)
//ファイルをクローズします。
close log
2) 1)で作成したファイルからエラー情報を取得する。
ファイルをオープンし、エラー発生時の文字列「Errors compiling routine」が含まれている場合にルーチン名を取り出して変数に設定する例をご紹介します。
//文字列ファイル操作用のインスタンスを生成
set file=##class(%Stream.FileCharacter).%New()
//ファイルとのリンク付け
do file.LinkToFile("c:\temp\result.log")
//ファイルの終わりを検出するまで読み取りながら情報抽出
//ファイルの終わりが検出されるとAtEndプロパティに1が設定される
while file.AtEnd=0 {
set reco=file.ReadLine()
//読み取った行にエラー字の文字列が含まれる場合
if reco["Errors compiling routine" {
//スペース区切りの1番目にルーチン名が含まれているので取得
set rtn=$piece(reco," ",1)
}
//ルーチン名が空だったら次のループへ移動
if $get(rtn)="" {
continue
}
//ローカル変数の添え字にルーチン名をセット
set val(rtn)=""
}
zwrite val
ターミナルで実行する場合
set file=##class(%Stream.FileCharacter).%New()
do file.LinkToFile("c:\temp\result.log")
while file.AtEnd=0 { set reco=file.ReadLine() if reco["Errors compiling routine" { set rtn=$piece(reco," ",1)} if $get(rtn)="" { continue } set val(rtn)=""}
zwrite val
クラスの場合
ネームスペースにあるクラスをターミナルで一括コンパイルするには、%SYSTEM.OBJクラスのCompileAll()メソッドを使用します。
以下実行例は、USERネームスペースにあるクラスを一括コンパイルした結果で、Dummy.ErrorClass1でコンパイルエラーが発生しています。
USER>do $system.OBJ.CompileAll("ck")
04/20/2023 12:17:49 に修飾子 'ck' でコンパイルを開始しました。
エラー #5373: クラス 'Dummy.ErrorClass1:property:XYZ' が使用するクラス '%Library.Strig' は、存在しません
Skip class Dummy.ErrorClass1
, 72 クラスをコンパイル中
クラスのコンパイル中 CookBook.Class1
クラスのコンパイル中 A.b3
クラスのコンパイル中 A.B1
《省略》
ルーチンのコンパイル中 F4.GoldMember.1
クラスのコンパイル中 MyApp.MyService.Test
ルーチンのコンパイル中 MyApp.MyService.Test.1
1.091s のコンパイル中に 1 エラーを検出しました。
第2引数を参照渡しで指定するとエラー情報が配列変数として設定されます。
USER>do $system.OBJ.CompileAll("ck",.log)
USER>zwrite log
log=1
log(1)="エラー #5373: クラス 'Dummy.ErrorClass1:property:XYZ' が使用するクラス '%Library.Strin' は、存在しません"
log(1,"caller")="findalldependencyclasses+149^%occDepend"
log(1,"code")=5373
log(1,"dcode")=5373
log(1,"domain")="%ObjectErrors"
log(1,"namespace")="USER"
log(1,"param")=2
log(1,"param",1)="%Library.Strin"
log(1,"param",2)="Dummy.ErrorClass1:property:XYZ"
log(1,"stack")=$lb("e^findalldependencyclasses+149^%occDepend^2","e^findalldependencyclasses+58^%occDepend^1","e^findalldependencyclasses+8^%occDepend^1","e^IncludeClasses+44^%occCompile^1","e^CompileList+59^%occCompile^1","e^CompileList+23^%apiOBJ^1","e^CompileAll+15^%apiOBJ^1","e^zCompileAll+1^%SYSTEM.OBJ.1^1","d^^^0")
複数エラーが発生した場合は、ログ用に指定した変数直下にエラー個数が設定されます。
エラーメッセージだけを取り出す方法は以下の通りです。
for i=1:1:log { write log(i),! }
マッピングされているクラス・ルーチンのコンパイル方法については、以下開発者コミュニティの記事をご参照ください。
マッピングされたクラス・ルーチンをコンパイルする方法
記事
Hiroshi Sato · 2020年8月18日
IRISでは.Net Bindingは非推奨機能となりました。
.Net Bindingを使ったアプリケーションは、IRISで提供されている.Net Native APIを利用して書き換えることができます。
ここでは、実際に書き換えをおこなったサンプルコードを示しながら、具体的な方法を説明していきます。
CacheDirect(VisM)エミュレーター
OpenExchangeに登録しているVisMエミュレーターは、元々Cachéの.Net Bindingを使用して作成されました。
それをIRISの標準機能で動作可能にするために、.Net Native APIを使用して書き換えをおこないました。
以下にどのように書き換えを行ったかを順を追って説明します。
参照の変更
まず以前の参照を削除します。
Visual Studioのソリューションエクスプローラーの所で参照をクリックします。
表示されるInterSystems.Data.CacheClientを削除します。(右クリックして削除を選ぶ)
次にプロジェクトメニューから参照の追加をクリックして、以下の2つのファイルを選択します。(プロジェクトの.Net Frameworkバージョンに合わせて、それに対応するファイルを選択する以下の例は、v4.5を選択)
c:\InterSystems\IRIS\dev\dotnet\bin\v4.5InterSystems.Data.IRISClient.dll
using句の変更
先頭のusing句の変更が必要になります。
using InterSystems.Data.CacheClient;using InterSystems.Data.CacheTypes;
上記を以下の様に書き換えます。
using InterSystems.Data.IRISClient;using InterSystems.Data.IRISClient.ADO;
connection情報
connectionオブジェクトをCachéからIRISに変更する必要があります。
CacheConnection conn;
public IRISConnection conn = new IRISConnection();
Proxyクラスの削除
.Net Native APIではプロキシークラスは必要なくなるので、その参照を削除します。
(プロジェクトからもUser.CacheDirect.csを削除します。)
public User.CacheDirect cd;
代わりにIRISオブジェクトを宣言します。
public IRISObject cd;
続いてプロキシークラスが保持していたプロパティ機能を実装するために、以下の宣言を追加します。(すべてのプロパティに対してプライベート変数とそれにパブリックアクセスするためのアクセッサメソッド)
private string p0; private string p1; private string p2; private string p3; private string p4; private string p5; private string p6; private string p7; private string p8; private string p9; private string plist; private string pdelim; private string value; private string code; private long execflag; private string errorname; private long error; public string P0 { set { this.p0 = value; } get { return this.p0; } } public string P1 { set { this.p1 = value; } get { return this.p1; } } public string P2 { set { this.p2 = value; } get { return this.p2; } } public string P3 { set { this.p3 = value; } get { return this.p3; } } public string P4 { set { this.p4 = value; } get { return this.p4; } } public string P5 { set { this.p5 = value; } get { return this.p5; } } public string P6 { set { this.p6 = value; } get { return this.p6; } } public string P7 { set { this.p7 = value; } get { return this.p7; } } public string P8 { set { this.p8 = value; } get { return this.p8; } } public string P9 { set { this.p9 = value; } get { return this.p9; } } public string PLIST { set { this.plist = value; } get { return this.plist; } } public string PDELIM { set { this.pdelim = value; } get { return this.pdelim; } } public string Value { set { this.value = value; } get { return this.value; } } public string Code { set { this.code = value; } get { return this.code; } } public long ExecFlag { set { this.execflag = value; if (value == 1) { this.Execute(this.code); } } get { return this.execflag; } } public string ErrorName { get { return this.errorname; } } public string Error { get { return this.error.ToString(); } }
サーバー接続処理
コンストラクターの処理の所で、サーバー接続処理をIRIS用に変更します。
conn = new CacheConnection(); conn.ConnectionString = constr; conn.Open(); cd = new User.CacheDirect(conn);
IRISでは、コネクションオブジェクトを作成した後、プロキシークラスのインスタンスを生成する代わりにIRISクラスのインスタンスを生成し、サーバーのCacheDirect.Emulatorクラスの%Newクラスメソッドを呼び出して、IRISObjectクラスのインスタンスを生成しています。
(.Net Binding版のクラスUser.CacheDirectから名前も変更)このインスタンスが従来のプロキシークラスのインスタンスと同様の機能を提供します。
conn.ConnectionString = constr; conn.Open(); IRIS iris = IRIS.CreateIRIS(conn); cd = (IRISObject)iris.ClassMethodObject("CacheDirect.Emulator", "%New");
プロキシークラスでの実装と異なり、.Net Native APIではプロパティに値を設定するにはIRISObjectクラスのSetメソッドを使って、明示的に値を設定する必要があります。
public long Execute(string command) { long status; cd.Set("P0", p0); cd.Set("P1", p1); cd.Set("P2", p2); cd.Set("P3", p3); cd.Set("P4", p4); cd.Set("P5", p5); cd.Set("P6", p6); cd.Set("P7", p7); cd.Set("P8", p8); cd.Set("P9", p9); cd.Set("PLIST", plist); cd.Set("PDELIM", pdelim)
サーバーのインスタンスメソッド(Execute)を呼び出すためには、IRISObjectクラスのInvokeメソッドを呼び出します。
status = (long)cd.Invoke("Execute", command);
サーバー側のExecuteメソッド実行後に変更された可能性のあるプロパティの値(P0-P9,PLIST,Valueなど)をクライアントのプロパティに反映させるためにIRISOBjectクラスのGetメソッドを呼び出します。
ここでは、サーバー側のプロパティのタイプに関わらず、戻り値によってタイプが動的に変化する可能性があるために戻り値の型をチェックして適切に処理する必要があります。
if (cd.Get("P0") is string) { p0 = (string)cd.Get("P0"); } else { if (cd.Get("P0") is null) { } else { p0 = cd.Get("P0").ToString(); } }
ErrorNameとErrorもサーバー側のプロパティからGetメソッドを使用して取得します。
errorname =(string) cd.Get("ErrorName"); error = (long)cd.Get("Error");
PLISTの処理用に追加したメソッドも同様にサーバー側のPLISTプロパティをGetメソッドで取得します。PLISTプロパティに値を設定するためにSetメソッドを使用します。
string[] PLISTArray = cd.Get("PLIST").ToString().Split(cd.Get("PDELIM").ToString().ToCharArray()); cd.Set("PLIST", string.Join(cd.Get("PDELIM").ToString(), PLISTArray));