#ベストプラクティス

0 フォロワー · 44 投稿

InterSystemsデータプラットフォーム上でソリューションをより適切に開発、テスト、展開、管理する方法に関するベストプラクティスの推奨事項。

記事 Toshihiko Minamoto · 11月 5, 2025 8m read

新しい InterSystems IRIS® Cloud SQL と InterSystems IRIS® Cloud IntegratedML® クラウド製品のユーザーであり、デプロイメントのメトリクスにアクセスして独自の可観測性プラットフォームに送信しようと考えている方のために、メトリクスを Google Cloud Platform Monitoring(旧称 StackDriver)に送信して手っ取り早く行う方法をご紹介します。

クラウドポータルには、概要メトリクス表示用のトップレベルのメトリクスが含まれており、ユーザーに公開されているメトリクスエンドポイントを使用しますが、ある程度探索しなければ、そこにあることには気づきません。

🚩 このアプローチは、「今後名前が付けられる予定の機能」を利用している可能性があるため、それを踏まえると将来性があるものではなく、確実に InterSystemsでサポートされているアプローチではありません。


では、より包括的なセットをエクスポートしたい場合はどうでしょうか?この技術的な記事/例では、メトリクスを取得して可観測性に転送する方法を紹介します。Open Telemetry Collector を使用して、任意のメトリクスターゲットを取得し、任意の可観測性プラットフォーム送信できるように、ニーズに合わせて変更することができます。

上記の結果に導く仕組みは多数の方法で得られますが、ここでは Kubernetes pod を使用して、1 つのコンテナーで Python スクリプトを実行し、もう 1 つのコンテナーで Otel を実行して、メトリクスのプルとプッシュを行います... 自分のやり方を選択することはできますが、この例と記事では、k8s を主人公に Python を使って行います。

手順:

  • 前提条件
  • Python
  • コンテナー
  • Kubernetes
  • Google Cloud Monitoring

前提要件:

  • IRIS®  Cloud SQL の有効なサブスクリプション
  • 実行中の 1 つのデプロイメント(オプションで Integrated ML を使用)
  • 環境に提供するシークレット

環境変数

 
 シークレットの取得
この内容は少し複雑で本題から少し外れているためティーザーに入れましたが、これがシークレットの生成に必要なる値です。
ENV IRIS_CLOUDSQL_USER 'user'
ENV IRIS_CLOUDSQL_PASS 'pass'

☝ これは https://portal.live.isccloud.io の認証情報です。

ENV IRIS_CLOUDSQL_USERPOOLID 'userpoolid'
ENV IRIS_CLOUDSQL_CLIENTID 'clientid'
ENV IRIS_CLOUDSQL_API 'api'

☝ これはブラウザの開発ツールから取得する必要があります。

  • `aud` = clientid
  • `userpoolid`= iss
  • `api` = request utl

ENV IRIS_CLOUDSQL_DEPLOYMENTID 'deploymentid'

☝これはクラウドサービスポータルから取得できます

 

Python:

以下に、クラウドポータルからメトリクスを取得し、それを Otel Collectorが取得するメトリクスとしてローカルにエクスポートする Python ハッキングを示します。

 
iris_cloudsql_exporter.py
import time
import os
import requests
import json

from warrant import Cognito from prometheus_client.core import GaugeMetricFamily, REGISTRY, CounterMetricFamily from prometheus_client import start_http_server from prometheus_client.parser import text_string_to_metric_families

classIRISCloudSQLExporter(object):definit(self): self.access_token = self.get_access_token() self.portal_api = os.environ['IRIS_CLOUDSQL_API'] self.portal_deploymentid = os.environ['IRIS_CLOUDSQL_DEPLOYMENTID']

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">collect</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-comment"># Requests fodder</span>
    url = self.portal_api
    deploymentid = self.portal_deploymentid
    print(url)
    print(deploymentid)

    headers = {
        <span class="hljs-string">'Authorization'</span>: self.access_token, <span class="hljs-comment"># needs to be refresh_token, eventually</span>
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
    }

    metrics_response = requests.request(<span class="hljs-string">"GET"</span>, url + <span class="hljs-string">'/metrics/'</span> + deploymentid, headers=headers)
    metrics = metrics_response.content.decode(<span class="hljs-string">"utf-8"</span>)

    <span class="hljs-keyword">for</span> iris_metrics <span class="hljs-keyword">in</span> text_string_to_metric_families(metrics):
        <span class="hljs-keyword">for</span> sample <span class="hljs-keyword">in</span> iris_metrics.samples:

            labels_string = <span class="hljs-string">"{1}"</span>.format(*sample).replace(<span class="hljs-string">'\''</span>,<span class="hljs-string">"\""</span>)
            labels_dict = json.loads(labels_string)
            labels = []

            <span class="hljs-keyword">for</span> d <span class="hljs-keyword">in</span> labels_dict:
                labels.extend(labels_dict)
            <span class="hljs-keyword">if</span> len(labels) &gt; <span class="hljs-number">0</span>:
                g = GaugeMetricFamily(<span class="hljs-string">"{0}"</span>.format(*sample), <span class="hljs-string">'Help text'</span>, labels=labels)
                g.add_metric(list(labels_dict.values()), <span class="hljs-string">"{2}"</span>.format(*sample))
            <span class="hljs-keyword">else</span>:
                g = GaugeMetricFamily(<span class="hljs-string">"{0}"</span>.format(*sample), <span class="hljs-string">'Help text'</span>, labels=labels)
                g.add_metric([<span class="hljs-string">""</span>], <span class="hljs-string">"{2}"</span>.format(*sample))
            <span class="hljs-keyword">yield</span> g

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_access_token</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-keyword">try</span>:
        user_pool_id = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_USERPOOLID'</span>] <span class="hljs-comment"># isc iss </span>
        username = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_USER'</span>]
        password = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_PASS'</span>]
        clientid = os.environ[<span class="hljs-string">'IRIS_CLOUDSQL_CLIENTID'</span>] <span class="hljs-comment"># isc aud </span>
        print(user_pool_id)
        print(username)
        print(password)
        print(clientid)
        
        <span class="hljs-keyword">try</span>:
            u = Cognito(
                user_pool_id=user_pool_id,
                client_id=clientid,
                user_pool_region=<span class="hljs-string">"us-east-2"</span>, <span class="hljs-comment"># needed by warrant, should be derived from poolid doh</span>
                username=username
            )
            u.authenticate(password=password)
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> p:
            print(p)
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(e)

    <span class="hljs-keyword">return</span> u.id_token

ifname == 'main':

start_http_server(<span class="hljs-number">8000</span>)
REGISTRY.register(IRISCloudSQLExporter())
<span class="hljs-keyword">while</span> <span class="hljs-keyword">True</span>:
    REGISTRY.collect()
    print(<span class="hljs-string">"Polling IRIS CloudSQL API for metrics data...."</span>)
    <span class="hljs-comment">#looped e loop</span>
    time.sleep(<span class="hljs-number">120</span>)</code></pre>

 

Docker:

 
Dockerfile
FROM python:3.8ADD src /src
RUN pip install prometheus_client
RUN pip install requests
WORKDIR /src
ENV PYTHONPATH '/src/'ENV PYTHONUNBUFFERED=1ENV IRIS_CLOUDSQL_USERPOOLID 'userpoolid'ENV IRIS_CLOUDSQL_CLIENTID 'clientid'ENV IRIS_CLOUDSQL_USER 'user'ENV IRIS_CLOUDSQL_PASS 'pass'ENV IRIS_CLOUDSQL_API 'api'ENV IRIS_CLOUDSQL_DEPLOYMENTID 'deploymentid'RUN pip install -r requirements.txt
CMD ["python" , "/src/iris_cloudsql_exporter.py"]
docker build -t iris-cloudsql-exporter .
docker image tag iris-cloudsql-exporter sween/iris-cloudsql-exporter:latest
docker push sween/iris-cloudsql-exporter:latest


デプロイメント:

k8s、ネームスペースを作成します:

kubectl create ns iris

k8s、シークレットを追加します:

kubectl create secret generic iris-cloudsql -n iris \
    --from-literal=user=$IRIS_CLOUDSQL_USER \
    --from-literal=pass=$IRIS_CLOUDSQL_PASS \
    --from-literal=clientid=$IRIS_CLOUDSQL_CLIENTID \
    --from-literal=api=$IRIS_CLOUDSQL_API \
    --from-literal=deploymentid=$IRIS_CLOUDSQL_DEPLOYMENTID \
    --from-literal=userpoolid=$IRIS_CLOUDSQL_USERPOOLID

otel、構成を作成します:

apiVersion: v1
data:
  config.yaml: |
    receivers:
      prometheus:
        config:
          scrape_configs:
          - job_name: 'IRIS CloudSQL'
              # Override the global default and scrape targets from this job every 5 seconds.
            scrape_interval: 30s
            scrape_timeout: 30s
            static_configs:
                    - targets: ['192.168.1.96:5000']
            metrics_path: /
exporters:
  googlemanagedprometheus:
    project: "pidtoo-fhir"
service:
  pipelines:
    metrics:
      receivers: [prometheus]
      exporters: [googlemanagedprometheus]

kind: ConfigMap metadata: name: otel-config namespace: iris

k8s、otel 構成を configmap としてロードします:

kubectl -n iris create configmap otel-config --from-file config.yaml

k8s、ロードバランサー(確実にオプション)、MetalLB をデプロイします。これはクラスタ外部からグスクレイピングして検査するために行っています。

cat <<EOF | kubectl apply -f -n iris -
apiVersion: v1
kind: Service
metadata:
  name: iris-cloudsql-exporter-service
spec:
  selector:
    app: iris-cloudsql-exporter
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 8000
EOF

gcp、Google Cloud へのキーが必要です。サービスアカウントにスコープを設定する必要があります

  • roles/monitoring.metricWriter
kubectl -n iris create secret generic gmp-test-sa --from-file=key.json=key.json

k8s; deployment/pod そのもの。2 つのコンテナー:

 
deployment.yaml
kubectl -n iris apply -f deployment.yaml

実行

特に問題がなければ、ネームスペースを詳しく調べて、状況を確認してみましょう。

✔ GCP と Otel 用の 2 つの configmap

 

✔ 1 つのロードバランサー

 

✔ 1 つの pod、2 つのコンテナーが正しくスクレイピングされます

  

Google Cloud Monitoring

可観測性を調べてメトリクスが正しく受信されていることを確認し、可観測性を高めましょう!

 

0
0 20
お知らせ Toshihiko Minamoto · 9月 12, 2024

Git を使用してIRIS でソリューションを構築することは、素晴らしいことです! 単にローカルの git リポジトリにVSCodeを使用し、サーバーに変更をプッシュする... それは非常に簡単です。

でも、次の場合はどうでしょうか。

  • 共有リモート開発環境で他の開発者と共同作業を行い、同じファイルの同時編集を回避したい場合
  • BPL、DTL、ピボット、ダッシュボードなどにおいて管理ポータルに基づくエディターを使用しており、 作業に簡潔なソース管理を使用したい場合
  • 一部の作業においては引き続き Studio を使用しているかたまに VSCode から Studio に戻っているか、チームがまだ VSCode を完全に採用しておらず、一部のチームメンバーが Studio の使用を希望している場合
  • 同じネームスペースで同時に多数の独立したプロジェクト(InterSystems Package Manager を使って定義された複数のパッケージなど)に取り組んでおり、(多数の個別のプロジェクトではなく)1 つの isfs 編集ビューからすべてのプロジェクトの作業を行い、適切な git リポジトリで変更を自動的に追跡する場合
0
0 198
記事 Toshihiko Minamoto · 8月 16, 2024 12m read

Visual Studio Code(VSCode)は、市場で最も一般的なコードエディターです。 Microsoft によって制作され、無料 IDE として配布されています。 VSCode は ObjectScript などの多数のプログラミング言語をサポートしており、2018 年までは Atelier(Eclipse ベース)もサポートしていました。 InterSystems 製品開発の主なオプションの 1 つとして考えられていましたが、 2018 年、InterSystems 開発者コミュニティが VSCode のサポートを発表した際に、関連する InterSystems のプロユーザーらが実際にこのエディターを使用し始め、以来、特に新しいテクノロジー(Docker、Kubernetes、NodeJS、Angular、React、DevOps、GitLab など)を使用する開発者の間でその使用が続いています。 VSCode の一番の機能の中にはデバッグ機能が挙げられます。 そこで、この記事では、クラスコードや %CSP.REST コードなどの ObjectScript コードをデバッグする方法を詳しく紹介します。

デバッグとは?

デバッグとは、ObjectScript コードに含まれる「バグ」と呼ばれるエラーを検出して解決するプロセスを指します。一部のエラーはコンパイルエラーではなく論理エラーです。 そのため、ソースコード実行ロジックのエラーを理解するには、プログラムを行ごとに実行して確認する必要があります。 条件またはプログラミングのロジックを特定して論理的なミスを見つけるには、これが最善の方法です。 その他のエラーには実行時エラーがあります。 この種の問題をデバッグできるには、まず、割り当てられた変数の値を確認することが不可欠です。

VSCode 用 InterSystems IRIS 拡張機能のインストール

まず、VSCode IDE に InterSystems IRIS 拡張機能をインストールする必要があります。 これを行うには、拡張機能に移動し、InterSystems を見つけてこれらの InsterSystems 拡張機能をインストールします。

最後の拡張機能(InterSystems ObjectScript Extension Pack)を最初にインストールする必要があります。この拡張機能パックは、ObjectScript の接続、編集、開発、およびデバッグに不可欠なものであるためです。 リストに含まれる他の拡張機能はオプションです。

サンプルアプリケーション

この記事のデバッグ例には、GitHub リポジトリ(https://github.com/yurimarx/debug-objectscript.git)のアプリケーションを使用します。 以下の手順に従って、VSCode でアプリケーションを取得、実行、およびデバッグしてください。
1.    ローカルディレクトリを作成し、プロジェクトソースコードをこのディレクトリに取得します。

$ git clone https://github.com/yurimarx/debug-objectscript.git

2.    このディレクトリ(iris-rest-api-template ディレクトリ)でターミナルを開いて実行します。

$ docker-compose up -d --build

3.    iris-rest-api-template ディレクトリ内で $code . を使うか、 フォルダを右クリックして「Open with Code」を選択し、VSCode を開きます。
4.    フッターで ObjectScript をクリックし、.vscode/launch.json で構成されたオプションを開きます。

5.    「Toggle Connection」を選択して同時接続を有効にします。

6.    この接続を有効にしたら、以下に示す接続が得られます。

7.    VSCode で IRIS に接続されていない場合は ObjectScript コードをデバッグできないため、 ObjectScript VSCode 拡張機能をクリックして接続を確認します。

8.    Person.cls を開き、行番号 25 に移動し、左側の数字 25 にマウスポインターを合わせます。

9.    次に、上部の ClassMethod にマウスポインターを合わせ、「Debug this method」をクリックします。

10.    VSCode に、パラメーターに値を設定するためのダイアログが開くため、下の例の通りに値を設定し、Enter をクリックします。

11.    VSCode は行 25 でコードの実行を停止します。

12.    左上で現在の変数値を確認できます。  

13.    左下でブレークポイントのリストと現在のブレークポイントのコールスタックを確認できます。  

14.    右上にはブレークポイントのあるソースコードとデバッグの実行を制御するための一連のボタンがあります。  

15.    デバッグコンソールを使ってそこに何かを入力する必要がある場合は、「Name_”,”_Title」と入力すると、デバッグコンソールで変数の連結を確認できます。

16.    結果の変数を選択し、右クリックして「Add to Watch」を選択します。

17.    これで、WATCH セッションで結果の変数の現在の値を監視し、変更できるようになりました。

18.    最後に、フッターバーはオレンジになっており、デバッグ中であることを示しています。

デバッグコンソールの使用

デバッグコンソールでは、デバッグ中に式を記述して、様々な変数(単純な方またはオブジェクト型)の値を確認したり、現在の値や他の条件を検証したりすることができます。 当たりを返すものであれば何でも実行できるため、$zv も機能します。 以下の手順に従って、いくつかのオプションを確認してください。

1.    デバッグターミナルプロンプトに「Name_", "_Title_" From "_Company」と入力すると、名前、タイトル、会社が連結された値を出力として確認できます。

デバッグツールバーの使用

デバッグツールバーではデバッグの実行を制御できるため、ここではボタンの機能を確認します。

1.    「Continue」ボタン(F9): 実行を次のブレークポイントまで続けます。 次のブレークポイントがない場合は、最後まで実行されます。
2.    Step Over(F8): 内部メソッドのソースコードに入らずに次の行に移動します(Populate メソッドに入りません)。
3.    Step Into(F7): 内部メソッドのソースコード内の次の行に移動します(Populate メソッドの最初の行に移動します)。
4.    Step Out(Shift + F8): メソッドの呼び出し元ソースコードの現在の行に移動します。
5.    Restart(Crtl + Shift + F5): デバッグをリプレイします。
6.    Stop: デバッグセッションを停止します。  

%CSP.REST クラスメソッドのデバッグ

REST クラスメソッドをデバッグするには、Fábio Gonçalves が見つけた簡単なトリック(こちらで記事をご覧ください: https://community.intersystems.com/post/atelier-debugging-attach-process)を適用する必要があります。 HANG を追加する必要があります(20~30 秒が推奨です)。 以下の手順に従ってください。

1.    ファイル src\dc\Sample\PersonREST.cls を開き、GetInfo() メソッドに移動して HANG 30 を追加します(このメソッドのデバッグの開始までに30 秒の時間を得えるには必要です)。

2.    HANG と SET の行にブレークポイントを設定します(赤い丸)。

3.    HTTP クライアントを開いて REST メソッドを呼び出します(ここではクラス PersonREST から GetInfo を呼び出します):

4.    Basic Auth を Authorization に設定します(ユーザー名: _SYSTEM、パスワード: SYS)。

5.    GET http://localhost:52773/crud/ を送信します。
6.    フッターバーの「ObjectScript Attach」をクリックします。

7.    ObjectScript Attach を選択します。

8.    PersonREST プロセスを選択します。

9.    VSCode がブレークポイントで現在の実行を停止するまでしばらく待ちます(約 30 秒)。

注意 1: HANG は実行を停止するため、デバッグを終了したら、HANG 30 を削除してください。

注意 2: PersonREST プロセスが表示されない場合は、VSCode を再起動してもう一度お試しください。

10.    これでデバッグプロセスを実行できるようになりました。

ブレークポイントの編集

ブレークポイントを右クリックすると、削除するか条件ブレークポイント(条件が true の場合にブレークポイントで停止)を構成できます。 コードに埋め込まれたブレークコマンドも、デバッガーによってキャッチされます。 これも試してみてください。

1.    ブレークポイントを右クリックし、「Edfit Breakpoint」を選択します。

2.    下に示す式を入力し、Enter を押します。

注意: version = 1.0.5 でブレークポイントが false になるのと、1.0.6 で true になるのを試してみてください。

.vscode/launch.json ファイルでデバッグオプションを構成する

Launch.json ファイルは、プロジェクトのデバッグオプションの構成に使用します。 他のオプションを見るには、こちらのファイルに移動してください。

このサンプルの場合、選択できるデバッグオプションは 2 つあります。

1.    直接プログラムで、プログラム属性に構成されたクラスでデバッグを起動します(この例では、デバッガーは PackageSample.ObjectScript クラスのメソッド Test() で開始します)。
2.    デバッグするために選択したプログラムプロセスに PickProcess をアタッチできます。 この例では、デバッガーはプロセスのリストの最上位で開くため、デバッグするプロセス(各プログラムにはそのプロセスがあります)を選択します。 2 つ目のオプションの方が一般的です。

一般的な方法では、任意のデバッグ構成においてこれらの属性は必須です(出典: https://intersystems-community.github.io/vscode-objectscript/rundebug/)。

  • type - 使用するデバッグのタイプを識別します。 この場合、objectscript は InterSystems ObjectScript 拡張機能によって提供されています。
  • request - この起動構成の操作タイプを識別します。 可能な値は launch と attach です。
  • name - 構成を識別するための任意の名前。 この名前は「Start Debugging」ドロップダウンリストに表示されます。

また、ObjectScript 起動構成では、属性 program を指定する必要があります。この属性は、次の例に示すように、デバッガーの起動時に実行するルーチンまたは ClassMethod を指定します。

"launch": {
    "version": "0.2.0",
    "configurations": [
       {
        "type": "objectscript",
        "request": "launch",
        "name": "ObjectScript Debug HelloWorld",
        "program": "##class(Test.MyClass).HelloWorld()",
      },
      {
        "type": "objectscript",
        "request": "launch",
        "name": "ObjectScript Debug GoodbyeWorld",
        "program": "##class(Test.MyOtherClass).GoodbyeWorld()",
      },
   ]
}

ObjectScript の attach 構成の場合、以下の属性を指定する必要がある場合があります。

  • processId - 文字列または数値にアタッチするプロセスの ID を指定します。 デフォルトは "${command:PickProcess}" で、実行時にアタッチするプロセス ID のドロップダウンリストを提供します。
  • system - システムプロセスへのアタッチを許可するかどうかを指定します。 デフォルトは false です。

次の例は、複数の有効な ObjectScript アタッチ構成を示しています。

"launch": {
    "version": "0.2.0",
    "configurations": [
        {
            "type": "objectscript",
            "request": "attach",
            "name": "Attach 1",
            "processId": 5678
        },
        {
            "type": "objectscript",
            "request": "attach",
            "name": "Attach 2",
            "system": true
        },
    ]
}

これで、デバッグサイドバーの上部にある Run and Debug フィールドに VS Code が提供するリストからデバッグ構成を選択することができます(出典: https://intersystems-community.github.io/vscode-objectscript/rundebug/)。

緑色の矢印をクリックすると、現在選択されているデバッグ構成が実行します。
ObjectScript 起動デバッグセッションを開始する際は、エディターとアクティブなタブでデバッグするプログラムを含むファイルが開いていることを確認してください。 VS Code はアクティブなエディター(フォーカスのあるタブ)でファイルのサーバーを使ってセッションのデバッグを開始します。 これは、アタッチされた ObjectScript デバッグセッションにも適用されます。
この拡張機能はセッション中、WebSocket を使用して InterSystems サーバーと通信します。 デバッグセッションを開始しようとする際に問題が発生する場合は、InterSystems サーバーの Web サーバーが WebSocket 通信を許可しているかを確認してください。
デバッグコマンドと Run メニューの項目は VS Code がサポートする他の言語でも同じように機能します。 VS Code のデバッグに関するその他の情報については、このセクションの最初にリストされているドキュメントリソースをご覧ください。

ローカルソースコードとサーバーソースコード(IRIS サーバー)の同期を設定する

ソースコードにフォルダ構造を用意しておくことも非常に重要です。これは "objectscript.export" 設定で行われます。 この情報はサーバーの食らう名をローカルファイルに変換するために使用されます。 これに誤りがある場合、ローカルにのみ存在する場合でも、読み取り専用モードでクラスが開く場合があります。
1.    .vscode\settings.json ファイルを開き、objectscript.export を構成します。

2.    フォルダにマウスポインターを合わせ、カテゴリとその他のパラメーターを追加してそれに関するドキュメントを表示します。

デバッグプロセスを補完するその他のテクニック

プログラムをデバッグするほかに、ソースコードを十分にドキュメント化し、重要なコードが実行した操作をログに記録し、ユニットテストとソースコード静的解析ツールを実行することも覚えておきましょう。 こうすることで、プログラムの品質ははるかに改善され、メンテナンスと調整時間、および費用を削減できます。

さらに学習する

以下のリソースを確認すると、VSCode での ObjectScript のデバッグに関してさらに読むことができます。
1.    https://www.youtube.com/watch?v=diLHwA0rlGM
2.    https://intersystems-community.github.io/vscode-objectscript/rundebug/
3.    https://code.visualstudio.com/docs/introvideos/debugging
4.    https://code.visualstudio.com/docs/editor/debugging
5.    https://docs.intersystems.com/iris20221/csp/docbook/Doc.View.cls?KEY=TOS_VSCode

0
1 310
記事 Toshihiko Minamoto · 5月 28, 2024 6m read

前回の記事では特定の HIS のデータベースに格納されたリソースを取得する方法を確認したので、今回は、HIS に、システムで受け取る FHIR リソースを起点とする新しいレコードを追加する方法を説明します。

FHIR の CRUD 操作

FHIR の主な機能の 1 つに、Rest API による CRUD 操作のサポートがあります。つまり、FHIR と連携するすべてのシステムには、GET、POST、PUT、および DELETE タイプの HTTP 呼び出しがサポートされていなければなりません。 この記事では、FHIR アダプターをインストールした際に自動的に構成されたエンドポイントへの POST 呼び出しを処理する方法を見てみましょう。

FHIR のリソースストレージ呼び出しに関する仕様を確認すると、呼び出しに使用する URL は、以下のフォーマットを使用している必要があります。

http(s)://server_url/{endpoint}/{Resource}

この記事の例では、セキュリティで保護された呼び出しは行わないため、以下のような URL になります。

http://localhost:52774/Adapter/r4/Patient

ここで行いたいのは新しい患者を記録することであるため、呼び出しの本文に患者のデータを含めた POST 呼び出しを行う必要があります。 application/fhir+xml として問題なく XML 形式を使用することもできますが、この場合の呼び出しのフォーマットは application/fhir+json とします。

患者のリソースの保存

前回の記事ですでに Patient リソースの定義を確認したのでここでは繰り返しません。ここでは、整形済みのテスト患者がどのように見えるかを確認しましょう。 ここで見るのは以下の患者です。

{
    "resourceType": "Patient",
    "address": [
        {
            "city": "SALAMANCA",
            "line": [
                "CALLE LAMENTOS 2 1ºA"
            ],
            "postalCode": "45021"
        }
    ],
    "birthDate": "1988-01-23",
    "gender": "F",
    "identifier": [
        {
            "type": {
                "text": "NHC"
            },
            "value": "803987"
        },
        {
            "type": {
                "text": "DNI"
            },
            "value": "87654321F"
        }
    ],
    "name": [
        {
            "family": "SANZ LÓPEZ",
            "given": [
                "REBECA"
            ]
        }
    ],
    "telecom": [
        {
            "system": "phone",
            "value": "699850017"
        },
        {
            "system": "email",
            "value": "rebek1988@hotmail.com"
        }
    ]
}

ご覧のように、Postman からサーバーのエンドポイントに送信する Patient タイプの Resource です。

200 を受け取り、患者が HIS に登録されたので、Patient テーブルを確認しましょう。

ここに、POST リクエストに対するレスポンスで受け取った識別子で HIS に正しく記録された Rebecca があります。 では、本番環境にアクセスし、FHIR メッセージが IRIS 内で通過したパスを確認しましょう。

IRIS での Patient リソースの操作

ここまでで、FHIR リソースをサーバーのエンドポイントに送信する方法を見てきました。では、受け取った FHIR リソースをどのように解釈し、特定の HIS に挿入するために変換できるかを確認しましょう。

まず、本番環境の構成をもう一度確認しましょう。

メッセージは InteropService から届き、ProcessFHIRBP に転送されます。そこで、FromAdapterToHIS が呼び出され、対応する操作が実行されます。 では、受信したメッセージのトレースを確認しましょう。

ここでは、受信したメッセージの詳細を確認できます。ご覧のように、Patient エンドポイントに HTTP POST リクエストが届いており、QuickStreamId に値があることがわかります。メッセージは Stream に格納されていることが示されています。

BO はこのメッセージをどのように処理するのでしょうか?

elseif (requestData.Request.RequestPath [ "Patient")
  {
    if (requestData.Request.RequestMethod = "POST")
    {
      If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
    <span class="hljs-keyword">set</span> dynamicPatient = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%DynamicAbstractObject</span>).<span class="hljs-built_in">%FromJSON</span>(quickStreamIn)

    <span class="hljs-keyword">set</span> sc = <span class="hljs-built_in">..InsertPatient</span>(dynamicPatient, .response)
  }      
}
<span class="hljs-keyword">elseif</span> (requestData.Request.RequestMethod = <span class="hljs-string">"GET"</span>)
{
  <span class="hljs-keyword">set</span> patientId = <span class="hljs-built_in">$Piece</span>(requestData.Request.RequestPath,<span class="hljs-string">"/"</span>,<span class="hljs-number">2</span>)
  <span class="hljs-keyword">set</span> sc = <span class="hljs-built_in">..GetPatient</span>(patientId, .response)
}

}

非常に単純です。受信したリクエストから得られる情報をもって、メッセージを適切なメソッドにリダイレクトできます。Patient リソースからの特定のリクエストであることとメソッドが POST であることを確認できるため、メッセージを格納している Stream を取得してそれを %DynamicObject に変換します。以下では、InsertPatient メソッドからこれを処理します。そのメソッドのコードを見てみましょう。

Method InsertPatient(dynamicPatient As%DynamicAbstractObject, Output patient As Adapter.Message.FHIRResponse) As%Status
{
  Set tSC = $$$OKkill pResp
  set pResp=$$$NULLOREFset sqlInsert="INSERT INTO his.patient (name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender) values (?,?,?,?,?,?,?,?,?,?,?)"//perform the Insertset tSC = ..Adapter.ExecuteUpdate(.nrows, sqlInsert, dynamicPatient.%Get("name").%Get(0).%Get("given").%Get(0), dynamicPatient.%Get("name").%Get(0).%Get("family"), dynamicPatient.%Get("telecom").%Get(0).value, 
    dynamicPatient.%Get("address").%Get(0).%Get("line").%Get(0), dynamicPatient.%Get("address").%Get(0).%Get("city"), dynamicPatient.%Get("telecom").%Get(1).value, dynamicPatient.%Get("identifier").%Get(1).value, 
    dynamicPatient.%Get("address").%Get(0).%Get("postalCode"), dynamicPatient.%Get("birthDate"), dynamicPatient.%Get("identifier").%Get(2).value, dynamicPatient.%Get("gender"))

set sql="SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE nhc = ?"//perform the Selectset tSC = ..Adapter.ExecuteQuery(.resultSet, sql, dynamicPatient.%Get("identifier").%Get(1).value)

If resultSet.Next() { set personResult = {"id":(resultSet.GetData(1)), "name": (resultSet.GetData(2)), "lastname": (resultSet.GetData(3)), "phone": (resultSet.GetData(4)), "address": (resultSet.GetData(5)), "city": (resultSet.GetData(6)), "email": (resultSet.GetData(7)), "nhc": (resultSet.GetData(8)), "postalCode": (resultSet.GetData(9)), "birthDate": (resultSet.GetData(10)), "dni": (resultSet.GetData(11)), "gender": (resultSet.GetData(12)), "type": ("Patient")} } else { set personResult = {} }

//create the response messagedo patient.Resource.Insert(personResult.%ToJSON())

<span class="hljs-keyword">Return</span> tSC

}

ご覧のように、最初に行うのは、HIS の Patient テーブルへの挿入です(誰かがその患者が存在しないことを確認済みであると仮定しましょう)。挿入が完了したら、この患者に対して生成された識別子を取得してリクエストを送信したクライアントアプリケーションにそれを返すために、同じテーブルで SELECT を実行します。 レスポンスメッセージを完成させるために、最も関連性があると思われるフィールドを取得できます。

取得した患者データとともに、クライアントに返すことのできる Patient タイプリソースを生成するために必要な変換を実行する BP に戻ります。

変換については、前の記事をご覧ください。

リソースが生成されたら、レスポンスに含めてクライアントアプリケーションに戻す Stream に変換します。

ご覧のように、非常に簡単です。患者リソースからデータを取得し、対応するテーブルに挿入するだけです。 最後に、登録されたオブジェクトを、GET リクエストで行ったのと同じ方法で返します。

次の記事では、Bundle またはリソースセットを処理する方法を説明します。 お読みいただきありがとうございました!

0
0 122
記事 Toshihiko Minamoto · 5月 23, 2024 11m read

HealthShare、HealthConnect、および InterSystems IRIS ユーザーが使用できる FHIR アダプターツールに関する連載記事を再開しましょう。

前回の記事では、ワークショップをセットアップした小さなアプリケーションを紹介し、FHIR アダプターをインストールした後に IRIS インスタンスにデプロイされたアーキテクチャを説明しました。 この記事では、最も一般的な CRUD(作成、読み取り、更新、削除)操作の 1 つである読み取り操作を実行する方法の例を確認します。ここではリソースの取得によって行います。

リソースとは?

FHIR のリソースはある種の臨床情報です。この情報は、患者(Patient)、臨床検査へのリクエスト(ServiceRequest)、または診断(Condition)などです。 各リソースはそれを構成するデータのタイプのほか、データの制約や他のリソースタイプとの関係を定義します。 リソースごとに、それが格納する情報の拡張が可能であるため、FHIR ポリシーの範囲外のニーズに 80% 対応できます(ユーザーの 80% が使用する要件に対応)。

この記事の例では、最も一般的なリソースである Patient を使用します。 その定義を確認しましょう。

{
  "resourceType" : "Patient",
  // from Resource: id, meta, implicitRules, and language
  // from DomainResource: text, contained, extension, and modifierExtension
  "identifier" : [{ Identifier }], // An identifier for this patient
  "active" : <boolean>, // Whether this patient's record is in active use
  "name" : [{ HumanName }], // A name associated with the patient
  "telecom" : [{ ContactPoint }], // A contact detail for the individual
  "gender" : "<code>", // male | female | other | unknown
  "birthDate" : "<date>", // The date of birth for the individual
  // deceased[x]: Indicates if the individual is deceased or not. One of these 2:
  "deceasedBoolean" : <boolean>,
  "deceasedDateTime" : "<dateTime>",
  "address" : [{ Address }], // An address for the individual
  "maritalStatus" : { CodeableConcept }, // Marital (civil) status of a patient
  // multipleBirth[x]: Whether patient is part of a multiple birth. One of these 2:
  "multipleBirthBoolean" : <boolean>,
  "multipleBirthInteger" : <integer>,
  "photo" : [{ Attachment }], // Image of the patient
  "contact" : [{ // A contact party (e.g. guardian, partner, friend) for the patient
    "relationship" : [{ CodeableConcept }], // The kind of relationship
    "name" : { HumanName }, // I A name associated with the contact person
    "telecom" : [{ ContactPoint }], // I A contact detail for the person
    "address" : { Address }, // I Address for the contact person
    "gender" : "<code>", // male | female | other | unknown
    "organization" : { Reference(Organization) }, // I Organization that is associated with the contact
    "period" : { Period } // The period during which this contact person or organization is valid to be contacted relating to this patient
  }],
  "communication" : [{ // A language which may be used to communicate with the patient about his or her health
    "language" : { CodeableConcept }, // R!  The language which can be used to communicate with the patient about his or her health
    "preferred" : <boolean> // Language preference indicator
  }],
  "generalPractitioner" : [{ Reference(Organization|Practitioner|
   PractitionerRole) }], // Patient's nominated primary care provider
  "managingOrganization" : { Reference(Organization) }, // Organization that is the custodian of the patient record
  "link" : [{ // Link to a Patient or RelatedPerson resource that concerns the same actual individual
    "other" : { Reference(Patient|RelatedPerson) }, // R!  The other patient or related person resource that the link refers to
    "type" : "<code>" // R!  replaced-by | replaces | refer | seealso
  }]
}

ご覧のように、実質的に患者のすべての管理情報のニーズに対応しています。

HIS から患者の取得

前回の記事を覚えていれば、HIS システムのデータベースをシミュレーションする PostgreSQL データベースをデプロイしました。特定の HIS にあるサンプルテーブルを確認してみましょう。

多くはありませんが、ここでの例には十分です。 patient テーブルをもう少し詳しく見てみましょう。

ここに、3 つの患者の例があります。ご覧のように、それぞれに一意の識別子(ID)のほか、医療組織に関連性のある一連の管理データがあります。 最初の目標は、1 人の患者の FHIR リソースを取得することです。

患者のクエリ

サーバーから患者データをリクエストするにはどうすればよいでしょうか? FHIR が作成した実装仕様によると、サーバーのアドレス、リソース名、および識別子を使って REST 経由で URL に GET を実行する必要があり、 以下のように呼び出す必要があります。

http://SERVER_PATH/Patient/{id}

この例では、Juan López Hurtado という患者を検索します。この患者の ID は 1 であるため、呼び出す URL は以下のようになります。

http://localhost:52774/Adapter/r4/Patient/1

テストするには、クライアントとして Postman を使用します。 サーバーのレスポンスを確認しましょう。

{
    "resourceType": "Patient",
    "address": [
        {
            "city": "TERUEL",
            "line": [
                "CALLE SUSPIROS 39 2ºA"
            ],
            "postalCode": "98345"
        }
    ],
    "birthDate": "1966-11-23",
    "gender": "M",
    "id": "1",
    "identifier": [
        {
            "type": {
                "text": "ID"
            },
            "value": "1"
        },
        {
            "type": {
                "text": "NHC"
            },
            "value": "588392"
        },
        {
            "type": {
                "text": "DNI"
            },
            "value": "12345678X"
        }
    ],
    "name": [
        {
            "family": "LÓPEZ HURTADO",
            "given": [
                "JUAN"
            ]
        }
    ],
    "telecom": [
        {
            "system": "phone",
            "value": "844324239"
        },
        {
            "system": "email",
            "value": "juanitomaravilla@terra.es"
        }
    ]
}

次に、本番環境内でリクエストが通過した経路を詳しく見てみましょう。

ルートは以下のようになっています。

  1. BS InteropService にリクエストが到着。
  2. BS の宛先として構成し、受信した呼び出しの患者識別子がクエリされる BP に転送。
  3. BO FromAdapterToHIS から HIS データベースにクエリ。
  4. 患者データを BP に転送し、それを FHIR Patient リソースに変換。
  5. レスポンスを BS に転送。

BP ProcessFHIRBP で受信するメッセージのタイプを見てみましょう。

クライアントからどの種類のオペレーションをリクエストされたかを特定するための鍵となる 3 つの属性を見てみましょう。

  • Request.RequestMethod: どの種のオペレーションを実行するかを示します。 この例では、患者の検索は GET になります。
  • Request.RequestPath: この属性には、サーバーに届いたリクエストのパスが含まれ、操作するリソースを示します。この場合、取得するための具体的な識別子が含まれます。
  • Quick.StreamId: FHIR Adapter は Stream に届いたすべての FHIR メッセージを変換し、この属性に保存される識別子が割り当てられます。 この例では、GET を実行しており、FHIR オブジェクトを送信していないため、これは必要ありません。

処理を行う GLP を詳しく分析して、メッセージの流れをさらに見ていきましょう。

ProcessFHIRBP:

本番環境に、ビジネスサービスから受け取る FHIR メッセージングを管理する BPL を実装しました。 どのように実装されているか見てみましょう。

ステップごとに実行する操作を見てみましょう。

FHIR オブジェクトの管理:

HIS データベースへの接続とデータベースクエリを処理する BO FromAdapterToHIS を呼び出します。

Method ManageFHIR(requestData As HS.FHIRServer.Interop.Request, response As Adapter.Message.FHIRResponse) As%Status
{
  set sc = $$$OKset response = ##class(Adapter.Message.FHIRResponse).%New()

if (requestData.Request.RequestPath = "Bundle") { If requestData.QuickStreamId '= "" { Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)

    <span class="hljs-keyword">set</span> dynamicBundle = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%DynamicAbstractObject</span>).<span class="hljs-built_in">%FromJSON</span>(quickStreamIn)

    <span class="hljs-keyword">set</span> sc = <span class="hljs-built_in">..GetBundle</span>(dynamicBundle, .response)
  }

} elseif (requestData.Request.RequestPath [ "Patient") { if (requestData.Request.RequestMethod = "POST") { If requestData.QuickStreamId '= "" { Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)

    <span class="hljs-keyword">set</span> dynamicPatient = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%DynamicAbstractObject</span>).<span class="hljs-built_in">%FromJSON</span>(quickStreamIn)

    <span class="hljs-keyword">set</span> sc = <span class="hljs-built_in">..InsertPatient</span>(dynamicPatient, .response)
  }      
}
<span class="hljs-keyword">elseif</span> (requestData.Request.RequestMethod = <span class="hljs-string">"GET"</span>)
{
  <span class="hljs-keyword">set</span> patientId = <span class="hljs-built_in">$Piece</span>(requestData.Request.RequestPath,<span class="hljs-string">"/"</span>,<span class="hljs-number">2</span>)
  <span class="hljs-keyword">set</span> sc = <span class="hljs-built_in">..GetPatient</span>(patientId, .response)
}

} Return sc }

BO は受信した HS.FHIRServer.Interop.Request タイプのメッセージを確認します。この場合は、GET を設定して、Partient リソースに対応するパスに、以下の GetPatient メソッドが呼び出されることを示して確認します。

Method GetPatient(patientId As%String, Output patient As Adapter.Message.FHIRResponse) As%Status
{
  Set tSC = $$$OKset sql="SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE id = ?"//perform the Selectset tSC = ..Adapter.ExecuteQuery(.resultSet, sql, patientId)

If resultSet.Next() { set personResult = {"id":(resultSet.GetData(1)), "name": (resultSet.GetData(2)), "lastname": (resultSet.GetData(3)), "phone": (resultSet.GetData(4)), "address": (resultSet.GetData(5)), "city": (resultSet.GetData(6)), "email": (resultSet.GetData(7)), "nhc": (resultSet.GetData(8)), "postalCode": (resultSet.GetData(9)), "birthDate": (resultSet.GetData(10)), "dni": (resultSet.GetData(11)), "gender": (resultSet.GetData(12)), "type": ("Patient")}

} else { set personResult = {} }

//create the response messagedo patient.Resource.Insert(personResult.%ToJSON())

<span class="hljs-keyword">Return</span> tSC

}

ご覧のように、このメソッドは HIS のデータベースにクエリを発行し、すべての患者情報を取得してから後で String に変換されて Adapter.Message.FHIRResponse タイプの変数に格納される DynamicObject を生成します。 後でトレースでレスポンスを表示できるように、Resource プロパティを String リストとして定義しました。 直接 DynamicObjects として定義し、後続の変換を省略することもできます。

Bundle かどうかの確認:

BO からのレスポンスによって、Bundle タイプであるか(今後公開される記事で説明します)単なる Resource であるかをチェックします。

DynamicObject の作成:

BO レスポンスを DynamicObject に変換し、それを一時コンテキスト変数(context.temporalDO)に割り当てます。 変換に使用される関数は以下のとおりです。

##class(%DynamicAbstractObject).%FromJSON(context.FHIRObject.Resource.GetAt(1))

FHIR の変換:

DynamicObject タイプの一時変数を使用して、クラス HS.FHIR.DTL.vR4.Model.Resource.Patient のオブジェクトへの変換を行います。 他のタイプのリソースを探す場合は、タイプごとに特定の変換を定義する必要があります。 では、変換を確認しましょう。

この変換によって、BS InteropService が解釈するオブジェクトが得られます。 結果を context.PatientResponse 変数に格納します。

Stream へのリソースの割り当て:

FHIR の変換で取得した変数 context.PatientResponse を Stream に変換します。

QuickStream への変換:

response 変数に、クライアントに戻す必要のあるすべてのデータを割り当てます。

set qs=##class(HS.SDA3.QuickStream).%New()
 set response.QuickStreamId = qs.%Id()
 set copyStatus = qs.CopyFrom(context.JSONPayloadStream)
 set response.Response.ResponseFormatCode="JSON"set response.Response.Status=200set response.ContentType="application/fhir+json"set response.CharSet = "utf8"

この場合、200 レスポンスを常に返します。 本番環境では、検索されたリソースが正しく取得されたことを確認し、取得されていない場合は、レスポンスのステータスを 200 から「見つかりません」に対応する 404 に変更します。 このコード箇所で見られるように、オブジェクト HS.FHIR.DTL.vR4.Model.Resource.Patient
は Stream に変換されて、 HS.SDA3.QuickStream として格納されます。そのオブジェクトの識別子を QuickStreamID 属性に追加されるため、その後 InteropService サービスによって結果を JSON として正しく戻せるようになります。

まとめ:

この記事で行ったことをまとめましょう。

  1. GET タイプのリクエストを送信して、定義された ID を持つ Patient リソースを検索しいました。
  2. BS InteropService は構成済みの BP にリクエストを転送しました。
  3. BP は、HIS データベースを操作する BO を呼び出しました。
  4. 構成済みの BO は、HIS データベースから患者データを取得しました。
  5. BP は結果を、デフォルトの InteropService が作成された BS によって理解可能なオブジェクトに変換しました。
  6. BS はレスポンスを受け取り、クライアントに転送しました。

ご覧のように、操作は比較的単純で、より多くのタイプのリソースをサーバーに追加する場合、BO に取得される新しいリソースに対応するデータベースのテーブルにクエリを追加し、BO の結果を対応する HS.FHIR.DTL.vR4.Model.Resource.* タイプのオブジェクトに変換する処理を BP に含めます。

次の記事では、Patient タイプの新しい FHIR リソースを HIS データベースに追加する方法を説明します。

お読みいただきありがとうございました!

0
0 141
記事 Toshihiko Minamoto · 5月 16, 2024 3m read

FHIR がシステム間の相互運用性と互換性に関するあらゆる問題に対する万能薬であり、ソリューションであることはご存知のことでしょう。 これは、FHIR リソースを手に掲げてそれに興じる戦士の画像です。

ですが、戦士ではない私たちのために、少しだけ紹介したいと思います。

FHIR とは?

早速定義を述べると、FHIR(Fast Healthcare Interoperability Resource; 高速ヘルスケア相互運用性リソース)とは、ヘルスケア産業において医療データを様々なシステム間で電子的にやり取りできるようにするために、HL7(Health Level 7) 規格化組織が作成した相互運用性の規格です。

FHIR の基盤テクノロジー

REST API や JSON 形式による HTTP 呼び出しの組み合わせを主としています(使用方法に応じて XML やその他の通信も可能)。

FHIR の操作方法

一般に、GET (サーバーからデータを取得)、PUT (データの更新)、POST (データの保存)、および DELETE (削除)などの HTTP 呼び出しを使用して通信する FHIR サーバーを使用するのが最も簡単です。 .

FHIR はサーバーとクライアント間でデータの送受信に使用されるリソースの概念を処理します。 これらのリソースはシステム間の 80% の相互通信のニーズに対応することを目指しています。 以下は、デフォルトで使用できるリソースの画像です。

ご覧のように、各リソースにはリソースの成熟度を示す数字か文字が備わっています(N は標準を表します)。 FHIR の公式ドキュメントには多数の例が記載されています。

Resource の発展形が Bundle です。これは大まかに言うと、同一の JSON 内にパッケージされたリソースのセットで、サーバーにクエリしたり、バッチやトランザクションで CRUD 操作を実行したりするために使用されます。

さて、FHIR が素晴らしいことはわかりますが、FHIR が定義する基準に従って動作するように設計されていないレガシーシステムでは、これをどのように応用できるのでしょうか?

FHIR アダプター

InterSystems はお客様に FHIR アダプター機能を提供しています。これを使用することで、既存のシステム上にビジネスレイヤーをセットアップし、FHIR ファサードとして知られるものを作成することができます。 以降の記事では、FHIR オブジェクトの操作方法と、PostgreSQL データベースを使用する HIS(健康情報サービス)システムがどのようなものかを示す簡単なシミュレーションの操作方法を説明します。

説明を理解しやすくするために、今後使用するサンプルを自動的にセットアップする OpenExchange アプリケーションを提供しています。

ワークショップの展開

今後の記事では、以下のポイントについて説明します。

  1. IRIS インスタンスにおける FHIR アダプターのアーキテクチャ
  2. 患者タイプリソースを HIS に登録する
  3. REST API 呼び出しを使って ID で患者をクエリする
  4. 患者と医療センターデータの Bundle を HIS に登録する

ご興味があれば、 コミュニティで近日公開される記事にご期待ください!

0
0 124
記事 Toshihiko Minamoto · 5月 9, 2024 3m read

VS Code でファイルを編集しているときに、グローバル値のチェックやいくつかの ObjectScript コマンドの実行が必要だったことはありませんか? これが可能になりました。しかもセットアップは不要です! vscode-objectscript 拡張機能バージョン 2.10.0 以上を持っており、InterSystems IRIS 2023.3 以降に接続している場合は、サーバーの場所に関係なくサーバーへのターミナル接続を開けるようになりました。

この新しいターミナルを開く方法には 3 つあります。

WebSocket ターミナルは、読み取り、ネームスペースの切り替え、中断、カスタムターミナルプロンプトなど、標準 ObjectScript シェルの多数の機能をサポートしていますが、 この記事では、これに特有の 3 つの機能に焦点を当てたいと思います。

  • コマンド入力は構文の色付けがされるため、実行する前に入力が構文的に正しいことを確認できます。 syntax colored input

  • ターミナルは複数行の編集モードをサポートしています。Enter を押すと、入力が実行されるのではなく新しい行が追加されます。 新しい行は、コマンド入力に閉じられていない開始波括弧 { または開始丸括弧 ( がある場合に追加されます。 multi-line input

  • VS Code のシェル統合に完全に組み込まれているため、VS Code はコマンドの入出力を検出できます。 このため、コマンドを簡単に再実行したり、カーソルでテキストをハイライトせずにコマンド出力をクリップボードにコピーしたりすることができます。 command output

機能の全リストについては、公式ドキュメントをご覧ください。 この機能の改善アイデアがありますか? ぜひお聞かせください! 提案は、拡張機能の GitHub リポジトリに投稿できます。 この投稿のすべてのスクリーンショットでは、新しい "InterSystems Default Dark Modern" VS Code テーマが使用されています。InterSystems Language Server 拡張機能バージョン 2.4.0 以降で使用可能です。

0
0 256
記事 Toshihiko Minamoto · 1月 11, 2024 3m read

DeepSee で階層を設計する場合、子メンバーに 1 つの親しか指定できません。 子が 2 つの親に対応する場合には、信頼性のない結果が得られることになります。 類似する 2 つのメンバーが存在する場合、そのキーがそれぞれ一意になるように変更する必要があります。 これが起きる場合とそれを回避する方法について、2 つの例を見ながら説明します。

例 1

(アメリカには)Boston と言う都市がある州がたくさんあります。 私のサンプルデータでは、Boston, MA(マサチューセッツ州ボストン)と Boston, NY(ニューヨーク州ボストン)のレコードがあります。 次元は次のように定義されています。

私の場合、City(都市)と State(州)は単純な文字列です。 キューブにビルドすると、"MA" と "NY" の2 つの州メンバー、"Boston" と "Boston" の 2 つの都市メンバーが得られます。 Boston が 1 つではなく 2 つあるのはなぜでしょうか。 メンバーには 2 つの親メンバーを指定できないため、親ごとに異なるメンバーを作成する必要があります。 残念ながら、1 つのキーが 2 つの異なるメンバーを持っているため、この時点で「不適切な階層」状態になっています。

これを修正するには、キーを一意にしなければなりません。 "City" プロパティをレベルのソースプロパティとして直接使用する代わりに、ソース式を使うとこのメンバーを一意にすることができます。

こうすることで問題は解決されますが、望ましくない副作用が発生する可能性があります。 この式では、ピボットテーブルで以下のような結果になります。

これは表示フォーマットとして適切な場合も、適切でない場合もあります。 この時点ではキーとメンバー名が同じであるため、もう少し手を加えれば "Boston" だけが表示されるようにできますが、その後ろに一意のキーがあります。 詳細は、ドキュメントをご覧ください。

まとめると、異なるメンバーには一意のキーが必要です。 特定のキーを持つ子メンバーに、同じキーを持つ既存の子メンバーとは異なる親メンバーがある場合、そのキーは再利用されますが、新しいメンバーが生成されます。 このため、階層が無効になります。

例 2

無効な階層は日付階層によく見られます。 自然と以下の階層を作成する傾向にあります:
Year
Month
Week
Day

*** ここでは、具体的に DeepSee における Year、MonthYear、WeekYear、および DayMonthYear 抽出関数について言及しています。

知っての通り、Week は 2 つの月、さらには 2 つの年に含まれる場合があります。 ここでの他のすべてのレベル(Year、Month、Day)はどれも親に適合し、2 つの親に含まれることはありません。 このように Week メンバーで定義された階層がある場合、DeepSee エンジンがツリーをトラバースする方法(Jan 3 2020 から December 2019 の Week 52 2019 まで。 ただし Jan 3 2020 は December 2019 の子ではないため、エンジンはこれらの結果を削除する可能性があります)が原因で予期しない結果となる可能性があります。

一般的な解決策は、Week メンバーのみの階層を新たに作成することです。 こうすることで、元の階層の整合性を保持しながら、クエリ内で Week を使用することができます。

DeepSeeButtons on Open Exchange には、生成レポート内にこれらの条件をチェックして、階層が無効であるかを通知するセクションがあります。

*** InterSystems IRIS 2020.3 以降では、DeepSeeButtons は製品に含まれています。 追加情報は、ドキュメント をご覧ください***

0
0 136
記事 Toshihiko Minamoto · 12月 28, 2023 35m read

はじめに {#Webinar:ImplementingIntegrationswith.NetorJava-Introduction}

InterSystems IRIS 2020.1 には、Java または .NET で記述されたコンポーネントで IRIS 相互運用性プロダクションの開発を容易にする PEX(プロダクション拡張フレームワーク)が含まれています。

この PEX により、Java または .NET の知識を備える統合開発者は、InterSystems IRIS 相互運用性フレームワークの力、スケーラビリティ、および堅牢性を利用し、すぐに生産性を高めることができます。

IRIS 相互運用性フレームワークエキスパートに対し、PEX は既存の Java 言語または .NET 言語の外部コンポーネントとの統合を簡単にリッチ化することができます。

このチュートリアルでは、PEX を詳しく見ながら、.NET 開発者による PEX のはじめての使用を説明します。 コードは、https://github.com/es-comunidad-intersystems/webinar-PE にあります。

このチュートリアルは、https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EPEX にあるオンライン PEX ドキュメントを補足するものです。

 

InterSystems IRIS 相互運用性のアーキテクチャ {#Webinar:ImplementarIntegracionescon.NetoJava-ArquitecturadeInteroperabilidadInterSystemsIRIS}

PEX では、任意のプログラミング言語(現時点では Java または .NET。将来的に Python も追加)を選択して、IRIS 相互運用性プロダクションを形成するコンポーネント(ビジネスサービス、ビジネスプロセス、ビジネスオペレーション、インバウンドアダプター、アウトバウンドアダプター、メッセージ)を実装できます。

IRIS 相互運用性には主に 3 つのコンポーネントがあります。

  • ビジネスサービス: 外部から使用できるサービスを提供するコンポーネント。 ビジネスサービスの例には REST サービスや SOAP Web サービスがありますが、ディレクトリに書き込まれた新しいファイルの読み取りと処理を行うサービス、データベーステーブルの行の読み取りと処理を行うサービス、FTP 経由でファイルを読み取るサービスなどもあります。 ビジネスサービスにはインバウンドアダプターが関連付けられる場合があり、特定のプロトコルの実装情報を管理します(SQL テーブルを読み取る SQLInboundAdapter、ファイルを読み取る FileInboundAdapter など)。 ビジネスサービスは、情報の処理、IRIS 相互運用性メッセージへの情報のコピー、およびビジネスプロセスまたはビジネスオペレーションへのメッセージの送信とレスポンスがある場合はその待機などを行います。  
  • ビジネスオペレーション: IRIS 相互運用性メッセージを受け取り、外部システムで稼働するコンポーネント。プロトコル実装情報(TCP、REST、SOAP、Files、SQL など)を管理する OutboundAdapter を使用する場合があります。 ビジネスオペレーションは呼び出し元にレスポンスを返す場合とそうでない場合があります。  
  • ビジネスプロセス: IRS 相互運用性メッセージを受信する際に 1 つ以上の他のビジネスプロセスまたはオペレーションを処理して呼び出しを行い、複雑なオペレーションを実行したり、それを送信したビジネスサービスに情報を追加してメッセージ返したりするオーケストレーションプロセスです。

コンポーネント構成は、「Production」にグループ化されます。これは、IRIS が起動する際にまとめて起動するすべての統合の定義と、これらの統合の各コンポーネントの構成を含む InterSystems IRIS クラスです。 IRIS クラスとして編集するか、管理ポータルから変更できます。

 

PEX のアーキテクチャ {#Webinar:ImplementarIntegracionescon.NetoJava-ArquitecturadePEX}

.NET または Java コードを実行するために、InterSystems IRIS は対応するオブジェクトゲートウェイを使用します。 このゲートウェイはローカルまたはリモートサーバー上にインスタンス化できます。

Java と .NET ゲートウェイ {#Webinar:ImplementarIntegracionescon.NetoJava-LosJavay.NetGateways}

ゲートウェイは、特定の TCP/IP ポートをリッスンし、IRIS プロセスからのリクエストを受信して実行し、結果を返すネイティブの .NET または Java コンポーネントです。

ビルド済みの PEX コンポーネント {#Webinar:ImplementarIntegracionescon.NetoJava-LosComponentesPEXpre-construidos}

IRIS プロダクションで PEX を使用するには、Java または .NET で開発されたコンポーネントごとに、ビルド済みのコンポーネントをプロダクションに追加する必要があります。 ビルド済みのコンポーネントはそれが参照する外部要素のラッパーとして機能し、そのプロパティと構成をプロダクション内で定義することができます。

対応するゲートウェイから実行している Java または .NET コンポーネントは、既存の PEX コンポーネントを継承している(サブクラス化される)必要があります。 これらの要素の概要を以下に示します。

IRIS コンポーネント {#Webinar:ImplementarIntegracionescon.NetoJava-ComponentesdeIRIS}

.NET と Java 用

<th>
  クラス
</th>

<th>
  コメント
</th>
<td>
  EnsLib.PEX.BusinessService
</td>

<td>
  構成されるアダプター: Ens.InboundAdapter
</td>
<td>
  EnsLib.PEX.BusinessProcess
</td>

<td>
   
</td>
<td>
  EnsLib.PEX.BusinessOperation
</td>

<td>
   
</td>
<td>
  EnsLib.PEX.InboundAdapter
</td>

<td>
   
</td>
<td>
  EnsLib.PEX.OutboundAdapter
</td>

<td>
   
</td>
<td>
  EnsLib.PEX.Message
</td>

<td>
  PEX コンポーネントに送信するメッセージ
</td>
<td>
  IRIS メッセージにマッピング
</td>

<td>
  IRIS コンポーネントへの PEX コンポーネントのメッセージ
</td>
機能
ビジネスサービス
ビジネスプロセス
ビジネスオペレーション
インバウンドアダプター
アウトバウンドアダプター
PEX メッセージ
IRIS メッセージ

 

Java または .NET コンポーネント

これらのコンポーネントは Java または .NET プロジェクトに追加されるライブラリに提供されています。 PEX プロジェクトで参照する最低限のライブラリは、ゲートウェイのライブラリと同じです。 また、IRISObject メッセージを使って PEX から IRIS コンポーネントを呼び出す場合、IRISClient ライブラリを参照する必要があります。

<th>
  ライブラリ
</th>
<td>
  &lt;installdir>\dev\dotnet\bin\v4.5\InterSystems.Data.Gateway64.exe 
  &lt;installdir>\dev\dotnet\bin\v4.5\InterSystems.Data.IRISClient.dll
</td>
<td>
  &lt;installdir>\dev\java\lib\JDK18\intersystems-gateway-3.1.0.jar 
  &lt;installdir>\dev\java\lib\JDK18\intersystems-jdbc-3.1.0.jar 
  &lt;installdir>\dev\java\lib\gson\gson-2.8.5.jar
</td>
言語
.NET
Java

 

.NET コンポーネントに使用するスーパークラス

<th>
  .NET クラス
</th>
<td>
  InterSystems.EnsLib.PEX.BusinessService
</td>
<td>
  InterSystems.EnsLib.PEX.BusinessProcess
</td>
<td>
  InterSystems.EnsLib.PEX.BusinessOperation
</td>
<td>
  InterSystems.EnsLib.PEX.InboundAdapter
</td>
<td>
  InterSystems.EnsLib.PEX.OutboundAdapter
</td>
<td>
  InterSystems.EnsLib.PEX.Message
</td>
<td>
  InterSystems.Data.IRISClient.ADO.IRISObject
</td>
機能
ビジネスサービス
ビジネスプロセス
ビジネスオペレーション
インバウンドアダプター
アウトバウンドアダプター
PEX メッセージ
IRIS メッセージ

 

Java コンポーネントに使用するスーパークラス

<th>
  Java クラス
</th>
<td>
  com.intersystems.enslib.BusinessService
</td>
<td>
  com.intersystems.enslib.BusinessProcess
</td>
<td>
  com.intersystems.enslib.BusinessOperation.
</td>
<td>
  com.intersystems.enslib.pex.InboundAdapter
</td>
<td>
  com.intersystems.enslib.pex.OutboundAdapter
</td>
<td>
  com.intersystems.enslib.pex.Message
</td>
<td>
  &lt;...>.IRISObject
</td>
機能
ビジネスサービス
ビジネスプロセス
ビジネスオペレーション
インバウンドアダプター
アウトバウンドアダプター
PEX メッセージ
IRIS メッセージ

 

ユーティリティ関数 {#Webinar:ImplementarIntegracionescon.NetoJava-FuncionesdeUtilidades}

ObjectScript に実装されているコンポーネントでは、InterSystems IRIS は、マクロの形式で IRIS イベントログに情報を入力するためのコマンドをいくつか提供しています。 これらのメソッドは、以下のように Java と .NET で使用できます。

<th>
  説明
</th>
<td>
  「info」ステータスのメッセージをイベントログに追加します。
</td>
<td>
  「alert」ステータスのメッセージをイベントログに追加します。
</td>
<td>
  「warning」ステータスのメッセージをイベントログに追加します。
</td>
<td>
  「error」ステータスのメッセージをイベントログに追加します。
</td>
<td>
  「assert」ステータスのメッセージをイベントログに追加します。
</td>
メソッド
LOGINFO(message)
LOGALERT(message)
LOGWARNING(message)
LOGERROR(message)
LOGASSERT(message)

 

ネイティブコンポーネントと PEX コンポーネントの相互運用性 {#Webinar:ImplementarIntegracionescon.NetoJava-InteroperabilidadentrecomponentesnativosycomponentesPEX}

InterSystems IRIS のネイティブ ObjectScript 言語のコンポーネントと Java または .NET で開発された PEX コンポーネントを組み合わせることが可能です。

  • 両方のコンポーネントが BusinessService タイプと InboundAdapter または BusinessOperation と OutboundAdapter である場合、開発者は呼び出しに使用するデータ/オブジェクトのタイプを選択できます。呼び出しは IRIS と Java/.NET ゲートウェイの間で、ゲートウェイを判定するデータ変換ルールに従って行われます。
  • 情報を交換する両方のコンポーネントがビジネスホスト(BS、BP、BO)である場合、メッセージ(リクエストまたはレスポンス)を受け取るコンポーネントがは、メッセージに使用するオブジェクトのタイプを強制します。
    • PEX コンポーネントは常にメッセージ EnsLib.PEX.Message を受信します。
    • IRIS コンポーネントはメッセージ IRISObject を受信します。

EnsLib.PEX.Message {#Webinar:ImplementarIntegracionescon.NetoJava-EnsLib.PEX.Message}

EnsLib.PEX.Message を使用すると、.NET または Java で定義された構造のメッセージを IRIS ObjectScript で表現できます。 IRIS は DynamicObject 関数を使用してプロパティを操作します。 内部的には、IRIS は JSON を IRIS と .NET/Java 間のトランスポート媒体として使用します。 IRIS で PEX メッセージを作成する際、メッセージを使用するためにインスタンス化される必要のある .NET/Java クラスの名前は %classname として設定される必要があります。

IRISObject {#Webinar:ImplementarIntegracionescon.NetoJava-IRISObject}

ビルド済みの InterSystems IRIS コンポーネントを PEX コンポーネントから呼び出すには、IRISObject タイプのメッセージが定義されている必要があります(.NET では、完全なクラスは InterSystems.Data.IRISClient.ADO.IRISObject です)。

PEX と .NET の最初のステップ {#Webinar:ImplementarIntegracionescon.NetoJava-PrimerosPasosconPEXy.NET}

最初のステップでは、以下を行います。

  • 必要な PEX ライブラリ、.NET フレームワークバージョン 4.5 を使って .NET プロジェクトを作成する
  • .NET プロジェクトに BusinessOperation と単純なメッセージを追加する
  • InterSystems IRIS の .NET ゲートウェイを構成する
  • 相互運用性プロダクションを作成する
  • ビルド済みの BO を追加する

Visual Studio 2019 で .NET プロジェクトを作成する {#Webinar:ImplementingIntegrationswith.NetorJava-Creatinga.NETProjectwithVisualStudio2019}

Visual Studio で新しい「Class Library .Net Standard in C #」タイプのプロジェクトを作成します。 これを「PEX.Webinar.FirstDemo」とします。

PEX を操作するには、「Solution Explorer」と「Add Reference(参照を追加)」コンテキストメニューから必要な依存関係を追加します。

次に「Browse(参照)」ボタンを使って、2 つのライブラリ(.NET Framework 4.5! 対応)を追加します。これは、InterSystems IRIS インストールのサブディレクトリにあります。

<installdir>\dev\dotnet\bin\v4.5\InterSystems.Data.Gateway64.exe <installdir>\dev\dotnet\bin\v4.5\InterSystems.Data.IRISClient.dll

ソリューション Explorer Class 1.cs から「FirstOperation.cs」に名前が変更され、クラスは BusinessOperation PEX クラス(InterSystems.EnsLib.PEX.BusinessOperation)から継承するように変更されます。 すべての 3 つの PEX.BusinessOperation メソッドは上書きされています。

<th>
  説明
</th>
<td>
  このコンポーネントがプロダクションで開始されるときに 1 回実行します。 これにより、ライブラリ、接続、および変数を起動できるようになります...
</td>
<td>
  このコンポーネントまたはプロダクションが停止するときに 1 回実行します。 コンポーネントのライフサイクルで使用されたリソースを解放できるようになります。
</td>
<td>
  受信するメッセージごとに実行します。 メッセージを処理し、レスポンスを返すことができます。
</td>
メソッド
OnInit
OnTearDown
OnMessage

 

この時点では、メッセージや実行するタスクは定義されていません。 そのため、LOGINFO 関数は 3 つのメソッドに追加されています。 一方で、スーパークラスメソッドが呼び出される必要がないため、基底クラス(base.OnInit ()、base.OnTearDown()、base.OnMessage)への呼び出しを除去できます。 実装を以下のようにします。

<th>
  初期デプロイ
</th>
<td>

OnInit

メソッド
OnInit
public override void OnInit()         {             LOGINFO("PEX.Webinar.FirstDemo.FirstOperation:OnInit()");         }
OnTearDown
<td>
  <b>OnTearDown</b>
public override void OnTearDown()        {            LOGINFO("PEX.Webinar.FirstDemo.FirstOperation:OnTearDown()");        }
OnMessage
<td>
  <b>OnMessage</b>
public override object OnMessage(object request)        {            LOGINFO("PEX.Webinar.FirstDemo.FirstOperation:OnMessage()");            return request;        }

 

これにより、「Build Solution」(ソリューションをビルド)メニューで、最初の .NET プロジェクトバージョンをコンパイルできます。 すると IRIS は以下のファイルを生成します。これは次に使用されるファイルです。

1>------ Build started: Project: PEX.Webinar.FirstDemo, Configuration: Debug Any CPU ------ 1>PEX.Webinar.FirstDemo -> C:\Dev\PEX\PEX.Webinar.FirstDemo\bin\Debug\netstandard2.0\PEX.Webinar.FirstDemo.dll ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

InterSystems IRIS 相互運用性プロダクションの作成 {#Webinar:ImplementingIntegrationswith.NetorJava-InterSystemsIRISInteroperabilityProductionCreation}

IRIS 管理ポータルで、「Interoperability」(相互運用性)メニュー、「Ensemble」ネームスペース、および「Configure」(構成)および「Production」(プロダクション)メニューを使用できるようになりました。 「Actions」(アクション)タブと「New」(新規)ボタンを使って、新しい IRIS 統合プロダクションを定義できます。

テストサービスを有効にするには、「Settings」(設定)タブを選択し、リストの最後のセクション(「Development and Debugging」)で「Testing Enabled」を有効にし、Apply(適用)ボタンをクリックします。

.NET ゲートウェイをプロダクションに追加する {#Webinar:ImplementingIntegrationswith.NetorJava-Addingthe.NETGatewaytotheProducción}

PEX を操作するには、対応する Java または .NET ゲートウェイが起動している必要があります。 InterSystems では、プロダクション環境(ライブ)の System Administration(システム管理)/Configuration(構成)/Connectivity(接続)/Object Gateways(オブジェクトゲートウェイ)メニューからこれらのゲートウェイを構成して起動することを推奨しています。 この開発環境においては、コンポーネントをプロダクションに直接追加してゲートウェイを起動することができます。 このため、ワンクリックで、ゲートウェイをプロダクションとして同時に開始・停止できるようになるため、再コンパイルする際に .NET プロジェクトの .DLL へのアクセスを解放することができます(Visual Studio では .NET ゲートウェイが起動している場合に再コンパイルすることができません)。

「.NET Gateway」コンポーネントを追加するには、「Services」(サービス)の横にある「+」ボタンを使用して、プロダクションにビジネスサービスを追加します。 追加されるコンポーネントクラス名(Service クラス)は「EnsLib.DotNetGateway.Service」で、「Enable now」(今すぐ有効化)を選択してこのコンポーネントを有効にします。

追加されたコンポーネントの構成パラメーターは、それをクリックして、「Settings」(設定)タブに値を入力し、「Apply」(適用)ボタンをクリックして編集できます。

<th>
  値
</th>

<th>
  説明
</th>
<td>
  44444
</td>

<td>
  デフォルトのポート 55000 が別のプロセスでビジー状態である場合に、このポートに変更できます。
</td>
<td>
  &lt;installdir>\dev\dotnet\bin\v4.5\
</td>

<td>
  これは、実行可能なゲートウェイ(InterSystems.Data.Gateway64.exe)が存在する場所を示し、 &lt;InstallDir> は IRIS インストールディレクトリです。例: C:\InterSystems\IRIS20201\dev\dotnet\bin\v4.5\
</td>
<td>
  true
</td>

<td>
  64 ビットゲートウェイバージョンを選択します
</td>
<td>
  4.5
</td>

<td>
  .NET バージョンは 4.5 である必要があります
</td>
パラメーター
Port(ポート)
FilePath(ファイルパス)
Exec64
.NET Version(.NET バージョン)

 

.NET で作成されたビジネスオペレーションを追加する  {#Webinar:ImplementingIntegrationswith.NetorJava-AddingtheBusinessOperationcreatedin.NET}

次に、前に作成された .NET コードを参照するために、「Operations」(オペレーション)の横にある「+」ボタンを使用して、PEX ビジネスオペレーションを追加します。 クラスタイプは 「EnsLib.PEX.BusinessOperation」で、(オプションの)コンポーネントは「PEX.Webinar.FirstOperation」と呼ばれます。「Enable Now」を使って有効にされます。

次に、コンポーネントを構成し(追加されたコンポーネントをクリックします)、右側の「Settings」(設定)を選択します。

<th>
  値
</th>

<th>
  説明
</th>
<td>
  PEX.Webinar.FirstDemo.FirstOperation
</td>

<td>
  生成される .NET クラスの名前
</td>
<td>
  44444
</td>

<td>
  .NET ゲートウェイが構成されている TCP ポート
</td>
<td>
  C:\Dev\PEX\PEX.Webinar.FirstDemo\bin\Debug\ netstandard2.0\PEX.Webinar.FirstDemo.dll
</td>

<td>
  含める .DLL の場所。 これは、Visual Studio ビルドが .DLL を生成した場所です。
</td>
パラメーター
Remote Classname(リモートクラス名)
Gateway Port(ゲートウェイポート)
Gateway Extra CLASSPATH(ゲートウェイ追加クラスパス)

変更を適用するために、「Apply」(適用)ボタンをクリックします。

プロダクションを追加してテストする {#Webinar:ImplementingIntegrationswith.NetorJava-AddingandStartingtheProduction}

「Start」(起動)ボタンによって、プロダクションとそのすべてのコンポーネントが起動します。

InterSystems IRIS では、コンポーネントの単純な隔離テストを実施できます。 「PEX.Webinar.FirstOperation」という「ビジネスオペレーション」を選択し、Actions(アクション)タブから「Test」(テスト)ボタンを選択します。

次の画面では以下のようにデータが入力されます。

<th>
  値
</th>

<th>
  説明
</th>
<td>
  EnsLib.PEX.Message
</td>

<td>
  送信される IRIS メッセージのタイプ。 常に EnsLib.PEX.Message です。 Java または .NET PEX コンポーネントに値を渡す動的プロパティを使用できます。
</td>
<td>
  InterSystems.EnsLib.PEX.Message
</td>

<td>
  Java または .NET にコンポーネントリクエストとして定義されるメッセージのクラス名。 .NET の場合、これは InterSystems.EnsLib.PEX.Message またはサブクラスである必要があります。
</td>
パラメーター
リクエストタイプ
%classname

「Invoke Testing Service」(テストサービスを呼び出す)をクリックすると、IRIS 相互運用性フレームワークはメッセージをコンポーネントに送信し、.NET コードが実行されます。

「Visual Trace」(ビジュアルトレース)をクリックすると、詳細を表示できます。 白いドット(#3)は、.NET に実装された「LOGINFO」トレースをひょじします。

IRIS イベントログには、OnInit() と OnTearDown() を含む生成されるすべての LOGINFO() メッセージのコンパイルが含まれます。

PEX での次のステップ: プロダクションの完成 {#Webinar:ImplementingIntegrationswith.NetorJava-NextStepswithPEX:CompletingtheProduction}

以下のように、他のタイプのコンポーネントが .NET で作成されます。

  • .NET メッセージ
  • .NET ビジネスサービス
  • .NET ビジネスプロセス

データによる PEX メッセージの作成と使用 {#Webinar:ImplementingIntegrationswith.NetorJava-CreatingandUsingaPEXMessagewithData}

Ensemble から PEX コンポーネントに情報を渡すために、渡される情報のプロパティを使って、サブクラス InterSystems.EnsLib.PEX.Message として、.NET でクラスを定義します。 単純化された例をそのまま使用し、メッセージに文字列「value」プロパティを追加します。 PEX ビジネスオペレーションは、コンテンツが大文字に変換された同じタイプのメッセージになります。

.NET のメッセージクラス {#Webinar:ImplementarIntegracionescon.NetoJava-Clasedemensajeen.NET}

新しい「FirstMessage.cs」ファイルは、以下の定義を使って .NET プロジェクトに追加されます。

FirstMessage

using System; using System.Collections.Generic; using System.Text; namespace PEX.Webinar.FirstDemo {     class FirstMessage : InterSystems.EnsLib.PEX.Message     {         public string value;     } }

.NET からメッセージを使用する {#Webinar:ImplementarIntegracionescon.NetoJava-Usodelmensajedesde.NET}

FirstOperation ビジネスオペレーションでは、OnMessage のデータ型はオブジェクトとして定義されます。 これを使用するには、「FirstMessage」クラスにキャストする必要があります。

OnMessageV2

public override object OnMessage(object request)         {             LOGINFO("PEX.Webinar.FirstDemo.FirstOperation:OnMessage()");             ///レスポンスメッセージがインスタンス化             FirstMessage response = new FirstMessage();             //値がリクエストから「uppercase」でコピーされる             response.value = ((FirstMessage)request).value.ToUpper();             //レスポンスが返される             return response;         }

IRIS プロダクションを停止する(「Stop」(停止)ボタンを使用)か、少なくとも .NET ゲートウェイを無効にして(コンポーネントをダブルクリック)、.NET プロジェクトをコンパイルする必要があります。

InterSystems IRIS からクラスを使用する {#Webinar:ImplementarIntegracionescon.NetoJava-UsodelaclasedesdeInterSystemsIRIS}

「Test」(テスト)ボタン(テストサービス)では、EnsLib クラスの動的プロパティを filled.PEX.Message にできません。 ただし、テストサービスは IRIS ネイティブの「Ens.StringRequest」メッセージタイプとともに使用でき、(データ変換を通じて)PEX メッセージに変換することができます。 変換は、ビルド済みのルータータイプのビジネスプロセスから呼び出されます。

データ変換 {#Webinar:ImplementingIntegrationswith.NetorJava-DataTransformation}

データ変換は、データをネイティブ Ensemble メッセージからコピーするために作成されます。

<th>
  ソース
</th>

<th>
  ターゲット
</th>

<th>
  コメント
</th>
<td>
  EnsLib.StringRequest
</td>

<td>
  EnsLib.PEX.Message
</td>

<td>
  変換ソースとターゲットのクラス
</td>
<td>
  該当なし
</td>

<td>
  PEX.Webinar.FirstDemo.FirstMessage
</td>

<td>
  PEX メッセージ(ターゲット)の場合、%classname は、使用される .NET/Java クラス名を定義します。
</td>
<td>
  source.StringValue
</td>

<td>
  target.%jsonObject.value
</td>

<td>
  ターゲット PEX メッセージでは、動的プロパティは %jsonObject 内にあります。
</td>
 
クラス
%classname
プロパティ

 

データ変換は、管理ポータルの「Interoperability」>「ビルド」>「データ変換」にある「New」(新規)ボタンを使用して作成できます。

その後、「ソース」から「ターゲット」メッセージへの変換のパラメーターは、グラフィックツールを使用して、下に表示される 2 行のテキストを取得するように右側の「Action」(アクション)タブにテキストを入力することで、前のテーブルで説明されたとおりに定義されます。 すると、「Tools」(ツール)タブで保存、コンパイル、およびテストすることができます。

次に、「Tools」(ツール)タブを開いて、このデータ変換をテストできます。

ソースメッセージの値を指定(StringValue)して、結果を検証できるテストウィンドウが開きます。

ご覧のとおり、PEX メッセージは内部 JSON 表現を使用して、IRIS と Java または .NET 間で値を送信しています。

ルータータイプのビジネスプロセスを作成する {#Webinar:ImplementingIntegrationswith.NetorJava-CreatingaRoutertypeBusinessProcess}

ルータータイプのビジネスプロセスをプロダクションに追加し、作成されるデータ変換を呼び出して既存のビジネスオペレーションにメッセージを送信するルーティングルールを定義できるようになりました。

管理ポータルで、「Production Configuration」(プロダクション構成)画面(「Interoperability」/「構成」/「プロダクション」)に戻り、「Processes」(プロセス)の横にある「+」をクリックして以下のように入力し、ビジネスプロセスタイプ「EnsLib.Message.Router」を追加します。

<th>
  値
</th>

<th>
  説明
</th>
<td>
  EnsLib.MsgRouter.RoutingEngine
</td>

<td>
  ルールベースのメッセージルーティングを行うビジネスプロセス
</td>
<td>
  TRUE
</td>

<td>
  ルーティングルールのスケルトンを定義します
</td>
<td>
  MessageRouter
</td>

<td>
  プロダクションにおけるこのコンポーネントの名前
</td>
<td>
  TRUE
</td>

<td>
  このコンポーネントを直ちに有効化します
</td>
<td>
  1
</td>

<td>
  スケーラビリティ。 同時に実行するアクティブプロセス数を定義できます。
</td>
パラメーター
Business Process Class(ビジネスプロセスクラス)
Auto-Create Rule(自動作成ルール)
Business Process Name(ビジネスプロセス名)
Enable Now(今すぐ有効化)
Pool Size(プールサイズ)

 

このコンポーネントのルーティングルールの編集が残っています。 これを行うために、プロダクションの「MessageRouter」コンポーネントを選択し、ビジネスルール名の横にある虫眼鏡をクリックします。

ルールエディターでは、変換が適用された後に、すべての「Ens.StringRequest」メッセージを「PEX.Webinar.FirstOperation」ビジネスオペレーションに送信するようにルールを編集できます。 「+」ボタンを使用すると、ルールに要素を追加し、あらかじめ要素を選択する際には要素を編集することができます。 以下のようにルールを作成します。

デフォルトでは、このコンポーネントはレスポンスを期待しません。 コンポーネントの「Settings」(設定)タブで、ビジネスオペレーションからレスポンスを受信するように設定を変更しましょう。

テストサービスでルーターをテストする {#Webinar:ImplementarIntegracionescon.NetoJava-PruebadelEnrutadorconelServiciodePruebas}

プロダクションページに戻り、「Start」(起動)ボタンを使ってプロダクションを起動し、「MessageRouter」コンポーネントを選択します。「Actions」(アクション)タブで、「Test」(テスト)ボタンをクリックし、「Ens.StringRequest」タイプのメッセージを送信します。

テストレスポンスメッセージを「Test Results」(テスト結果)で確認できます。完全なメッセージログには、IRIS 相互運用性プロダクションの様々なコンポーネントに関する実行の全詳細が表示されます。

レスポンス:

  {#Webinar:ImplementarIntegracionescon.NetoJava-}

メッセージのトレース:

.NET ビジネスサービス {#Webinar:ImplementarIntegracionescon.NetoJava-BusinessServiceen.NET}

.NET の実装 {#Webinar:ImplementingIntegrationswith.NetorJava-Implementationin.NET}

Visual Studio に戻り、.NET ビジネスサービスを作成します。 この最初の例では、このビジネスサービスに特定の「インバウンドアダプター」は関連付けられていません。(CallInterval 値の設定に従って)定期的に実行し、CallInterval ごとにビジネスサービスを呼び出す「EnsLib.InboundAdapter」コンポーネントを使用します。 

Visual Studio で、「FirstService」クラスの新しい「FirstService.cs」ファイルが生成されます。 インバウンドアダプターが検出する各イベントの処理は、「OnProcessInput」メソッドを使って行われます。 パラメーターに設定されるオブジェクトは、InboudAdapter の実装によって異なりますが、この場合は使用されていません。

FirstService:OnProcessInput

public override object OnProcessInput(object messageInput)         {             //新しいリクエストオブジェクトを作成             FirstMessage myRequest = new FirstMessage();             myRequest.value = "The send time is: " + System.DateTime.Now.ToString();             //レスポンスを待たずに送信:             //SendRequestAsync("PEX.Webinar.FirstOperation", myRequest);             //送信して、20秒のタイムアウトでレスポンスを待機する:             FirstMessage myResponse=(FirstMessage) SendRequestSync("PEX.Webinar.FirstOperation", myRequest, 20);             return null;         }

FirstService:OnProcessInput

public override object OnProcessInput(object messageInput)         {             //新しいリクエストオブジェクトを作成             FirstMessage myRequest = new FirstMessage();             myRequest.value = "The send time is: " + System.DateTime.Now.ToString();             //レスポンスを待たずに送信:             //SendRequestAsync("PEX.Webinar.FirstOperation", myRequest);             //送信して、20秒のタイムアウトでレスポンスを待機する:             FirstMessage myResponse=(FirstMessage) SendRequestSync("PEX.Webinar.FirstOperation", myRequest, 20);             return null;         }
 

プロダクションのコンポーネントにコンパイルせずにメッセージを送信するために、管理ポータルで構成されたパラメーターを使用します。値は「RemoteSettings」構成パラメーター内に JSON 文字列として指定されています。

パラメーター

class FirstService : InterSystems.EnsLib.PEX.BusinessService     {          /// <summary>         /// ポータルから変更できるパラメーター         /// </summary>         public string TargetConfigName;      (...)

「TargetConfigName」に指定された値が null に設定されている場合、ビジネスサービスはどの宛先にもメッセージを送信しません。 この問題をできるだけ早期に検出するためには、1 つのオプションとして、コンポーネントが開始したときに、OnInit() 呼び出し内で TargetConfigName の値を検証することができます。

FirstService:OnInit

public override void OnInit()         {            //プロパティが正しく報告されることを検証            if (TargetConfigName==null )             {                 LOGWARNING("TargetConfigName is missing; assign it a value in RemoteSettings");             }else             {                 LOGINFO("TargetConfigname=" + TargetConfigName);             }         }

次に、OnProcessInput を、TargetConfigName の値を使用するように変更しましょう。

OnProcessInput

//送信して、20 秒のタイムアウトでレスポンスを待機: FirstMessage myResponse=(FirstMessage) SendRequestSync(TargetConfigName, myRequest, 20);

InterSystems IRIS での PEX ビジネスサービス {#Webinar:ImplementarIntegracionescon.NetoJava-BusinessServicePEXenInterSystemsIRIS}

管理ポータルで「+」をクリックして、プロダクションの定義にビジネスサービスタイプのコンポーネントを追加します。 

次に、(追加されたコンポーネントをクリックして)コンポーネントを構成し、右側の「Settings」(設定)タブを選択します。 

<th>
  値
</th>

<th>
  説明
</th>
<td>
  20
</td>

<td>
  関連するアダプターの OnTask() の実行と OnProcessInput() への呼び出し間の時間
</td>
<td>
  PEX.Webinar.FirstDemo.FirstService
</td>

<td>
  生成される .NET クラスの名前
</td>
<td>
  TargetConfigName=PEX.Webinar.FirstOperation
</td>

<td>
  param=value フォーマットによるパラメーターの &lt;newline> 区切りのリスト
</td>
<td>
  44444
</td>

<td>
  .NET ゲートウェイが構成されている TCP ポート
</td>
<td>
  C:\Dev\PEX\PEX.Webinar.FirstDemo\bin\Debug\ netstandard2.0\PEX.Webinar.FirstDemo.dll
</td>

<td>
  含める .DLL の場所。 これは、Visual Studio ビルドが .DLL を生成した場所です。
</td>
パラメーター
CallInterval
Remote Classname(リモートクラス名)
Remote Settings(リモート設定)
Gateway Port(ゲートウェイポート)
Gateway Extra CLASSPATH(ゲートウェイ追加クラスパス)

 

このビジネスサービスが有効になると、関連アダプター(Ens.InboundAdapter)のOnTask() の CallInterval が実行されます。 ここでは、OnTask は FirstService.OnProcessInput() を呼び出すだけです。 つまり、CallInterval と FirstService.OnProcessInput() ごとに、「TargetConfigName」で定義されたコンポーネントにメッセージが送信されます。 これはメッセージビューアーで確認可能です。

呼び出しの詳細が含まれます。

.NET ビジネスプロセス {#Webinar:ImplementarIntegracionescon.NetoJava-BusinessProcess.NET}

.NET の実装 {#Webinar:ImplementingIntegrationswith.NetorJava-.NETImplementation}

どのビジネスホストとも同様に、ビジネスプロセスにはコールバックの OnInit() と OnTearDown() メソッドがあります。 具体的に、ビジネスプロセスのメソッドは以下のとおりです。

<th>
  説明
</th>
<td>
  ビジネスプロセスに送信されるメッセージごとに実行されます。 プロセスの最初のアクションを実装する場所であり、他のコンポーネント(プロセスまたはオペレーション)に非同期配信を行います。
</td>
<td>
  非同期呼び出しの応答ごとに 1 回実行されます。 レスポンスをマージして結果を保存できます。
</td>
<td>
  ビジネスプロセスの実行の最後に 1 回実行されます。 プロセスの最終レスポンスメッセージが作成されます。
</td>
メソッド
OnRequest(message)
OnResponse
OnComplete

ビジネスプロセスの実行時間は長期(数時間から数日、非同期レスポンスを待機)になる可能性があることに注意しておくことが重要です。 これを可能にするために、IRIS フレームワークはプロセスの実行を中断して再開することができます。 プロセス中に維持したいすべての値は、IRIS が永続的に保存するクラスのプロパティに追加しておく必要があります。 [persistent] という表記で指示する必要があります。

この例では、最小限の実装が行われており、ビジネスプロセスは受信する PEX メッセージを宛先にルーティングします。

管理ポータルで変更可能な 2 つの変数が使用されています。

FirstProcess

class FirstProcess: InterSystems.EnsLib.PEX.BusinessProcess     {         //呼び出しのタイムアウト
          public string Timeout = "PT10S";

          public string TargetConfigName;

          public override void OnInit()
          {
              //プロパティが正しく入力されていることを確認
              if (TargetConfigName == null)
              {
                  LOGWARNING("Missing value for TargetConfigName; It must be assigned a value in RemoteSettings");
              }
              else
              {
                  LOGINFO("TargetConfigname=" + TargetConfigName);
              }
          }
</td>

 

メッセージを受信すると、TargetConfigName に非同期に送信されます。

FirstProces:OnRequest

public override object OnRequest(object request)         {             LOGINFO("OnRequest");             SendRequestAsync(TargetConfigName, (InterSystems.EnsLib.PEX.Message)request, true); //ResponseRequired=true             SetTimer(Timeout, "HasTimedOut");             return null;         }

OnResponse では、レスポンスをマージすることができます。

FirstProcess:OnResponse

public override object OnResponse(object request, object response, object callRequest,  object callResponse, string completionKey)        {            LOGINFO("OnResponse, CompletionKey=" + completionKey);            if (completionKey!= "HasTimedOut")            {                response = (FirstMessage)callResponse;            }            LOGINFO("Response:" + response.ToString());            return response;        }

プロセスの最後に、レスポンスが返されます。

FirstProcess:OnComplete

public override object OnComplete(object request, object response)       {           LOGINFO("OnComplete");           return response;       }

InterSystems IRIS での PEX ビジネスプロセス {#Webinar:ImplementarIntegracionescon.NetoJava-BusinessProcessPEXenIntersystemsIRIS}

EnsLib.PEX.BusinessProcess ビジネスプロセスを追加し、必要に応じて構成し、メッセージを BO に直接送信する代わりに新しいプロセスに送信するように「PEX.Webinar.FirstProcess」ビジネスサービスの TargetCongiNmae を変更します。 

コンポーネントの「Settings」(設定)は以下のように定義されます。

<th>
  値
</th>
<td>
  PEX.Webinar.FirstDemo.FirstProcess
</td>
<td>
  Timeout=PT20STargetConfigName=PEX.Webinar.FirstOperation
</td>
<td>
  44444
</td>
<td>
  C:\Dev\PEX\PEX.Webinar.FirstDemo\bin\Debug\netstandard2.0\PEX.Webinar.FirstDemo.dll
</td>
パラメーター
Remote Classname(リモートクラス名)
Remote Settings(リモート設定)
Gateway Port(ゲートウェイポート)
Gateway Extra Classpath(ゲートウェイ追加クラスパス)

トレースの結果は以下のようになります。

まとめ

PEX では、統合を .NET または Java で非常に流ちょうに実装でき、これらの言語の経験が豊富な開発者に対して InterSystems IRIS 相互運用性レイヤーの全能力と堅牢性を提供しています。

0
0 186
記事 Toshihiko Minamoto · 12月 20, 2023 15m read

開発者の皆さん、こんにちは!

多くの方が、Open Exchange と GitHub で InterSystems ObjectScript ライブラリを公開しています。

でも、開発者がプロジェクトの使用とコラボレーションを簡単に行えるようにするにはどうしていますか?

この記事では、ファイルの標準セットをリポジトリにコピーするだけで、ObjectScript プロジェクトを簡単に起動して作業する方法をご紹介します。

では始めましょう!

概要 - 以下のファイルをこちらのリポジトリからお使いのリポジトリにコピーしてください。

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

すると、プロジェクトを起動して共同作業する標準的な方法が得られます。 以下は、この仕組みと動作する理由についての記事です。

注意: この記事では、InterSystems IRIS 2019.1 以降で実行可能なプロジェクトを対象としています。

InterSystems IRIS プロジェクトの起動環境の選択

通常、開発者には、プロジェクト/ライブラリを試して、素早く安全な方法であることを確認していただきたいと思っています。

私見としては、新しいものを素早く安全に起動するには、Docker コンテナが理想的だと考えています。起動、インポート、コンパイル、計算するあらゆるものがホストマシンにとって安全であり、いかなるシステムやコードも破壊されたり損なわれたりすることがないことを開発者に保証できるためです。 何らかの問題が発生した場合は、コンテナを止めて削除するだけで済みます。 アプリケーションが膨大なディスクスペースを占有するのであれば、コンテナを削除すれば、容量を解放できます。 アプリケーションがデータベース構成を破損するのであれば、破損した構成のあるコンテナを削除するだけです。 このように単純で安全なのです。

Docker コンテナでは、安全と標準化を得られます。

バニラ InterSystems IRIS Docker コンテナを実行するには、IRIS Community Edition イメージを実行するのが最も簡単です。

  1. Docker デスクトップをインストールします。 

  2. OS のターミナルで以下を実行します。

docker run --rm -p 52773:52773 --init --name my-iris store/intersystems/iris-community:2020.1.0.199.0
  1. 次に、ホストブラウザで管理ポータルを開きます。

http://localhost:52773/csp/sys/UtilHome.csp

  1. または IRIS へのターミナルを開きます。

    docker exec -it my-iris iris session IRIS

  2. IRIS コンテナが不要になれば、それを停止します。

    docker stop my-iris

さて、 IRIS を Docker コンテナで実行しますが、 開発者にはコードを IRIS にインストールして、いくらかの設定を行ってほしいと考えているとします。 以下ではこれについて説明します。

ObjectScript ファイルのインポート

最も単純な InterSystems ObjectScript プロジェクトには、クラス、ルーチン、マクロ、グローバルなどの一連の ObjectScript ファイルが含めることができます。 命名規則フォルダ構造の提案についての記事をご覧ください。

問題は、このコードをどのようにして IRIS コンテナにインポートするかです。

ここで役立つのが Dockerfile です。これを使用して、バニラ IRIS コンテナを取得し、リポジトリから IRIS にすべてのコードをインポートして、必要に応じて IRIS で設定を行います。 リポジトリに Dockerfile を追加する必要があります。

ObjectScript テンプレートリポジトリから取得した Dockerfile を調べてみましょう。

ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community
ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0
ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0
ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0
FROM $IMAGE

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src
COPY iris.script /tmp/iris.script # run iris and initial 

RUN iris start IRIS \
    && iris session IRIS &lt; /tmp/iris.script

 

最初の ARG の行は $IMAGE 変数を設定しており、それを FROM で使用します。 これは、$IMAGE 変数を変更するために FROM の前の最後の行が何であるかだけを切り替えて、さまざまな IRIS バージョンでコードをテスト/実行するのに適しています。 

以下のコードがあります。 

ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0

FROM $IMAGE

これは、IRIS 2020 Community Edition ビルド 199 を使用するということです。

リポジトリからコードをインポートするため、リポジトリのファイルを Docker コンテナにコピーする必要があります。 以下の行はそれを行います。

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src

USER root - ここで、ユーザーをルートに切り替えて、フォルダを作成してファイルを Docker にコピーします。

WORKDIR /opt/irisapp - この行では、ファイルをコピーする workdir をセットアップしています。

RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp   -  ここでは、irisowner ユーザーと IRIS を実行するグループに権限を付与しています。

USER irisowner - ユーザーを root から irisowner に切り替えます。

COPY Installer.cls .  - workdir のルートに Installer.cls をコピーしています。 このピリオドを忘れないでください。

COPY src src - ソースファイルをリポジトリの src フォルダから Docekr の workdir の src フォルダにコピーします。

次のブロックでは、初期スクリプトを実行し、インストーラーと ObjectScript コードを呼び出します。

COPY iris.script /tmp/iris.script # run iris and initial 
RUN iris start IRIS \
    && iris session IRIS &lt; /tmp/iris.script

COPY iris.script / - iris.script をルートディレクトリにコピーします。 コンテナをセットアップするために呼び出す ObjectScript が含まれます。

RUN iris start IRIS</span>  - IRIS を起動します。

&& iris session IRIS < /tmp/iris.script - IRIS ターミナルを起動し、それに最初の ObjectScript を入力します。

以上です! Docker にファイルをインポートする Dockerfile ができました。 installer.cls と iris.script の 2 つのファイルが残っています。ではそれらを詳しく見てみましょう。

Installer.cls

Class App.Installer
{

XData setup
{
<Manifest>
  <Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
  <Default Name="Namespace" Value="IRISAPP"/>
  <Default Name="app" Value="irisapp" />

  <Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

</Manifest>
}

ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
  #; Let XGL document generate code for this method. 
  Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")
}

}

率直に言って、ファイルのインポートに Installer.cls は必要ありません。 これは 1 行で実行可能です。 ただし、コードをインポートするほかに、CSP アプリのセットアップ、セキュリティ設定の追加、データベースとネームスペースの作成を行わなければなりません。

この Installer.cls では、 IRISAPP という名前で新しいデータベースとネームスペースを作成し、このネームスペースのデフォルトの /csp/irisapp アプリケーションを作成します。

すべては、<Namespace> 要素で行います。

<Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

そして、Import タグを使って、SourceDir からすべてのファイルをインポートします。

<Import File="${SourceDir}" Flags="ck" Recurse="1"/>

この SourceDir は変数であり、現在のディレクトリ/src フォルダに設定されています。

<Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>

これらの設定を含む Installer.cls によって、src フォルダから任意の ObjectScript コードをインポートするわかりやすい新しいデータベース IRISAPP を作成できるという確信を持つことができます。

iris.script

ここに、IRIS コンテナを起動する初期の ObjectScript セットアップコードを挿入してください。

例: 開発にはパスワードのプロンプトは不要であるため、ここでは、installer.cls を読み込んで実行してから、パスワードの初回変更リクエストを回避するために、UserPasswords を永久にしています。

; run installer to create namespace
do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
set sc = ##class(App.Installer).setup()  zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*") ; call your initial methods here
halt

docker-compose.yml

docker-compose.yml はなぜ必要なのでしょうか。Dockerfile と同様に、イメージをただビルドして実行するだけではいけないのでしょうか。 もちろん、そうすることは可能です。 ただし、docker-compose.yml を使用すれば作業が単純になります。

通常、docker-compose.yml は、1 つのネットワークに接続された複数の Docker イメージを起動するために使用されます。

docker-compose.yml は、多数のパラメーターを処理する場合に、1 つの Docker イメージの起動をより簡単にするためにも使用できます。 これを使用すれは、ポートのマッピング、ボリューム、VSCode 接続パラメーターなどを Docker に渡すことができます。

version: '3.6' 
services:
  iris:
    build: 
      context: .
      dockerfile: Dockerfile
    restart: always
    ports: 
      - 51773
      - 52773
      - 53773
    volumes:
      - ~/iris.key:/usr/irissys/mgr/iris.key
      - ./:/irisdev/app

ここでは、サービス iris を宣言しています。これは Dockerfile を使用し、IRIS の 51773、52773、53773 ポートを公開するサービスです。 また、このサービスは、ホストマシンのホームディレクトリの iris.key と期待される IRIS フォルダ、およびソースコードのルートフォルダと /irisdev/app フォルダの 2 つのボリュームのマッピングも行います。

docker-compose によって、セットアップされるパラメーターに関係なく、イメージをビルドして実行するためのコマンドをより短く、統一することができます。

いずれの場合でも、以下のようにしてイメージをビルドします。

$ docker-compose up -d

 そして以下のようにして IRIS ターミナルを開きます。

$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>

また、docker-compose.yml では、VSCode ObjectScript プラグインの接続もセットアップできます。

.vscode/settings.json

ObjectScript アドオン接続設定に関連しているのは、以下の部分です。

{
    "objectscript.conn" :{
      "ns": "IRISAPP",
      "active": true,
      "docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }
    }     

}

ここにあるのは設定です。VSCode ObjectScript プラグインのデフォルトの設定とは異なります。

ここでは、IRISAPP ネームスペース(Installer.cls で作成)に接続すると述べています。

"ns": "IRISAPP",

そして、docker-compose の設定があります。これは、サービス「iris」内の docker-compose ファイルで、VSCode が 52773 がマッピングされているポートに接続すると書かれています。

"docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }

52773 について調べたところ、これはマップされたポートが 52773 に対して定義されていないことがわかります。

ports: 
      - 51773
      - 52773
      - 53773

つまり、ホストマシンのポートで利用できるランダムなポートが取得され、VSCode は自動的にランダムなポートを介して、docker 上でこの IRIS に接続するということです。

これは非常に便利な機能です。IRIS を使用して任意の量の Docker イメージをランダムなポート上で実行し、VSCode をそれらのポートに自動的に接続するオプションが提供されるためです。

他のファイルはどうでしょうか?

以下のファイルもあります。

.dockerignore  - 作成した Docker ファイルにコピーしない不要なホストマシンのファイルをフィルターするために使用できるファイル。 通常、.git や .DS_Store は必須です。

.gitattributes - git の属性。ソース内の ObjectScript ファイルの行末を統一します。 Windows と Mac/Ubuntu オーナーがリポジトリで共同作業する場合に非常に便利です。

.gitignore - Git で変更履歴を追跡しないファイル。 通常、.DS_Store などの非表示の OS レベルのファイルです。

以上です!

リポジトリを Docker 実行可能にし、コラボレーションしやすくするにはどうすればよいでしょうか。

  1. このリポジトリをクローンします。

  2. 以下のファイルをすべてコピーします。

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

上記をリポジトリにコピーしてください。

Dockerfile のこの行を IRIS にインポートする ObjectScript のあるリポジトリ内のディレクトリに一致するように変更します(in /src フォルダにある場合は変更しません)。

それだけです。 すべての人(あなた自身も含む)が、新しい IRISAPP ネームスペースでコードをインポートできるようになります。

プロジェクトの起動方法

IRIS で ObjectScript プロジェクトを実行するためのアルゴリズムは以下の通りです。

  1. プロジェクトをローカルに Git clone します。

  2. プロジェクトを実行します。

$ docker-compose up -d
$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>zn "IRISAPP"

**開発者によるプロジェクトへの貢献方法 **

  1. リポジトリをフォークして、フォークされたリポジトリをローカルに Git clone します。

  2. VSCode でフォルダを開きます(DockerObjectScript の拡張機能が VSCode にインストールされている必要があります)。

  3. docker-compose.yml を右クリックし、再起動します。VSCode ObjectScript が自動的に接続され、編集/コンパイル/デバッグできるうようになります。

  4. リポジトリに変更をコミット、プッシュ、およびプルリクエストします。

以下は、この仕組みを説明する簡単な Gif です。

以上です! それでは、コーディングをお楽しみください!

0
0 172
記事 Toshihiko Minamoto · 12月 14, 2023 4m read

IRIS コンテナに VSCode を追加する

繰り返し利用できる開発環境をセットアップするには、環境用のコンテナを起動するのが最も簡単な方法の 1 つです。 素早く繰り返す際には、自分の開発コンテナ内に vscode インスタンスをホストするのが非常に便利なことが分かりました。 そこで、ブラウザベースの vscode を IRIS コンテナに追加するための簡易コンテナスクリプトを作成しました。 これは、ほとんどの 2021.1+ のコンテナで動作するはずです。 私のコードリポジトリはこちらにあります

vscode を含み事前に接続された InterSystems IRIS コンテナ

認証情報
ユーザー_SYSTEM
パスワードSYS

画像

概要

このプロジェクトでは、ホストされた(Web ベース)バージョンの vscode を同じコンテナ内で利用できる IRIS コンテナを作成します。 これには、以下が含まれます。

  • 同じコンテナコードの編集
  • コンテナの IRIS インスタンスへの事前接続
  • 管理ポータルからのリンク
  • コンテナによる IDE の自動起動

クイックスタート

  1. ダウンロードするか、git clone https://github.com/nickmitchko/Hosting-vscode-in-a-container.git を実行します。
  2. プロジェクトのルートで次を実行します: docker build . -t vscode-irishealth-ml:latest --no-cache
  3. docker-compose up を実行します。
    • Docker Compose を使用していませんか? こちらをご覧ください。
  4. 管理ポータルに移動します。
  5. このガイドの始めにあるユーザーとパスワードを使ってログインします。
  6. お気に入りのペインで VSCODE リンクをクリックします。
  7. プロンプトが表示されたら、vscode で同じパスワードを使用して IRIS インスタンスに接続します。
# プロジェクト用の新しいフォルダ
mkdir vscode-iris
cd vscode-iris

# ここでリポジトリをクローン
git clone https://github.com/nickmitchko/Hosting-vscode-in-a-container.git .

# イメージをビルド
docker build . -t vscode-irishealth-ml:latest --no-cache

# (A) または (B) のいずれかを実行
#
# (A) Compose ファイルを実行
docker-compose up
# または (B) デーモンを使用する場合
docker-compose up -d

永続性を追加する

永続的な IRIS インスタンスを使用する場合は、docker-compose.yml ファイルの 16 行目から 20 行目のコメントを解除します。 これにより、コンテナに永続ストレージマウントが追加されます。

    volumes:
    - "./durable/:/durable/"
    environment:
    - ISC_DATA_DIRECTORY=/durable/iconfig

ベースイメージを変更する

このイメージは、InterSystems 開発者コミュニティ zpm イメージに基づいてビルドされています(こちらにあります)。 これらのイメージには、パッケージリポジトリから簡単にインストールれきる zpm コマンドが含まれていますが、90 日間のコミュニティライセンスしか付帯していません。

ビルドに使用されているイメージタグは以下のとおりです。

FROM intersystemsdc/irishealth-ml-community:latest

イメージを変更する場合は、Docker ファイルの最初の行を希望するイメージタグ(カスタム IRIS インスタンスかサポートされているインスタンスのいずれか)に変更してください。 以下に例を示します。

FROM containers.intersystems.com/intersystems/irishealth-community:2021.2.0.651.0

Docker-Compose を使用しない場合

Docker Compose を使用しない場合でも、以下のようにしてコンテナを実行できます。

# コンテナをビルドした後
# --after コマンドは必須
docker run --name vscode -d \
    --publish 1972:1972 \
    --publish 52773:52773 \
    --publish 51773:51773 \
    --publish 53773:53773 \
    --publish 8080:8080 \
    --publish 8888:8888 \
    vscode-irishealth-ml:latest \
    --after "/bin/bash /install/boot.sh"
0
0 250
記事 Toshihiko Minamoto · 9月 5, 2023 10m read

Caché と InterSystems IRIS データベースの整合性は、システム障害から完全に保護されてはいますが、物理ストレージデバイスが保管しているデータは、デバイスの障害によって破損してしまいます。  そのため、多くのサイトでは、データベースの整合性チェックを定期的に実行するように選択しており、特に特定のバックアップが災害時に信頼できるかどうかを検証するためにバックアップが行われています。  システム管理者がストレージの破損を伴う災害に対応するために、整合性チェックも緊急に必要となる場合もあります。  整合性チェックはチェックされているグローバルの各ブロック(すでにバッファーにない場合)を、グローバル構造で指示された順序で読み取る必要があります。 これには膨大な時間がかかりますが、整合性チェックは、ストレージサブシステムが維持できる限り高速に読み取ることができます。  一部の状況においては、できる限り迅速に結果を取得するために、そのように実行することが望ましい場合がありますが、  他の状況においては、ストレージサブシステムの帯域幅の過剰な消費を避けるために、整合性チェックをより保守的に行う必要があります。 

攻撃への対応計画

以下の概要は、ほとんどの状況に対応します。  この記事の残りの部分では、以下に示すいずれの攻撃にも対応するため、または他の対応方針を導き出すために必要な情報を詳しく説明しています。 

  1. Linux を使用しており、整合性チェックが低速化している場合は、非同期 I/O を有効にする作業について以下の情報をご覧ください。 
  2. 隔離された環境で実行しているか緊急に結果が必要であるため、整合性チェックをできるだけ素早く完了する必要がある場合は、マルチプロセス整合性チェックを使って、複数のグローバルまたはデータベースを並行してチェックします。  実行中の同時読み取り数の制限は、プロセス数 x 各プロセスが実行する同時非同期読み取りの数(デフォルトでは 8、または非同期 I/O が無効になっている Linux を使用している場合は 1)です。  平均はこの半分である場合があることを考慮し、ストレージサブシステムの能力と比較します。  たとえば、ストレージが 20 個のドライブにストライプ化されており、プロセスごとにデフォルトで 8 つの同時読み取りが行われる場合、ストレージサブシステムの全能力を得るには 5 つ以上のプロセスが必要になる可能性があります(5*8/2=20)。
  3. 整合性チェックの速度をプロダクションに対するインパクトに合わせて調整する場合、まず、マルチプロセス整合性チェックのプロセス数を調整してから、必要に応じて SetAsyncReadBuffers を調整します。  長期的なソリューション(および誤検出の排除)については、以下の「整合性チェックの分離」をご覧ください。
  4. すでに単一のプロセスに限定されており(1 つの非常に大きなグローバル制約またはその他の外部制約がある場合)、整合性チェックの速度の増減を調整する必要がある場合は、以下の SetAsyncReadBuffers 調整パラメーターをご覧ください。

マルチプロセス整合性チェック

整合性チェックをより素早く完了させる(より高い速度でシステムリソースを使用する)ための一般的なソリューションは、複数の並列プロセスで作業を分割することです。  整合性チェックの一部のユーザーインターフェースと API は分割するようになっていますが、単一のプロセスを使用するものもあります。  プロセスへの割り当てはグローバルごとに行われるため、1 つのグローバルのチェックは必ず 1 つのプロセスによって行われます(Caché 2018.1 より前のバージョンでは、グローバルではなくデータベースによって作業が分割されていました)。

マルチプロセス整合性チェックの主要 API は、CheckList^Integrity です(詳細はドキュメントを参照)。 一時グローバルに結果を収集して、Display^Integrity によって表示されます。 以下は、5 つのプロセスを使って 3 つのデータベースをチェックしている例です。 ここでデータベースリストのパラメーターを省略すると、すべてのデータベースがチェックされます。

set dblist=$listbuild(“/data/db1/”,”/data/db2/”,”/data/db3/”)
set sc=$$CheckList^Integrity(,dblist,,,5)
do Display^Integrity()
kill ^IRIS.TempIntegrityOutput(+$job)

/* 注意: 結果を表示するだけであれば、上記の ‘sc’ を評価する必要はありませんが...
   $system.Status.IsOK(sc) - 正常に実行され、エラーは見つからなかった
   $system.Status.GetErrorCodes(sc)=$$$ERRORCODE($$$IntegrityCheckErrors) // 267
                           - 正常に実行されたが、エラーが見つかった。
   その他の値  - 問題により、一部が実行できなかった。‘sc’ に複数のエラーコードがある可能性あり。うち 1 つは $$$IntegrityCheckErrors の可能性がある。 */

このように CheckList^Integrity を使用するのが、私たちが求めている制御レベルを達成する最も簡単な方法です。  管理ポータルのインターフェースと整合性チェックタスク(組み込み、ただしスケジュールなし)は複数のプロセスを使用しますが、目的に適う十分な制御が提供されていない場合があります。*

他の整合性チェックインターフェース、特にターミナルユーザーインターフェースの ^INTEGRIT または ^Integrity、および Silent^Integrity は、単一のプロセスで整合性チェックを実行します。 したがって、これらのインターフェイスは、可能な限り早くチェックを完了することはできず、使用するリソースも少なくなります。  ただし、目に見える結果を得られるというメリットがあります。ファイルにログされるか、グローバルごとにチェックされる過程で十分に定義された順番でターミナルに出力されます。 

非同期 I/O

整合性チェックプロセスは、グローバルの各ポインターブロックを一度に 1 つずつ、それが指すデータブロックのコンテンツに対して検証しながら行われます。  データブロックは非同期 I/O で読み取られ、ストレージサブシステムが処理できる多数の読み取りリクエストを処理し続け、それぞれの読み取りが完了するたびに検証が実行されます。 

Linux のみ: 非同期 I/O は、ダイレクト I/O と組み合わせた場合にのみ効果がありますが、ダイレクト I/O は InterSystems IRIS 2020.3 までデフォルトで有効になっていません。  これは、Linux 上で整合性チェックに時間がかかりすぎるという多くのケースの原因です。  幸いにも、Cache 2018.1 と IRIS 2019.1 以降では、.cpf ファイルの [config] セクションで wduseasyncio=1 を設定して再起動することで有効にできます。  このパラメーターは、ビジー状態のシステムにおいて I/O スケーラビリティを得るために一般的に推奨されており、Caché 2015.2 以降の Linux 以外のプラットフォームでデフォルトとなっています。  これを有効にする前に、データベースキャッシュ(グローバルバッファー)用に十分なメモリが構成されていることを確認してください。ダイレクト I/O を使用すると、データベースは Linux では(冗長して)キャッシュされなくなるためです。  有効でない場合、整合性チェックによって行われる読み取りは同期的に行われるため、ストレージで効率よく使用できません。 

すべてのプラットフォーム: 整合性チェックプロセスが一度に実行する読み取りの数は、デフォルトで 8 に設定されています。  1 回の整合性チェックプロセスがディスクから読み取る速度を変更する必要がある場合は、このパラメーターを調整できます。単一のプロセスをより素早く完了する場合は増やし、ストレージ帯域幅の使用を抑える場合は減らします。  以下の点に注意してください。

  • このパラメーターは整合性チェックプロセスごとに適用されます。  複数のプロセスが使用される場合、プロセスの数とこの実行中の読み取りの数を乗算します。同時整合性チェックプロセスの数の変更にははるかに大きな影響があるため、最初に行われるのが通例です。  各プロセスは、特に計算時間によって制限されるため、このパラメーターの値の増加のメリットには制限があります。
  • これは、ストレージサブシステムの同時読み取りの処理能力にのみ適用されます。 データベースが単一のローカルドライブに格納されている場合には、値を高くしても何のメリットもありませんが、数十個のドライブでストライプされているストレージアレイであれば、同時に数十個の読み取りを処理できます。

%SYS ネームスペースからこのパラメーターを調整するには、do SetAsyncReadBuffers^Integrity(value) を使用します。 現在の値を確認するには、write $$GetAsyncReadBuffers^Integrity() を使用します。 変更は、次のグローバルがチェックされるときに適用されます。  現時点では設定はシステムの再起動によって永続されませんが、SYSTEM^%ZSTART に追加することが可能です。

ディスク上でブロックが連続している(またはほぼ連続している)場合に、各読み取りの最大サイズを制御する同様のパラメーターがあります。  このパラメーターはそれほど必要ではありませんが、ストレージレーテンシの高いサブシステムまたはブロックサイズのより大きなデータベースの場合は、微調整を行うとメリットが得られる可能性があります。  値は 64KB 単位であるため、値 1 は 64KB、4 は 256KB、のようになります。0(デフォルト)にすると、システムによって選択され、現時点では 1(64KB)が選択されるようになっています。  このパラメーターの ^Integrity 関数は、上述の関数と同様に SetAsyncReadBufferSizeGetAsyncReadBufferSize です。

整合性チェックの分離

多くのサイトでは、直接プロダクションシステムで定期的な整合性チェックを実行しています。 もちろん最も単純な構成ではありますが、理想的ではありません。  ストレージ帯域幅に対する整合性チェックの影響についての懸念だけでなく、同時データベース更新アクティビティによって、(チェックアルゴリズムには対策が組み込まれてはいるものの)誤検出エラーが発生する可能性もあります。  そのため、プロダクションで実行された整合性チェックから報告されるエラーを、管理者が評価または再チェックする必要があります。

多くの場合、さらに優れたオプションが存在します。  ストレージスナップショットまたはバックアップイメージを別のホストにマウントする方法です。そこでは、分離された Caché または IRIS インスタンスによって整合性チェックが実行されます。  こうすることで、誤検出の可能性が防止されるだけでなく、ストレージもプロダクションから分離されていれば、ストレージ帯域幅を最大限に利用してはるかに素早く完了するように整合性チェックを実行できます。  この方法は、整合性チェックがバックアップの検証に使用されているモデルによく適しており、検証されたバックアップによって、バックアップが作成された時点でのプロダクションが効果的に検証されます。  また、クラウドと可視化プラットフォームを使用すれば、さらに簡単にスナップショットから使用可能な分離環境を確立することができます。


* 管理ポータルのインターフェース、整合性チェックタスク、および SYS.Database の IntegrityCheck メソッドはかなり多数のプロセス(CPU コアの数に等しい数)を選択するため、多くの状況で必要な制御ができなくなります。 管理ポータルとタスクも、同時更新によって発生した可能性のある誤検出を特定するために、エラーを報告したグローバルの完全な再チェックを実行します。 この再チェックは、整合性チェックアルゴリズムに組み込まれた誤検出対策を超えて行われ、時間がさらにかかるため、状況によっては望ましくない場合があります(再チェックは単一プロセスで実行され、グローバル全体をチェックします)。 この動作は今後変更される可能性があります。

0
0 335
記事 Toshihiko Minamoto · 8月 30, 2023 2m read

cAdvisorContainer Advisor)は、実行中のコンテナのリソースの使用率とパフォーマンスデータを分析して公開します。 cAdvisor は初期設定のままで Prometheus メトリクスを公開します。 

https://prometheus.io/docs/guides/cadvisor/

Prometheus は SAM に統合されています。 このため、cAdvisor メトリクスと利用して Prometheus と Grafana で公開することが可能です。

cAdvisor はポート 8080 でリッスンしますが、これは Nginx のポートと競合するため、それに対応するように Nginx ポートを変更することができます。

構成手順:

  1. nginx ポートを変更します。

nghix.conf を変更します。

    server {
        listen 9991; 

これにより、http://server:8080/ 経由で多数のサンプルダッシュボードが含まれる cAdvisor UI にアクセスできるようになります。

  1. cAdvisor コンテナを追加するように docker-compose を構成します。

docker-compose.yml に以下を追加します。

  cadvisor:
    image: google/cadvisor:latest
    ports:
      - 8080:8080   
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

  1. cAdvisor のジョブを追加するように prometheus を構成します。

isc_prometheus.yml を変更して以下を追加します。

  • job_name: cadvisor
      scrape_interval: 5s
      static_configs:
      - labels:
          cluster: "1"
          group: node 
        targets:
        - cadvisor:8080

これで完了です! prometheus が cAdvisor のメトリクスを取得していることを確認するには、http://server:9090/ で prometheus UI に移動し、[Status]->[Targets]で cAdvisor のエンドポイントとステータスを確認してください。

cAdvisor メトリクスを含めるように事前にビルドされた優れたダッシュボードをダウンロードできます。各クエリに、クラスタパラメーターのみを追加してください。

0
0 622
記事 Toshihiko Minamoto · 8月 23, 2023 7m read

この「DeepSee トラブルシューティングガイド」は、DeepSee プロジェクトの問題を追跡して修正する支援を提供することを目的としています。

このガイドラインに従って問題を修正できない場合でも、少なくとも DeepSee サポートに WRC の問題を提出し、すべての証拠を提供するのに十分な情報を得ることができます。この情報によって、一緒に調査を続け、より迅速に解決することが可能となります。

ご注意ください: 特定のアクションやコマンドによってどのような結果がもたらされるかがよくわからない場合は、実行しないでください。本番システムに影響を与える可能性があります。  この場合は、サポートセンターにお問い合わせください。 

左から右の操作列に進むと、このガイドを最も簡単に活用できます。

<th>
  発生事項
</th>

<th>
  分析
</th>

<th>
  解決方法
</th>
<td>
  ビルド中のエラー
</td>

<td>
  エラーメッセージを確認<br>ビルドでエラーが表示されなかった場合は $System.OBJ.DisplayError() を実行<br>^DeepSee.BuildErrors/ run ##class(%DeepSee.Utils).%PrintBuildErrors(pCube) を確認
</td>

<td>
  メッセージのエラーを修正
</td>
<td>
  キューブのビルド制限を確認
</td>

<td>
  ビルド制限を削除または受領する
</td>
<td>
  maxfacts を削除
</td>
<td>
  ビルドエラーを修正
</td>
<td>
  ソースクラスのインデックス再構築
</td>
<td rowspan="6">
  ソースのレコードが<br> DeepSee で使用でき<br>ない
</td>

<td>
  前のセクションでビルドの問題 / 制限を確認
</td>

<td>
  制限を削除します
</td>
<td>
  必要に応じてメソッドを修正してください
</td>
<td>
  ビルドエラーを修正してください
</td>
<td>
  メソッドを修正してください
</td>
<td>
  不一致の原因を特定して解決してください
</td>
<td>
   
</td>
<td>
  レベルのメンバーが<br>重複
</td>

<td>
  ディメンションの階層が有効かを確認
</td>

<td>
  [この記事](https://community.intersystems.com/post/designing-valid-hierarchies-deepsee)に従ってレベルを変更してください
</td>
<td>
  データが欠落していないか前セクションを確認。キャッシュに問題がないか確認<br> - do $System.DeepSee.Shell()<br>  - cache off
</td>

<td>
  ビルドのエラーを修正<br>do $System.DeepSee.Reset()<br>kill ^DeepSee.Cache
</td>
<td>
  セグメントの問題を修正
</td>
<td>
  do ##class(%DeepSee.TaskMaster).%Reset()
</td>
<td>
  リストが正常かを確認
</td>

<td>
  ソーステーブルから選択する権限を付与
</td>
<td>
  WHERE 条件をデバッグ<br>^DeepSee.SQLError を確認<br>^DeepSee.QueryLog を確認
</td>
<td>
  DeepSee が期待どおりに反応しない
</td>

<td>
  システムの健全性を確認します
</td>

<td>
   
</td>
<td>
  エージェントステー<br>タスを確認
</td>

<td>
   
</td>
<td>
  ライセンスの追加購入
</td>
<td>
  ^DeepSee.AgentLog を確認
</td>

<td>
  do ##class(%DeepSee.TaskMaster).%Reset()
</td>
<td>
   
</td>
<td>
  発見したエラーを修正
</td>
<td rowspan="7">
  ビルドパフォーマンスが低い
</td>

<td>
  do ##class(%DeepSee.TaskMaster).%PrintLog() を実行
</td>

<td>
   
</td>
<td>
  do ##class(%DeepSee.TaskMaster).%Reset()
</td>
<td>
  低負荷の状態でビルドを実行
</td>
<td>
  可能であれば、ソース式を避ける
</td>
<td>
  不要なディメンション/レベルを削除
</td>
<td>
  キューブを単純かつスリムに保つ
</td>
<td>
  DeepSee にデータウェアハウス設計または個別のネームスペースを使用して、そのネームスペースのジャーナリングを無効にする
</td>
<td>
  レポートを実行します: do ##class(%DeepSee.Diagnostic.MDXUtils).%Run(&lt;query>)
</td>

<td>
  長時間実行される可能性のあるクエリは簡略化するなど回避策が必要
</td>
<td>
  リソースの解放
</td>
<td>
  可能であれば、バッファを増やす
</td>
<td>
  ランタイム時のパフォーマンス問題への支援については、サポートセンターまでお問い合わせください
</td>
<td>
  ネームスペースが DS メニューにリストされていない
</td>

<td>
  DeepSee が web-application 設定 /csp/&lt;ネームスペース> で有効であるかを確認
</td>

<td>
  ウェブ・アプリケーションで "DeepSee" を有効にする
</td>
<td>
  ライセンスを確認
</td>

<td>
  DeepSee 対応のライセンスを取得
</td>
<td>
  リソース %DeepSee_Architect , %DeepSee_ArchitectEdit に Use を追加
</td>
<td>
  サポートされているブラウザを使用
</td>
問題の領域
ビルドの問題
ソーステーブルの行
よりも少ないレコー
ドが構築される
maxfacts が使用されているかを確認
^DeepSee.BuildErrors/ run ##class(%DeepSee.Utils).%PrintBuildErrors(pCube) を確認
ソースクラスのインデックスを確認
データの欠落
キューブ定義の "%OnProcessFact" を確認します
ビルドエラーを確認します
sourceExpression のメソッドをデバッグします
ファクトテーブルのレコードを確認します
ファクトテーブルとソーステーブルを比較します
結果が誤っている
クエリで予期しない
結果が表示される
クエリをより小さなセグメントに分割
(各軸を個別に分割)
問題のあるセグメントを特定
^DeepSee.AgentLog を確認します
リストが空
カスタム SQL リストかを確認します
ハング / 予期しないイベント
ハング
ライセンスの使用状況を確認
以前に動作していた操作が失敗する
do ##class(%DeepSee.TaskMaster).%PrintLog() を実行
^DeepSee.LastLogError を確認
^DeepSee.PivotError() を確認
パフォーマンスの問題
利用できるエージェント数を確認
メモリと CPU の使用率を確認
ソースの式を確認
全般的なキューブの状態を確認
 - do ##class(%DeepSee.Utils).%Analyze("Holefoods")
高いジャーナリングアクティビティ
クエリパフォーマンスが低い
メモリと CPU の使用率を確認
バッファの設定を確認
一般的なシステムパフォーマンス分析を実施
報告済みの一般的な問題
アーキテクトがグレー表示される
ユーザーのロールを確認
ブラウザが IE 8 でないことを確認

以下は、各問題領域のマインドマップです。

0
0 128
記事 Toshihiko Minamoto · 8月 15, 2023 9m read

Docker による Apache Web ゲートウェイ

コミュニティの皆さん、こんにちは。

この記事では、以下を使用して、Docker でプログラムによって Apache Web ゲートウェイを構成します。

  • HTTPS プロトコル
  • Web ゲートウェイと IRIS インスタンス間の安全な通信を確保する TLS/SSL

画像

イメージには、Web ゲートウェイ用と IRIS インスタンス用の 2 つを使用します。

すべての必要なファイルは、こちらの GitHub リポジトリで入手可能です。

では、git clone から始めましょう。

git clone https://github.com/lscalese/docker-webgateway-sample.git
cd docker-webgateway-sample

システムの準備

権限に関する問題を回避するために、システムにユーザーとグループが必要です。

  • www-data
  • irisowner

コンテナと証明書ファイルの共有に必要です。 これらがシステムに存在しない場合は、以下を実行します。

sudo useradd --uid 51773 --user-group irisowner
sudo groupmod --gid 51773 irisowner
sudo useradd –user-group www-data

証明書の生成

この例では、以下の証明書を使用します。

  1. HTTPS Web サーバーの使用状況
  2. Web ゲートウェイクライアントの TLS/SSL 暗号化
  3. IRIS インスタンスの TLS/SSL 暗号化

これらを生成するためにすぐに使用できるスクリプトが用意されています。

ただし、証明書の件名はカスタマイズする必要がありますが、これは、 gen-certificates.sh ファイルを編集するだけです。

以下は、OpenSSL subj 引数の構造です。

  1. C: 国コード
  2. ST: 州
  3. L: 場所
  4. O: 組織
  5. OU: 組織ユニット
  6. CN: コモンネーム(基本的に、ドメイン名またはホスト名)

これらの値を自由に変更してください。

# sudo is needed due chown, chgrp, chmod ...
sudo ./gen-certificates.sh

すべてが正しく設定されたら、証明書を含む ./certificates/~/webgateway-apache-certificates/ という新しいディレクトリが 2 つ表示されます。

ファイルコンテナ説明
./certificates/CA_Server.cerwebgateway、iris認証局サーバー証明書
./certificates/iris_server.ceririsIRIS インスタンスの証明書(ミラーと webgateway の通信暗号化に使用)
./certificates/iris_server.keyiris関連する秘密鍵
~/webgateway-apache-certificates/apache_webgateway.cerwebgatewayApache Web サーバーの証明書
~/webgateway-apache-certificates/apache_webgateway.keywebgateway関連する秘密鍵
./certificates/webgateway_client.cerwebgatewaywebgateway と IRIS の通信を暗号化するための証明書
./certificates/webgateway_client.keywebgateway関連する秘密鍵

自己署名証明書がある場合、Web ブラウザにセキュリティ警告が表示されることに注意してください。 認証許可から配布された証明書がある場合はもちろん、自己署名証明書の代わりにそれを使用できます(特に Apache サーバー証明書の場合)。

Web ゲートウェイ構成ファイル

構成ファイルを見てみましょう。

CSP.INI

CSP.INI ファイルは webgateway-config-files ディレクトリにあります。
イメージにプッシュされますが、コンテンツはランタイム時に変更可能です。 このファイルをテンプレートとして考えてください。

この例では、コンテナの起動時に、以下のパラメーターがオーバーライドされます。

  • Ip_Address
  • TCP_Port
  • System_Manager

詳細は、startUpScript.sh をご覧ください。 大まかに言えば、sed コマンドラインで置換が実行されます。

また、このファイルには、IRIS インスタンスとの通信を保護する SSL/TLS 構成が含まれています。

SSLCC_Certificate_File=/opt/webgateway/bin/webgateway_client.cer
SSLCC_Certificate_Key_File=/opt/webgateway/bin/webgateway_client.key
SSLCC_CA_Certificate_File=/opt/webgateway/bin/CA_Server.cer

これらは重要な行です。 証明書ファイルがコンテナで使用できることを確認する必要があります。
これは、後で docker-compose ファイルの volume で行います。

000-default.conf

これは、Apache 構成ファイルです。 HTTPS プロトコルの使用を許可し、HTTP 呼び出しを HTTPS にリダイレクトします。
証明書と秘密鍵ファイルはこのファイルにセットアップされます。

SSLCertificateFile /etc/apache2/certificate/apache_webgateway.cer
SSLCertificateKeyFile /etc/apache2/certificate/apache_webgateway.key

IRIS インスタンス

IRIS インスタンスでは、Web ゲートウェイとの SSL/TLS 通信を許可するための最小限の要件のみを以下の手順で構成します。

  1. %SuperServer SSL 構成。
  2. SSLSuperServer セキュリティ設定を有効にします。
  3. Web ゲートウェイサービスを使用できる IP のリストを制限します。

構成を容易にするために、config-api は単純な JSON 構成ファイルと合わせて使用されます。

{
   "Security.SSLConfigs": {
       "%SuperServer": {
           "CAFile": "/usr/irissys/mgr/CA_Server.cer",
           "CertificateFile": "/usr/irissys/mgr/iris_server.cer",
           "Name": "%SuperServer",
           "PrivateKeyFile": "/usr/irissys/mgr/iris_server.key",
           "Type": "1",
           "VerifyPeer": 3
       }
   },
   "Security.System": {
       "SSLSuperServer":1
   },
   "Security.Services": {
       "%Service_WebGateway": {
           "ClientSystems": "172.16.238.50;127.0.0.1;172.16.238.20"
       }
   }
}

操作は必要ありません。 この構成はコンテナの起動時に自動的に読み込まれます。

tls-ssl-webgateway イメージ

dockerfile

ARG IMAGEWEBGTW=containers.intersystems.com/intersystems/webgateway:2021.1.0.215.0
FROM ${IMAGEWEBGTW}
ADD webgateway-config-files /webgateway-config-files
ADD buildWebGateway.sh /
ADD startUpScript.sh /
RUN chmod +x buildWebGateway.sh startUpScript.sh && /buildWebGateway.sh
ENTRYPOINT ["/startUpScript.sh"]

デフォルトのエントリポイントは /startWebGateway ですが、Web サーバーを起動する前にいくつかの操作を実行する必要があります。 CSP.ini ファイルはテンプレートであり、起動時にいくつかのパラメーター(IP、ポート、システムマネージャー)を変更する必要があることに注意してください。 これらの変更は startUpScript.sh によって行われ、その後で初期化エントリポイントのスクリプト /startWebGateway が実行されます。

コンテナの起動

docker-compose ファイル

コンテナを起動する前に、docker-compose.yml ファイルを変更する必要があります。

  • **SYSTEM_MANAGER**Web Gateway Managementhttps://localhost/csp/bin/Systems/Module.cxw へのアクセスが許可された IP で設定する必要があります。基本的に、あなたの IP アドレスです(カンマ区切りのリストである場合があります)。

  • **IRIS_WEBAPPS** は、あなたの CSP アプリケーションのリストで設定する必要があります。 リストはスペース区切りです(例: IRIS_WEBAPPS=/csp/sys /swagger-ui)。 デフォルトでは、/csp/sys のみが公開されます。

  • ポート 80 と 443 はマッピングされています。 これらのポートがすでに使用されている場合は、他のポートに調整します。

version: '3.6'
services:

 webgateway:
   image: tls-ssl-webgateway
   container_name: tls-ssl-webgateway
   networks:
     app_net:
       ipv4_address: 172.16.238.50
   ports:
     # change the local port already used on your system.
     - "80:80"
     - "443:443"
   environment:
     - IRIS_HOST=172.16.238.20
     - IRIS_PORT=1972
     # Replace by the list of ip address allowed to open the CSP system manager
     # https://localhost/csp/bin/Systems/Module.cxw 
     # see .env file to set environement variable.
     - "SYSTEM_MANAGER=${LOCAL_IP}"
     # the list of web apps
     # /csp allow to the webgateway to redirect all request starting by /csp to the iris instance
     # You can specify a list separate by a space : "IRIS_WEBAPPS=/csp /api /isc /swagger-ui"
     - "IRIS_WEBAPPS=/csp/sys"
   volumes:
     # Mount certificates files.
     - ./volume-apache/webgateway_client.cer:/opt/webgateway/bin/webgateway_client.cer
     - ./volume-apache/webgateway_client.key:/opt/webgateway/bin/webgateway_client.key
     - ./volume-apache/CA_Server.cer:/opt/webgateway/bin/CA_Server.cer
     - ./volume-apache/apache_webgateway.cer:/etc/apache2/certificate/apache_webgateway.cer
     - ./volume-apache/apache_webgateway.key:/etc/apache2/certificate/apache_webgateway.key
   hostname: webgateway
   command: ["--ssl"]

 iris:
   image: intersystemsdc/iris-community:latest
   container_name: tls-ssl-iris
   networks:
     app_net:
       ipv4_address: 172.16.238.20
   volumes:
     - ./iris-config-files:/opt/config-files
     # Mount certificates files.
     - ./volume-iris/CA_Server.cer:/usr/irissys/mgr/CA_Server.cer
     - ./volume-iris/iris_server.cer:/usr/irissys/mgr/iris_server.cer
     - ./volume-iris/iris_server.key:/usr/irissys/mgr/iris_server.key
   hostname: iris
   # Load the IRIS configuration file ./iris-config-files/iris-config.json
   command: ["-a","sh /opt/config-files/configureIris.sh"]

networks:
 app_net:
   ipam:
     driver: default
     config:
       - subnet: "172.16.238.0/24"

ビルドして起動します。


docker-compose up -d --build

コンテナ tls-ssl-iris と tls-ssl-webgateway が起動します。

Web アクセスのテスト

Apache のデフォルトページ

http://localhost ページを開きます。 自動的に https://localhost にリダイレクトされます。
ブラウザにセキュリティ警告が表示されます。 これは、自己署名証明書の場合の通常の動作です。リスクを受け入れて続行してください。

image

Web ゲートウェイ管理ページ

https://localhost/csp/bin/Systems/Module.cxw を開き、サーバー接続をテストします。 image

管理ポータル

https://localhost/csp/sys/utilhome.csp を開きます。

画像

上出来です! Web ゲートウェイの例が動作しています!

IRIS ミラーと Web ゲートウェイ

前回の記事では、ミラー環境を構築しましたが、Web ゲートウェイが不足していました。 今回は、それを改善できます。
Web ゲートウェイといくつかの改善が追加された新しい iris-miroring-with-webgateway リポジトリを利用できます。

  1. 証明書はオンザフライではなく、別のプロセスで生成されるようになっています。
  2. IP アドレスは、docker-compose と JSON 構成ファイルの環境変数に置き換えられています。 変数は '.env' ファイルで定義されています。
  3. リポジトリをテンプレートとして使用できます。

以下のように環境を実行するには、リポジトリ README.md ファイルをご覧ください。

image

0
0 331
記事 Toshihiko Minamoto · 8月 7, 2023 7m read

この記事では、.Net/Java ゲートウェイを簡単にコンテナ化する方法を説明します。

この例では、Apache Kafka との統合を開発します。

Java/.Net と相互運用するために、PEX を使用しています。

アーキテクチャ

このソリューションは完全に docker で実行し、以下のように構成されます。

Java ゲートウェイ

まず、メッセージを Kafka に送信する Java オペレーションを開発しましょう。 このコードはお好きな IDE で書くことができ、こちらのようになります。

要約すると:

  • 新しい PEX ビジネスオペレーションを開発するには、抽象型の com.intersystems.enslib.pex.BusinessOperation クラスを実装する必要があります。
  • public プロパティはビジネスホスト設定です。
  • OnInit メソッドは Kafka への接続を初期化し、InterSystems IRIS へのポインターを取得するために使用されます。
  • OnTearDown は、(プロセスのシャットダウン時に)Kafka から切断するために使用されます。
  • OnMessagedc.KafkaRequest メッセージを受け取って、Kafka に送信します。

では、これを Docker にパックしましょう!

これがこの例の dockerfile です。

FROM eclipse-temurin:8-jre-alpine AS builder

ARG APP_HOME=/tmp/app

COPY src $APP_HOME/src

COPY --from=intersystemscommunity/jgw:latest /jgw/*.jar $APP_HOME/jgw/

WORKDIR $APP_HOME/jar/
ADD https://repo1.maven.org/maven2/org/apache/kafka/kafka-clients/2.5.0/kafka-clients-2.5.0.jar .
ADD https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar .
ADD https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar .
ADD https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar .

WORKDIR $APP_HOME/src

RUN javac -classpath $APP_HOME/jar/*:$APP_HOME/jgw/* dc/rmq/KafkaOperation.java && \
    jar -cvf $APP_HOME/jar/KafkaOperation.jar dc/rmq/KafkaOperation.class

FROM intersystemscommunity/jgw:latest

COPY --from=builder /tmp/app/jar/*.jar $GWDIR/

1 行ずつ見ながら、ここに何が起きているのかを確認しましょう(マルチステージ docker ビルドを理解していることが前提です)。

FROM eclipse-temurin:8-jre-alpine AS builder

最初のイメージは JDK 8 です(openjdk:8 イメージは廃止されたことに注意してください。代わりに推奨されているものを使用します)。

ARG APP_HOME=/tmp/app
COPY src $APP_HOME/src

/src フォルダから /tmp/app フォルダにソースをコピーしています。

COPY --from=intersystemscommunity/jgw:latest /jgw/*.jar $APP_HOME/jgw/

Java ゲートウェイソースを /tmp/app/jgw フォルダにコピーしています。

WORKDIR $APP_HOME/jar/
ADD https://repo1.maven.org/maven2/org/apache/kafka/kafka-clients/2.5.0/kafka-clients-2.5.0.jar .
ADD https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar .
ADD https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar .
ADD https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar .

WORKDIR $APP_HOME/src

RUN javac -classpath $APP_HOME/jar/*:$APP_HOME/jgw/* dc/rmq/KafkaOperation.java && \
    jar -cvf $APP_HOME/jar/KafkaOperation.jar dc/rmq/KafkaOperation.class

これですべての依存関係が追加され、jar ファイルのコンパイルに javac/jar が呼び出されます。 実際のプロジェクトでは、Maven または Gradle の使用をお勧めします。

FROM intersystemscommunity/jgw:latest

COPY --from=builder /tmp/app/jar/*.jar $GWDIR/

そして最後に、jar がベース jgw イメージにコピーされます(ベースイメージは、ゲートウェイと関連タスクの開始も処理します)。

.Net ゲートウェイ

次は、Kafka からメッセージを受け取る .Net サービスです。 このコードはお好きな IDE で書くことができ、こちらのようになります。

要約すると:

  • 新しい PEX ビジネスサービスを開発するには、抽象型の InterSystems.EnsLib.PEX.BusinessService クラスを実装する必要があります。
  • public プロパティはビジネスホスト設定です。
  • OnInit メソッドは Kafka への接続を初期化し、トピックを購読し、InterSystems IRIS へのポインターを取得するために使用されます。
  • OnTearDown は、(プロセスのシャットダウン時に)Kafka から切断するために使用されます。
  • OnMessage は Kafka からのメッセージを入力として使用し、他の相互運用性ホストに Ens.StringContainer メッセージを送信します。

では、これを Docker にパックしましょう!

これがこの例の dockerfile です。

FROM mcr.microsoft.com/dotnet/core/sdk:2.1 AS build

ENV ISC_PACKAGE_INSTALLDIR /usr/irissys
ENV GWLIBDIR lib
ENV ISC_LIBDIR ${ISC_PACKAGE_INSTALLDIR}/dev/dotnet/bin/Core21

WORKDIR /source
COPY --from=store/intersystems/iris-community:2020.2.0.211.0 $ISC_LIBDIR/*.nupkg $GWLIBDIR/

# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# copy and publish app and libraries
COPY . .
RUN dotnet publish -c release -o /app

# final stage/image
FROM mcr.microsoft.com/dotnet/core/runtime:2.1
WORKDIR /app
COPY --from=build /app ./

# Configs to start the Gateway Server
RUN cp KafkaConsumer.runtimeconfig.json IRISGatewayCore21.runtimeconfig.json && \
    cp KafkaConsumer.deps.json IRISGatewayCore21.deps.json

ENV PORT 55556

CMD dotnet IRISGatewayCore21.dll $PORT 0.0.0.0

1 行ずつ見ていきましょう。

FROM mcr.microsoft.com/dotnet/core/sdk:2.1 AS build

このアプリの構築には、完全な .Net Core 2.1 SDK を使用します。

ENV ISC_PACKAGE_INSTALLDIR /usr/irissys
ENV GWLIBDIR lib
ENV ISC_LIBDIR ${ISC_PACKAGE_INSTALLDIR}/dev/dotnet/bin/Core21

WORKDIR /source
COPY --from=store/intersystems/iris-community:2020.2.0.211.0 $ISC_LIBDIR/*.nupkg $GWLIBDIR/

正式な InterSystems Docker イメージから .Net Gateway NuGets をビルダーイメージにコピーします。

# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# copy and publish app and libraries
COPY . .
RUN dotnet publish -c release -o /app

ライブラリを構築します。

# final stage/image
FROM mcr.microsoft.com/dotnet/core/runtime:2.1
WORKDIR /app
COPY --from=build /app ./

ライブラリ dll を、実際に実行する最終コンテナにコピーします。

# Configs to start the Gateway Server
RUN cp KafkaConsumer.runtimeconfig.json IRISGatewayCore21.runtimeconfig.json && \
    cp KafkaConsumer.deps.json IRISGatewayCore21.deps.json

現在、.Net ゲートウェイは起動時にすべての依存関係を読み込む必要があるため、考えられるすべての依存関係を認識するようにしています。

ENV PORT 55556

CMD dotnet IRISGatewayCore21.dll $PORT 0.0.0.0

すべてのインターフェースでリッスンするポート 55556 でゲートウェイを開始します。

以上です!

こちらは、すべてをまとめて実行する完全な docker-compose です(Kafka とメッセージを見るための Kafka UI も含まれています)。

このデモを実行するには、以下を行う必要があります。

  1. インストール:
  2. 実行:
git clone https://github.com/intersystems-community/pex-demo.git cd pex-demo docker-compose pull docker-compose up -d

 

注意事項: Java ゲートウェイと .Net ゲートウェイライブラリは、InterSystems IRIS クライアントと同じバージョンのものである必要があります。

0
0 189
記事 Toshihiko Minamoto · 7月 24, 2023 8m read

Python は世界で最も使用されているプログラミング言語になり(出典: https://www.tiobe.com/tiobe-index/)、SQL はデータベース言語としての道をリードし続けています。 Python と SQL が連携して、SQL だけでは不可能であった新しい機能を提供できれば、素晴らしいと思いませんか? 結局のところ、Python には 380,000 を超える公開ライブラリがあり(出典: https://pypi.org/)、Python 内で SQL クエリを拡張できる興味深い機能が提供されています。 この記事では、Embedded Python を使用して、InterSystems IRIS データベースに新しい SQL ストアドプロシージャを作成する方法を詳しく説明します。

サンプルとして使用する Python ライブラリ

この記事では、IRIS で SQL を扱う人にとって非常に便利な GeoPy と Chronyk という 2 つのライブラリを使用します。 

Geopy は、ジオコーディング(住所と地理座標の修飾)を住所データに適用するために使用するライブラリです。 これを使用すると、通りの名前から郵便番号と完全な住所を郵便局の形式で取得することができます。 多くのレコードには住所が含まれるため、非常に便利です。

Chronyk は、人間の言語で日付と時刻を処理するために使用されます。 これは、IRIS と Python の両方において、日付は内部的に最初の日付から経過した時間を表す数字でたるため、非常に便利です。 人間の場合、日付は 7 月 20 日、または昨日や明日、または 2 時間前と表現しますが、 Chronyk では、このような日付を受け取って、ユニバーサル日付形式に変換します。

InterSystems IRIS への Python サポート

バージョン 2021.1 より、Python を使用してクラスメソッド、ストアドプロシージャ、相互運用プロダクション、Python と IRIS(ObjectScript)間の双方向のネイティブ呼び出しを作成できるようになりました。 Python とこれほど深く連携するデータプラットフォームを他に知りません。 これが機能するためには、要件として、Python が IRIS と同じ同じ物理マシンか仮想マシンまたはコンテナにインストールされている必要があります。 詳細は、https://docs.intersystems.com/iris20221/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_epython をご覧ください。 Python をインストールするには、以下を実行します。

# install libraries required for python and pip
RUNapt-get-yupdate\
    &&apt-get-yinstallapt-utils\
    &&apt-getinstall-ybuild-essentialunzippkg-configwget\
    &&apt-getinstall-ypython3-pip  

InterSystems IRIS への Python ライブラリサポート

InterSystems IRIS が Python ライブラリを使用できるようにするには、Python ライブラリが <installdir>/mgr/python にインストールされている必要があります。 installdir は IRIS がインストールされているフォルダです。 新しいパッケージをインストールするには、以下を実行します。

# use pip3 (the python zpm) to install geopy and chronyk packages
RUNpip3install--upgradepipsetuptoolswheel
RUNpip3install--target/usr/irissys/mgr/pythongeopychronyk

Pip3 は Python の最も一般的なパッケージマネージャーおよびインストーラー Pip です。

Python 言語でストアドプロシージャを作成する

InterSystems IRIS で Python を使用する可能性の 1 つは、Python を使用してストアドプロシージャを作成することです。 以下の 2 つの可能性があります。

  1. Create 関数またはプロシージャの SQL 文を使用したストアドプロシージャ Python の作成。
  2. sqlProc タグと language=Python タグを使用した ObjectScript クラス内での ClassMethod の作成。

Create プロシージャの SQL 文を使用したストアドプロシージャ Python の作成

InterSystems ドキュメントによると、以下に示すとおり、CREATE ステートメントに LANGUAGE PYTHON 引数を指定することで、Embedded Python を使って SQL 関数またはストアドプロシージャを記述することもできます(出典: https://docs.intersystems.com/iris20221/csp/docbook/DocBook.UI.Page.cls?KEY=AEPYTHON#AEPYTHON_runpython_sql)。

CREATE FUNCTION tzconvert(dt TIMESTAMP, tzfrom VARCHAR, tzto VARCHAR)
    RETURNS TIMESTAMP
    LANGUAGE PYTHON
{
    from datetime import datetime
    from dateutil import parser, tz
    d = parser.parse(dt)
    if (tzfrom is not None):
        tzf = tz.gettz(tzfrom)
        d = d.replace(tzinfo = tzf)
    return d.astimezone(tz.gettz(tzto)).strftime("%Y-%m-%d %H:%M:%S")
}

この新しい SQL 関数を実行する場合:

SELECT tzconvert(now(), 'US/Eastern', 'UTC')

関数は以下のようなものを返します。

2022-07-20 15:10:05

sqlProc タグと language=Python タグを使用した ObjectScript クラス内での ClassMethod の作成

正直に言うと、sqlProc タグと language=Python タグを使って ClassMethod を作成するこのアプローチが私のお気に入りです。 私の意見では、管理しやすく、十分な文書化で明確に示されており、ソースコードバージョンの管理もうまく行えます。 このアプローチのためのサンプルアプリケーションを公開しました(https://openexchange.intersystems.com/package/Python-IRIS-SQL-Procedures-Sample)。 これを使用して、この 2 番目のアプローチを詳しく説明します。

サンプルアプリケーションのインストール

サンプルアプリケーションをインストールするには、以下の手順に従います。

  1. リポジトリを任意のローカルディレクトリに Clone/git pull します。
$ git clone https://github.com/yurimarx/iris-sql-python-sample.git
  1. このディレクトリで Docker ターミナルを開き、以下を実行します。
$ docker-compose build
  1. IRIS コンテナを実行します。
$ docker-compose up -d

もう 1 つのインストール方法は、ZPM を使用する方法です。

zpm "install iris-sql-python-sample"

Python を使用したストアドプロシージャのサンプル

最初の例は、住所のジオコーディングを処理するストアドプロシージャです。ソースコードを参照してください。

ClassMethodGetFullAddress(StreetAs%String,CityAs%String,StateAs%String)
  <div>
    <span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%String</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">Language</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">python</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">SqlName</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">GetFullAddress</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">SqlProc</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">]</span>
  </div>
  
  <div>
    <span style="color: #ffffff;">{</span>
  </div>
  
  <div>
    <span style="color: #85a6ff;">    </span><span style="color: #85a6ff;">import</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">geopy</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">geocoders</span>
  </div>
  
  <div>
    <span style="color: #85a6ff;">    </span><span style="color: #85a6ff;">from</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">geopy</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">geocoders</span><span style="color: #85a6ff;"> </span><span style="color: #85a6ff;">import</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">Nominatim</span>
  </div>
  
  <div>
    <span style="color: #ade2ff;">    </span><span style="color: #ade2ff;">geopy</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">geocoders</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">options</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">default</span><span style="color: #ade2ff;">_</span><span style="color: #ade2ff;">timeout</span><span style="color: #ffffff;"> =</span><span style="color: #d4b57c;"> </span><span style="color: #d4b57c;">7</span>
  </div>
  
  <div>
    <span style="color: #ade2ff;">    </span><span style="color: #ade2ff;">geolocator</span><span style="color: #ffffff;"> =</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">Nominatim</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">user</span><span style="color: #ade2ff;">_</span><span style="color: #ade2ff;">agent</span><span style="color: #ffffff;">=</span><span style="color: #d4b57c;">"intersystems_iris"</span><span style="color: #ffffff;">)</span>
  </div>
  
  <div>
    <span style="color: #ade2ff;">    </span><span style="color: #ade2ff;">location</span><span style="color: #ffffff;"> =</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">geolocator</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">geocode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Street</span><span style="color: #ffffff;"> +</span><span style="color: #d4b57c;"> </span><span style="color: #d4b57c;">", "</span><span style="color: #ffffff;"> +</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">City</span><span style="color: #ffffff;"> +</span><span style="color: #d4b57c;"> </span><span style="color: #d4b57c;">", "</span><span style="color: #ffffff;"> +</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">State</span><span style="color: #ffffff;">,</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">country</span><span style="color: #ade2ff;">_</span><span style="color: #ade2ff;">codes</span><span style="color: #ffffff;">=</span><span style="color: #d4b57c;">"US"</span><span style="color: #ffffff;">)</span>
  </div>
  
  <div>
    <span style="color: #85a6ff;">    </span><span style="color: #85a6ff;">return</span><span style="color: #ade2ff;"> </span><span style="color: #ade2ff;">location</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">address</span>
  </div>
  
  <div>
    <span style="color: #ffffff;">}</span>
  </div>
</div>

ClassMethod が [ Language = python, SqlProc] タグで(dc.pythonsql.Company クラス内に)宣言されているのが分かります。
SqlName タグによって、新しいストアドプロシージャの名前を SQL 分に設定できます。

管理ポータルの[システム]>[SQL]に移動し、以下のコードを実行します。

SELECT 
ID, City, Name, State, Street, Zip, dc_pythonsql.GetFullAddress(Street, City, State) As FullAddress 
FROM dc_pythonsql.Company

結果が表示されます。

Geopy

不完全な住所から「完全な」住所(完全修飾)が返されるようになりました。

注意: 何も返されない場合は、#class(dc.pythonsql.Company).CreateFiveCompanies() を実行してください。 テストに使用する 5 つの会社が作成されます。

このパッケージは、主なオープンのジオコーディングサービスと有料のサービスと連携できます。 この例では、Nominatim というオープンサービスを使用していますか、Bing、Google、ArcGIS などを使用することも可能です。 利用可能なサービスについて、https://geopy.readthedocs.io/en/stable/#module-geopy.geocoders をご覧ください。

2 つ目の例は、人間が読み取れる形式による Chronyk という日付と時刻のパッケージです。

「明日」、「昨日」、「今から 4 時間後」、「2022 年 7 月 4 日」などの文を送信し、ユニバーサル日付形式の結果を取得できます。 ストアドプロシージャの作成を参照してください。

ClassMethodGetHumanDate(SentenceAs%String)As%String[Language=python,SqlName=GetHumanDate,SqlProc]
{
    fromchronykimportChronyk
    t =Chronyk(Sentence)
    returnt.ctime()
}

管理ポータル >[システム]>[SQL]で、以下の呼び出しを実行します。

SELECT 
ID, City, Name, State, Street, Zip, dc_pythonsql.GetHumanDate('yesterday') As Datetime      
FROM dc_pythonsql.Company

結果が表示されます。

https://raw.githubusercontent.com/yurimarx/iris-sql-python-sample/main/screen2.png

ストアドプロシージャを呼び出すだけの場合は、この SQL 分を使用できます。

select dc_pythonsql.GetHumanDate('yesterday') as Datetime  

このライブラリには、人間が読み取れる日付と時刻の例が複数含まれています。https://github.com/KoffeinFlummi/Chronyk をご覧ください。

Python ストアドプロシージャは簡単に作成できます。ぜひ試してみてください!

0
0 399
記事 Toshihiko Minamoto · 6月 27, 2023 13m read

はじめに

今日の相互運用性分野に従事する多くの人にとって、REST は最高峰にあります。 REST API 開発用のツールとアプローチが溢れかえる中、コードを書き始める前に、どのツールを選び、何を計画する必要があるでしょうか? この記事では、堅牢性、適応性、および一貫性に優れた REST API を構築できるようにする設計パターンと考慮事項を焦点としています。 CORS サポートと認証管理の課題に他する実行可能なアプローチについて、REST API 開発の全段階に適用できる様々なヒントとテクニック、最適なツールを織り交ぜながら説明します。 InterSystems IRIS Data Platform で利用できるオープンソース REST API と複雑化し続ける API の課題にどのように取り組むかについてお読みください。 これは、同じトピックに関する最近のウェビナーを記事にしたものです。

2020 年のメモ

この記事を書いてから 1 年半が過ぎました。 現在でも関連性があると思いますが、REST API の開発をこれまでになく簡単にする画期的な新機能を紹介したいと思います。

この記事の内容

  • REST API アーキテクチャ
  • ブローカー
  • ブローカーの分離
  • ブローカーのパラメーター
  • コードの一般化
  • CORS
  • 認証
  • 開発ツール
  • JSON
  • まとめ
  • リンク

REST API アーキテクチャ

この記事の読者が REST API と InterSystems テクノロジーを使った REST API の実装の基本についてよく理解していることを希望します。 このトピックに特化した記事、ウェビナー、トレーニングコースがたくさんあります。 いずれにせよ、まず初めに、REST API を含む、アプリケーションの共通高レベルアーキテクチャから始めたいと思います。 以下のようなアーキテクチャです。

REST API アーキテクチャ

アプリケーションの主なレイヤーを見てみましょう。

データ

クラスなどの永続データ。

ターミナル API

これは、データを管理するレイヤーです。 データとのすべてのやり取りは、この API を通じて行われます。 この API のメソッドはデバイスへの書き込みを行わず、次のいずれかのみを返します。

  • オブジェクト
  • ステータスまたは例外

Web API

受信リクエストを、ターミナル API が理解する形式に変換して呼び出します。 ターミナル API が返す結果は、クライアントが読み取れる形式(通常は JSON)に変換されて、クライアントに返されます。 Web API は直接データを操作しません。 私は、「REST API」ではなく「Web API」という用語を使用しています。これは、Web API は InterSystems プラットフォームでもサポートされている WebSocket などに基づく別のアーキテクチャを使って実装することが可能であるためです。

クライアント

通常は JavaScript で書かれますが必ずしもそうとは限りません。これが、エンドユーザーが使用できるアプリケーションです。 このレベルは、直接データベースを操作したり、ビジネスロジックを実装したリ、アプリケーションの状態を保管してはいけません。 このレベルでは通常、インターフェース、事前入力検証、単純なデータ操作(並べ替え、グループ化、集計)など、最も単純なビジネスロジックのみが使用可能です。

この分離によって、アプリケーションから複雑さが取り除かれ、デバッグが容易になります。 これは、多層アーキテクチャと呼ばれるアプローチです。

ブローカー

ブローカーは、REST API ロジックを含むクラスです。 以下の特徴があります。

  • %CSP.REST のサブクラスである
  • URL とコード内のメソッドを一致させるルートが含まれる

たとえば、以下のルートがあります。

<Route Url="/path/:param1/:param2" Method="GET" Call="Package.Class:ClassMethod" />

クライアントが /path/10/20 に GET リクエストを送信すると、Package.ClassClassMethod1020 の引数で呼び出されます。

ブローカーの分離

「物理的」な分離

REST API には多くのルートが存在することがあるため、1 つのブローカーがすぐにメソッドでオーバーロードしてしまいます。 これを防止するには、以下のようにブローカを複数のクラスに分割します。

「物理的」な分離

説明:

  • Abstract broker(抽象ブローカー)はリクエストを変換し、CORS リクエストを確認して、REST API ロジックに関係のない他の技術的タスクを実行します。
  • Broker 1..N には、リクエストに対し、ターミナル API メソッドを呼び出してターミナル API のレスポンスをクライアントに返す処理を行うメソッドが含まれます。

「論理的」な分離とバージョン管理

通常、ブローカーには呼び出されたメソッドに URL を一致させるルートが含まれていますが、ブローカー間でリクエストを渡すマップも含まれています。 その仕組みは以下のとおりです。

<Map Prefix="/form" Forward="Destination.Broker"/>

この例では、/form から開始するすべてのリクエストが Destination.Broker に渡されます。 これにより、ブローカーの分離が可能になります。 ブローカーは次のように分離する必要があります。

  • バージョン
  • ドメイン

「物理的」な分離

ブローカーのパラメーター

REST ブローカーのパラメーターには、以下が推奨されます。

Parameter CONTENTTYPE = {..#CONTENTTYPEJSON};
Parameter CHARSET = "UTF-8";
Parameter UseSession As BOOLEAN = 1;

説明

  • CONTENTTYPE — レスポンスが JSON であることに関する情報を追加します。
  • CHARSET — レスポンスを UTF8 に変換します。
  • UseSession — セッションを使用してユーザーを追跡します。詳細は「認証」をご覧ください。

すべてのブローカーは 1 つの抽象ブローカーから継承されるため、これらのパラメーターをその抽象ブローカーに 1 回指定するだけで十分です(プライマリスーパークラスのパラメーターのみが継承されることに注意してください)。

コードの一般化

REST API の開発時によく発生する問題の 1 つは、ブローカー間でのコードのコピーアンドペーストです。多数のルートとコードのコピーが発生してしまうため、これは不適切な実践です。 これを回避するには、メソッドジェネレーターに必要となる可能性のあるすべてのメタ情報が含まれた %Dictionary パッケージでコード生成を使用することをお勧めします。 コード生成の大規模な使用例には、RESTForms ライブラリがあります。 最後に、コード生成を使用すると、REST API の一貫性を高めることができます。

CORS

クロスオリジンリソース共有(CORS)は Web ページ上の制限付きリソース(フォントなど)を、最初にリソースが提供されたドメイン外の別のドメインからリクエストできるようにする仕組みです。 CORS を使用して REST API にアクセスできるようにするには、次のパラメーターをブローカーに追加します。

Parameter HandleCorsRequest = 1;

これにより、他のドメインのページが REST API にアクセスできるようになります。 これは安全な方法ではないため、OnHandleCorsRequest メソッド、そのシグネチャーもオーバーライドする必要があります。

ClassMethod OnHandleCorsRequest(pUrl As %String) As %Status {}

このメソッドはリクエストのオリジンをチェックし、ホワイトリストに登録されたオリジンからのみのアクセスを許可します。 次は、オリジンを取得するメソッドです。

ClassMethod GetOrigins() As %String
{
    set url = %request.GetCgiEnv("HTTP_REFERER")
    return $p(url,"/",1,3) // get http(s)://origin.com:port
}

認証

1 人のユーザーが 1 つのライセンスを使用して 1 つのセッション内で作業するには、次の条件が満たされるように、システム管理ポータルで REST と CSP アプリケーションを構成する必要があります。

  1. すべてのブローカーに実質的に Parameter UseSession = 1; が設定されている。
  2. すべての Web アプリケーションで認証済み(パスワード)アクセスのみを許可する。
  3. すべての Web アプリケーションに適切な Session タイムアウト(900、3600)が設定されている。
  4. すべての Web アプリケーションに同じ GroupById 値が設定されている。
  5. すべての Web アプリケーションに同じ cookie path が設定されている。

開発ツール

デバッグには、以下のような外部ツールを使用できます。

  • REST クライアント(Postman)- REST API 開発者向けの主要デバッグツールです。 リクエストの保存とドキュメント化、JSON レスポンスの「Pretty」表示、複数の環境へのリクエストの送信が可能で、開発者向けにその他多数のツールが提供されます。
  • プロキシサーバーのインターセプト(Fiddler)- 開発者は、リクエストのインターセプト、表示、編集、および複製が可能です。
  • パケットアナライザー(Wireshark)- パケットの破損、エンコードの問題、リモートヘッドレスシステムの分析、および上記のツールでは不十分なその他の状況に役立ちます。

curl や wget などのコマンドラインツールを使用しないことを強くお勧めします。 また、REST API(REST API 以外)の様々なデバッグアプローチに関する連載記事(パート 1 - 外部ツールパート 2 - 内部ツール)も用意しました。

動作する REST API の例をいくつか紹介します。

InterSystems IRIS は、%API パッケージでいくつかの REST API を提供しています。

  • Atelier
  • API 管理
  • InterSystems: DocDB
  • InterSystems: BI
  • InterSystems: Text Analytics
  • InterSystems: UIMA

さらに、ドキュメントには REST に関するチュートリアルも含まれています。 Learning.InterSystems.com には、REST に関するコースがいくつか用意されています。

また、GitHub には、オープンソースの REST API もあります。

InterSystems IRIS: BI - MDX2JSON

MDX2JSON — MDX2JSON 変換用の REST API です。 キューブ、ダッシュボード、ウィジェット、およびその他の要素に関するメタ情報も提供されています。 2014.1+ で利用可能です。 クライアントは、AngularJS とハイチャートで構築される InterSystems IRIS: BI ダッシュボード用の Web ベースのレンダラーです。 以下のように表示されます。

DeepSeeWeb

InterSystems IRIS: Interoperability Workflow

Workflow は、自動ビジネスプロセスの実行にユーザーが参加できるようにするモジュールです。 Workflow REST API はユーザーのタスクを公開します。 2014.1+ で利用可能です。 Web クライアントで可視化を確認できます。

EnsembleWorkflowUI

EnsembleWorkflowUI

RESTForms

RESTForms には、自己検出型の堅牢な汎用 REST API ソリューションが提供されているため、新しい REST API の作成が簡単に行えるようになります。 2016.1+ で利用可能です。 RESTForms については、 パート 1パート 2 に分けて詳しく説明した記事を書きました。

RESTForms では以下のようないくつかのルートを利用できます。

メソッドURL説明
GETform/infoRESTForms 対応のクラスをすべてリストします。
GETform/info/all利用可能なすべてのクラスに関するメタ情報を取得します。
GETform/info/:class利用可能な 1 つのクラスに関するメタ情報を取得します。
GETform/object/:class/:idID とクラスでオブジェクトを取得します。
GETform/object/:class/:id/:propertyID、クラス、およびプロパティ名でオブジェクトのプロパティを取得します。
POSTform/object/:classオブジェクトを作成します。
PUTform/object/:class/:id動的オブジェクトを介してオブジェクトを更新します。
PUTform/object/:class標準オブジェクトを介してオブジェクトを更新します。
DELETEform/object/:class/:idオブジェクトを削除します。
GETform/objects/:class/:querySQL クエリを実行します。

このプロジェクトでは、コードの一般化(コード生成と %Dictionary パッケージを使用)が積極的に使用されています。 クライアントは Angular と React で利用でき、以下のように表示されます。

RESTForms

JSON

JavaScript Object Notation の略で、REST API でよく使用されるテキストシリアル化形式です。 InterSystems 製品の JSON サポートはバージョン 2009.2 以降で提供されていましたが、バージョン 2016.2 において大幅に改善されました。 ドキュメントには JSON だけに関するものがあります。

まとめ

  • REST は最も一般的で広く採用されている API テクノロジーの 1 つです。
  • REST API を提供する場合は、以下に限定されず、複数のクライアントテクノロジーの中から選択できます。
    • JavaScript(AngularJS、React、ExtJS など)
    • モバイルアプリ(Cordova などのテクノロジー、またはネイティブアプリ)
  • InterSystems テクノロジーを使えば、簡単に REST API を作成できます。

リンク

0
1 409
記事 Toshihiko Minamoto · 5月 30, 2023 7m read

コミュニティの皆さん、こんにちは!

最近では、誰もが GithubGitLabbitbucket などのリポジトリにプロジェクトのソースコードを保管していると思います。 InterSystems IRIS プロジェクトについても同様で、Open Exchange にチェックされています。

InterSystems Data Platform で特定のリポジトリの操作を開始または継続するたびに、何をしているでしょうか?

ローカルの InterSystems IRIS マシン、プロジェクト用の環境のセットアップ、ソースコードのインポートが必要です。

つまり、すべての開発者は以下を実行しています。

  1. リポジトリからコードをチェックアウトする。
  2. ローカル IRIS インストールをインストール/実行する。
  3. プロジェクト用の新しいネームスペース/データベースを作成する
  4. コードをこの新しいネームスペースにインポートする。
  5. すべての残りの環境をセットアップする。
  6. プロジェクトのコーディングを開始/継続する。 

リポジトリを Docker 化すると、この手順は次の 3 つのステップに短縮できます。

  1. リポジトリからコードをチェックアウトする。
  2. docker-compose build を実行する。 
  3. プロジェクトのコーディングを開始/継続する。 

メリット: 実行に数分かかる上、頭が痛くなるようなステップ 3、4、5 を行わなくて済みます。

次の数ステップで、(ほぼ)すべての InterSystems リポジトリを Dcoker 化できます。 では、やってみましょう!

**リポジトリを Docker 化する方法は?その意味は? **

基本的に、マシンに Docker をインストールし、コードと環境をコンテナに構築し、そのコンテナを Docker で実行すれば、最初に開発者が導入した方法で動作するようにするという考えです。 「OS のバージョンは何?」や「この IRIS インストールには他に何かあった?」といった疑問はありません。

毎回、クリーンなページ(またはクリーンな IRIS コンテナ)を使用して環境(ネームスペース、データベース、ウェブアプリ、ユーザー/ロール)がセットアップされ、作成したばかりのクリーンなデータベースにコードがインポートされます。

この「Docker 化」手順によって、現在のリポジトリに大きな悪影響が及ぶでしょうか? 

及びません。 リポジトリのルートに独自にセットアップできるいくつかのツールに従った 2~3 個の新しいファイルを追加する必要があります。

前提条件

docker をダウンロードしてインストールします。

IRIS docker イメージをダウンロードしてインストールします。 この例では、WRC-preview からダウンロードできる完全な InterSystems IRIS プレビュー(iris:2019.1.0S.111.0)を使用します。詳細をご覧ください。

キーを必要とするインスタンスを操作する場合は、常に使用する場所に iris.key を配置します。  私の場合は、Mac の Home ディレクトリに置きました。

リポジトリの Docker 化

リポジトリを Docker 化するには、リポジトリのルートフォルダに 3 つのファイルを追加する必要があります。

こちらは、ISC-DEV プロジェクトの Docker 化されたレポジトリの例です。これは、IRIS データベースからソースコードをインポート/エクスポートするのに役立ちます。  このリポジトリには、以下に説明する追加の Dockerfile、docker-compose.yml、および installer.cls が含まれています。

まず、Dockerfile です。これは、docker-compose build コマンドで使用されます。

Dockerfile
FROM intersystems/iris:2019.1.0S.111.0 # インストールされたものと同じイメージである必要があります 
<p>
</p>

<p>
  WORKDIR /opt/app
</p>

<p>
  COPY ./Installer.cls ./
</p>

<p>
  COPY ./cls/ ./src/
</p>

<p>
  RUN iris start $ISC_PACKAGE_INSTANCENAME quietly EmergencyId=sys,sys && \
</p>

<p>
      /bin/echo -e "sys\nsys\n" \
</p>

<p>
              # ユーザー管理者に %ALL を付与
</p>

<p>
              " Do ##class(Security.Users).UnExpireUserPasswords(\"*\")\n" \
</p>

<p>
              " Do ##class(Security.Users).AddRoles(\"admin\", \"%ALL\")\n" \
</p>

<p>
              # インストーラのインポートと実行
</p>

<p>
              " Do \$system.OBJ.Load(\"/opt/app/Installer.cls\",\"ck\")\n" \
</p>

<p>
              " Set sc = ##class(App.Installer).setup(, 3)\n" \
</p>

<p>
              " If 'sc do \$zu(4, \$JOB, 1)\n" \
</p>

<p>
              # OS レベルの認証を導入(コンテナのログイン/パス プロンプトを削除するため)
</p>

<p>
              " Do ##class(Security.System).Get(,.p)\n" \
</p>

<p>
              " Set p(\"AutheEnabled\")=p(\"AutheEnabled\")+16\n" \
</p>

<p>
              " Do ##class(Security.System).Modify(,.p)\n" \
</p>

<p>
              " halt" \
</p>

<p>
      | iris session $ISC_PACKAGE_INSTANCENAME && \
</p>

<p>
      /bin/echo -e "sys\nsys\n" \
</p>

<p>
      | iris stop $ISC_PACKAGE_INSTANCENAME quietly
</p>

<p>
  CMD [ "-l", "/usr/irissys/mgr/messages.log" ]
</p>   

<p>
   
</p>

 

この Dockerfile は installer.cls とソースコードをリポジトリの /cls フォルダから /src フォルダ、そしてコンテナにコピーします。

また、いくつかの構成設定も実行します。これにより、管理者ユーザーに %All ロールと無期限のパスワード「SYS」が付与され、OS レベルの認証が導入され、%Installer が実行されます。 

%Installer とは何でしょうか?

Class App.Installer
{
XData MyInstall [ XMLNamespace = INSTALLER ]
{
<Manifest>
  <Default Name="NAMESPACE" Value="ISCDEV"/>
  <Default Name="DBNAME" Value="ISCDEV"/>
  <Default Name="APPPATH" Dir="/opt/app/" />
  <Default Name="SOURCESPATH" Dir="${APPPATH}src" />
  <Default Name="RESOURCE" Value="%DB_${DBNAME}" /> 

  <Namespace Name="${NAMESPACE}" Code="${DBNAME}-CODE" Data="${DBNAME}-DATA" Create="yes" Ensemble="0">
    <Configuration>
      <Database Name="${DBNAME}-CODE" Dir="${APPPATH}${DBNAME}-CODE" Create="yes" Resource="${RESOURCE}"/>
      <Database Name="${DBNAME}-DATA" Dir="${APPPATH}${DBNAME}-DATA" Create="yes" Resource="${RESOURCE}"/>
    </Configuration>
    <Import File="${SOURCESPATH}" Recurse="1"/>
  </Namespace>

</Manifest>
}

ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
  Return ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyInstall")
}

}

 

これは、ネームスペース/データベース ISCDEV を作成し、ソースフォルダである -/src からコードをインポートします。

次は、docker-compose.yml ファイルです。これは、docker-compose up コマンドでコンテナを実行するときに使用されます。

version: '2.4'
services:
  iris:
    build: .
    restart: always
    ports:
      - 52773:52773
    volumes:
      - ~/iris.key:/usr/irissys/mgr/iris.key

この構成によって、Docker に対し、ホストのどのポートで IRIS の動作が期待されるかを指示します。 最初(52773)はホスト、2 つ目はコンテナのコンテナ内部ポート(52773)です。

volumes セクションでは、docker-compose.yml は、IRIS が検索するコンテナ内でマシン上の iris キーへのアクセスを提供します。

- ~/iris.key:/usr/irissys/mgr/iris.key

このリポジトリでコーディングを始めるには、以下を実行します。

  1. 任意のローカルディレクトリにリポジトリを Clone/git pull します。

 2. このディレクトリでターミナルを開き、以下を実行します。

user# docker-compose build

これによりコンテナがビルドされます。

  1. プロジェクトで IRIS コンテナを実行します。
user# docker-compose up -d

お好きな IDE を開いて、localhost://52773 のサーバーに接続し、InterSystems IRIS Data Platform で作業を始めましょう!;)

この 3 つのファイルを使用して、リポジトリを Docker 化できます。 Dockerfile のソースコードに適切な名前、Installer.cls に適切なネームスペースを付けて、docker-compose.yml に iris.key を配置するだけで、InterSystems IRIS を使った日常の開発で、Docker コンテナを活用することができます。

0
1 360
記事 Toshihiko Minamoto · 5月 23, 2023 8m read

Web スクレイピングとは:

簡単に言えば、Web スクレイピングWeb ハーベスティング、または Web データ抽出とは、Web サイトから大量のデータ(非構造化)を収集する自動プロセスです。 ユーザーは特定のサイトのすべてのデータまたは要件に従う特定のデータを抽出できます。 収集されたデータは、さらに分析するために、構造化された形式で保存することができます。

Web スクレイピングとは? — James Le

Web スクレイピングの手順:

  1. スクレイピングする Web ページの URL を見つけます。
  2. 検査により、特定の要素を選択します。
  3. 選択した要素のコンテンツを取得するコードを記述します。
  4. 必要な形式でデータを保存します。

たったそれだけです!!

Web スクレイピングに使用される一般的なライブラリ/ツール

  • Selenium - Web アプリケーションをテストするためのフレームワーク
  • BeautifulSoup – HTML、XML、およびその他のマークアップ言語からデータを取得するための Python ライブラリ
  • Pandas - データ操作と分析用の Python ライブラリ

Beauthiful Soup とは?

Beautiful Soup は、Web サイトから構造化データを抽出するための純粋な Python ライブラリです。 HTML と XML ファイルからデータを解析できます。 これはヘルパーモジュールとして機能し、利用できる他の開発者ツールを使って Web ページを操作する方法と同じ方法かより優れた方法で HTML と対話します。

  • lxmlhtml5lib などの使い慣れたパーサーと連携して、有機的な Python の方法で、解析ツリーを移動操作、検索、および変更できるようにするため、通常、プログラマーは数時間または数日間に及ぶ作業を節約できます。
  • Beautiful Soup のもう 1 つの強力で便利な機能は、フェッチされるドキュメントを Unicode に変換し、送信されるドキュメントを UTF-8 に変換するインテリジェンスです。 ドキュメント自体にエンコーディングが指定されていないか、Beautiful Soup がエンコーディングを検出できない場合を除き、開発者がその操作に注意する必要はありません。
  • 他の一般的な解析またはスクレイピング手法と比較した場合も高速と見なされています。

今日の記事では、Embedded Python と Object Script を使用して、ae.indeed.com にある Python の求人情報と企業をスクレイピングします。

ステップ 1 - スクレイピングする Web ページの URL を見つけます。

Url = https://ae.indeed.com/jobs?q=python&l=Dubai&start=0

スクレイピングするデータのある Web ページは以下のようになります。

  単純化と学習の目的で、"Job Title"(役職)と "Company"(会社)を抽出します。出力は以下のスクリーンショットのようになります。

 

以下の 2 つの Python ライブラリを使用します。

  • requests Requests は、Python プログラミング言語の HTTP ライブラリです。 プロジェクトの目標は、HTTP リクエストを単純化し、人間が読みやすくすることです。  
  • bs4 for BeautifulSoup Beautiful Soup は、HTML と XML ドキュメントを解析するための Python パッケージです。 HTML からデータを抽出するために使用できる解析済みページの解析ツリーを作成します。Web スクレイピングに役立ちます。

以下の Python パッケージをインストールしましょう(Windows)。

irispip install --target C:\InterSystems\IRISHealth\mgr\python bs4

irispip install --target C:\InterSystems\IRISHealth\mgr\python requests

Python ライブラリを ObjectScript にインポートしましょう
 

Class PythonTesting.WebScraper Extends%Persistent
{

// pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start=// pPage = 0ClassMethod ScrapeWebPage(pUrl, pPage)
{
    // imports the requests python libraryset requests = ##class(%SYS.Python).Import("requests")
    // import the bs4 python libraryset soup = ##class(%SYS.Python).Import("bs4")
    // import builtins package which contains all of the built-in identifiersset builtins = ##class(%SYS.Python).Import("builtins")
}

Requests を使って HTML データを収集しましょう。

注意: 「my user agent」でグーグル検索し取得した、ユーザーエージェント
URL は "https://ae.indeed.com/jobs?q=python&l=Dubai&start=" で、pPage はページ番号です。

Requests を使って URL に HTTP GET リクエストを行い、そのレスポンスを "req" に格納します。

set headers  = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"}
    set url = "https://ae.indeed.com/jobs?q=python&l=Dubai&start="_pPage
    
    set req = requests.get(url,"headers="_headers)

req オブジェクトには、Web ページから返された HTML が含まれます。

これを BeautifulSoup HTML パーサーで実行し、求人データを抽出できるようにします。

set soupData = soup.BeautifulSoup(req.content, "html.parser")
set title = soupData.title.text
W !,title

タイトルは以下のように表示されます

ステップ 2 - 検査し、必要な要素を選択します。

このシナリオでは、通常 <div> タグに含まれる求人リストに注目しています。ブラウザ内で要素を検査すると、その div クラスが見つかります。

ここでは、必要な情報は、「 <div class="cardOutline tapItem ... </div>」 に格納されています。

ステップ 3 - 選択した要素のコンテンツを取得するコードを記述します。

BeautifulSoup の find_all 機能を使用して、クラス名 "cardOutline" を含むすべての <div> タグを検索します。

//parameters to python would be sent as a python dictionaryset divClass = {"class":"cardOutline"}
set divsArr = soupData."find_all"("div",divClass...)

これによりリストが返されます。これをループ処理すると、Job Titles と Company を抽出できます。

###ステップ 4 - 必要なフォーマットでデータを保存/表示します。

以下の例では、データをターミナルに書き出します。

set len = builtins.len(divsArr)
    
W !, "Job Title",$C(9)_" --- "_$C(9),"Company"for i = 1:1:len {
    Set item = divsArr."__getitem__"(i - 1)
    set title = $ZSTRIP(item.find("a").text,"<>W")
    set companyClass = {"class_":"companyName"}
    set company = $ZSTRIP(item.find("span", companyClass...).text,"<>W")
    W !,title,$C(9)," --- ",$C(9),company
}

builtins.len() を使用して、divsArr リストの長さを取得していることに注意してください。

識別子名: ObjectScript と Python の識別子の命名規則は異なります。 たとえば、Python のメソッド名ではアンダースコア(_)を使用でき、_getitem_ や _class_ のようにいわゆる「ダンダー」といわれる特殊なメソッドや属性で実際に広く使用されています(「ダンダー」は「double underscore = 二重アンダースコア」の略です)。 このような識別子を ObjectScript で使用するには、二重引用符で囲みます:

識別子名に関する InterSystems ドキュメント

クラスメソッドの例

ClassMethod ScrapeWebPage(pUrl, pPage)
// pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start=// pPage = 0ClassMethod ScrapeWebPage(pUrl, pPage)
{
    set requests = ##class(%SYS.Python).Import("requests")
<span class="hljs-keyword">set</span> soup = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%SYS.Python</span>).Import(<span class="hljs-string">"bs4"</span>)

<span class="hljs-keyword">set</span> builtins = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%SYS.Python</span>).Builtins()

<span class="hljs-keyword">set</span> headers  = {<span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"</span>}
<span class="hljs-keyword">set</span> url = pUrl_pPage

<span class="hljs-keyword">set</span> req = requests.get(url,<span class="hljs-string">"headers="</span>_headers)

<span class="hljs-keyword">set</span> soupData = soup.BeautifulSoup(req.content, <span class="hljs-string">"html.parser"</span>)

<span class="hljs-keyword">set</span> title = soupData.title.text

<span class="hljs-keyword">W</span> !,title

<span class="hljs-keyword">set</span> divClass = {<span class="hljs-string">"class_"</span>:<span class="hljs-string">"cardOutline"</span>}
<span class="hljs-keyword">set</span> divsArr = soupData.<span class="hljs-string">"find_all"</span>(<span class="hljs-string">"div"</span>,divClass...)

<span class="hljs-keyword">set</span> len = builtins.len(divsArr)

<span class="hljs-keyword">W</span> !, <span class="hljs-string">"Job Title"</span>,<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>)_<span class="hljs-string">" --- "</span>_<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>),<span class="hljs-string">"Company"</span>
<span class="hljs-keyword">for</span> i = <span class="hljs-number">1</span>:<span class="hljs-number">1</span>:len {
        <span class="hljs-keyword">Set</span> item = divsArr.<span class="hljs-string">"__getitem__"</span>(i - <span class="hljs-number">1</span>)
        <span class="hljs-keyword">set</span> title = <span class="hljs-built_in">$ZSTRIP</span>(item.find(<span class="hljs-string">"a"</span>).text,<span class="hljs-string">"<>W"</span>)
        <span class="hljs-keyword">set</span> companyClass = {<span class="hljs-string">"class_"</span>:<span class="hljs-string">"companyName"</span>}
        <span class="hljs-keyword">set</span> company = <span class="hljs-built_in">$ZSTRIP</span>(item.find(<span class="hljs-string">"span"</span>, companyClass...).text,<span class="hljs-string">"<>W"</span>)
        <span class="hljs-keyword">W</span> !,title,<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>),<span class="hljs-string">" --- "</span>,<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>),company
 }

}

</div>

今後の内容..

ObjectScript とEmbedded Python と数行のコードを使用して、いつも使用する求人サイトのデータをスクレイピングし、求人タイトル、会社、給料、職務内容、メールアドレス/リンクを簡単に収集できます。

たとえば、ページが複数ある場合、ページを使用して簡単にそれらをトラバースできます。 このデータを Pandas データフレームに追加して重複を削除したら、関心のある特定のキーワードに基づいてフィルターを適用できます。 このデータを NumPy で実行してラインチャートを取得します。 または、One-Hot エンコーディングをデータに実行し、ML モデルを作成/トレーニングします。興味のある特定の求人情報がある場合は、自分に通知を送信するようにします。 😉

それではコーディングをお楽しみください!!!

「いいね」ボタンも忘れずに押してください 😃

0
0 431
記事 Toshihiko Minamoto · 2月 27, 2023 7m read

今回は、「孤立メッセージ」について説明します。

孤立メッセージとは何ですか?

すべてのメッセージボディは、メタデータを保持するメッセージヘッダと関連付けらます。ヘッダーには、ソース構成名称、ターゲット構成名称、作成時刻、処理時刻、関連するメッセージボディ参照、セッション情報、メッセージボディのクラス名称、メッセージステータスなどの情報が格納されます。 メッセージボディに対応するヘッダーレコードが存在しない場合、そのメッセージボディは孤立メッセージボディと呼ばれます。ここでは、孤立メッセージボディの原因となる可能性があるものについて説明します。

ヘッダーのみを削除する場合

削除タスクの設定において、BodiesToo メッセージヘッダとともにメッセージボディも削除するかどうかを指定するものです。この設定をOFFにすると、削除タスクはメッセージヘッダーのみを削除し、メッセージボディは残します。これらのメッセージボディは、参照されたヘッダが削除されることから、孤立したレコードとなります。 メッセージヘッダの削除したが、メッセージボディは残している場合、マネジメントポータルでは孤立メッ セージボディを削除する方法はありません。この場合、プログラムによってメッセージボディを削除する必要があります。

 

削除タスクについては、ドキュメントをご参照ください。

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EGMG_purge#EGMG_purge_basic

複合メッセージボディの クラス (オブジェクトの値によるプロパティ)

Ensemble がメッセージボディを削除する際、メッセージボディのオブジェクトの値によるプロパティが削除されるとは限り ません。具体的には、他のオブジェクトを削除するのは、それらがシリアルオブジェクトであるか、子オブジェクト(関係によって定義される)である場合のみである。その他のオブジェクトについては、削除トリガーを定義するか、メッセージボディのクラスで %OnDelete() 方法を実装して、適切に削除を処理する必要があります。

OnDelete実装のサンプルコード

Class Sample.Address Extends %Persistent{
/// 所番地。
Property Street As %String(MAXLEN = 80);
/// 市名。
Property City As %String(MAXLEN = 80);
/// 2文字の州の略称。
Property State As %String(MAXLEN = 2);
/// 米国の5桁のZIP(Zone Improvement Plan)コード。
Property Zip As %String(MAXLEN = 5);
}
Class Sample.Person Extends %Persistent{
/// 本人の名前。
Property Name As %String [ Required ];
/// 本人の社会保障番号。これはパターンマッチで検証さ れます。
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
/// 本人の生年月日。
Property DOB As %Date;
/// 本人の自宅住所。
Property Home As Address;
/// 本人の勤務先住所。
Property Office As Address;
///オブジェクトを削除するためのコールバック。
ClassMethod %OnDelete(oid As %ObjectIdentity) As %Status [ Private ]{
      // プロパティオブジェクトの参照を削除することです。
      Set tSC = $$$OK, tThis = ##class(Sample.Person).%Open(oid)
      If $ISOBJECT(tThis.Home) Set tSC = ##class(Sample.Address).%DeleteId(tThis.Home.%Id())
      If $ISOBJECT(tThis.Office) Set tSC = ##class(Sample.Address).%DeleteId(tThis.Office.%Id())
      Quit tSC
}
///SQL削除のコールバック/トリガー
Trigger OnDelete [ Event = DELETE ]{
      // プロパティ・オブジェクトの参照を削除します。{%%ID} は削除されるレコードの ID を保持します。
      Set tID={%%ID}
      Set tThis = ##class(Sample.Person).%OpenId(tID)
      If $ISOBJECT(tThis.Home) Do ##class(Sample.Address).%DeleteId(tThis.Home.%Id())
      If $ISOBJECT(tThis.Office) Do ##class(Sample.Address).%DeleteId(tThis.Office.%Id())
      Quit
}
}

メッセージオブジェクトが作成さられましたが、他のホストに送信されませんでした。

メッセージが別のホストに送信/転送さ れようとしたら、Ensemble は新しいメッセージヘッダを作成し、対応するメッセージボディを関連付けます。 ビジネスサービス/プロセスで作成されたメッセージボディ/オブジェクトのインスタンスがディスク/データベースに保存され、かつプロダクション内の別のホストに送信されなかった場合、関連するヘッダーがなく、孤立メッセージボディとして残ります。ベストプラクティスは、転送されない限りメッセージボディを作成しないこと、またはオブジェクトのインスタンスの %Save() をコールしないことです (SendRequestSync/SendRequestAsync API は、メッセージをターゲット設定キューに入れる前にそれを保存します)。この方法では、メッセージボディのオブジェクトは、他のホストに送信されない限り、永続化されることはありません。

この問題の最も一般的な原因は、開発者が:

  1. メッセージボディを複製し、複製されたメッセージボディを転送しない場合です。
  2. コンテキスト変数(BPL)で作成されたメッセージボディオブジェクトが転送されない場合です。

孤立メッセージの効果

孤立メッセージは、削除タスクによって削除されません。これらのメッセージはディスクスペースを消費し、その数が増えるにつれて、ディスク使用量も増加します。ディスク使用量は、メッセージボディのデータだけでなく、これらの孤立メッセージボディレコードのインデックス/サーチテーブルのエントリも含めて使用されることになります。

孤立したメッセージの特定

孤立したメッセージの存在は、メッセージヘッダとボディを照会することで 特定することができます。各メッセージヘッダは、対応するメッセージボディを参照します。メッセージボディのオブジェクトIdの参照はヘッダのMessageBodyIdプロパティに、メッセージボディのクラス名はヘッダのMessageBodyClassNameプロパティに格納されます。

HL7 メッセージ・テーブルで孤立メッセージを見つけるためのクエリの例:

SELECT HL7.Id  FROM EnsLib_HL7.Message HL7

   LEFT JOIN Ens.MessageHeader hdr

   ON HL7.Id=hdr.MessageBodyId

   WHERE hdr.MessageBodyId IS NULL

上記のクエリは、対応するヘッダを持たないすべての HL7 メッセージを返します。このクエリは、メッセージボディのテーブル名称を置き換えるだけで、他のメッセージタイプに対するクエリに変更することができます。

孤立メッセージの削除

マネジメントポータルでは孤立メッ セージボディを削除する方法はありません。この場合、プログラムによってメッセージボディを削除する必要があります。ENSDEMO データベースでは、Demo.Util.CleanupSet というクラスが、これを実行する方法の例を示してくれます。このルーチンは、オブジェクトのプロパティ参照も含めて深く削除します。

孤立メッセージを削除するためのルーチンがもう一つありますが、このルーチンは深い削除はしませんし、メッセージボディの削除だけを助けます。ソースをダウンロードするために、以下にgithubへのリンクを載せておきます。

https://gist.github.com/suriyasv/2ed7f2dbcfd8c79f3b9938762c17c0b5

ベストプラクティスは、

  1. 孤立メッセージを防ぐために、常にプログラミングエラーを避けることです(前述)。
  2. BodiesToo設定をOFFにした削除タスクを、ボディが必要な場合のみ設定し、これらのメッセージボディはプログラムによってのみ削除できることを認識することです。
  3. 永続オブジェクトプロパティに対するRelationshipsまたはOnDelete実装することです。

この記事が、プロダクションを構築する際にお役に立てば幸いです。また、ご不明な点などございましたら、お知らせください。ありがとうございました。

0
0 169
記事 Toshihiko Minamoto · 9月 10, 2022 49m read


このフォーメーション私の GitHub にあり、30 分で csv ファイルと txt ファイルの読み取りと書き込み方法、Postgres を使ったIRIS データベースリモートデータベースの挿入とアクセス方法、FLASK API の使用方法について説明します。これらすべてに、PEP8 命名規則に従った、Python のみのインターオペラビリティフレームワークを使用します。

このフォーメーションは、ほとんどをコピー&ペースト操作で実行でき、グローバル演習を行う前に、ステップごとの操作が説明されています。
記事のコメント欄、Teams、またはメール(lucas.enard@intersystems.com)でご質問にお答えします。

このフォーメーションに関するあらゆる点において、ご意見やご感想をお送りいただけると幸いです。

1. Ensemble / Interoperability のフォーメーション

このフォーメーションでは、Python および特に以下を使用した InterSystems のインターオペラビリティフレームワークを学習することを目標としています。

  • 本番環境
  • メッセージ
  • ビジネスオペレーション
  • アダプター
  • ビジネスプロセス
  • ビジネスサービス
  • REST サービスと運用

目次:

2. フレームワーク

以下は、IRIS フレームワークです。

フレームワーク全体図

IRIS 内部のコンポーネントは、本番環境を表します。 インバウンドアダプターとアウトバウンドアダプターは、様々な種類のフォーマットをデータベースの入力と出力として使用できるようにします。
複合アプリケーションにより、REST サービスなどの外部アプリケーションを通じて本番環境にアクセスできます。

これらのコンポーネントを繋ぐ矢印はメッセージで、 リクエストかレスポンスを表します。

3. フレームワークの適用

ここでは、CSV ファイルから行を読み取り、IRIS データベースに .txt ファイルで保存します。

次に、外部データベースにオブジェクトを保存できるようにするオペレーションを db-api を使って追加します。 このデータベースは Docker コンテナに配置されており、Postgres を使用します。

最後に、複合アプリケーションを使用して、新しいオブジェクトをデータベースに挿入する方法またはこのデータベースを照会する方法を確認します(ここでは、REST サービスを使用します)。

この目的に合わせて構成されたフレームワークは、以下のようになります。

WIP 適応したフレームワーク

4. 前提条件

このフォーメーションでは、以下の項目が必要です。

5. セットアップ

5.1. Docker コンテナ

InterSystems イメージにアクセスするために、次の URL に移動してください: http://container.intersystems.com。 InterSystems の資格情報にリンクすると、レジストリに接続するためのパスワードを取得できます。 Docker の VSCode アドオンのイメージタブで、[レジストリを接続]を押し、汎用レジストリとして上記の URL(http://container.intersystems.com)を入力すると、資格情報の入力を求められます。 このログインは通常のログインですが、パスワードはウェブサイトから取得したものを使用します。

これが済むと、コンテナを構築・作成(指定された docker-compose.ymlDockerfile を使用)できるようになります。

5.2. 管理ポータルと VSCode

このリポジトリは、VS Code 対応です。

ローカルにクローンした formation-template-python を VS Code で開きます。

指示されたら(右下に表示)、推奨される拡張機能をインストールしてください。

5.3. コンテナ内でフォルダを開く

コーディングする前に、コンテナ内部にアクセスしていることが非常に重要です
これには、docker が VSCode を開く前に有効である必要があります。
次に VSCode 内で指示されたら(右下に表示)、コンテナ内のフォルダを開き直すと、その中にある Python コンポーネントを使用できるようになります。
これを初めて行う場合、コンテナの準備が整うまで数分かかる場合があります。

詳細情報はこちらをご覧ください。

アーキテクチャ


リモートのフォルダを開くと、その中で開く VS Code とターミナルで、コンテナ内の Python コンポーネントを使用できます。 /usr/irissys/bin/irispython を使用するように構成してください。

Pythonインタープリター

5.4. コンポーネントの登録

本番環境に対して Python で作成しているコンポーネントを登録するには、grongier.pex._utils モジュールから register_component 関数を使用する必要があります。

重要: コンポーネントはすでに登録済みです(グローバル演習を除く)。
情報までに、またグローバル演習で使用できるように、以下の手順でコンポーネントを登録します。
これには、プロジェクトで作業している際に、最初に組み込み Python コンソールを使用して手動でコンポーネントを追加することをお勧めします。

これらのコマンドは、misc/register.py ファイルにあります。
このコマンドを使用するには、まずコンポーネントを作成してから、VSCode でターミナルを起動する必要があります(5.25.3 の手順を実行している場合は、自動的にコンテナ内に移動しています)。
IrisPython コンソールを起動するには、以下のように入力します。

/usr/irissys/bin/irispython

次に、以下を入力します。

from grongier.pex._utils import register_component

そして、以下のようにして、コンポーネントを登録できます。

register_component("bo","FileOperation","/irisdev/app/src/python/",1,"Python.FileOperation")

この行は、bo モジュール内にコーディングされている FileOperation クラスを登録します。このファイルは /irisdev/app/src/python/(このコースに従って作業している場合のパス)にあり、管理ポータルでは Python.FileOperation という名前を使用しています。

コンポーネントが登録済みである際にこのファイルの名前、クラスまたはパスを変更しない場合、VSCode でそれらを変更することが可能です。登録し直す必要はありません。 管理ポータルでの再起動は、必ず行ってください。

5.5. 完成ファイル

フォーメーションのある時点でやり方がわからなくなったり、さらに説明が必要となった場合は、GitHub の solution ブランチにすべての正しい内容と動作する本番環境をご覧いただけます。

6. 本番環境

本番環境は、Iris 上のすべての作業の土台であり、サービスプロセス、およびオペレーションをまとめるフレームワークの外殻として考える必要があります。
本番環境内のすべては関数を継承します。この関数は、このクラスのインスタンスの作成時に解決する on_init 関数と、インスタンスがキルされたときに解決する on_tear_down 関数です。 これは、書き込みを行うときに変数を設定する際、または使用された開いているファイルをクローンする際に役立ちます。

ほぼすべてのサービス、プロセス、およびオペレーションを持つ本番環境は、すでに作成済みであることに注意してください。
指示されたら、ユーザー名の SuperUser とパスワードの SYS を使用して接続してください。

本番環境が開いていない場合は、[Interoperability] > [Configure] メニューに移動して、[Production] をクリックし、 次に [Open] をクリックして iris / Production を選択します。


これを行ったら、ビジネスオペレーションに直接進むことができます。


ただし、本番環境の作成方法に興味がある場合は、以下のようにして作成することができます。
管理ポータルに移動し、ユーザー名: SuperUser、パスワード: SYS を使用して接続します。
次に、[Interoperability] と [Configure] メニューに進みます。

本番環境メニュー

次に、[New] を押して [Formation] パッケージを選択し、本番環境の名前を設定します。

本番環境の作成

本番環境を作成した直後、[Operations] セクションの真上にある [Production Settings(本番環境の設定)] をクリックする必要があります。 右のサイドバーメニューで、[Settings] タブの [Development and Debugging(開発とデバッグ)] で [Testing Enabled(テストを有効)] をアクティブにします(忘れずに [Apply] を押してください)。

本番環境のテスト

この最初の本番環境で、ビジネスオペレーションを追加していきます。

7. ビジネスオペレーション

ビジネスオペレーション(BO)は、IRIS から外部アプリケーション/システムにリクエストを送信できるようにする特定のオペレーションです。 必要なものを IRIS に直接保存するためにも使用できます。
BO には、このインスタンスがソースからメッセージを受信するたびに呼び出される on_message 関数もあるため、フレームワークで確認できるように、情報を外部クライアントと送受信することが可能です。

これらのオペレーションは、VSCode でローカルに作成します。つまり、src/python/bo.py ファイルです。
このファイルを保存すると、IRIS でコンパイルされます。

ここでの最初のオペレーションでは、メッセージのコンテンツをローカルデータベースに保存し、同じ情報をローカルの .txt ファイルに書き込みます。

まずは、このメッセージを保存する方法を作成する必要があります。

7.1. オブジェクトクラスの作成

dataclass を使用して、メッセージの情報を格納することにします。

すでに存在する src/python/obj.py ファイルを以下のようにします。
インポート:

from dataclasses import dataclass

コード:

@dataclass
class Formation:
    id_formation:int = None
    nom:str = None
    salle:str = None

@dataclass
class Training:
    name:str = None
    room:str = None

Formation クラスは、csv の情報を格納して「8. ビジネスプロセス」に送信する Python オブジェクトとして使用し、Training クラスは、「8. ビジネスプロセス」から複数のオペレーションに情報を送信し、Iris データベースに保存するか .txt ファイルに書き込むために使用されます。

7.2. メッセージクラスの作成

これらのメッセージには、7.1 で作成された obj.py ファイルにある Formation オブジェクトまたは Training オブジェクトが含まれます。

メッセージ、リクエスト、およびレスポンスはすべて grongier.pex.Message クラスの継承であることに注意してください。

すでに存在する src/python/msg.py ファイルを以下のようにします。
インポート:

from dataclasses import dataclass
from grongier.pex import Message

from obj import Formation,Training

コード:

@dataclass
class FormationRequest(Message):
    formation:Formation = None

@dataclass
class TrainingRequest(Message):
    training:Training = None

繰り返しますが、FormationRequest クラスは、csv の情報を格納して「8. ビジネスプロセス」に送信するメッセージとして使用し、TrainingRequest クラスは、「8. ビジネスプロセス」から複数のオペレーションに情報を送信し、Iris データベースに保存するか .txt ファイルに書き込むために使用されます。

7.3. オペレーションの作成

必要な要素がすべて揃ったので、オペレーションを作成できるようになりました。
すべてのビジネスオペレーションは、grongier.pex.BusinessOperation クラスを継承していることに注意してください。
すべてのオペレーションは、src/python/bo.py に保存されます。これらを区別できるよう、ファイルに現時点で確認できるように複数のクラスを作成する必要があります。オペレーションのすべてのクラスはすでに存在していますが、現時点ではほぼ空の状態です。

オペレーションがメッセージ/リクエストを受信すると、各関数のシグネチャに指定されたメッセージ/リクエストの種類に応じて自動的に正しい関数にメッセージ/リクエストを送信します。 メッセージ/リクエストの種類が処理されない場合は、on_message 関数に転送されます。

すでに存在する src/python/bo.py ファイルを以下のようにします。
インポート:

from grongier.pex import BusinessOperation
import os
import iris

from msg import TrainingRequest,FormationRequest

FileOperation クラスのコード:

class FileOperation(BusinessOperation):
    """
    トレーニングまたは患者をファイルに書き込むオペレーションです。
    """
    def on_init(self):
        """
        現在の作業ディレクトリを、オブジェクトの path 属性に指定されたディレクトリか、path 属性が指定されていない場合は /tmp ディレクトリに変更します。 
        また、filename 属性が設定されていない場合は、toto
.csv に設定します。
        :return: None
        """
        if hasattr(self,'path'):
            os.chdir(self.path)
        else:
            os.chdir("/tmp")
        return None

    def write_training(self, request:TrainingRequest):
        """
        ファイルにトレーニングを書き込みます。

        :param request: The request message
        :type request: TrainingRequest
        :return: None
        """
        romm = name = ""
        if request.training is not None:
            room = request.training.room
            name = request.training.name
        line = room+" : "+name+"\n"
        filename = 'toto.csv'
        self.put_line(filename, line)
        return None

    def on_message(self, request):
        return None


    def put_line(self,filename,string):
        """
        ファイルを開き、文字列を追加し、ファイルを閉じます。

        :param filename: The name of the file to write to
        :param string: The string to be written to the file
        """
        try:
            with open(filename, "a",encoding="utf-8",newline="") as outfile:
                outfile.write(string)
        except Exception as error:
            raise error

ご覧のとおり、FileOperationmsg.TrainingRequest タイプのメッセージを受信すると、request のシグネチャが TrainingRequest であるため、write_training 関数に送信されます。
この関数では、メッセージが格納する情報が toto.csv ファイルに書き込まれます。

path はオペレーションのパラメーターであるため、filename を管理ポータルで直接変更できる変数にし、基本値を toto.csv とすることができます。
このようにするには、on_init 関数を以下のように編集する必要があります。

    def on_init(self):
        if hasattr(self,'path'):
            os.chdir(self.path)
        else:
            os.chdir("/tmp")
        if not hasattr(self,'filename'):
            self.filename = 'toto.csv'
        return None

次に、オペレーションに直接コーディングして filename = 'toto.csv' を使用する代わりに、self.filename を呼び出します。
すると、write_training 関数は、以下のようになります。

    def write_training(self, request:TrainingRequest):
        romm = name = ""
        if request.training is not None:
            room = request.training.room
            name = request.training.name
        line = room+" : "+name+"\n"
        self.put_line(self.filename, line)
        return None

独自の filename の選択方法にいては、「7.5 テスト」をご覧ください。

情報を .txt ファイルに書き込めるようになりましたが、iris データベースはどうなっているでしょうか?
src/python/bo.py ファイルでは、IrisOperation クラスのコードは以下のようになっています。

class IrisOperation(BusinessOperation):
    """
    iris データベースにトレーニングを書き込むオペレーション
    """

    def insert_training(self, request:TrainingRequest):
        """
        `TrainingRequest` オブジェクトを取り、新しい行を `iris.training` テーブルに挿入し、
        `TrainingResponse` オブジェクトを返します

        :param request: 関数に渡されるリクエストオブジェクト
        :type request: TrainingRequest
        :return: TrainingResponse メッセージ
        """
        sql = """
        INSERT INTO iris.training
        ( name, room )
        VALUES( ?, ? )
        """
        iris.sql.exec(sql,request.training.name,request.training.room)
        return None

    def on_message(self, request):
        return None

ご覧のとおり、IrisOperationmsg.TrainingRequest タイプのメッセージを受信すると、このメッセージが保有する情報は、iris.sql.exec IrisPython 関数によって SQL クエリに変換されて実行されます。 このメソッドによって、メッセージは IRIS ローカルデータベースに保存されます。


これらのコンポーネントは、事前に本番環境に登録済みです。

情報までに、コンポーネントを登録する手順は、5.4. に従い、以下を使用します。

register_component("bo","FileOperation","/irisdev/app/src/python/",1,"Python.FileOperation")

および、以下を使用します。

register_component("bo","IrisOperation","/irisdev/app/src/python/",1,"Python.IrisOperation")

7.4. 本番環境へのオペレーションの追加

オペレーションは、こちらで事前に登録済みです。
ただし、オペレーションを新規作成する場合は、手動で追加する必要があります。

今後の参考までに、オペレーションの登録手順を説明します。
登録には、管理ポータルを使用します。 [Operations] の横にある [+] 記号を押すと、[Business Operation Wizard(ビジネスオペレーションウィザード)] が開きます。
そのウィザードで、スクロールメニューから作成したばかりのオペレーションクラスを選択します。

オペレーションの作成

新しく作成したすべてのオペレーションに対して必ず実行してください!

7.5. テスト

オペレーションをダブルクリックすると、オペレーションが有効化されるか、再起動して変更内容が保存されます。
重要: この無効化して有効化し直す手順は、変更内容を保存する上で非常に重要な手順です。
重要: その後で、Python.IrisOperationオペレーションを選択し、右のサイドバーメニューの [Actions] タブに移動すると、オペレーションテストすることができます。
(うまくいかない場合は、テストを有効化し、本番環境が開始していることを確認してから、本番環境をダブルクリックして再起動をクリックし、オペレーションをリロードしてください)。

IrisOperation については、テーブルは自動的に作成済みです。 情報までに、これを作成するには、管理ポータルを使用します。[System Explorer(システムエクスプローラー)] > [SQL] > [Go] に移動して、Iris データベースにアクセスします。 次に、[Execute Query(クエリを実行)] を開始します。

CREATE TABLE iris.training (
    name varchar(50) NULL,
    room varchar(50) NULL
)

管理ポータルのテスト機能を使用して、前に宣言したタイプのメッセージをオペレーションに送信します。 すべてがうまくいけば、仮想トレースを表示すると、プロセスとサービスとオペレーション間で何が起きたかを確認できるようになります。
Request Type として使用した場合:

Grongier.PEX.Message

%classname として使用した場合:

msg.TrainingRequest

%json として使用した場合:

{
    "training":{
        "name": "name1",
        "room": "room1"
    }
}

ここでは、メッセージがプロセスによってオペレーションに送信され、オペレーションがレスポンス(空の文字列)を送り返すのを確認できます。
以下のような結果が得られます。 IrisOperation


FileOperation については、以下のように管理ポータルの %settingspath を入力できます(7.3.filename に関するメモに従った場合は、設定に filename を追加できます)。

path=/tmp/
filename=tata.csv

結果は以下のようになります。 FileOperation の設定


繰り返しますが、Python.FileOperationオペレーションを選択して、右サイドバーメニューの [Actions] タブに移動すると、オペレーションテストできます。
(うまくいかない場合は、テストを有効化し、本番環境が起動していることを確認してください)。
Request Type として使用した場合:

Grongier.PEX.Message

%classname として使用した場合:

msg.TrainingRequest

%json として使用した場合:

{
    "training":{
        "name": "name1",
        "room": "room1"
    }
}

結果は以下のようになります。 FileOperation


オペレーションが動作したかどうかを確認するには、toto.csv(7.3.filename に関するメモに従った場合は tata.csv)ファイルと Iris データベースにアクセスして変更内容を確認する必要があります。
次の手順を実行するには、コンテナの中に移動する必要がありますが、5.2.5.3 を実行した場合には、省略できます。
toto.csv にアクセスするには、ターミナルを開いて、以下を入力する必要があります。

bash
cd /tmp
cat toto.csv

または、必要であれば "cat tata.csv" を使用してください。
重要: ファイルが存在しない場合、管理ポータルでオペレーションを再起動していない可能性があります。
再起動するには、オペレーションをクリックして再起動を選択してください(または、無効化してからもう一度ダブルクリックして有効化してください)。
もう一度テストする必要があります。


Iris データベースにアクセスするには、管理ポータルにアクセスし、[System Explorer(システムエクスプローラー)] > [SQL] > [Go] を選択する必要があります。 次に、[Execute Query(クエリを実行)] を開始します。

SELECT * FROM iris.training

8. ビジネスプロセス

ビジネスプロセス(BP)は、本番環境のビジネスロジックです。 リクエストをプロセスしたり、本番環境の他のコンポーネントにそのリクエストをリレーしたリするために使用されます。
BP には、インスタンスがソースからリクエストを受信するたびに呼び出される on_request 関数もあるため、情報を受信して処理し、正しい BO に送信することができます。

これらのプロセスは、VSCode でローカルに作成します。つまり、src/python/bp.py ファイルです。
このファイルを保存すると、IRIS でコンパイルされます。

8.1. 単純な BP

サービスから受信する情報を処理し、適切に配信するビジネスプロセスを作成しましょう。 オペレーションを呼び出す単純な BP を作成することにします。

この BP は情報をリダイレクトするだけであるため、これを Router と呼び、src/python/bp.py に作成します。
インポート:

from grongier.pex import BusinessProcess

from msg import FormationRequest, TrainingRequest
from obj import Training

コード:


class Router(BusinessProcess):

    def on_request(self, request):
        """
        リクエストを受信し、フォーメーションリクエストであるかを確認し、そうである場合は
        TrainingRequest リクエストを FileOperation と IrisOperation に送信します。IrisOperation が 1 を返すと、PostgresOperation に送信します。

        :param request: 受信したリクエストオブジェクト
        :return: None
        """
        if isinstance(request,FormationRequest):

            msg = TrainingRequest()
            msg.training = Training()
            msg.training.name = request.formation.nom
            msg.training.room = request.formation.salle

            self.send_request_sync('Python.FileOperation',msg)
            self.send_request_sync('Python.IrisOperation',msg)
        return None

Router は FormationRequest タイプのリクエストを受信し、TrainingRequest タイプのメッセージを作成して IrisOperationFileOperation オペレーションに送信します。 メッセージ/リクエストが求めているタイプのインスタンスでない場合、何も行わず、配信しません。


これらのコンポーネントは、事前に本番環境に登録済みです。

情報までに、コンポーネントを登録する手順は、5.4. に従い、以下を使用します。

register_component("bp","Router","/irisdev/app/src/python/",1,"Python.Router")

8.2. 本番環境へのプロセスの追加

プロセスは、こちらで事前に登録済みです。
ただし、プロセスを新規作成する場合は、手動で追加する必要があります。

今後の参考までに、プロセスの登録手順を説明します。
登録には、管理ポータルを使用します。 [Process] の横にある [+] 記号を押すと、[Business Process Wizard(ビジネスプロセスウィザード)] が開きます。
そのウィザードで、スクロールメニューから作成したばかりのプロセスクラスを選択します。

8.3. テスト

プロセスをダブルクリックすると、プロセスが有効化されるか、再起動して変更内容が保存されます。
重要: この無効化して有効化し直す手順は、変更内容を保存する上で非常に重要な手順です。
重要: その後で、プロセスを選択し、右のサイドバーメニューの [Actions] タブに移動すると、プロセステストすることができます。
(うまくいかない場合は、テストを有効化し、本番環境が開始していることを確認してから、本番環境をダブルクリックして再起動をクリックし、プロセスをリロードしてください)。

こうすることで、プロセスに msg.FormationRequest タイプのメッセージを送信します。 Request Type として使用した場合:

Grongier.PEX.Message

%classname として使用した場合:

msg.FormationRequest

%json として使用した場合:

{
    "formation":{
        "id_formation": 1,
        "nom": "nom1",
        "salle": "salle1"
    }
}

Router のテスト

すべてがうまくいけば、仮想トレースを表示すると、プロセスとサービスとプロセス間で何が起きたかを確認できるようになります。
ここでは、メッセージがプロセスによってオペレーションに送信され、オペレーションがレスポンスを送り返すのを確認できます。 Router の結果

9. ビジネスサービス

ビジネスサービス(BS)は、本番環境の中核です。 情報を収集し、ルーターに送信するために使用されます。 BS には、フレームワークの情報を頻繁に収集する on_process_input 関数もあるため、REST API やその他のサービス、またはサービス自体などの複数の方法で呼び出してサービスのコードをもう一度実行することが可能です。 BS には、クラスにアダプターを割り当てられる get_adapter_type 関数もあります。たとえば、Ens.InboundAdapter は、サービスがその on_process_input を 5 秒おきに呼び出すようにすることができます。

これらのサービスは、VSCode でローカルに作成します。つまり、python/bs.py ファイルです。
このファイルを保存すると、IRIS でコンパイルされます。

9.1. 単純な BS

CSV を読み取って、msg.FormationRequest として各行をルーターに送信するビジネスサービスを作成しましょう。

この BS は csv を読み取るため、これを ServiceCSV と呼び、src/python/bs.py に作成します。
インポート:

from grongier.pex import BusinessService

from dataclass_csv import DataclassReader

from obj import Formation
from msg import FormationRequest

コード:

class ServiceCSV(BusinessService):
    """
    csv ファイルを 5 秒おきに読み取り、メッセージとして各行を Python Router プロセスに送信します。
    """

    def get_adapter_type():
        """
        登録済みのアダプタ名
        """
        return "Ens.InboundAdapter"

    def on_init(self):
        """
        現在のファイルパスをオブジェクトの path 属性に指定されたパスに変更します。
        path 属性が指定されていない場合は '/irisdev/app/misc/' に変更します。
        :return: None
        """
        if not hasattr(self,'path'):
            self.path = '/irisdev/app/misc/'
        return None

    def on_process_input(self,request):
        """
        formation.csv ファイルを読み取り、各行のFormationRequest メッセージを作成し、
        Python.Router プロセスに送信します。

        :param request: リクエストオブジェクト
        :return: None
        """
        filename='formation.csv'
        with open(self.path+filename,encoding="utf-8") as formation_csv:
            reader = DataclassReader(formation_csv, Formation,delimiter=";")
            for row in reader:
                msg = FormationRequest()
                msg.formation = row
                self.send_request_sync('Python.Router',msg)
        return None

FlaskService はそのままにし、ServiceCSV のみを入力することをお勧めします。

ご覧のとおり、ServiceCSV は、独立して機能し、5 秒おき(管理ポータルのサービスの設定にある基本設定で変更できるパラメーター)に on_process_input を呼び出せるようにする InboundAdapter を取得します。

サービスは、5 秒おきに formation.csv を開き、各行を読み取って、Python.Router に送信される msg.FormationRequest を作成します。


これらのコンポーネントは、事前に本番環境に登録済みです。

情報までに、コンポーネントを登録する手順は、5.4. に従い、以下を使用します。

register_component("bs","ServiceCSV","/irisdev/app/src/python/",1,"Python.ServiceCSV")

9.2. 本番環境へのサービスの追加

サービスは、こちらで事前に登録済みです。
ただし、サービスを新規作成する場合は、手動で追加する必要があります。

今後の参考までに、サービスの登録手順を説明します。
登録には、管理ポータルを使用します。 [service] の横にある [+] 記号を押すと、[Business Service Wizard(ビジネスサービスウィザード)] が開きます。
そのウィザードで、スクロールメニューから作成したばかりのサービスクラスを選択します。

9.3. テスト

サービスをダブルクリックすると、サービスが有効化されるか、再起動して変更内容が保存されます。
重要: この無効化して有効化し直す手順は、変更内容を保存する上で非常に重要な手順です。
前に説明したように、サービスは 5 秒おきに自動的に開始するため、ここでは他に行うことはありません。
すべてがうまくいけば、視覚的トレースを表示すると、プロセスとサービスとプロセス間で何が起きたかを確認することができます。
ここでは、メッセージがサービスによってプロセスに送信され、プロセスによってオペレーションに送信され、オペレーションがレスポンスを送り返すのを確認できます。 サービス CSV の結果

10. db-api による外部データベースへのアクセス

このセクションでは、外部データベースにオブジェクトを保存するオペレーションを作成します。 db-api を使用し、その他の Docker コンテナをセットアップして Postgres を設定します。

10.1. 前提条件

Postgres を使用するには psycopg2 が必要です。これは、単純なコマンドで Postgres データベースに接続できるようにする Python モジュールです。
これは自動的に完了済みですが、情報までに説明すると、Docker コンテナにアクセスし、pip3 を使って psycopg2 をインストールします。
ターミナルを開始したら、以下を入力します。

pip3 install psycopg2-binary

または、requirements.txt にモジュールを追加して、コンテナを再構築できます。

10.2. 新しいオペレーションの作成

新しいオペレーションは、src/python/bo.py ファイルの他の 2 つのオペレーションの後に追加してください。 以下は、新しいオペレーションとインポートです。
インポート:

import psycopg2

コード:

class PostgresOperation(BusinessOperation):
    """
    トレーニングを Postgres データベースに書き込むオペレーションです。
    """

    def on_init(self):
        """
        Postgres データベースに接続して接続オブジェクトを初期化する関数です。
        :return: None
        """
        self.conn = psycopg2.connect(
        host="db",
        database="DemoData",
        user="DemoData",
        password="DemoData",
        port="5432")
        self.conn.autocommit = True

        return None

    def on_tear_down(self):
        """
        データベースへの接続を閉じます。
        :return: None
        """
        self.conn.close()
        return None

    def insert_training(self,request:TrainingRequest):
        """
        トレーニングを Postgre データベースに挿入します。

        :param request: 関数に渡されるリクエストオブジェクト
        :type request: TrainingRequest
        :return: None
        """
        cursor = self.conn.cursor()
        sql = "INSERT INTO public.formation ( name,room ) VALUES ( %s , %s )"
        cursor.execute(sql,(request.training.name,request.training.room))
        return None

    def on_message(self,request):
        return None

このオペレーションは、最初に作成したオペレーションに似ています。 msg.TrainingRequest タイプのメッセージを受信すると、psycopg モジュールを使用して、SQL リクエストを実行します。 これらのリクエストは、Postgres データベースに送信されます。

ご覧のとおり、接続はコードに直接書き込まれています。コードを改善するために、他のオペレーションで前に行ったようにし、hostdatabase、およびその他の接続情報を変数にすることができます。基本値を dbDemoData にし、管理ポータルで直接変更できるようにします。
これを行うには、以下のようにして on_init を変更します。

    def on_init(self):
        if not hasattr(self,'host'):
          self.host = 'db'
        if not hasattr(self,'database'):
          self.database = 'DemoData'
        if not hasattr(self,'user'):
          self.user = 'DemoData'
        if not hasattr(self,'password'):
          self.password = 'DemoData'
        if not hasattr(self,'port'):
          self.port = '5432'

        self.conn = psycopg2.connect(
        host=self.host,
        database=self.database,
        user=self.user,
        password=self.password,
        port=self.port)

        self.conn.autocommit = True

        return None


これらのコンポーネントは、事前に本番環境に登録済みです。

情報までに、コンポーネントを登録する手順は、5.4. に従い、以下を使用します。

register_component("bo","PostgresOperation","/irisdev/app/src/python/",1,"Python.PostgresOperation")

10.3. 本番環境の構成

オペレーションは、こちらで事前に登録済みです。
ただし、オペレーションを新規作成する場合は、手動で追加する必要があります。

今後の参考までに、オペレーションの登録手順を説明します。
登録には、管理ポータルを使用します。 [Operations] の横にある [+] 記号を押すと、[Business Operation Wizard(ビジネスオペレーションウィザード)] が開きます。
そのウィザードで、スクロールメニューから作成したばかりのオペレーションクラスを選択します。


その後、接続を変更する場合は、オペレーションの [parameter] ウィンドウで、[Python] の %settings に変更するパラメーターを追加すれば完了です。 詳細については、「7.5 テスト」の 2 つ目の画像をご覧ください。

10.4. テスト

オペレーションをダブルクリックすると、オペレーションが有効化されるか、再起動して変更内容が保存されます。
重要: この無効化して有効化し直す手順は、変更内容を保存する上で非常に重要な手順です。
重要: その後で、オペレーションを選択し、右のサイドバーメニューの [Actions] タブに移動すると、オペレーションテストすることができます。
(うまくいかない場合は、テストを有効化し、本番環境が開始していることを確認してから、本番環境をダブルクリックして再起動をクリックし、オペレーションをリロードしてください)。

PostGresOperation については、テーブルは自動的に作成済みです。

こうすることで、オペレーションに msg.TrainingRequest タイプのメッセージを送信します。 Request Type として使用した場合:

Grongier.PEX.Message

%classname として使用した場合:

msg.TrainingRequest

%json として使用した場合:

{
    "training":{
        "name": "nom1",
        "room": "salle1"
    }
}

以下のとおりです。 Postgres のテスト

仮想トレースをテストすると、成功が表示されます。

これで、外部データベースに接続することができました。

ここまで、ここに記載の情報に従ってきた場合、プロセスやサービスは新しい PostgresOperation を呼び出さない、つまり、管理ポータルのテスト機能を使用しなければ呼び出されないことを理解していることでしょう。

10.5. 演習

演習として、bo.IrisOperation がブール値を返すように変更し、そのブール値に応じて bp.Routerbo.PostgresOperation を呼び出すようにしてみましょう。
そうすることで、ここで新しく作成したオペレーションが呼び出されるようになります。

ヒント: これは、bo.IrisOperation レスポンスの戻り値のタイプを変更し、新しいメッセージ/レスポンスタイプに新しいブール値プロパティを追加して、bp.Router に if 操作を使用すると実現できます。

10.6. ソリューション

まず、bo.IrisOperation からのレスポンスが必要です。 src/python/msg.py の他の 2 つのメッセージの後に、以下のようにして新しいメッセージを作成します。
コード:

@dataclass
class TrainingResponse(Message):
    decision:int = None

次に、そのレスポンスによって、bo.IrisOperation のレスポンスを変更し、decision の値を 1 または 0 にランダムに設定します。
src/python/bo.py で 2 つのインポートを追加し、IrisOperation クラスを変更する必要があります。
インポート:

import random
from msg import TrainingResponse

コード:

class IrisOperation(BusinessOperation):
    """
    トレーニングを iris データベースに書き込むオペレーションです。
    """

    def insert_training(self, request:TrainingRequest):
        """
        `TrainingRequest` オブジェクトを取り、新しい行を`iris.training` テーブルに挿入し、
        `TrainingResponse` オブジェクトを返します。

        :param request: 関数に渡されるリクエストオブジェクト
        :type request: TrainingRequest
        :return: A TrainingResponse message
        """
        resp = TrainingResponse()
        resp.decision = round(random.random())
        sql = """
        INSERT INTO iris.training
        ( name, room )
        VALUES( ?, ? )
        """
        iris.sql.exec(sql,request.training.name,request.training.room)
        return resp

    def on_message(self, request):
        return None

次に、`src/python/bp.py` で `bp.Router` プロセスを変更します。ここでは、IrisOperation からのレスポンスが 1 の場合に PostgresOperation を呼び出すようにします。 新しいコードは以下のようになります。
class Router(BusinessProcess):

    def on_request(self, request):
        """
        リクエストを受け取り、フォーメーションリクエストであるかどうかをチェックします。
        その場合は、TrainingRequest リクエストを FileOperation と IrisOperation に送信し、IrisOperation が 1 を返すと PostgresOperation に送信します。

        :param request: 受信したリクエストオブジェクト
        :return: None
        """
        if isinstance(request,FormationRequest):

            msg = TrainingRequest()
            msg.training = Training()
            msg.training.name = request.formation.nom
            msg.training.room = request.formation.salle

            self.send_request_sync('Python.FileOperation',msg)

            form_iris_resp = self.send_request_sync('Python.IrisOperation',msg)
            if form_iris_resp.decision == 1:
                self.send_request_sync('Python.PostgresOperation',msg)
        return None

非常に重要: オペレーションの呼び出しには、send_request_async ではなく、必ず send_request_sync を使用する必要があります。そうでない場合、この操作はブール値のレスポンスを受け取る前に実行されます。


テストする前に、必ず変更したすべてのサービス、プロセス、およびオペレーションをダブルクリックして再起動してください。これを行わない場合、変更内容は適用されません。


テスト後、視覚的トレースで、csv で読み取られたおよそ半数のオブジェクトがリモートデータベースにも保存されていることがわかります。
bs.ServiceCSV を開始するだけでテストできることに注意してください。リクエストは自動的にルーターに送信され、適切に配信されます。
また、サービス、オペレーション、またはプロセスをダブルクリックしてリロードを押すか、VSCode で保存した変更を適用するには再起動を押す必要があることにも注意してください。

11. REST サービス

ここでは、REST サービスを作成して使用します。

11.1. 前提条件

Flask を使用するには、flask のインストールが必要です。これは、REST サービスを簡単に作成できるようにする Python モジュールです。 これは、自動的に実行済みですが、今後の情報までに説明すると、Docker コンテナ内にアクセスして iris python に flask をインストールします。 ターミナルを開始したら、以下を入力します。

pip3 install flask

または、requirements.txt にモジュールを追加して、コンテナを再構築できます。

11.2. サービスの作成

REST サービスを作成するには、API を本番環境にリンクするサービスが必要です。このために、src/python/bs.pyServiceCSV クラスの直後に新しい単純なサービスを作成します。

class FlaskService(BusinessService):

    def on_init(self):    
        """
        API の現在のターゲットをオブジェクトの target 属性に指定されたターゲットに変更します。
        または、target 属性が指定されていない場合は 'Python.Router' に変更します。
        :return: None
        """    
        if not hasattr(self,'target'):
            self.target = "Python.Router"        
        return None

    def on_process_input(self,request):
        """
        API から直接 Python.Router プロセスに情報を送信するために呼び出されます。
        :return: None
        """
        return self.send_request_sync(self.target,request)

このサービスに on_process_input を行うと、リクエストが Router に転送されます。


これらのコンポーネントは、事前に本番環境に登録済みです。

情報までに、コンポーネントを登録する手順は、5.4. に従い、以下を使用します。

register_component("bs","FlaskService","/irisdev/app/src/python/",1,"Python.FlaskService")


REST サービスを作成するには、Flask を使って getpost 関数を管理する API を作成する必要があります。 新しいファイルを python/app.py として作成してください。

from flask import Flask, jsonify, request, make_response
from grongier.pex import Director
import iris

from obj import Formation
from msg import FormationRequest


app = Flask(__name__)

# GET Infos
@app.route("/", methods=["GET"])
def get_info():
    info = {'version':'1.0.6'}
    return jsonify(info)

# GET all the formations
@app.route("/training/", methods=["GET"])
def get_all_training():
    payload = {}
    return jsonify(payload)

# POST a formation
@app.route("/training/", methods=["POST"])
def post_formation():
    payload = {} 

    formation = Formation()
    formation.nom = request.get_json()['nom']
    formation.salle = request.get_json()['salle']

    msg = FormationRequest(formation=formation)

    service = Director.CreateBusinessService("Python.FlaskService")
    response = service.dispatchProcessInput(msg)

    return jsonify(payload)

# GET formation with id
@app.route("/training/<int:id>", methods=["GET"])
def get_formation(id):
    payload = {}
    return jsonify(payload)

# PUT to update formation with id
@app.route("/training/<int:id>", methods=["PUT"])
def update_person(id):
    payload = {}
    return jsonify(payload)

# DELETE formation with id
@app.route("/training/<int:id>", methods=["DELETE"])
def delete_person(id):
    payload = {}  
    return jsonify(payload)

if __name__ == '__main__':
    app.run('0.0.0.0', port = "8081")

Flask API は Director を使用して、前述のものから FlaskService のインスタンスを作成してから適切なリクエストを送信することに注意してください。

上記の子k-度では、POST フォーメンション関数を作成しました。希望するなら、これまでに学習したものすべてを使用して、適切な情報を get/post するように、他の関数を作成することが可能です。ただし、これに関するソリューションは提供されていません。

11.3. テスト

Python Flask を使用して flask アプリを開始しましょう。
Flask app.py の開始方法

最後に、Router サービスをリロードしてから、任意の REST クライアントを使用してサービスをテストできます。

(Mozilla の RESTer として)REST サービスを使用するには、以下のようにヘッダーを入力する必要があります。

Content-Type : application/json

REST のヘッダー

ボディは以下のようになります。

{
    "nom":"testN",
    "salle":"testS"
}

REST のボディ

認証は以下のとおりです。
ユーザー名:

SuperUser

パスワード:

SYS

REST の認証

最後に、結果は以下のようになります。 REST の結果

12. グローバル演習

Iris DataPlatform とそのフレームワークのすべての重要な概念について学習したので、グローバル演習で腕試しをしましょう。この演習では、新しい BS と BP を作成し、BO を大きく変更して、新しい概念を Python で探ります。

12.1. 指示

こちらのエンドポイントhttps://lucasenard.github.io/Data/patients.json を使用して、患者と歩数に関する情報を自動的に取得するようにします。 次に、ローカルの csv ファイルに書き込む前に、各患者の平均歩数を計算します。

必要であれば、フォーメーション全体または必要な箇所を読むか、以下のヒントを使って、ガイダンスを得ることをお勧めします。

管理ポータルでコンポーネントにアクセスできるように、コンポーネントの登録を忘れずに行いましょう。

すべてを完了し、テストしたら、または演習を完了するのに十分なヒントを得られなかった場合は、全過程をステップごとに説明したソリューションをご覧ください。

12.2. ヒント

ここでは、演習を行うためのヒントを紹介します。
読んだ分だけヒントが得られてしまうため、必要な箇所のみを読み、毎回すべてを読まないようにすることをお勧めします。

たとえば、bs の「情報の取得」と「リクエストによる情報の取得」のみを読み、他は読まないようにしましょう。

12.2.1. bs

12.2.1.1. 情報の取得

エンドポイントから情報を取得するには、Python の requests モジュールを検索し、jsonjson.dumps を使用して文字列に変換してから bp に送信します。

12.2.1.2. リクエストによる情報の取得

オンライン Python Web サイトまたはローカルの Python ファイルを使用してリクエストを使用し、取得した内容をさらに深く理解するために、出力とそのタイプを出力します。

12.2.1.3. リクエストによる情報の取得とその使用

新しいメッセージタイプと情報を保持するオブジェクトタイプを作成し、プロセスに送信して平均を計算します。

12.2.1.4. 情報の取得のソリューション

リクエストを使用してデータを取得する方法と、この場合に部分的に、それをどう処理するかに関するソリューションです。

r = requests.get(https://lucasenard.github.io/Data/patients.json)
data = r.json()
for key,val in data.items():
    ...

繰り返しますが、オンライン Python Web サイトまたはローカルの Python ファイルでは、key、val、およびタイプを出力し、それらを使用して何をできるかを理解することが可能です。
json.dumps(val) を使用して val を格納してから、SendRequest の後にプロセスにいるときに、json.loads(request.patient.infos) を使用してその val を取得します(val の情報を patient.infos に格納した場合)。

12.2.2. bp

12.2.2.1. 平均歩数と dict

statistics は、算術演算を行うために使用できるネイティブライブラリです。

12.2.2.2. 平均歩数と dict: ヒント

Python のネイティブ map 関数では、たとえばリスト内または辞書内の情報を分離することができます。

list ネイティブ関数を使用して、map の結果をリストに変換し直すことを忘れないようにしましょう。

12.2.2.3. 平均歩数と dict: map を使用する

オンライン Python Web サイトまたはローカルの Python ファイルを使用して、以下のように、リストのリストまたは辞書のリストの平均を計算することができます。

l1 = [[0,5],[8,9],[5,10],[3,25]]
l2 = [["info",12],["bidule",9],[3,3],["patient1",90]]
l3 = [{"info1":"7","info2":0},{"info1":"15","info2":0},{"info1":"27","info2":0},{"info1":"7","info2":0}]

#最初のリストの最初の列の平均(0/8/5/3)
avg_l1_0 = statistics.mean(list(map(lambda x: x[0]),l1))

#最初のリストの 2 つ目列の平均(5/9/10/25)
avg_l1_1 = statistics.mean(list(map(lambda x: x[1]),l1))

#12/9/3/90 の平均
avg_l2_1 = statistics.mean(list(map(lambda x: x[1]),l2))

#7/15/27/7 の平均
avg_l3_info1 = statistics.mean(list(map(lambda x: int(x["info1"])),l3))

print(avg_l1_0)
print(avg_l1_1)
print(avg_l2_1)
print(avg_l3_info1)

12.2.2.4. 平均歩数と dict: 解答

リクエストに、日付と歩数の dict の json.dumps である infos 属性を持つ患者が保持されている場合、以下のようにして平均歩数を計算できます。

statistics.mean(list(map(lambda x: int(x['steps']),json.loads(request.patient.infos))))

12.2.3. bo

bo.FileOperation.WriteFormation に非常に似たものを使用できます。

bo.FileOperation.WritePatient のようなものです。

12.3. ソリューション

12.3.1. obj と msg

obj.py に以下を追加できます。

@dataclass
class Patient:
    name:str = None
    avg:int = None
    infos:str = None

msg.py に以下を追加できます。
インポート:

from obj import Formation,Training,Patient

コード:

@dataclass
class PatientRequest(Message):
    patient:Patient = None

この情報を単一の obj に保持し、get リクエストから得る dict の str を直接 infos 属性に入れます。 平均は、プロセスで計算されます。

12.3.2. bs

bs.py に以下を追加できます。 インポート:

import requests

コード:

class PatientService(BusinessService):

    def get_adapter_type():
        """
        登録されたアダプタの名前
        """
        return "Ens.InboundAdapter"

    def on_init(self):
        """
        API の現在のターゲットをオブジェクトの target 属性に指定されたターゲットに変更します。
        target 属性が指定されていない場合は、'Python.PatientProcess' に変更します。
        API の現在の api_url をオブジェクトの taget 属性に指定された api_url に変更します。
        api_url 属性が指定されていない場合は、'https://lucasenard.github.io/Data/patients.json' に変更します。
        :return: None
        """
        if not hasattr(self,'target'):
            self.target = 'Python.PatientProcess'
        if not hasattr(self,'api_url'):
            self.api_url = "https://lucasenard.github.io/Data/patients.json"
        return None

    def on_process_input(self,request):
        """
        API にリクエストを行い、検出される患者ごとに Patient オブジェクトを作成し、
        ターゲットに送信します。

        :param request: サービスに送信されたリクエスト
        :return: None
        """
        req = requests.get(self.api_url)
        if req.status_code == 200:
            dat = req.json()
            for key,val in dat.items():
                patient = Patient()
                patient.name = key
                patient.infos = json.dumps(val)
                msg = PatientRequest()
                msg.patient = patient                
                self.send_request_sync(self.target,msg)
        return None

ターゲットと api url 変数を作成します(on_init を参照)。
requests.getreq 変数に情報を入れた後、json で情報を抽出する必要があります。これにより dat が dict になります。
dat.items を使用して、患者とその情報を直接イテレートできます。
次に、patient オブジェクトを作成し、json データを文字列に変換する json.dumps を使用して val を文字列に変換し、patient.infos 変数に入れます。
次に、プロセスを呼び出す msg.PatientRequestmsg リクエストを作成します。

コンポーネントの登録を忘れずに行いましょう。 5.4. に従い、以下を使用します。

register_component("bs","PatientService","/irisdev/app/src/python/",1,"Python.PatientService")

12.3.3. bp

bp.py に以下を追加できます。 インポート:

import statistic

コード:

class PatientProcess(BusinessProcess):

    def on_request(self, request):
        """
        リクエストを取り、PatientRequest であるかをチェックします。その場合は、患者の平均歩数を計算し、
        リクエストを Python.FileOperation サービスに送信します。

        :param request: サービスに送信されたリクエストオブジェクト
        :return: None
        """
        if isinstance(request,PatientRequest):
            request.patient.avg = statistics.mean(list(map(lambda x: int(x['steps']),json.loads(request.patient.infos))))
            self.send_request_sync('Python.FileOperation',request)

        return None

取得したばかりのリクエストを取り、PatientRequest である場合は、歩数の平均を計算して、FileOperation に送信します。 これは患者の avg 変数に正しい情報を挿入します(詳細は、bp のヒントを参照してください)。

コンポーネントの登録を忘れずに行いましょう。 5.4. に従い、以下を使用します。

register_component("bp","PatientProcess","/irisdev/app/src/python/",1,"Python.PatientProcess")

12.3.4. bo

bo.py に以下を追加できます。FileOperation クラス内:

    def write_patient(self, request:PatientRequest):
        """
        患者の名前と平均歩数をファイルに書き込みます。

        :param request: リクエストメッセージ
        :type request: PatientRequest
        :return: None
        """
        name = ""
        avg = 0
        if request.patient is not None:
            name = request.patient.name
            avg = request.patient.avg
        line = name + " avg nb steps : " + str(avg) +"\n"
        filename = 'Patients.csv'
        self.put_line(filename, line)
        return None

前に説明したとおり、FileOperation は以前に登録済みであるため、もう一度登録する必要はありません。

12.4. テスト

7.4. を参照して、オペレーションを追加しましょう。

9.2. を参照して、サービスを追加しましょう。

次に、管理ポータルに移動し、前と同じように行います。 新しいサービスは、InboundAdapter を追加したため、自動的に実行されます。

toto.csv と同様にして、Patients.csv を確認してください。

12.5. グローバル演習のまとめ

この演習を通じて、メッセージ、サービス、プロセス、およびオペレーションの作成を学習し、理解することができました。
Python で情報をフェッチする方法とデータに対して単純なタスクを実行する方法を発見しました。

すべての完成ファイルは、GitHub の solution ブランチにあります。

13. まとめ

このフォーメーションを通じ、csv ファイルから行を読み取り、読み取ったデータを db-api を使ってローカル txt、IRIS データベース、および外部データベースに保存できる IrisPython のみを使用して、フル機能の本番環境を作成しました。
また、POST 動詞を使用して新しいオブジェクトを保存する REST サービスも追加しました。

InterSystems のインターオペラビリティフレームワークの主要要素を発見しました。

これには、Docker、VSCode、および InterSystems の IRIS 管理ポータルを使用して実行しました。

0
0 247
記事 Toshihiko Minamoto · 8月 17, 2022 11m read


InterSystems IRIS には、暗号化、復号化、およびハッシュ操作の優れたサポートが備わっています。 クラス %SYSTEM.Encryption(https://docs.intersystems.com/iris20212/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&PRIVATE=1&CLASSNAME=%25SYSTEM.Encryption)の中には、市場に出回っている主なアルゴリズムのクラスメソッドがあります。

IRIS アルゴリズムと暗号化/復号化の方式

ご覧のとおり、操作は鍵に基づいており、3 つのオプションが含まれます。

  • 対称鍵: 暗号化と復号化の操作を実行する部分で同じ秘密鍵が使用されます。
  • 非対称鍵: 暗号化と復号化の操作を実行する部分で、暗号化に同じ秘密鍵が使用されますが、 復号化においては、各パートナーが秘密鍵を所有します。 この鍵は身元を証明するものであるため、他人と共有することはできません。
  • ハッシュ: 暗号化だけが必要で、復号化が不要である場合に使用されます。強力なユーザーパスワードを保存する際に一般的なアプローチです。

対称暗号化と非対称暗号化の違い

  • 対称暗号化では、メッセージを受信する必要のある人の間で単一の鍵を共有して使用しますが、非対称暗号化では、通信時に公開鍵と秘密鍵のペアを使用してメッセージの暗号化と復号化を行います。
  • 対称暗号化は古いテクニックであるのに対し、非対称暗号化は比較的最近のテクニックです。
  • 非対称暗号化は、対称暗号化モデルで鍵を共有する必要性に関わる固有の問題を補完するために導入されました。公開鍵と秘密鍵のペアを使用することで、鍵を共有する必要がなくなっています。
  • 非対称暗号化には、対称暗号化よりも比較的時間がかかります。
  <th>
    対称暗号化
  </th>
  
  <th>
    非対称暗号化
  </th>
</tr>

<tr>
  <td>
    暗号文のサイズ
  </td>
  
  <td>
    元のプレーンテキストファイルより小さい暗号文。
  </td>
  
  <td>
    元のプレーンテキストファイルより大きい暗号文。
  </td>
</tr>

<tr>
  <td>
    データサイズ
  </td>
  
  <td>
    ビッグデータの送信に使用される。
  </td>
  
  <td>
    スモールデータの送信に使用される。
  </td>
</tr>

<tr>
  <td>
    リソース使用率
  </td>
  
  <td>
    対称鍵暗号化は、リソース使用率が低い場合に機能します。
  </td>
  
  <td>
    非対称暗号化では、大量のリソースを消費する必要があります。
  </td>
</tr>

<tr>
  <td>
    鍵の長さ
  </td>
  
  <td>
    128 または 256 ビットの鍵サイズ。
  </td>
  
  <td>
    RSA 2048 ビット以上の鍵サイズ。
  </td>
</tr>

<tr>
  <td>
    セキュリティ
  </td>
  
  <td>
    暗号化に単一の鍵を使用するため、セキュリティが低下。
  </td>
  
  <td>
    暗号化と復号化を 2 つの異なる鍵で行うため、はるかに安全。
  </td>
</tr>

<tr>
  <td>
    鍵の数
  </td>
  
  <td>
    対称暗号化では、暗号化と復号化に単一の鍵を使用する。
  </td>
  
  <td>
    非対称暗号化では、暗号化と復号化に 2 つの異なる鍵を使用する。
  </td>
</tr>

<tr>
  <td>
    テクニック
  </td>
  
  <td>
    古いテクニック。
  </td>
  
  <td>
    最新のテクニック。
  </td>
</tr>

<tr>
  <td>
    機密性
  </td>
  
  <td>
    暗号化と復号化に使用される単一の鍵には、その鍵が改ざんされる可能性がある。
  </td>
  
  <td>
    暗号化と復号化で個別に 2 つの鍵が作成されるため、鍵を共有する必要がない。
  </td>
</tr>

<tr>
  <td>
    速度
  </td>
  
  <td>
    対称暗号化は高速な手法。
  </td>
  
  <td>
    非対称暗号化は速度が低下する。
  </td>
</tr>

<tr>
  <td>
    アルゴリズム
  </td>
  
  <td>
    RC4、AES、DES、3DES、および QUAD。
  </td>
  
  <td>
    RSA、Diffie-Hellman、ECC アルゴリズム。
  </td>
</tr>
主な違い

出典: https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences 

%SYSTEM.Encryption クラスを使用して暗号化、復号化、ハッシュを実行

IRIS の暗号化、復号化、およびハッシュ操作のサポートを使用するには、https://github.com/yurimarx/cryptography-samples にアクセスし、以下の手順に従ってください。

  1. リポジトリを任意のローカルディレクトリに Clone/git pull します。
$ git clone https://github.com/yurimarx/cryptography-samples.git
  1. このディレクトリで Docker ターミナルを開き、以下を実行します。
$ docker-compose build
  1. IRIS コンテナを実行します。
$ docker-compose up -d
  1. IRIS ターミナルを開きます。
$ docker-compose exec iris iris session iris -U IRISAPP

IRISAPP>
  1. 非対称暗号化の RSA Encrypt を実行するには、以下を実行します。
IRISAPP>Set ciphertext = ##class(dc.cryptosamples.Samples).DoRSAEncrypt("InterSystems")
IRISAPP>Write ciphertext
Ms/eR7pPmE39KBJu75EOYIxpFEd7qqoji61EfahJE1r9mGZX1NYuw5i2cPS5YwE3Aw6vPAeiEKXF
rYW++WtzMeRIRdCMbLG9PrCHD3iQHfZobBnuzx/JMXVc6a4TssbY9gk7qJ5BmlqRTU8zNJiiVmd8
pCFpJgwKzKkNrIgaQn48EgnwblmVkxSFnF2jwXpBt/naNudBguFUBthef2wfULl4uY00aZzHHNxA
bi15mzTdlSJu1vRtCQaEahng9ug7BZ6dyWCHOv74O/L5NEHI+jU+kHQeF2DJneE2yWNESzqhSECa
ZbRjjxNxiRn/HVAKyZdAjkGQVKUkyG8vjnc3Jw==
  1. 非対称復号化の RSA Decrypt を実行するには、以下を実行します。
IRISAPP>Set plaintext = ##class(dc.cryptosamples.Samples).DoRSADecrypt(ciphertext)
IRISAPP>Write plaintext
InterSystems
  1. 非対称暗号化の AES CBC Encrypt を実行するには、以下を実行します。
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoAESCBCEncrypt("InterSystems")
8sGVUikDZaJF+Z9UljFVAA==
  1. 対称暗号化の AES CBC Decrypt を行うには、以下を実行します。
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoAESCBCDecrypt("8sGVUikDZaJF+Z9UljFVAA==")
InterSystems
  1. 古いハッシュアプローチの MD5 hash を実行するには、以下を実行します。
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoHash("InterSystems")
rOs6HXfrnbEY5+JBdUJ8hw==
  1. 推奨されるハッシュアプローチの SHA hash を実行するには、以下を実行します。
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoSHAHash("InterSystems")
+X0hDlyoViPlWOm/825KvN3rRKB5cTU5EQTDLvPWM+E=
  1. ターミナルを終了するには、以下を実行します。
HALT または H を入力(大文字と小文字は区別されません)

 

ソースコードについて

1. 対称鍵について

# 対称暗号化/復号化で使用する
ENVSECRETKEY=InterSystemsIRIS

Dockerfile に、対称操作で秘密鍵として使用される環境鍵が作成されました。

2. 非対称鍵について

# 非対称暗号化/復号化で使用する RUN openssl req -new -x509 -sha256 -config example-com.conf -newkey rsa:2048 -nodes -keyout example-com.key.pem -days 365 -out example-com.cert.pem

Dockerfile に、非対称操作で使用される秘密鍵と公開鍵が生成されました。

3. 対称暗号化

//暗号化するための対称鍵の例
 
ClassMethodDoAESCBCEncrypt(plaintextAs%String)As%Status
{
    // utf-8 に変換
    Settext=$ZCONVERT(plaintext,"O","UTF8")
   
    // 秘密鍵を設定
    Setsecretkey=$system.Util.GetEnviron("SECRETKEY")
    SetIV=$system.Util.GetEnviron("SECRETKEY")
   
    // テキストを暗号化
    Settext=$SYSTEM.Encryption.AESCBCEncrypt(text,secretkey,IV)
    Setciphertext=$SYSTEM.Encryption.Base64Encode(text)
   
    Writeciphertext
}

テキストの暗号化に AES CBC Encrypt 操作が使用されました。
Base64 Encode は、プリティ/読み取り可能なテキストとしてユーザーに結果を返します。

4. 対称復号化

//復号化するための対称鍵の例
 
ClassMethodDoAESCBCDecrypt(ciphertextAs%String)As%Status
{
    // 秘密鍵を設定
    Setsecretkey=$system.Util.GetEnviron("SECRETKEY")
    SetIV=$system.Util.GetEnviron("SECRETKEY")
   
    // テキストを復号化
    Settext=$SYSTEM.Encryption.Base64Decode(ciphertext)
    Settext=$SYSTEM.Encryption.AESCBCDecrypt(text,secretkey,IV)
   
    Setplaintext=$ZCONVERT(text,"I","UTF8")
    Writeplaintext
}

テキストの復号化に AES CBC Decrypt 操作が使用されました。
Base64 Decode は、復号化に使用できるように、暗号化されたテキストをバイナリテキストに返します。

5. 非対称暗号化

//暗号化するための非対称鍵の例
 
ClassMethodDoRSAEncrypt(plaintextAs%String)As%Status
{
    // 公開鍵証明書を取得
    SetpubKeyFileName="/opt/irisbuild/example-com.cert.pem"
    SetobjCharFile=##class(%Stream.FileCharacter).%New()
    SetobjCharFile.Filename=pubKeyFileName
    SetpubKey=objCharFile.Read()
 
    // RSA を使って暗号化
    Setbinarytext=$System.Encryption.RSAEncrypt(plaintext,pubKey)
    Setciphertext=$SYSTEM.Encryption.Base64Encode(binarytext)
   
    Returnciphertext
}

RSA で暗号化するには、公開鍵ファイルの内容を取得する必要があります。
テキストの暗号化に、RSA Encrypt 操作が使用されました。

6. 非対称復号化

//復号化するための非対称鍵の例
 
ClassMethodDoRSADecrypt(ciphertextAs%String)As%Status
{
    // 秘密鍵を取得
    SetprivKeyFileName="/opt/irisbuild/example-com.key.pem"
    SetprivobjCharFile=##class(%Stream.FileCharacter).%New()
    SetprivobjCharFile.Filename=privKeyFileName
    SetprivKey=privobjCharFile.Read()
 
    // 暗号文をバイナリ形式で取得
    Settext=$SYSTEM.Encryption.Base64Decode(ciphertext)
 
    // RSA を使って復号化
    Setplaintext=$System.Encryption.RSADecrypt(text,privKey)
 
    Returnplaintext
}

RSA で復号化するには、秘密鍵ファイルの内容を取得する必要があります。
テキストの復号化に、RSA Decrypt 操作が使用されました。

7. MD5 を使ったテキストのハッシュ化(古いアプローチ)

//ハッシュの例
 
ClassMethodDoHash(plaintextAs%String)As%Status
{
    // utf-8 に変換
    Settext=$ZCONVERT(plaintext,"O","UTF8")
   
    // テキストをハッシュ化
    Sethashtext=$SYSTEM.Encryption.MD5Hash(text)
   
    Setbase64text=$SYSTEM.Encryption.Base64Encode(hashtext)
 
    // 16 進テキストを以下のベストプラクティスに変換
    Sethextext=..GetHexText(base64text)
 
    // 小文字を使って返す
    Write$ZCONVERT(hextext,"L")
}

MD5 Hash 操作でテキストを暗号化すると、それを復号化することはできません。
MD5 を使ったハッシュ化は、安全でないと見なされているため、新しいプロジェクトには推奨されません。 そのため、この方式は SHA に置き換えられています。 InterSystems IRIS は SHA をサポートしています(次の例で紹介します)。

8. SHA を使ったテキストのハッシュ化(推奨されるアプローチ)

この例では、SHA-3 Hash 方式を使用します。 InterSystems のドキュメントによると、この方式は、US Secure Hash Algorithm - 3 を使ってハッシュを生成します。 (詳細は、連邦情報処理規格 202 をご覧ください。)

//SHA を使ったハッシュ
 
ClassMethodDoSHAHash(plaintextAs%String)As%Status
{
    // utf-8 に変換
    Settext=$ZCONVERT(plaintext,"O","UTF8")
   
    // テキストをハッシュ化
    Sethashtext=$SYSTEM.Encryption.SHA3Hash(256,text)
   
    Setbase64text=$SYSTEM.Encryption.Base64Encode(hashtext)
 
    // 16 進テキストを以下のベストプラクティスに変換
    Sethextext=..GetHexText(base64text)
 
    // 小文字を使って返す
    Write$ZCONVERT(hextext,"L")
}

SHA 方式では、ハッシュ操作で使用するビット長を設定できます。 ビット数が多いほど、ハッシュを解読するのがより困難になりますが、 ハッシュ化の処理速度が低下してしまいます。 この例では、256 ビットが使用されました。 ビット長については以下のオプションを選択できます。

  • 224(SHA-224)
  • 256(SHA-256)
  • 384(SHA-384)
  • 512(SHA-512)
0
0 537
記事 Toshihiko Minamoto · 7月 1, 2021 5m read

クラスのコンパイル時に、定義済みのプロパティ、クエリ、またはインデックスごとに対応する複数のメソッドが自動的に生成されます。 これらのメソッドは非常に便利です。 この記事では、その一部について説明します。

プロパティ

「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クエリまたはカスタムクラスクエリのどちらでも構いません。これに関する記事をご覧ください)、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には、これらのメソッドを実演するデモクラスがあります。

    0
    1 201
    記事 Toshihiko Minamoto · 6月 29, 2021 5m read

    RESTフレームワークの有用な機能の1つに、ディスパッチクラスがリクエストのプレフィックスを識別して別のディスパッチクラスに転送するという機能があります。 URLマップをモジュール化するこの手法により、コードの可読性が向上し、インターフェースの個別のバージョンが管理しやすくなります。また、特定のユーザーのみがアクセスできるように、API呼び出しを保護する手段も得ることができます。

    概要

    CachéインスタンスにRESTサービスをセットアップするには、専用のSCPアプリケーションを定義して、それに関連付けられた、受信リクエストを処理するディスパッチクラスを作成する必要があります。 ディスパッチクラスは、%CSP.RESTを拡張し、URLマップを含むXDataブロックを含めます。 こうすることで、システムに、特定のリクエストを受信したときにどのメソッドを呼び出すのかを指示します。

    以下に、例を示します。

    XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
    {
    <Routes>
      <RouteUrl="/orders"Method="GET"Call="GetOrders"/>
      <RouteUrl="/orders"Method="POST"Call="NewOrder"/>
    </Routes>
    }

    <Route> 要素は、サービスが処理するさまざまなリクエストを定義しています。 「/orders」リソースのGETリクエストは、クラスに「GetOrders」メソッドを呼び出しています。 同じリソースに対して行われるPOSTリクエストは、代わりに「NewOrder」メソッドを呼び出しています。

    CSPアプリケーション名は、URLマップでリクエストされるリソース名の一部としてみなされないことに注意しておくことが重要です。 次のアドレスに対して行われるリクエストについて考えてみましょう。

    http://localhost:57772/csp/demo/orders

    CSPアプリケーションを「/csp/demo」とした場合、ディスパッチクラスが処理するリクエストのセグメントは、アプリケーション名の後に続くものだけになります。 つまり、この場合は「/orders」のみということになります。

    リクエストの転送

    URLマップで利用できる、ディスパッチクラス内のメソッドを呼び出す以外のオプションは、特定のプレフィックスに一致するすべてのリクエストを別のディスパッチクラスに転送する方法です。

    これは、UrlMapセクションの<Map> 要素を使って行います。 この要素には、**Prefix Forward**の2つの属性があります。 リクエストURLがプレフィックスの1つに一致する場合、そのリクエストを特定のディスパッチクラスに送信して処理を続けることができます。

    以下に、例を示します。

    XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
    {
    <Routes>
      <MapPrefix="/shipping"Forward="Demo.Service.Shipping"/>
      <RouteUrl="/orders"Method="GET"Call="GetOrders"/>
      <RouteUrl="/orders"Method="POST"Call="NewOrder"/>
    </Routes>
    }

    /orders」のGETリクエストまたはPOSTリクエストは、このクラスによって直接処理されますが、 「/shipping」プレフィックスに一致するリクエストは、独自のURLマップを持つ「/shipping」ディスパッチクラスにリダイレクトされます。

    XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
    {
    <Routes>
      <RouteUrl="/track/:id"Method="GET"Call="TrackShipment"/>
    </Routes>
    }

    URLルーティングの詳細

    リクエストされたURLの各コンポーネントが、最終的に呼び出されるメソッドにどのような影響があるのかを示すために、次のアドレスのリクエストを分解して説明します。

    http://localhost:57772/csp/demo/shipping/track/123
    http://The protocol used for the request.
    localhost:57772The server that we connect to.
    /csp/demo/shipping/track/123The resource being requested.
    /csp/demoThe CSP application name.
    A dispatch class is defined for the application, route the request there.
    /shipping/track/123The resource segment sent to the first dispatch class.
    /shippingThe prefix that matches the <Map> element in the URL map.
    Redirect to the Demo.Service.Shipping class.
    /track/123The resource segment sent to the second dispatch class.
    Matches the route "/track/:id".
    Call the method TrackShipment(123).

     使用時のメリット

    • ソース管理 — REST APIを複数のクラスに分離すると、各クラスの全体的なサイズが小さくなるため、ソースの管理履歴を明確かつ読みやすく維持することができるようになります。  
    • バージョン管理 — 転送を使用すると、複数のバージョンのAPIを簡単に同時にサポートすることができます。 1つのディスパッチクラスは、「/v1」または「/v2」プレフィックスに一致するリクエストを、そのバージョンのAPIを実装するディスパッチクラスに転送できます。 私たちの新しいIDEであるAtelierの中心にあるREST APIでは、これと同じバージョン管理スキームが使用されています。  
    • セキュリティ — 特定の種類のリクエストを管理者だけが実行できるようにする場合など、特定のユーザーに制限されたルーティングをAPIで使用する必要がある場合、独自のクラスにルートを分離すると、特定のプレフィックスを使用して合理的にリクエストを転送することができます。 2つ目のディスパッチクラスでOnPreDispatchメソッドが定義されている場合、各リクエストを処理する前に、そのコードが実行されます。 サービスはこれを利用して、ユーザーの権限を確認し、処理を続行するか、リクエストをキャンセルするかを決定できます。
    0
    0 440
    記事 Toshihiko Minamoto · 6月 23, 2021 10m read

    この記事では、InterSystems Cachéにおけるマクロについて説明します。 マクロは、コンパイル中に一連の命令に置き換えられるシンボリック名です。 マクロは、渡されたパラメーターとアクティブ化したシナリオに応じて、呼び出されるたびに一連の命令セットに「展開」されます。 これは、静的コードの場合もあれば、ObjectScriptを実行して得られる結果である場合もあります。 それでは、アプリケーションでマクロをどのように使用できるのかを見てみましょう。

    コンパイル

    まず、ObjectScriptコードがどのようにコンパイルされているのかを見てみましょう。

  • クラスコンパイラはクラス定義を使用してMACコードを生成します。
  • 場合によっては、コンパイラはクラスを元に追加クラスを生成します。 これらのクラスはStudioで閲覧できますが、変更してはいけません。 この動作は、たとえば、WebサービスやWebクライアントのクラスを生成する際に発生します。
  • クラスコンパイラはランタイム時にCaché が使用するクラス記述子も生成します。
  • プリプロセッサ(マクロプロセッサまたはMPPとも呼ばれます)が、INCファイルを使用してマクロを置き換えます。 さらに、ObjectScriptルーチンにある埋め込みSQLも処理します。
  • これらの変更はすべてメモリで発生するため、ユーザーのコードに変化はありません。
  • その後、コンパイラはObjectScriptルーチンのINTコードを作成します。 このレイヤーは中間コードとして知られるレイヤーです。 このレベルでのデータへのすべてのアクセスは、グローバルを介して提供されます。
  • INTコードはコンパクトで、人間が読み取ることができます。 Studioで閲覧するには、Ctrl+Shift+Vを押してください。
  • INTコードを使用して、OBJコードが生成されます。
  • OBJコードは、Caché仮想マシンが使用するコードです。 OBJコードが生成されるとCLS/MAC/INTコードは不要になるため、不要となったそれらのコードは削除することができます(ソースコードを含めずに製品を出荷する場合など)。
    1. クラスが永続クラスである場合、SQLコンパイラは対応するSQLテーブルを作成します。

    マクロ

    前に述べたように、マクロは、プリプロセッサによって命令セットに置き換えられるシンボリック名です。 マクロは#Defineコマンドにマクロ名(おそらく引数のリストを含む)とその値を続けて定義します。

    #Define Macro[(Args)] [Value]
    

    マクロはどこに定義されるのでしょうか。 マクロの定義は、コード内か、マクロのみを含む独立したINCファイルで行われます。 必要なファイルは、クラス定義の最初にInclude MacroFileNameコマンドを使用してクラスに含められます。これがマクロをクラスに含めるための推奨される主な方法です。 この方法で含められるマクロは、クラスのどの部分にでも使用できます。 #Include MacroFileNameコマンドを使ってマクロを含むINCファイルをMACルーチンや特定のクラスメソッドのコードに含めることができます。

    マクロをコンパイル時に使用する場合、またはクラスにIncludeGeneratorキーワードを使用する場合は、メソッドジェネレーターの本文に#Includeを使用する必要があることに注意してください。

    Studioの自動補完でマクロを使用できるようにするには、前の行に///を追加します。

    ///
    #Define Macro[(Args)] [Value]

    例1

    では、例をいくつか見てみましょう。標準的な「Hello World」メッセージから始めます。 COSコードは次のようになります。 

    Write "Hello, World!"
    

    HWという、次の行を書き込むマクロを作成します。

    #define HW Write "Hello, World!"

    後は、$$$HW(マクロを呼び出す$$$と、その後にマクロ名を指定)を記述するのみです。

    ClassMethod Test()
    {
         #define HW Write "Hello, World!"
         $$$HW
    }

    これは、コンパイル中に次のINTコードに変換されます。

    zTest1() public {
         Write "Hello, World!" }

    このメソッドが呼び出されると、ターミナルに次のテキストが表示されます。

    Hello, World!

    例2

    次の例では、変数を使用し見ましょう。

    ClassMethod Test2()
    {
         #define WriteLn(%str,%cnt) For ##Unique(new)=1:1:%cnt { ##Continue
             Write %str,! ##Continue
         }
         
         $$$WriteLn("Hello, World!",5)
    }

    上記のコードでは、%str文字列が%cnt回書き込まれます。 変数名は%で始まる必要があります。 ##Unique(new) コマンドで、生成されたコードに新しい一意の変数を作成し、##Continueによって、次の行にマクロの定義が続くことを示します。 このコードは、次のINTコードに変換されます。

    zTest2() public {
         For %mmmu1=1:1:5 {
             Write "Hello, World!",!
         } }
    

    ターミナルには次のように表示されます。

    Hello, World!
    Hello, World!
    Hello, World!
    Hello, World!
    Hello, World!

     

    例3

    より複雑な例に進みましょう。 ForEach演算子は、グローバルを反復処理する上で非常に役立ちます。それでは作成してみましょう。

    ClassMethod Test3()
    {
        #define ForEach(%key,%gn) Set ##Unique(new)=$name(%gn) ##Continue
        Set %key="" ##Continue
        For { ##Continue
            Set %key=$o(@##Unique(old)@(%key)) ##Continue
            Quit:%key=""
        
        #define EndFor    }
        
           Set ^test(1)=111
           Set ^test(2)=222
           Set ^test(3)=333
           
           $$$ForEach(key,^test)
               Write "key: ",key,!
               Write "value: ",^test(key),!
           $$$EndFor
    }

    INTコードでは次のようになります。

    zTest3() public {
           Set ^test(1)=111
           Set ^test(2)=222
           Set ^test(3)=333
           Set %mmmu1=$name(^test)
           Set key=""
           For {
               Set key=$o(@%mmmu1@(key))
               Quit:key=""
               Write "key: ",key,!
               Write "value: ",^test(key),!
           } }

    これらのマクロでは何が起こっているのでしょうか。

    1. グローバルの名前を新しい%mmmu1変数に書き込みます($name関数)。
    2. キーは、初期の空の文字列値です。
    3. 反復サイクルが開始します。
    4. 間接演算子$order関数を使って、キーに次の値が割り当てられます。
    5. キーが""値を取っているかどうかを、事後条件を使ってチェックします。取っている場合は反復が完了し、サイクルが終了します。
    6. 任意のユーザーコードが実行されます。この場合、キーと値が出力されます。
  • サイクルが終了します。
  • このメソッドが呼び出されると、ターミナルには次のように表示されます。

    key: 1
    value: 111
    key: 2
    value: 222
    key: 3
    value: 333

    %Collection.AbstractIteratorクラスから継承したリストと配列を使用している場合は、同様のイテレーターを記述できます。

    例4

    マクロにはさらに、コンパイル段階で任意のObjectScriptコードを実行し、マクロの代わりにその結果に置き換えるという別の機能があります。 コンパイル時間を示すマクロを作成してみましょう。

    ClassMethod Test4()
    {
          #Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
          Write $$$CompTS
    }

    これは、次のINTコードに変換されます。

    zTest4() public {
          Write "Compiled: 18.10.2016 15:28:45",! }
    

    このメソッドが呼び出されると、ターミナルには次の行が表示されます。

    Compiled: 18.10.2015 15:28:45

    ##Expressionは、コードを実行して結果を置き換えます。 入力には、ObjectScript言語の次の要素を使用できます。

    • 文字列: "abc"
    • ルーチン: $$Label^Routine
    • クラスメソッド: ##class(App.Test).GetString()
    • COS関数: $name(var)
    • 上記の要素の任意の組み合わせ

    例5

    コンパイル時に、ディレクティブの後に続く式の値に応じてソースコードを選択するには、プリプロセッサディレクティブの#If、#ElseIf、#Else、#EndIfを使用します。 たとえば、次のメソッドがあるとします。

    ClassMethod Test5()
    {
        #If $SYSTEM.Version.GetNumber()="2016.2.0" && $SYSTEM.Version.GetBuildNumber()="736"
            Write "You are using the latest released version of Caché"
        #ElseIf $SYSTEM.Version.GetNumber()="2017.1.0"
            Write "You are using the latest beta version of Caché"
        #Else
            Write "Please consider an upgrade"
        #EndIf
    }

    Cachéバージョン2016.2.0.736では、このメソッドは次のINTコードにコンパイルされます。

    zTest5() public {
        Write "You are using the latest released version of Caché"
    }

    ターミナルには次のように表示されます。

    You are using the latest released version of Caché

    ベータポータルからダウンロードしたCachéを使用している場合、コンパイルされたINTコードは異なります。

    zTest5() public {
        Write "You are using the latest beta version of Caché"
    }

    ターミナルには次のように表示されます。

    You are using the latest beta version of Caché

    古いバージョンのCachéは、次のようにプログラムの更新を提案するINTコードをコンパイルします。

    zTest5() public {
        Write "Please consider an upgrade"
    }
    

    ターミナルには次のように表示されます。

    Please consider an upgrade

    この機能は、クライアントアプリケーションで古いバージョンと新しいバージョンとの互換性を保証したい場合に、Cachéの新機能が使用される可能性があるときなどに役立ちます。 プリプロセッサディレクティブの#IfDef#IfNDefは、順にマクロの存在と不在を検証することで、同じ目的を果たすことができます。

    まとめ

    マクロは、コンパイル段階で、頻繁に使用される構造を単純化することでコードを読みやすくして一部のアプリケーションのビジネスロジックを実装しやすくするため、ランタイム時の負荷を軽減することができます。

    次の内容

    次の記事では、アプリケーションにおけるマクロのより実用的な使用例について説明します。ロギングシステムです。

    リンク

  • 第2部: ロギング
  • 0
    0 355
    記事 Toshihiko Minamoto · 6月 21, 2021 9m read

    この記事では、syslogテーブルについて説明したいと思います。  syslogとは何か、どのように確認するのか、実際のエントリはどのようなものか、そしてなぜそれが重要であるのかについて説明します。  syslogテーブルには、重要な診断情報が含まれることがあります。  システムに何らかの問題が生じている場合に、このテーブルの確認方法とどのような情報が含まれているのかを理解しておくことが重要です。

    syslogテーブルとは?

    Cachéは、共有メモリのごく一部を使って、関心のある項目をログに記録しています。  このテーブルは、次のようなさまざまな名前で呼ばれています。

    • Cachéシステムエラーログ
    • errlog
    • SYSLOG
    • syslogテーブル

    この記事では、単に「syslogテーブル」と呼ぶことにします。

    syslogテーブルのサイズは構成可能です。  デフォルトは500エントリで、  10~10,000エントリの範囲で構成できます。  syslogテーブルのサイズを変更するには、システム管理ポータル -> システム管理 -> 構成 -> 追加設定 -> 詳細メモリに移動し、「errlog」行の「編集」を選択します。  そこにsyslogテーブルに必要なエントリの数を入力してください。

    syslogテーブルのサイズを変更する理由は?

    syslogテーブルが500エントリに構成されていると、501番目のエントリによって最初のエントリが上書きされ、そのエントリの情報は失われてしまいます。  これはメモリ内にあるテーブルであるため、出力を明示的に保存しない限りどこにも永続されません。  また、Cachéを停止した場合には、以下に説明する方法でエントリをconsole.logファイルに保存するように構成していない限り、すべてのエントリは失われてしまいます。

    Cachéが多くのエントリをsyslogテーブルに書き込んでおり、問題の診断目的でそのエントリを確認する場合、テーブルのサイズが十分に大きくなければ、エントリは失われてしまいます。  syslogテーブルのDate/Time列を見ると、テーブルが書き込まれた期間を判別できます。  その上で、どれくらいのエントリ数を設定するのかを決めることができます。  個人的には、エントリを失わない数で判断を誤る方が良いと思います。  このことについては、以下でより詳しく説明します。

    syslogテーブルの確認方法

    syslogテーブルの確認方法にはいくつかあります。

    1. Cachéターミナルプロンプトから、%SYSネームスペースで「Do ^SYSLOG」を実行する。
    2. Cachéターミナルプロンプトから、%SYSネームスペースで「do ^Buttons」を実行する。
    3. 管理ポータル -> システム操作 -> 診断レポートに移動する。
    4. -e1オプションでcstatを実行する。
    5. Cachehungを実行する。
    6. シャットダウン中にsyslogテーブルをconsole.logファイルに書き込むようにCaché を設定し、console.logファイルを確認する。  設定するには、システム管理ポータル -> 構成 -> 追加設定 -> 互換性に移動し、「ShutDownLogErros」行で「編集」を選択します。  Cachéのシャッドダウン中にsyslogのコンテンツをconsole.logに保存する場合は「true」、保存しない場合は「false」を選択します。

    syslogのエントリとは?

    syslogテーブルの例を以下に示します。  この例は、Cachéターミナルプロンプトで「^SYSLOG」を実行して得られたものです。

    %SYS>d ^SYSLOG
    
    Device:
    Right margin: 80 =>
    Show detail? No => No
    Cache System Error Log printed on Nov 02 2016 at  4:29 PM
    --------------------------------------------------------
    Printing the last 8 entries out of 8 total occurrences.
    Err  Process         Date/Time                             Mod     Line      Routine                             Namespace
    9     41681038     11/02/2016 04:44:51PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:43:34PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:42:06PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:41:21PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:39:29PM     93        5690     systest+3^systest         %SYS
    9     41681036     11/02/2016 04:38:26PM     93        5690     systest+3^systest         %SYS
    9     41681036     11/02/2016 04:36:57PM     93        5690     systest+3^systest         %SYS
    9     41681036     11/02/2016 04:29:45PM     93        5690     systest+3^systest         %SYS
    

    列の見出しを見ればエントリの各項目が何であるかは明確のようですが、それらについて説明します。

    Printing the last 8 entries out of 8 total occurrences

    これは、syslogテーブルエントリの一部ではありませんが、確認することは重要なので、ここで説明します。  この行から、いくつのエントリがsyslogテーブルに書き込まれたかがわかります。  この例では、Cachéが起動してから8つのエントリしか書き込まれていません。  エントリ数が少ないのは、これが私のテストシステムであるためです。  「Printing the last 500 entries out of 11,531 total occurrences(発生総数11,531件中最新の500件のエントリを出力)」と書かれていれば、多数のエントリが見逃されていることがわかります。  見逃されたエントリを確認する場合は、テーブルサイズは最大10,000に増やすか、より頻繁にSYSLOGを実行してください。 

    Err

    これは、関心のあるイベントについてログに記録される情報です。  エラーは必ずOSレベルで/usr/include/errno.h(Unix)から発生しているものと思われがちですが、実際には「必ず」ではなく、ほとんどの場合です。  ログに記録できるものであれば、何でも記録されます。  たとえば、診断アドホックのデバッグ情報、C変数の値、エラーコードの定義(10000を超えるもの)などを記録することができます。  どのように区別すればよいのでしょうか。  実際に、エントリのModLineに示される、Cコードの行を確認する必要があります。  つまり、InterSystemsに連絡を取らなければ、それが実際に何であるかを区別することはできません。  では、わざわざ確認する必要はあるのでしょうか。  errの意味を正確に知らずとも、ほかの情報を見ることで把握できることがたくさんあるからです。  エントリがたくさんある場合や、普段から目にするエントリとは異なるエントリがある場合には、InterSystemsに問い合わせることもできます。  syslogテーブルのエントリは、必ずしもエラー状態を示すものではないことに注意してください。

    Process

    これは、syslogテーブルにエントリを書き込んだプロセスのプロセスIDです。  たとえば、スタックしたプロセス、スピンし続けるプロセス、またはデッドプロセスがある場合、syslogテーブルに何か記録されていないかを確認できます。  記録されていれば、プロセスで障害が起きた理由の重要な手がかりになるかもしれません。

    Date/Time

    これは、エントリが書き込まれた日時です。  問題になった原因の手がかりを得るために、エントリの日時とシステムイベントの日時を相関することは非常に重要です。

    ModとLine

    Modは特定のCファイルに対応しており、Lineは、そのエントリをsyslogテーブルに書き込んだファイルの行番号です。  カーネルコードにアクセスできるInterSystemsの従業員のみが、これを検索できます。  このコードを調べるだけで、エントリに何が記録されたのかを正確に知ることができます。

    Routine

    syslogテーブルにエントリが書き込まれたときにプロセスが実行していたタグ、オフセット、およびルーチンです。  何が起こっているのかを理解する上で非常に役立ちます。

    Namespace

    これは、プロセスが実行していたネームスペースです。

    では、err 9がsyslogテーブルに書き込まれた理由をどのようにして知ることができますか?

    まず、示されているルーチンを確認します。  私の^systestルーチンは次のようになっています。

    systest  ;test for syslog post
             s file="/home/testfile"
             o file:10
             u file w "hello world"
             c file
             q
    

    syslogエントリでは、エントリが書き込まれた時に実行していたものはsystest+3だったと示されています。  この行は次のようになっています。

    u file w "hello world"
    

    プロセスがファイルに書き込もうとしていたため、これは実際にOSレベルのエラーである可能性があります。そこで、/usr/include/errno.hで9を探すと、次のようになっています。

    #define EBADF   9       /* Bad file descriptor                  */
    

    9はファイル関連であり、示されたコードの行はファイルに書き込もうとしていたため、これは実際にOSエラーコードであると推測するのが合理的です。 

    何がエラーになっているのかわかりますか?

    これを解決するために、まず、/homeディレクトリとtestfileファイルの権限を確認しました。  両方とも777となっていたため、ファイルを開いて書き込むのは可能なはずです。  そこでコードをよく見ると、エラーに気づきました。  10秒のタイムアウトの前にコロンを2つ付けていなかったこと、そしてOpenコマンドにはパラメーターを使用していなかったことに気づいたのです。  以下は更新したルーチンです。実際にエラーなしで終了し、ファイルに書き込みます。

    systest  ;test for syslog post
             s file="/scratch1/yrockstr/systest/testfile"
             o file:"WNSE":10
             u file w "hello world"
             c file
             q
    

    最後に

    syslogテーブルは、正しく使用すればデバッグに役立つ貴重なツールです。  使用するときは、次の点に注意してください。

    1. errは必ずしもオペレーティングシステムのエラーとは限りません。ログに記録できるものは何でも記録されます。  記録された内容については、InterSystemsにお問い合わせください。
    2. ログに記録されているその他の情報を使用して、何が起きているのかを判断します。  エラーと組み合わされたCOSコードの行から、それがOSのエラーであるかどうかについて、合理的に推測することができます。
    3. 解決できない問題がある場合は、syslogテーブルを確認しましょう。手がかりが見つかるかもしれません。
    4. Date/Time、エントリ数、および合計発生数を使って、syslogテーブルのサイズを増やす必要があるかどうかを判断します。
    5. システムがsyslogテーブルに何を記録しているのかを把握しておきましょう。エントリに何らかの変更があったり、新しいエントリや異なるエントリが記録されたことに気づくことができます。
    6. syslogテーブルのエントリは、必ずしも問題を示すものではありません。
    0
    0 566
    記事 Toshihiko Minamoto · 6月 17, 2021 4m read

    注記(2019年6月): 多くの変更がありました。最新の情報についてはこちらをご覧ください。 注記(2018年9月): この記事が最初に投稿されて以来、多くの変更がありました。Dockerコンテナバージョンを使用することをお勧めします。コンテナとして実行するためのプロジェクトと説明は、以前と同じGitHubの公開場所に残っていますので、ダウンロードして実行し、必要に応じて変更してください。

    お客様と共にパフォーマンスレビュー、キャパシティ計画、およびトラブルシューティングに取り組む際、pButtonsから、Cachéとオペレーティングシステムのメトリクスを解凍してレビューすることがよくあります。 しばらく前、HTMLファイルを参照して、グラフ化するセクションを切り取ってExcelに貼り付ける代わりに、UNIXシェル、Perl、およAWKスクリプトで書かれたpButtonsメトリクスのアンパックユーティリティを使用する記事を投稿しました。 これは、時間を節約できる便利な手法ではありますが、内容が全部網羅されているわけではありません。 また、メトリクスを素早く確認して、レポートに含められるように、メトリクスを自動的にグラフ化するスクリプトも使用しています。 ただし、これらのグラフ作成スクリプトの管理は簡単に行えるわけではなく、iostatやwindows perfmonのディスクのリストなど、サイト固有の構成が必要な場合は特に複雑になるため、グラフ作成ユーティリティを一般に公開したことはありませんでしたが、 現在では、もっと単純なソリューションがあります。

    この幸運は偶然にも、私がお客様の現場でシステムパフォーマンスを確認していたときに訪れました。Fabianが便利なPythonモジュールを使ってグラフ作成しているのを見せてくれたのです。 この方法は、私が使っていたスクリプトよりもはるかに柔軟で、管理しやすいソリューションです。 Pythonモジュールを統合してファイルの管理とグラフ作成を簡単に行え、さらにインタラクティブHTMLで共有できるため、出力にさらに高い使用効果があります。 そこで、Fabianの記事を基に、お客様のpButtonsファイルを素早く簡単に抽出して、いくつかの形式でグラフ化する__Yape__を作成しました。 このプロジェクトは、GitHubで公開しているため、ダウンロードして実行し、必要であれば変更することができます。

    概要

    現時点では、このプロセスには_2つ_のステップがあります。

    ステップ 1. extract_pButtons.py

    関心のあるセクションをpButtonsから抽出し、Excelで開くか、graph_pButtons.pyでグラフ作成の処理を行えるように、.csvファイルに書き込みます。

    ステップ 2. graph_pButtons.py

    ステップ1で作成したファイルをグラフ化します。 現在、出力は折れ線グラフか点グラフで、.pngか、拡大縮小や印刷のオプションが備わったインタラクティブな .html形式で出力されます。

    2つのPythonスクリプトのセットアップと実行方法については、GitHubの_Readme.md_に説明されています。それが最新のリファレンスです。

    その他の注意事項

    例として、出力ディレクトリと入力ディレクトリにプレフィックスを追加するオプションを使用すると、ある一式(weeksなど)のpButtons HTMLファイルのあるディレクトリ内を簡単にナビゲートし、pButtonsファイルごとに個別のディレクトリに出力することができます。

    for i in `ls *.html`; do ./extract_pButtons.py $i -p ${i}_; done
    
    for i in `ls *.html`; do ./graph_pButtons.py ./${i}_metrics -p ${i}_; done
    

    Cachéのキャパシティ計画とパフォーマンスに関する連載を続ける間、短期的には、これらのユーティリティを使ってグラフを作成しようと思います。

    OSXではテストしましたが、Windowsではテストしていません。 WindowsにもPyathonをインストールして実行できるはずですが、Windowsでの使用体験について、フィードバックを書き込んでください。 おそらく、ファイルパスのスラッシュなどを変更する必要があると思います。

    注意: 数週間前まで、Pythonを使ったことはありませんでした。Pythonエキスパートの方は、コードの書き方がベストプラクティスではないことに気づくことがあるかもしれません。 スクリプトはほぼ毎日使用しているので、今後も精進していきます。 私のPythonスキルが向上することを期待してはいますが、修正が必要な点があれば、遠慮なくご教授ください!

    スクリプトが皆さんのお役に立てられたなら、そのことをお知らせください。また、定期的に記事をご覧の上、新機能や更新情報を入手してください。

    0
    0 158
    記事 Toshihiko Minamoto · 6月 8, 2021 17m read

    以前、WRCケースのエスカレーションを受けました。お客様は、Cachéに、rawDEFLATE圧縮/解凍機能が組み込まれているかを尋ねていました。

    DEFLATEについて話すには、Zlibについても話す必要があります。Zlibは、90年代半ばに開発された無料の圧縮/解凍ライブラリで、、デファクトスタンダードとなっているからです。

    Zlibは特定のDEFLATE圧縮/解凍アルゴリズムと、ラッパー(gzip、zlibなど)内でのカプセル化するという考えの下で動作します。
    https://en.wikipedia.org/wiki/Zlib

    Caché Object Script(COS)ではすでにGZIPがサポートされており、gzipファイルを使用するために、ファイルデバイスまたはtcpデバイス、またはStreamclassで/GZIP=1を使用できるようになっています。
    http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_propstream_gzip

    「CSP-Gateway/Web-Gateway」Webサーバーモジュールでも、Caché-Serverから受信するhttp-data送信をGZIP圧縮/解凍するために、Zlibライブラリが使用されています。 (CSP、Zen、SOAP、RESTなど)

    しかし、GZIP形式には、DEFLATEで圧縮された生の本文にラップされた追加のヘッダーとトレーラーが含まれていました。

    お客様望んでいるのはこれではありません。 お客様のユースケースでは、DEFLATEで圧縮された生のコンテンツの作成・解凍のみを行う必要がありました。

    これはZlibライブラリではサポートされていますが、Caché API/関数内からは現在公開されていません。

    追加するにはどうすればいいでしょうか?

    「どうにかしてZlibライブラリにアクセスする必要があります。」

    コールアウトを使って、Caché内からZlibを利用できるようにすることはできないだろうか?

    「はい、可能です。」

    Cachéのコールアウトを使うと、C/C++呼び出し規則をサポートするほかの言語で書かれたほかのライブラリ(WindowsのDLL、UnixのSO)から、実行可能ファイル、オペレーティングシステムのコマンド、または関数を呼び出すことができます。

    Cachéコールアウトは、$ZF関数で提供されています。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=BGCLをご覧ください。

    たとえば、オペレーティングシステムのコマンドを発行する場合、$ZF(-1) と$ZF(-2) 関数を使用できます。

    $ZF(-1, command) はプログラムまたはオペレーティングシステムのコマンドを、生成された子プロセスとして実行し、その子プロセスがexitステータスを返すまで、現在のプロセスの実行を一時的に停止しますが、$ZF(-2, command) は、非同期的に動作するため、生成された子プロセスが完了するのを待ちません。そのためそのプロセスから直接ステータス情報を受信することができません。

    もう1つの方法は、オペレーティングシステムレベルと同様に、コマンド・パイプを使用してプロセスと通信する方法です。 ここでは、パイプを介して出力を送信してプロセスを制御し、パイプを読み取って入力を受信し、プロセス出力を取得することができます。
    http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GIOD_ipc_pipes

    注意: 今後、$ZFとパイプを使ったCachéコールアウトの仕組みのサポートを終了し、より安全なコールアウト方法に置き換える予定です。 最新情報をお楽しみに。

    私はWeb専門のエンジニアなので、JavaScriptを好んで使用しています。 しかし、皆さんもよくご存知のとおり、WebページのコンテキストにおいてWebブラウザでクライアントがJavaScriptを実行するのではなく、サーバーで実行するための何かが必要です。

    一般的に使用されている人気の高いJavaScriptサーバーのランタイム環境/エンジンは、Node.jsです。
    これは、ChromeのV8 JavaScriptエンジンを基礎に構築された、コミュニティが主導するJavaScriptランタイム環境です。 Node.jsは、イベント駆動型のノンブロッキング非同期I/Oモデルを使用しており、軽量で非常に効率的です。
    https://nodejs.org/en/

    幸いなことに、Node.jsにはzlibモジュールが含まれており、私たちの計画に最適です。 https://nodejs.org/api/zlib.html

    CachéもNode.jsをサポートしていますが、わずかに異なります。 強力なcache.nodeコネクタ/インターフェースが備わっているため、Caché内のデータとメソッドをNode.jsから簡単に利用することができます。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=BXJS

    この特定のユースケースと要件の場合、私たちが求めているものはこのことではありません

    Node.jsを介して純粋なJavaScriptを実行し、Cachéに結果を返す必要があるからです。
    つまり、これは逆方向になります。

    前述のコマンドパイプのコールアウトの仕組みが適しているようです。

    Node.jsをダウンロードしてインストールしたら、このプランが機能するかどうかを試してみましょう。

    USER>set cmd="node -v",oldIO=$IO open cmd:"QR" use cmd read result close prog use oldIO
    USER>w result
    v8.9.1

    このテストでわかるように、期待どおりに動作しています。 「node-v」コマンドによって、現在インストールされているNode.jsのランタイム環境に関するバージョン情報が返されています。

    「できました!」

    ここで、指定されたコマンドラインの引数から、zlibモジュールと生のDEFLATE/INFLATEアルゴリズムを使って、ファイルのコンテンツを圧縮/解凍するノードスクリプトをコーディングしましょう。

    これは簡単に行えます。 次のコードを使って、プロジェクトフォルダに_zlib.js_を作成します。

    //zlib.js
    Const
      func = process.argv[2],
      infile = process.argv[3],
      outfile = process.argv[4];
    
      const zlib = require('zlib');
      const fs = require('fs');
    
      if (func=='DEFLATERAW') {
        var wobj = zlib.createDeflateRaw();
      }
      else  {
        var wobj = zlib.createInflateRaw();
      }
    
      const instream = fs.createReadStream(infile);
      const outstream = fs.createWriteStream(outfile);
    
      instream.pipe(wobj).pipe(outstream);
    
      console.log(func + ' ' + infile + ' -> ' + outfile);

    このスクリプトは、次のようなコマンドを使ってOSコンソールから実行し、生のDEFLATEを使用して、既存のinput.txtファイルをoutput.zzに圧縮できます。

    C:\projects\zlib>node zlib.js DEFLATERAW input.txt output.zz
    DEFLATERAW input.txt -> output.zz

    注意: 便宜上、このコードは、ノードスクリプトが実行しているフォルダ(c:\projects\zlibなど)にあるファイルの圧縮/解凍のみをサポートしています。 そのため、少なくともinput.txtファイルをこの場所に作成するかコピーするようにしてください。
     

    最初に、スクリプトコードは、「zlib」(Zlibライブラリノード)と「fs」(ファイルアクセス/操作用のファイルシステムノード)モジュールの機能を使用するために、これらを配置しています。

    その後で、_process.argv_を使って、受信するコマンドライン引数にアクセスしています。
    _argv_は「引数ベクトル(argument vector)」の略で、最初の2つの要素である「node」とスクリプトファイルへのフルパスを含む配列です。 3つ目の要素(インデックス2)は「関数/アルゴリズム名」、4つ目と5つ目の要素(インデックス3と4)は、入力ファイルの引数「infile」と出力ファイルの引数「outfile」です。

    最後に、パイプ処理を使用して、入力ファイルストリームと出力ファイルストリームの両方に適切なzlibメソッドを使用しています。

    関数の結果を戻すには、コンソールで結果メッセージを出力するだけです。

    「以上です。」
     

    これがCachéから動作するか試してみましょう。

    USER>set cmd="node c:\projects\zlib\zlib.js DEFLATERAW input.txt output.zz",oldIO=$IO open cmd:"QR" use cmd read result close cmd use oldIO
    USER>w result
    DEFLATERAW input.txt -> output.zz

    「期待どおりに動作しました。」
     

    次のコマンドを使用して、圧縮済みのoutput.zzファイルをoutput.txtに解凍(inflate)することができます。

    USER>Set cmd="node c:\projects\zlib\zlib.js INFLATERAW output.zz output.txt",...

    こうすると、output.txtファイルのコンテンツとサイズは、input.txtファイルと全く同じになります。

    「問題は解決しました。」

    コマンドパイプを使用して、ノードスクリプトへのコールアウトにより、Cachéでファイルの生のDEFLATE圧縮/解凍を利用できるようになりました。

    しかし、パフォーマンスの観点から、考慮すべきことがあります。コールアウトの仕組みには、コールアウトのたびに新しい子プロセスが起動されるというオーバーヘッドが伴います。

    パフォーマンスが重要でない場合、または完了すべき処理作業が時間のかかるものである場合、ファイルサイズが大きくなっても圧縮と解凍は問題はないでしょうし、プロセスの開始にかかる時間のオーバーヘッドも無視できます。 しかし、多数の比較的小さなファイルを次々と集中して圧縮/解凍するのであれば、このオーバーヘッドは回避した方が良いと言えます。

    では、どのようにして回避すればよいのでしょうか。

    コールアウトが行われるたびに新しい子プロセスが作成されないようにする必要があります。

    「どうすればよいですか?」

    受信するリクエストをリスンし、要求どおりに目的の操作を行うサーバーとして実行するように、スクリプトを準備する必要があります。

    もっともらしく聞き覚えのある方法でしょうか。そうです。これが現在、RESTful HTTP API/サービスで行えるとされている方法です。

    Node.jsであれば、HTTPプロトコルに基づく単純なサーバーを非常に簡単に作成できます。

    Node.jsには、組み込みの「http」モジュールを使用して、すぐに使用できるオーバーヘッドの低いHTTPサーバーがサポートされています。

    「http」モジュールを含めるには、以下に示されるように、_simple_https.js_スクリプトファイルに、通常どおりノードのrequire() メソッドを使用します。

    //simple_https.js
    const
      http = require('http'),
      server = http.createServer(function (request, response) {
        response.writeHead(200, {'Content-Type' : 'text/plain'});
        response.end('Hello World!\n');
      });
    server.listen(3000, function(){
        console.log('ready captain!');
    });

    この単純なhttpサーバーをOSコンソールから起動するには、次のコマンドを使用します。

    C:\projects\zlib>node simple_http.js
    ready captain!

    ここでは、「curl」を使って、これをテストしています。curlは一般的に使用される便利なコマンドラインツールで、HTTPリクエストを特定のサーバーに発行します。
    https://curl.haxx.se/

    「-i」フラグを追加して、レスポンス本文のほかにHTTPヘッダーを出力する必要があるということを、curlに指示しています。

    C:\curl>curl -i http://localhost:3000
    HTTP/1.1 200 OK
    Content-Type: text/plain
    Date: Mon, 22 Jan 2018 13:07:06 GMT
    Connection: keep-alive
    Transfer-Encoding: chunked
    
    Hello World!

    これはうまく動作しますが、低レベルの「http」モジュールに対してhttpサービスを直接記述するのは面倒であり、時間のかかる作業です。

    Node.jsには活発に活動しているオープンソースコミュニティがあり、Node.jsアプリケーションに機能を追加できる優れたモジュールがたくさん作成されているため、このケースのRESTful APIを開発するには「Express」を使用することにします。

    Express.js」または単に「Express」とも呼ばれるモジュールは、WebアプリとAPIを構築するために設計された、Node.js用のWebアプリケーションフレームワークです。

    無ければ結局は自分で書くことになるのですが、その手間を省くことのできる多数の配管コードが提供されています。 URLパスに基づいて受信リクエストをルーティングしたり、受信データを解析したり、不正な形式のリクエストを拒否したりなどすることができます。

    Expressフレームワークを使えば、こういったタスクやその他無数のタスクを実現できるようになります。 Node.jsの標準的なサーバーフレームワークであるのも、当然でしょう。
    http://expressjs.com/
     

    すべてのNodeモジュールと同様に、「Express」を使用するには、まずnpm(ノードパッケージマネージャー)を使ってインストールしなければ、使うことはできません。

    C:\projects\zlib>node install express
    ...

    「express」とそのほかに必要なモジュールを含めるには、以下に示されるように、_zlibserver.js_スクリプトファイルに、通常どおりノードのrequire() メソッドを使用します。

    //zslibserver.js
    const express = require('express');
    const zlib = require('zlib');
    const fs = require('fs');
     
    var app = express();
     
     app.get('/zlibapi/:func/:infile/:outfile', function(req, res) {
        res.type('application/json');
        
        var infile=req.params.infile;
        var outfile=req.params.outfile;
        
        try {
            
            var stats = fs.statSync(infile);
            var infileSize = stats.size;
            
            switch(req.params.func) {
              case "DEFLATERAW":
                var wobj = zlib.createDeflateRaw();
                break;
              case "INFLATERAW":
                var wobj = zlib.createInflateRaw();
                break;
              case "DEFLATE":
                var wobj = zlib.createDeflate();
                break;
              case "INFLATE":
                var wobj = zlib.createInflate();
                break;
              case "GZIP":
                var wobj=zlib.createGzip();
                break;
              case "GUNZIP":
                var wobj=zlib.createGunzip();
                break;
              default:
                res.status(500).json({ "error" : "bad function" });
                return;
            }
        
            const instream = fs.createReadStream(infile);
            const outstream = fs.createWriteStream(outfile);
          
            var d = new Date();
            console.log(d.toLocaleDateString() + ' ' + d.toLocaleTimeString() + ' : ' + req.params.func + ' ' + infile + ' -> ' + outfile + '...');
          
            instream.pipe(wobj).pipe(outstream).on('finish', function(){
                
                var d = new Date();
                console.log(d.toLocaleDateString() + ' ' + d.toLocaleTimeString() + ' : ' + 'finished!');
            
                var stats = fs.statSync(outfile);
                var outfileSize = stats.size
            
                res.status(200).json( { "result" : "OK" , "infileSize" : infileSize, "outfileSize" : outfileSize, "ratio" : (outfileSize / infileSize * 100).toFixed(2) + "%" } );
                return;
          });
          
        }
        catch(err) {
          res.status(500).json({ "error" : err.message});
          return;
        }
        
    });
    app.listen(3000, function(){
        console.log("zlibserver is ready captain.");
    });

    まず、「zlib」、「fs」、および「express」モジュールを取り込み、expressの「app」(アプリケーション)コンテキストを作成しています。

    Expressの機能は、リクエストオブジェクトとレスポンスオブジェクトを操作して処理を行える非同期のミドルウェア関数を介して提供されます。

    app.get() では、Expressに、ルートの /zlibapi/:func/:infile/:outfile_パスへのHTTP GETリクエストを処理するのかを指示しています。 app.get() を使用すると、ルート/パスに複数のハンドラを登録することができます。 パスの「:variable_」のチャンクは「名前付きルートパラメーター」と呼ばれます。
    APIがヒットしたら、expressはURLのその部分を取得し、req.paramsで使用できるようにします。

    コードには、RAWDEFLATE/RAWINFLATEのほかに、zlibがサポートする圧縮/解凍ラッパー形式のGZIP/GUZIPやDEFLATE/INFLATEのサポートが追加されています。

    また、出発点として、基本的なTry/Catchエラー処理も追加しました。

    結果とともにJSONオブジェクトを送り返すには、レスポンスオブジェクトのresと、res.sendStatus() に相当するres.status() を使用しています。
    詳細については、Expressのドキュメントをご覧ください。

    最後に、受信するHTTPリクエストのリスンをTCPポート3000で開始しています。

    それでは、「zlibserver」アプリを実行して、実際に動作するかを見てみましょう。

    C:\projects\zlib>node zlibserver.js
    zlibserver is ready captain.

    実行することがわかったので、サービスとして使用してみることにします。

    ここではCachéから試してみますが、「curl」やその他の「Postman」などのサードパーティツールを使って、「zlibserver」RESTful APIをテストすることもできます。

    %Net.HttpRequest_を使用して、Caché COSに、GETリクエストを実行する単純なRESTクライアントを実装する必要があります。これはそれほど手間のかからない作業ですが、コードを数行、記述する必要があります。 以下は、クラスの_utils.Http:getJSON() メソッドです。

    Include %occErrors
    Class utils.Http [ Abstract ]
    {
      ClassMethod getJSON(server As %String = "localhost", port As %String = "3000", url As %String = "",  
        user As %String = "", pwd As %String = "", test As %Boolean = 0) As %DynamicAbstractObject
      {
         set prevSLang=##class(%Library.MessageDictionary).SetSessionLanguage("en")
                  
         set httprequest=##class(%Net.HttpRequest).%New()
         set httprequest.Server=server
         set httprequest.Port=port
    
         if user'="" do httprequest.SetParam("CacheUserName",user)
         if pwd'="" do httprequest.SetParam("CachePassword",pwd)
    
         set sc=httprequest.SetHeader("Accept","application/json")
         if $$$ISERR(sc) $$$ThrowStatus(sc)
         set sc=httprequest.SetHeader("ContentType","application/json")
         if $$$ISERR(sc) $$$ThrowStatus(sc)
    
         try {
             set sc=httprequest.Get(url,test)
             if $$$ISERR(sc) $$$ThrowStatus(sc)
             if (httprequest.HttpResponse.StatusCode \ 100) = 2 {
                 set response = ##class(%DynamicAbstractObject).%FromJSON(httprequest.HttpResponse.Data)
             }
             else {
                 Throw ##class(%Exception.General).%New(httprequest.HttpResponse.ReasonPhrase, $$$GeneralError,,httprequest.HttpResponse.StatusLine)
             }            
         }
         catch exception  {
             set response = $$$NULLOREF
             throw exception             
         }            
         Quit response
      }
    }

    Cachéから、次のようにして使用することができます。
     

    USER>try { set res="",res = ##class(utils.Http).getJSON(,,"/zlibapi/DEFLATERAW/input.txt/output.zz"),result=res.result } catch (exc) { Set result=$system.Status.GetOneErrorText(exc.AsStatus()) }
    USER>w result
    OK
    USER>w res.%ToJSON()
    {"result":"OK","infileSize":241243,"outfileSize":14651,"ratio":"6.07%"}

    「うまく動作しました!」

    以下は、curlを使ったAPIのテスト方法です(エクステントtest.logファイルを使用しています)。

    C:\curl>curl -i http://localhost:3000/zlibapi/GZIP/test.log/test.gz
    
    HTTP/1.1 200 OK
    X-Powered-By: Express
    Content-Type: application/json; charset=utf-8
    Content-Length: 76
    ETag: W/"4c-iaOk5W3g6IlIEkzJaRbf3EmxrKs"
    Date: Fri, 26 Jan 2018 07:43:17 GMT
    Connection: keep-alive
    
    {"result":"OK","infileSize":36771660,"outfileSize":8951176,"ratio":"24.34%"}
    
    C:\curl>curl -i http://localhost:3000/zlibapi/GUNZIP/test.gz/test.txt
    
    HTTP/1.1 200 OK
    X-Powered-By: Express
    Content-Type: application/json; charset=utf-8
    Content-Length: 77
    ETag: W/"4d-tGgowYnW3G9ctHKcpvWmnMgnUHM"
    Date: Fri, 26 Jan 2018 07:43:36 GMT
    Connection: keep-alive
    
    {"result":"OK","infileSize":8951176,"outfileSize":36771660,"ratio":"410.80%"}
    
    C:\curl>curl -i http://localhost:3000/zlibapi/DEFLATERAW/test.log/test.zz
    
    HTTP/1.1 200 OK
    X-Powered-By: Express
    Content-Type: application/json; charset=utf-8
    Content-Length: 76
    ETag: W/"4c-4svUs7nFvjwm/JjYrPrSSwhDklU"
    Date: Fri, 26 Jan 2018 07:44:26 GMT
    Connection: keep-alive
    
    {"result":"OK","infileSize":36771660,"outfileSize":8951158,"ratio":"24.34%"}
    
    C:\curl>curl -i http://localhost:3000/zlibapi/INFLATERAW/test.zz/test.txt
    
    HTTP/1.1 200 OK
    X-Powered-By: Express
    Content-Type: application/json; charset=utf-8
    Content-Length: 77
    ETag: W/"4d-7s7jwh1nxCU+6Qi7nX2TB3Q1IzA"
    Date: Fri, 26 Jan 2018 07:44:42 GMT
    Connection: keep-alive
    
    {"result":"OK","infileSize":8951158,"outfileSize":36771660,"ratio":"410.80%"}

    ここで、受信ジョブを実行/受信/処理する間のzlibserverのコンソール出力を確認できます。

    C:\projects\zlib>node zlibserver
    zlibserver is ready captain.
    2018-1-26 08:43:14 : GZIP test.log -> test.gz...
    2018-1-26 08:43:17 : finished!
    2018-1-26 08:43:36 : GUNZIP test.gz -> test.txt...
    2018-1-26 08:43:36 : finished!
    2018-1-26 08:44:23 : DEFLATERAW test.log -> test.zz...
    2018-1-26 08:44:26 : finished!
    2018-1-26 08:44:42 : INFLATERAW test.zz -> test.txt...
    2018-1-26 08:44:42 : finished!

     

    お客様の問題とそれを解決する上で達成したことをまとめましょう。

    RESTを使ったNode.jsのコールアウトによって、Cachéを非常に簡単に拡張できることを学びました。

    まず、この記事のきっかけとなった元々の特定のユースケースを「ブラインドアウト」し、優れたモードモジュールが豊富に用意され、APIによって広範な機能の可能性が提供されている、素晴らしいNode.jsエコシステムの観点から前向きに考えてみると、魅力的なソリューションで、Cachéから簡単にAPIにアクセスして制御することができるようになりました。

    もっとも頻繁に使用されているNode.jsモジュール/APIのリストについては、次のリンクを参照してください。
    http://www.creativebloq.com/features/20-nodejs-modules-you-need-to-know

    「サポートの現場からは以上です!」 :)

    お役に立てられれば幸いです。

    Bernd
     

    0
    0 979
    記事 Toshihiko Minamoto · 6月 3, 2021 4m read

    コンテナ

    InterSystems IRIS Data Platformの公開により、Dockerコンテナでも製品を提供しています。 コンテナとは一体何でしょうか。

    基本的なコンテナの定義は、プロセスのサンドボックスの定義です。  

    コンテナは、たとえば実行できるという点において、仮想マシン(VM)に似た部分を持つソフトウェア定義パッケージです。 

    コンテナは、完全なOSエミュレーションを使わずに分離することができるため、 VMよりもはるかに軽量です。 

    本質的に、コンテナは、どのようにアプリケーションをシステムから別のシステムに確実に移動し、それが動作することを保証できるのかという問題に対する答えと言えます。 アプリケーションのすべての依存関係をコンテナにカプセル化し、プロセス分離領域を作成することにより、アプリケーションソリューションがプラットフォーム間で移動した場合でも動作するという高い保証を得ることができます。  

    プロセスは、オペレーティングシステムによって実行が可能です。 これらのプロセスはアドレス領域、ネームスペース、cgroupなどを共有し、通常、OSの環境全体にアクセスすることができます。それらのスケジュールと管理は、OSが行います。 すべて良いことではありますが、特定のプロセスやいくつかのプロセスを分離して、特定のタスクや演算、またはサービスを実行したい場合はどうでしょうか。 手短に言えば、プロセスを分離できる機能こそ、コンテナが提供している機能なのです。 したがって、コンテナをプロセスのサンドボックスとして定義することができるでしょう。 

    では、サンドボックスとは何でしょうか。 サンドボックスは、コンテナがプロセスを持つ分離レベルです。 この機能は、ネットワークインターフェース、マウントポイント、インタープロセス通信(IPC)、ユニバーサルタイムシェアリング(UTS)といったシステムのほかの重要な部分もサンドボックス化することのできるネームスペースと呼ばれるLinuxのカーネル機能により実装されています(https://en.wikipedia.org/wiki/Linux_namespaces)。 

    コンテナまたはサンドボックスは、制御グループまたはcgroupと呼ばれる別のカーネル機能によっても管理または制御することができます(https://en.wikipedia.org/wiki/Cgroups)。 コンテナがほかのコンテナやホストとリソースを共有する上で、ほかのネイバーに害を与えないように、コンテナにルールが適用されます。 

    コンテナとVMの違いを理解するには、VMを_家_に、そしてコンテナを_マンション_に例えることができます。  

    VMは、一軒家のように、自己完結型で独立しています。 それぞれの家には、配管、暖房、電気といった固有のインフラストラクチャが備わっており、 最低要件も与えられています(最低、寝室は1室、屋根は1つなど)。 

    一方のコンテナは、共有のインフラストラクチャを使用するように作られているため、マンションとして捉えることができます。 マンションの建物は、配管、暖房、電機システム、正面玄関、エレベーターなどを共有していますが、これと同様に、コンテナも、Linuxカーネルを通じてホストが提供しているリソースを使用します。 また、マンションの各居住戸のサイズや形には様々なものがあることも考えましょう。 

    コンテナには完全なOSはなく、/binの実行可能ファイルや /etcの構成ファイルと定義ファイルといった、最小限必要なLinux OSしか備わっていないため、サイズが非常に小さくなります。そのため、その場所を移動したり、スピンアップする場合に、一秒きっかりで敏速に行うことができます。  コンテナを構築した瞬間から、ソフトウェアファクトリーのプロビジョニングパイプラインを通じ、本番環境での最終実行に至るまで、アジリティを得られることになります。 ちなみに、コンテナはCI/CDマイクロサービスアーキテクチャのコンテキストにピッタリと収まりますが、これはまた別の機会にお話ししましょう。

    コンテナ内のプロセスは、コンテナのライフサイクルと密接な関係があり、 コンテナを_起動_すれば、アプリのすべてのサービスも起動して実行し(たとえば、あるWebサーバーコンテナのポート80と、InterSystems IRISコンテナのポート57772と1972)、 コンテナを_停止_すれば、すべてのプロセスも停止します。 

    この記事では、コンテナのランタイムの基本的な概念について説明しました。コンテナは、プロセスをホストやその他のコンテナから分離するサンドボックスです。 

    コンテナを理解するには、そのイメージに関する別の側面もあります。 これについては、第2部の記事で説明することにします。

    0
    0 403