0 フォロワー · 27 投稿

JSON(JavaScript Object Notation)は、軽量のデータ交換フォーマットです。 人が簡単に読み書きできます。

記事 Hiroshi Sato · 9月 3, 2025 2m read

これは InterSystems FAQ サイトの記事です。
 

ダイナミックオブジェクトの%ToJSONメソッドを利用することで簡単にJSONデータを送信することができます。

但し、標準的な方法では、出力するJSONのデータがIRIS文字列の最大長(約32万文字 正確には$SYSTEM.SYS.MaxLocalLength()が返す値)を超えると<MAXLENGTH>エラーとなります。

これを回避するためには、文字列として返すのではなく、%ToJSONメソッドの出力先としてStreamを指定し、その結果作成されたそのStreamデータを順次読み取って、出力先に書き出すようにする必要があります。

以下のように処理できます。

0
0 51
記事 Hiroshi Sato · 9月 3, 2025 3m read

これは InterSystems FAQ サイトの記事です。
 

JSON利用の普及に伴いインターシステムズは、JSONに関連する様々な機能強化をIRISに対して行なっています。

その一環として、SQLのJSON_OBJECTのサポートがあります。

この機能に関して現時点より(2025年6月)古いバージョンでは残念ながら制限や不具合が存在しています。

今後も機能強化やバグフィックスを継続していく予定となっているため、この機能の利用を検討および既に利用している方は最新バージョンでのご利用をお勧めします。

ここでは、現時点でわかっている制限事項/不具合についてお知らせします。

  • VIEW経由でJSON_OBJECTを利用した場合にエラーとなる場合がある 以下のようなクラスとVIEWを定義します。  
Class User.test Extends%Persistent
{

  Property p1 As%String;Property p2 As%String;
}
Class User.myview [ ClassType = view, ViewQuery = { select p1 as v10, p1 as v11, p2 as v12 from test } ] { }
0
0 24
記事 Hiroshi Sato · 7月 27, 2025 2m read

これは InterSystems FAQ サイトの記事です。
 

ダイナミックオブジェクトの%FromJSONFileメソッドを利用すると簡単にJSON形式のファイルの内容をダイナミックオブジェクトにコピーすることができます。

以下の形式のJSONファイルをそのメソッドを使用して取り込む例は以下のようになります。

{
    "ShipTo": {
        "City": "Tokyo",
        "Street": "Ginza",
        "PostalCode": "160-0001"
    },
    "CustomerId": 1,
    "Items": [
        {
            "ProductId": "MNT001",
            "Amount": 2
        },
        {
            "ProductId": "PC001",
            "Amount": 2
        }
    ]
}
0
0 28
記事 Toshihiko Minamoto · 5月 20, 2025 7m read

Django フレームワークは長年学習したいと思ってきましたが、いつも他の差し迫ったプロジェクトが優先されてきました。 多くの開発者と同様に、機械学習においては Python を使用していますが、初めてウェブプログラミングについて学習したころは、PHP がまだまだ優勢でした。そのため、機械学習の作品を公開する目的でウェブアプリケーションを作成するための新しい複雑なフレームワークを選択する機会が訪れても、私は依然として PHP に目を向けていました。 ウェブサイトの構築には Laravel と呼ばれるフレームワークを使用してきましたが、この PHP フレームワークから最新の MVC(モデルビューコントローラー)というウェブプログラミングのパターンに出会いました。 さらに複雑なことに、私は最新の JavaScript フレームワークを使用してフロントエンドを構築するのを好んでいます。 React を使用するのがより一般的のようですが、私は Vue.js に一番慣れているため、このプロジェクトではそれを使用することにしました。

なぜ複雑なフレームワークを使用するのでしょうか? Django、Laravel、React、または Vue などのフレームワークを学習する際の最大の難関は何でしょうか?

答えは人それぞれですが、私は MVC フレームワークがアプリの構造化に関するガイドを非常に多く提供してくれるため、気に入っています。毎回、作り直す必要がありません。 初めはこういったフレームワークは制約が多くて難解に思えるかもしれませんが、構造に慣れてしまえば、新しい機能をより追加しやすいと思います。

問題は、物事があまりにも単純になりすぎる可能性があることです。 Django のようなフレームワークは、よく知られた概念に基づいているかもしれませんが、Django では特に馴染みのない名前と構造を持つ多くの省略形や仮定に依存しています。 私のアプリケーションでは、Django は API とすべてのウェブルーティングを処理しています。 新しい API エンドポイントを追加する場合は、views.py のファイルに関数を追加してから、urls.py ファイルに移動して、その関数をインポートするステートメントと、API エンドポイントが提供されている URL を定義する別のステートメントを追加する必要があります。 その後で、データを取得してユーザーに表示するか操作するために、そのエンドポイントをクエリする JavaScript を使って、フロントエンドの Vue コンポーネントを編集する必要があります。

プロジェクトのセットアップが完了したら、このような機能の追加は迅速に行えます。 約 4 行のコードを追加するだけで、HTTP リクエストを処理し、必要なデータを JSON 形式で返すように、views.py ファイルの新しい関数に必要なロジックに集中できます。 難しいのは、それらのファイルが何であるか、そしてそれらがどのように連携してアプリケーション全体を作り上げるかを学ぶことです。

Django のようなフレームワークを学習するには、実際に動作する例を探して、データのフローを感じ取れる小さな変更を適用して見るのが最適な方法だと思います。 概念が明確になり始めて理解できるようになってきたら、ドキュメントを参考にしましょう。 AI モデルにコードを説明してもらい、様々な標準ファイルがフレームワークでどのように動作するかを尋ねましょう。 これらのツールが、長期的には時間を節約し、アプリケーションの保守と更新を容易にする方法として登場したことにすぐに気づくでしょう。 Django と Vue フレームワークには標準の構造があるため、後で戻ってきても、なぜ特定の方法でコーディングしたのかをすぐに理解でき、作業についての理解を再び深めやすくなっているでしょう。 また、アプリケーションの基本構造に慣れているため、他の人のアプリケーションを理解し、主な機能を把握するのもより簡単です。

では、これから始めようとしている人の支援となる Django の基礎とは何でしょうか? 私にとっては、最初に理解すべきことは、Django プロジェクトは Django の新規プロジェクトを作成するコマンドの実行によって 生成され、これによって構築を開始するために使用できる「基本プロジェクト」を構成する一連の基本ファイルとフォルダが生成されるということです。 プロジェクトフォルダには、プロジェクト全体に適用される設定を含むいくつかの Python ファイルがあります。 頻繫にアクセスする重要なフォルダは、すべての設定が含まれる settings.py と、urls.py です。 「Django はどのようにして静的ファイルを配置する場所を決定しているのか」といった疑問がある場合、その答えは通常 settings.py のどこかにあります。 アプリケーションに新しい URL を追加する場合は、urls.py ファイルを更新する必要があります。

これらのプロジェクトレベルのファイルと共に、プロジェクト内のアプリごとにフォルダを作成します。 これらのアプリは登録されている必要があります。つまり、 settings.py ファイルで名前を付ける必要があります。 プロジェクト内のメインのアプリフォルダはドキュメントと呼ばれます。 どのフォルダ内には、models.py ファイル、serializer.py ファイル、views.py ファイルがあります。 ファイルは他にもありますが、これらが重要な 3 つのファイルです。

models.py 内には、Document オブジェクトとそのフィールドを指定します。 Document オブジェクトに保管する予定の情報を保存するために必要なスキーマを使って IRIS データベースに Documents テーブルを作成するのは、Django に任せられます。 私の models.py ファイルでは、Documents には 255 文字以内の名前、大量のテキストであるコンテンツフィールド、ベクトルが補完されるデータベース名(別のテキストフィールド)、埋め込みタイプ(別のテキストフィールド)、および数値で表現されるベクトル埋め込みの次元が含まれることを指定しています。 これらの定義を使用することで、Fjango は必要な列タイプで必要なデータベーステーブルを作成します。 すると、データベースへのオブジェクトの保存は、Document.save() だけで完了です。

serializer.py ファイル内には、オブジェクトと JSON の変換方法に関する定義が含まれます。 基本的なユースケースでは、これを定義する標準的な方法があり、このプロジェクトで確認できます。

では、Django の核心である views.py ファイルを確認しましょう。 ここに、HTTP リクエストを受け取って、HTTP レスポンス全体、または JSON API の場合は JSON API レスポンスなどのデータを返す関数を定義します。 つまり、Django ではウェブページ全体を制作して、アプリのフロントエンドとしても使用することも、JSON データのみを提供して、フロントエンドを全く別のプラットフォームで構築することもできます。

最初は、一見恣意的なファイルや規則をすべて使用するのは面倒に感じるかもしれませんが、そうすることでアプリケーションが動作し始め、HTTP リクエストを処理して、レスポンスとして正しいデータを提供するようになることが分かれば、新しい機能を構築し続けるのが非常に楽しくなります。 HTTP リクエストを処理するオブジェクト、ウェブルート、および関数を 1 つ定義すれば、2 つ目、そして 3 つ目を簡単に定義してアプリケーションに機能を追加できるようになります。

私のプロジェクトは、github: https://github.com/grongierisc/iris-django-template にある @Guillaume Rongier が作成した Iris Django Template をフォークしたものです。

このテンプレートには Django のみが含まれており、Django フレームワークの学習に非常に役立ちました。私が行った主な追加項目の 1 つは、Tailwind CSS を使用した Vue.js の追加です。最新の JavaScript フレームワークをこのパッケージに統合して、IRIS で実行する単一ページのアプリケーションを作成できます。 単一ページのアプリケーションは、xhr リクエストを送信して JSON データを取得し、完全に再読み込みすることなく動的にページを更新する JavaScript アプリケーションです。 これには長所と短所がありますが、最新のウェブ開発の特徴です。

RAG と IRIS 上のベクトルストアの例としてだけでなく、Vue.js と Tailwind を使って IRIS 上に最新の柔軟なウェブアプリケーションを簡単に素早く作成する目的で Django を使用するためのテンプレートとして、私のプロジェクトを確認することをお勧めします。 リポジトリはこちらの GitHub にあります: https://github.com/mindfulcoder49/iris-django-template

ご質問があれば、ぜひお答えします。このプロジェクトを独自のユースケースに適合しようとするする際に問題が発生した場合は、私の洞察を提供いたします。

0
0 63
記事 Toshihiko Minamoto · 4月 11, 2025 10m read

django_logo

説明

これは、ネイティブウェブアプリケーションとして IRIS にデプロイできる Django アプリケーションのテンプレートです。

インストール

  1. リポジトリをクローンする
  2. 仮想環境を作成する
  3. 要件をインストールする
  4. docker-compose ファイルを実行する
git clone
cd iris-django-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

使用法

ベース URL は http://localhost:53795/django/ です。

エンドポイント

  • /iris - IRISAPP ネームスペースに存在する上位 10 個のクラスを持つ JSON オブジェクトを返します。
  • /interop - IRIS の相互運用性フレームワークをテストするための ping エンドポイント。
  • /api/posts - Post オブジェクトの単純な CRUD エンドポイント。
  • ``/api/comments` - Comment オブジェクトの単純な CRUD エンドポイント。

このテンプレートからの開発方法

WSGI 導入記事をご覧ください: wsgi-introduction

概要: セキュリティポータルで DEBUG フラグをトグルすると、開発作業の過程で変更内容がアプリケーションに反映されるようになります。

コードの説明

Django アプリケーションは次のように構造化されています。

  • app - Django プロジェクトフォルダ
    • app - 構成用の Django アプリフォルダ
      • settings.py - Django の設定ファイル
      • urls.py - ビューを URL に接続する Django URL 構成ファイル
      • wsgi.py - Django WSGI ファイル
      • asgi.py - Django ASGI ファイル
    • community - コミュニティアプリの Django アプリフォルダ、Post と Comment オブジェクトでの CRUD 操作
      • models.py - Post と Comment オブジェクトの Django モデルファイル
      • views.py - Post と Comment オブジェクトにアクセスするための Django ビューファイル
      • serializers.py - Post と Comment オブジェクトの Django シリアライザーファイル
      • admin.py - 管理インターフェースに CRUD 操作を追加する Django 管理ファイル
      • migrations - データベースを構築するための Django マイグレーションフォルダ
      • fixtures - Django fixtures フォルダデモデータ
    • sqloniris - IRIS アプリでの SQL に使用する Django アプリフォルダ
      • views.py - IRISAPP ネームスペースをクエリするための Django ビューファイル
      • apps.py - Django アプリ構成ファイル
    • interop - 相互運用性アプリ用の Django アプリフォルダ
      • views.py - 相互運用性フレームワークをテストするための Django ビューファイル
      • apps.py - Django アプリ構成ファイル
    • manage.py - Django 管理ファイル

app/settings.py

このファイルには、アプリケーションの Django 設定が含まれます。

...

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'community',
    'sqloniris',
    'interop',
    'rest_framework'
]

...

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 20
}

...

DATABASES = {
    "default": {
        "ENGINE": "django_iris",
        "EMBEDDED": True,
        "NAMESPACE": "IRISAPP",
        "USER":"SuperUser",
        "PASSWORD":"SYS",
    }
}

一部の重要な設定:

  • INSTALLED_APPS - Django プロジェクトにインストール済みのアプリのリストが含まれます。
    • community - Post と Comment オブジェクトでの CRUD 操作用 Django アプリ。
    • sqloniris - IRIS での SQL 操作に使用する Django アプリ。
    • interop - 相互運用性操作用の Django アプリ。
    • rest_framework - REST API 用の Django REST フレームワーク。
  • REST_FRAMEWORK - Django REST フレームワークの設定が含まれます。
    • DEFAULT_PERMISSION_CLASSES - 認証済みのユーザーのみが CRUD 操作を実行できます。
    • DEFAULT_PAGINATION_CLASS - REST API のページネーションクラス。
  • DATABASES - IRIS データベース接続の設定が含まれます。
    • ここでは、django_iris エンジンを使って IRIS データベースに接続しています。

app/urls.py

このファイルには、Django アプリケーションの URL 構成が含まれます。

from django.contrib import admin
from django.urls import path,include
from rest_framework import routers
from community.views import PostViewSet, CommentViewSet
from sqloniris.views import index
from interop.views import index as interop_index

router = routers.DefaultRouter()
router.register(r'posts', PostViewSet)
router.register(r'comments', CommentViewSet)


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
    path('iris/', index),
    path('interop/', interop_index)
]
  • router - REST API のデフォルトのルーターが含まれます。
  • routeer.register - Post と Comment ビューセットをルーターに登録します。
  • urlpatterns - Django アプリケーションの URL パターンが含まれます。
    • /admin/ - Django 管理インターフェース。
    • /api/ - Post と Comment オブジェクトの REST API。
    • /iris/ - IRIS エンドポイントでの SQL。
    • /interop/ - 相互運用性エンドポイント。

app/wsgi.py

このファイルには、Django アプリケーションの WSGI 構成が含まれます。

これが、Django アプリケーションを実行するために IRIS に提供する必要のあるファイルです。

Security->Applications->Web Applications セクションで、このファイルへのパスを指定する必要があります。

  • アプリケーション名
    • app.wsgi
  • コーラブル名
    • application
  • WSGI アプリディレクトリ
    • /irisdev/app/app

community/models.py

このファイルには、Post と Comment オブジェクトの Django モデルが含まれます。

from django.db import models

# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

class Comment(models.Model):
    content = models.TextField()
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
  • Post - Post オブジェクトのモデル。
    • title - 投稿のタイトル。
    • content - 投稿のコンテンツ。
  • Comment - Comment オブジェクトのモデル。
    • content - コメントのコンテンツ。
    • post - Post オブジェクトの外部キー。
    • related_name - コメントの関連名。

community/seializers.py

このファイルには、Post と Comment オブジェクトの Django シリアライザーが含まれます。

Django REST フレームワークでは、Django モデルを JSON オブジェクトにシリアル化できます。

from rest_framework import serializers
from community.models import Post, Comment

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('id', 'title', 'content', 'comments')

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ('id', 'content', 'post')
  • PostSerializer - Post オブジェクトのシリアライザー。
  • CommentSerializer - Comment オブジェクトのシリアライザー。
  • fields - シリアル化されるフィールド。

community/views.py

このファイルには、Post と Comment オブジェクトの Django ビューが含まれます。

Django REST フレームワークを使て、Django モデルの CRUD 操作を作成できます。

from django.shortcuts import render
from rest_framework import viewsets

# Import the Post and Comment models
from community.models import Post, Comment

# Import the Post and Comment serializers
from community.serializers import PostSerializer, CommentSerializer

# Create your views here.
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
  • PostViewSet - Post オブジェクトのビューセット。
  • CommentViewSet - Comment オブジェクトのビューセット。
  • queryset - ビューセットのクエリセット。
  • serializer_class - ビューセットのシリアライザークラス。

sqloniris/views.py

このファイルには、IRIS 操作における SQL の Django ビューが含まれます。

from django.http import JsonResponse

import iris

def index(request):
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Convert the result to a list of dictionaries
    result = []
    for row in rs:
        result.append(row)
    return JsonResponse(result, safe=False)
  • index - IRIS 操作における SQL のビュー。
  • query - IRIS データベースで実行される SQL クエリ。
  • rs - クエリの結果セット。
  • result - 結果セットからのリストのリスト。
  • JsonResponse - ビューの JSON レスポンス。リストのリスト表示を許可するには safe を False に設定します。

interop/views.py

このファイルには、相互運用性操作における SQL の Django ビューが含まれます。

from django.http import HttpResponse

from grongier.pex import Director

bs = Director.create_python_business_service('BS')

def index(request):
    result = bs.on_process_input(request)
    return HttpResponse(result, safe=False)
  • bs - Director クラスを使用して作成されるビジネスサービスオブジェクト。
  • index - 相互運用性操作のビュー。
  • result - ビジネスサービスの結果。

注: コードを単純化するために JsonResponse は使用しません。JSON オブジェクトを返す場合は使用できます。

トラブルシューティング

スタンドアロンモードで Django アプリケーションを実行する方法

スタンドアロンモードで Django アプリケーションを実行するには、以下のコマンドを使用できます。

cd /irisdev/app/app
python3 manage.py runserver 8001

これは、デフォルトのポート 8001 で Django アプリケーションを実行します。

注: このコマンドを実行するには、コンテナー内にいる必要があります。

docker exec -it iris-django-template-iris-1 bash

IRIS でアプリケーションを再起動する

DEBUG モードでアプリケーションに複数の呼び出しを行うと、変更はアプリケーションに反映されます。

IRIS 管理ポータルへのアクセス方法

http://localhost:53795/csp/sys/UtilHome.csp に移動すると、IRIS 管理ポータルにアクセスできます。

このテンプレートをローカルで実行する

これには、マシンに IRIS がインストールされている必要があります。

次に、IRISAPP というネームスペースを作成する必要があります。

要件をインストールします。

# Move to the app directory
cd /irisdev/app/app

# python manage.py flush --no-input
python3 manage.py migrate
# create superuser
export DJANGO_SUPERUSER_PASSWORD=SYS
python3 manage.py createsuperuser --no-input --username SuperUser --email admin@admin.fr

# load demo data
python3 manage.py loaddata community/fixtures/demo.json

# collect static files
python3 manage.py collectstatic --no-input --clear

# init iop
iop --init

# load production
iop -m /irisdev/app/app/interop/settings.py

# start production
iop --start Python.Production

静的ファイルの配信方法

Django アプリケーションで静的ファイルを配信するには、以下のコマンドを使用できます。

cd /irisdev/app
python3 manage.py collectstatic

これは、Django アプリケーションから静的ファイルを収集して、/irisdev/app/static ディレクトリに配信します。

IRIS で静的ファイルを公開するには、Security->Applications->Web Applications セクションを構成します。

web_applications

0
0 37
InterSystems公式 Seisuke Nakahashi · 4月 3, 2025

IRIS 2024.3 で発生する2つの製品障害が確認されました。お使いの環境が該当する場合は、それぞれの解決方法にしたがってご対応いただきますよう、よろしくお願いします。

0
0 45
記事 Hiroshi Sato · 4月 1, 2025 1m read

これは InterSystems FAQ サイトの記事です。

Content-Typeにcharset 情報が含まれていない場合、IRIS/Cachéは文字コードを判断できず文字コード変換が行われません。

そこで、以下のように、「自動的な文字変換を行わず、UTF-8に変換する処理を記述」することで、その指定がある無しに関わらず、対応することができます。
 

// requestオブジェクトは以下のように生成 // 詳細処理は省略// set request=##class(%Net.HttpRequest).%New()// リクエスト時に、文字変換を行わないように指定set request.ReadRawMode=1do request.Send("POST",URL)

 // 受取ったデータを、UTF-8に変換してから、JSON変換set response = request.HttpResponse.Data
 set data = response.Read()
 set data2 = $zcvt(data,"I","UTF8")
 set response = {}.%FromJSON(data2)
 write response.%ToJSON()
0
0 85
記事 Toshihiko Minamoto · 3月 27, 2025 8m read

fastapi_logo

説明

これは、ネイティブウェブアプリケーションとして IRIS にデプロイできる FastAPI アプリケーションのテンプレートです。

インストール

  1. リポジトリをクローンする
  2. 仮想環境を作成する
  3. 要件をインストールする
  4. docker-compose ファイルを実行する
git clone
cd iris-fastapi-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

使用法

ベース URL は http://localhost:53795/fastapi/ です。

エンドポイント

  • /iris - IRISAPP ネームスペースに存在する上位 10 個のクラスを持つ JSON オブジェクトを返します。
  • /interop - IRIS の相互運用性フレームワークをテストするための ping エンドポイント。
  • /posts - Post オブジェクトの単純な CRUD エンドポイント。
  • /comments - Comment オブジェクトの単純な CRUD エンドポイント。

このテンプレートからの開発方法

WSGI 導入記事をご覧ください: wsgiサポートの概要

概要: セキュリティポータルで DEBUG フラグをトグルすると、開発作業の過程で変更内容がアプリケーションに反映されるようになります。

コードの説明

app.py

これは FastAPI アプリケーションのメインのファイルです。 FastAPI アプリケーションとルートが含まれます。

from fastapi import FastAPI, Request

import iris

from grongier.pex import Director

# import models
from models import Post, Comment, init_db
from sqlmodel import Session,select

app = FastAPI()

# create a database engine
url = "iris+emb://IRISAPP"
engine = init_db(url)
  • from fastapi import FastAPI, Request - FastAPI クラスト Request クラスをインポートします。
  • import iris - IRIS モジュールをインポートします。
  • from grongier.pex import Director: Flask アプリを IRIS 相互運用性フレームワークにバインドする Director クラスをインポートします。
  • from models import Post, Comment, init_db - モデルと init_db 関数をインポートします。
  • from sqlmodel import Session,select - Session クラスと sqlmodel モジュールの選択された関数をインポートします。
  • app = FastAPI() - FastAPI アプリケーションを作成します。
  • url = "iris+emb://IRISAPP" - IRIS ネームスペースの URL を定義します。
  • engine = init_db(url) - sqlmodel ORM のデータベースエンジンを作成します。

models.py

このファイルには、アプリケーションのモデルが含まれます。

from sqlmodel import Field, SQLModel, Relationship, create_engine

class Comment(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    post_id: int = Field(foreign_key="post.id")
    content: str
    post: "Post" = Relationship(back_populates="comments")

class Post(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    title: str
    content: str
    comments: list["Comment"] = Relationship(back_populates="post")

説明することは特にありません。外部キーとリレーションによる単なるモデルの定義です。

init_db 関数は、データベースエンジンの作成に使用されます。

def init_db(url):

    engine = create_engine(url)

    # create the tables
    SQLModel.metadata.drop_all(engine)
    SQLModel.metadata.create_all(engine)

    # initialize database with fake data
    from sqlmodel import Session

    with Session(engine) as session:
        # Create fake data
        post1 = Post(title='Post The First', content='Content for the first post')
        ...
        session.add(post1)
        ...
        session.commit()

    return engine
  • engine = create_engine(url) - データベースエンジンを作成します。
  • SQLModel.metadata.drop_all(engine) - すべてのテーブルをドロップします。
  • SQLModel.metadata.create_all(engine) - すべてのテーブルを作成します。
  • with Session(engine) as session: - データベースを操作するためのセッションを作成します。
  • post1 = Post(title='Post The First', content='Content for the first post') - Post オブジェクトを作成します。
  • session.add(post1) - Post オブジェクトをセッションに追加します。
  • session.commit() - 変更内容をデータベースにコミットします。
  • return engine - データベースエンジンを返します。

/iris エンドポイント

######################
# IRIS Query example #
######################

@app.get("/iris")
def iris_query():
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Convert the result to a list of dictionaries
    result = []
    for row in rs:
        result.append(row)
    return result
  • @app.get("/iris") - /iris エンドポイントの GET ルートを定義します。
  • query = "SELECT top 10 * FROM %Dictionary.ClassDefinition" - IRIS ネームスペースで上位 10 個のクラスを取得するクエリを定義します。
  • rs = iris.sql.exec(query) - クエリを実行します。
  • result = [] - 結果を保存する空のリストを作成します。
  • for row in rs: - 結果セットを反復処理します。
  • result.append(row) - 結果リストを行にアペンドします。
  • return result - 結果リストを返します。

/interop エンドポイント

########################
# IRIS interop example #
########################
bs = Director.create_python_business_service('BS')

@app.get("/interop")
@app.post("/interop")
@app.put("/interop")
@app.delete("/interop")
def interop(request: Request):
    
    rsp = bs.on_process_input(request)

    return rsp

  • bs = Director.create_python_business_service('BS') - Python ビジネスサービスを作成します。
    • ビジネスサービスの複数のインスタンスを防止するために、ルート定義の外に作成する必要があります。
  • @app.get("/interop") - /interop エンドポイントの GET ルートを定義します。
  • @app.post("/interop") - /interop エンドポイントの POST ルートを定義します。
  • ...
  • def interop(request: Request): - ルートハンドラーを定義します。
  • rsp = bs.on_process_input(request) - ビジネスサービスの on_process_input メソッドを呼び出します。
  • return rsp - レスポンスを返します。

/posts エンドポイント

############################
# CRUD operations posts    #
############################

@app.get("/posts")
def get_posts():
    with Session(engine) as session:
        posts = session.exec(select(Post)).all()
        return posts
    
@app.get("/posts/{post_id}")
def get_post(post_id: int):
    with Session(engine) as session:
        post = session.get(Post, post_id)
        return post
    
@app.post("/posts")
def create_post(post: Post):
    with Session(engine) as session:
        session.add(post)
        session.commit()
        return post

このエンドポイントは、Post オブジェクトで CRUD 操作を実行するために使用されます。

説明することは特にありません。すべての投稿を取得し、ID で投稿を取得し、投稿を作成するためのルートの定義です。

すべては sqlmodel ORM を使って行われます。

/comments エンドポイント

############################
# CRUD operations comments #
############################


@app.get("/comments")
def get_comments():
    with Session(engine) as session:
        comments = session.exec(select(Comment)).all()
        return comments
    
@app.get("/comments/{comment_id}")
def get_comment(comment_id: int):
    with Session(engine) as session:
        comment = session.get(Comment, comment_id)
        return comment
    
@app.post("/comments")
def create_comment(comment: Comment):
    with Session(engine) as session:
        session.add(comment)
        session.commit()
        return comment

このエンドポイントは、Comment オブジェクトで CRUD 操作を実行するために使用されます。

説明することは特にありません。すべてのコメントを取得し、ID でコメントを取得し、コメントを作成するためのルートの定義です。

すべては sqlmodel ORM を使って行われます。

トラブルシューティング

スタンドアロンモードで FastAPI アプリケーションを実行する方法

以下のコマンドを使用して、いつでもスタンドアロンの Flask アプリケーションを実行できます。

python3 /irisdev/app/community/app.py

注: このコマンドを実行するには、コンテナー内にいる必要があります。

docker exec -it iris-fastapi-template-iris-1 bash

IRIS でアプリケーションを再起動する

DEBUG モードでアプリケーションに複数の呼び出しを行うと、変更はアプリケーションに反映されます。

IRIS 管理ポータルへのアクセス方法

http://localhost:53795/csp/sys/UtilHome.csp に移動すると、IRIS 管理ポータルにアクセスできます。

このテンプレートをローカルで実行する

これには、マシンに IRIS がインストールされている必要があります。

次に、IRISAPP というネームスペースを作成する必要があります。

要件をインストールします。

IoP のインストール:

#init iop
iop --init

# load production
iop -m /irisdev/app/community/interop/settings.py

# start production
iop --start Python.Production

セキュリティポータルでアプリケーションを構成します。

0
0 75
記事 Mihoko Iijima · 10月 10, 2023 15m read

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

この記事では、複雑なJSON形式の文書を「JSONテンプレートエンジン」を利用して生成させる方法をご紹介します。

「JSONテンプレートエンジン」については、6月のウェビナーで使用例をご紹介しましたが、JSON生成対象として医療情報交換標準規格であるFHIRリソースのJSON(例:Patientリソース)を例に解説しています。

このエンジンは、JSON形式の文書であればどのような種類のデータでもご利用いただけますので、一般的なJSON形式の文書を利用して使い方をご紹介します。

例に使用するJSONはこちら👉 https://api.openbd.jp/v1/get?isbn=978-4-7808-0204-7&pretty

このサンプルから以下の部分を抜き出して、ObjectScriptでJSON形式の文書を組み立てていく方法をご紹介します。

7
0 1403
記事 Toshihiko Minamoto · 2月 27, 2025 7m read

Flask_logo

説明

これは、ネイティブウェブアプリケーションとして IRIS にデプロイできる Flask アプリケーションのテンプレートです。

インストール

  1. リポジトリをクローンする
  2. 仮想環境を作成する
  3. 要件をインストールする
  4. docker-compose ファイルを実行する
git clone
cd iris-flask-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

使用法

ベース URL は http://localhost:53795/flask/ です。

エンドポイント

  • /iris - IRISAPP ネームスペースに存在する上位 10 個のクラスを持つ JSON オブジェクトを返します。
  • /interop - IRIS の相互運用性フレームワークをテストするための ping エンドポイント。
  • /posts - Post オブジェクトの単純な CRUD エンドポイント。
  • /comments - Comment オブジェクトの単純な CRUD エンドポイント。

このテンプレートからの開発方法

WSGI 導入記事をご覧ください: wsgi-introduction

概要: セキュリティポータルで DEBUG フラグをトグルすると、開発作業の過程で変更内容がアプリケーションに反映されるようになります。

コードの説明

app.py

これはアプリケーションのメインのファイルです。 Flask アプリケーションとエンドポイントが含まれます。

from flask import Flask, jsonify, request
from models import Comment, Post, init_db

from grongier.pex import Director

import iris

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'iris+emb://IRISAPP'

db = init_db(app)
  • from flask import Flask, jsonify, request: Flask ライブラリをインポートします。
  • from models import Comment, Post, init_db: モデルとデータベース初期化関数をインポートします。
  • from grongier.pex import Director: Flask アプリを IRIS 相互運用性フレームワークにバインドする Director クラスをインポートします。
  • import iris: IRIS ライブラリをインポートします。
  • app = Flask(__name__): Flask アプリケーションを作成します。
  • app.config['SQLALCHEMY_DATABASE_URI'] = 'iris+emb://IRISAPP': データベース URI を IRISAPP ネームスペースに設定します。
    • iris+emb URI スキームは、埋め込み接続として IRIS に接続するために使用されます(別の IRIS インスタンスの必要はありません)。
  • db = init_db(app): Flask アプリケーションでデータベースを初期化します。

models.py

このファイルには、アプリケーションの SQLAlchemy モデルが含まれます。

from dataclasses import dataclass
from typing import List
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

@dataclass
class Comment(db.Model):
    id:int = db.Column(db.Integer, primary_key=True)
    content:str = db.Column(db.Text)
    post_id:int = db.Column(db.Integer, db.ForeignKey('post.id'))

@dataclass
class Post(db.Model):
    __allow_unmapped__ = True
    id:int = db.Column(db.Integer, primary_key=True)
    title:str = db.Column(db.String(100))
    content:str = db.Column(db.Text)
    comments:List[Comment] = db.relationship('Comment', backref='post')

説明することは特にありません。モデルはデータクラスとして定義されており、db.Model クラスのサブクラスです。

__allow_unmapped__ 属性は、comments 属性を使用せずに Post オブジェクトを作成できるようにするために使用する必要があります。

dataclasses はオブジェクトを JSON にシリアル化するのに使用されます。

init_db 関数は、Flask アプリケーションでデータベースを初期化します。

def init_db(app):
    db.init_app(app)

    with app.app_context():
        db.drop_all()
        db.create_all()
        # Create fake data
        post1 = Post(title='Post The First', content='Content for the first post')
        ...
        db.session.add(post1)
        ...
        db.session.commit()
    return db
  • db.init_app(app): Flask アプリケーションでデータベースを初期化します。
  • with app.app_context(): アプリケーションのコンテキストを作成します。
  • db.drop_all(): データベースのすべてのテーブルをドロップします。
  • db.create_all(): データベースのすべてのテーブルを作成します。
  • アプリケーションの偽データを作成します。
  • データベースオブジェクトを返します。

/iris エンドポイント

######################
# IRIS クエリ例 #
######################

@app.route('/iris', methods=['GET'])
def iris_query():
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Convert the result to a list of dictionaries
    result = []
    for row in rs:
        result.append(row)
    return jsonify(result)

このエンドポイントは、IRIS データベースでクエリを実行し、IRISAPP ネームスペースに存在する上位 10 個のクラスを返します。

/interop エンドポイント

########################
# IRIS interop example #
########################
bs = Director.create_python_business_service('BS')

@app.route('/interop', methods=['GET', 'POST', 'PUT', 'DELETE'])
def interop():
    
    rsp = bs.on_process_input(request)

    return jsonify(rsp)

このエンドポイントは、IRIS の相互運用性フレームワークをテストするために使用されます。 ビジネスサービスオブジェクトを作成し、それを Flask アプリケーションにバインドします。

注: bs オブジェクトは有効な状態を維持するために、リクエストの範囲外にある必要があります。

  • bs = Director.create_python_business_service('BS'): 'BS' というビジネスサービスオブジェクトを作成します。
  • rsp = bs.on_process_input(request): リクエストオブジェクトを引数としてビジネスサービスオブジェクトの on_process_input メソッドを呼び出します。

/posts エンドポイント

############################
# CRUD operations posts    #
############################

@app.route('/posts', methods=['GET'])
def get_posts():
    posts = Post.query.all()
    return jsonify(posts)

@app.route('/posts', methods=['POST'])
def create_post():
    data = request.get_json()
    post = Post(title=data['title'], content=data['content'])
    db.session.add(post)
    db.session.commit()
    return jsonify(post)

@app.route('/posts/<int:id>', methods=['GET'])
def get_post(id):
    ...

このエンドポイントは、Post オブジェクトで CRUD 操作を実行するために使用されます。

dataclasses モジュールにより、Post オブジェクトは簡単に JSON にシリアル化できます。

以下では、すべての投稿を取得する sqlalchemy の query メソッドと、新しい投稿を作成するための addcommit メソッドを使用しています。

/comments エンドポイント

############################
# CRUD operations comments #
############################

@app.route('/comments', methods=['GET'])
def get_comments():
    comments = Comment.query.all()
    return jsonify(comments)

@app.route('/comments', methods=['POST'])
def create_comment():
    data = request.get_json()
    comment = Comment(content=data['content'], post_id=data['post_id'])
    db.session.add(comment)
    db.session.commit()
    return jsonify(comment)

@app.route('/comments/<int:id>', methods=['GET'])
def get_comment(id):
    ...

このエンドポイントは、Comment オブジェクトで CRUD 操作を実行するために使用されます。

Comment オブジェクトは外部キーによって Post オブジェクトにリンクされます。

トラブルシューティング

スタンドアロンモードで Flask アプリケーションを実行する方法

以下のコマンドを使用して、いつでもスタンドアロンの Flask アプリケーションを実行できます。

python3 /irisdev/app/community/app.py

注: このコマンドを実行するには、コンテナー内にいる必要があります。

docker exec -it iris-flask-template-iris-1 bash

IRIS でアプリケーションを再起動する

DEBUG モードでアプリケーションに複数の呼び出しを行うと、変更はアプリケーションに反映されます。

IRIS 管理ポータルへのアクセス方法

http://localhost:53795/csp/sys/UtilHome.csp に移動すると、IRIS 管理ポータルにアクセスできます。

このテンプレートをローカルで実行する

これには、マシンに IRIS がインストールされている必要があります。

次に、IRISAPP というネームスペースを作成する必要があります。

要件をインストールします。

IoP のインストール:

#init iop
iop --init

# load production
iop -m /irisdev/app/community/interop/settings.py

# start production
iop --start Python.Production

セキュリティポータルでアプリケーションを構成します。

0
0 69
記事 Mihoko Iijima · 1月 6, 2025 2m read

これは InterSystems FAQ サイトの記事です。

POST要求で受信したBodyのJSON文字列を、REST ディスパッチクラス内メソッドでダイナミックオブジェクト(%DyamicObject)に変換する際、以下エラーが発生する場合があります。

{
    "errors": [
        {
            "code": 5035,
            "domain": "%ObjectErrors",
            "error": "エラー #5035: 一般例外 名前 'Premature end of data' コード '12' データ ''",
            "id": "GeneralException",
            "params": [
                "Premature end of data",
                12,
                ""
            ]
        }
    ],
    "summary": "エラー #5035: 一般例外 名前 &#39;Premature end of data&#39; コード &#39;12&#39; データ &#39;&#39;"
}
0
0 129
記事 So Ochi · 10月 13, 2024 10m read

はじめに

生成AIを活用したアプリケーション開発は、Python、JavaScriptなどのメジャー言語による体験記事がよく見られます。一方、IRISのObjectScriptの開発に言及された記事は比較的少ないのが現状です。そこで、本記事では生成AIがObjectScriptの開発にどこまで活用できるのかを検証しました。

特にDevOpsのプロセスにおいて、生成AIは様々なシーンでの活用が期待できます。今回は開発工程に注目し、以下の観点から生成AIの有効性を調査しました。

  • 開発
    • コードの自動生成
    • 環境構築のアシスタント(テーブルの作成)
  • 検証
    • テストデータ生成のサポート

環境

本記事の検証は以下の環境で行いました。

開発環境

  • OS: macOS Sonoma
  • IRIS: 2023.4 (linux)

開発ツール IRISの開発にはStudioやVSCodeなどが利用可能ですが、今回は生成AIの活用に特化したエディタ「Cursor」を使用しました。

Cursorを選定した理由 Cursorは、生成AIによる支援機能に特化したコードエディタで、以下の特徴があります:

  • 生成AIの支援:コードの自動生成や提案、バグの検出、修正提案を行います。また、外部のドキュメントや複数のソースを指定し、生成内容に反映させる簡易なRAG機能も搭載されています。

  • VSCodeとの互換性:VSCodeをフォークして作られており、VSCodeユーザーはスムーズに移行できます。拡張機能もそのまま利用可能です。

IRISではv2024よりStudioは廃止となり、VSCodeが標準ツールとなります。そこで、VSCodeと互換性があり、生成AIとの親和性が高いCursorを選定しました。

選択した生成AIモデル GPT-4を使用しましたが、Claudeでも検証を行ったところ、どのモデルも大差ない結果となりました。 利用しやすいモデルを選んで試してみてください。

検証内容

今回は以下の内容を検証しました。

  • 簡単なプログラムの生成とターミナルでの実行
  • IRISのREST通信機能を使った商品情報検索APIの作成

サンプルプログラムの作成

まず、ユーザーに名前を入力させ、挨拶を返す簡単なルーチンを生成します。CursorでCommand+Lを押してチャットウィンドウを開き、以下のプロンプトを入力します。

あなたはObjectScriptの開発者です。
{仕様}をもとにコードを作成してください。
#仕様
- 以下の処理を実行するRoutineを作成する。
- macファイル名は"TEST.mac"とする。
1. ユーザーに名前の入力を促します。
2. 入力された名前が空であればエラーメッセージを表示します。
3. 名前が入力された場合、その名前を使って挨拶メッセージを表示します。

image

生成されたルーチンをTEST.macにコピーし、ターミナルで実行して動作を確認します。

image

REST APIの作成

簡易なプログラムの生成が確認できたので、次はより複雑なアプリケーションを作成してみます。

アプリケーションの仕様 IRISのREST通信機能を利用したAPIを作成します。ユーザーからのリクエストに基づき、サーバー上のデータベースから該当する商品情報をJSON形式で返します。

テーブルの作成 まずは、テーブルを作成しましょう。DDLの作成をAIに依頼します。

InterSystemsのIRISで{テーブル}を作成するためのDDLを出力してください
#テーブル
##名前
- Sample.Product
##列
- JanCd VARCHAR(13)
- ProductName VARCHAR(100)
- Price INT
## プライマリキー
- JanCd

image

生成されたDDLをターミナルから実行し、テーブルが正常に作成されたことを確認します。

image

テストデータの作成 テストデータの作成もAIにアシストしてもらいましょう。

image

テストデータが正常に登録されたことを確認します。

image

APIの作成 準備が整ったので、APIクラスを生成します。以下のプロンプトを使ってコードを作成します。

あなたはObjectScriptの開発者です。
{仕様}をもとに"Api.Product"クラスを作成してください。
#仕様
- REST通信でJSON形式で商品情報を返す。
- apiのURLは/products/:jancd
- %CSP.RESTクラスを継承する。
- Jsonデータの生成は%DynamicObjectクラスを利用する。
- レスポンスコードは、%responseを利用する。
1. jancdが指定されなかった場合、404を返す。指定された場合、以降の処理を続行する。 
2. "Sample.Product"テーブルをSQLで検索する。
- 取得項目: JanCd,ProductName,Price
- 検索条件: :jancdが指定された場合、テーブルをJanCdが一致するレコードを検索する。
3. 検索結果をJSON形式でREST通信結果として出力する。

image

生成されたコードを確認し、必要に応じて修正を加えます。クエリ結果の取得箇所で%Next()が実行されていなかったため、以下の修正を行いました。

Set tResult = tStatement.%Execute(jancd)
// -*-*-*-*- 処理を追加しました -*-*-*-*-
Do tResult.%Next()

If tResult.%SQLCODE = 100 {

curlコマンドを使用してAPIの動作を確認し、正常にJSONデータが返却されることを確認します。

image

最終的なソースは以下の通りです。今回は一部修正が必要でしたが、基本的な部分は生成AIが正しく対応していました。

  Class Api.Product Extends %CSP.REST
  {

  /// URLマッピング
  XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
  {
  <Routes>
      <Route Url="/products/:jancd" Method="GET" Call="GetProductInfo"/>
  </Routes>
  }

  /// 商品情報を取得するメソッド
  ClassMethod GetProductInfo(jancd As %String) As %Status
  {
      // jancdが指定されていない場合、404を返す
      If jancd = "" {
          Set %response.Status = 404
          Quit $$$OK
      }

      // "Sample.Product"テーブルをSQLで検索する
      Set tStatement = ##class(%SQL.Statement).%New()
      Set tSQL = "SELECT JanCd, ProductName, Price FROM Sample.Product WHERE JanCd = ?"
      Set tStatus = tStatement.%Prepare(tSQL)
      
      If $$$ISERR(tStatus) {
          Set %response.Status = 500
          Quit tStatus
      }
      
      Set tResult = tStatement.%Execute(jancd)
      Do tResult.%Next()
      
      // 検索結果がない場合、404を返す
      If tResult.%SQLCODE = 100 {
          Set %response.Status = 404
          Quit $$$OK
      }
      
      // 検索結果をJSON形式で出力する
      Set tProduct = ##class(%DynamicObject).%New()
      Do tProduct.%Set("JanCd", tResult.%Get("JanCd"))
      Do tProduct.%Set("ProductName", tResult.%Get("ProductName"))
      Do tProduct.%Set("Price", tResult.%Get("Price"))
      
      Write tProduct.%ToJSON(),!
      
      Quit $$$OK
  }

  }

既存コードの活用

次に、生成したAPIに商品名を部分一致で検索するメソッドを追加します。Cursorの外部ファイル参照機能を使い、先ほど生成したクラスを参考にしながら新しいメソッドを作成します。プロンプトは以下の通りです。

@Api.Product.cls 
以下の{仕様}で、"Api.Product"クラスにメソッドを追加してください。
#仕様
- GetProductInfo()の処理を参考にすること。
- apiのURLは/products/name/:productname
- Sample.ProductテーブルからAPIで指定された値が、ProductNameと部分一致するレコードを検索し、検索した結果をJson形式で返す。
- 検索結果は配列にして返す。

image

生成したコードは以下になります。(追加部分のみ抜粋)

  Class Api.Product Extends %CSP.REST
  {
      // ... 既存のコード ...

      /// URLマッピング
      XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
      {
      <Routes>
          // ... 既存のルート ...
          <Route Url="/products/name/:productname" Method="GET" Call="GetProductInfoByName"/>
      </Routes>
      }

      // ... 既存のコード ...

      /// 商品名で商品情報を取得するメソッド
      ClassMethod GetProductInfoByName(productname As %String) As %Status
      {
          // productnameが指定されていない場合、404を返す
          If productname = "" {
              Set %response.Status = 404
              Quit $$$OK
          }

          // "Sample.Product"テーブルをSQLで検索する
          Set tStatement = ##class(%SQL.Statement).%New()
          Set tSQL = "SELECT JanCd, ProductName, Price FROM Sample.Product WHERE ProductName LIKE ?"
          Set tStatus = tStatement.%Prepare(tSQL)
          
          If $$$ISERR(tStatus) {
              Set %response.Status = 500
              Quit tStatus
          }
          
          Set tResult = tStatement.%Execute("%" _ productname _ "%")
          
          // 検索結果がない場合、404を返す
          If tResult.%SQLCODE = 100 {
              Set %response.Status = 404
              Quit $$$OK
          }
          
          // 検索結果をJSON形式で出力する
          Set tProducts = ##class(%DynamicArray).%New()
          While tResult.%Next() {
              Set tProduct = ##class(%DynamicObject).%New()
              Do tProduct.%Set("JanCd", tResult.%Get("JanCd"))
              Do tProduct.%Set("ProductName", tResult.%Get("ProductName"))
              Do tProduct.%Set("Price", tResult.%Get("Price"))
              Do tProducts.%Push(tProduct)
          }
          
          Write tProducts.%ToJSON(),!
          
          Quit $$$OK
      }

      // ... 既存のコード ...
  }

生成されたコードを確認し、curlコマンドで動作を確認します。正常に実行されました。外部ファイル参照機能の利用により、自動生成の精度が向上していることがうかがえます。 image

開発上の課題と解決策

自動生成の精度向上 基本コマンドを用いたプログラムは高い精度を発揮しますが、クラスを利用した複雑な処理では精度が低下する印象を受けました。プロンプトの指示を工夫することで、精度を高めることができそうです。 また、Cursorの外部ドキュメント、ファイル参照機能には大きな可能性を感じました。この機能を使えば、既存のリソースや、AIが学習していないライブラリの活用が期待できます。

セキュリティとプライバシー Cursorはプライバシーモードを備えており、データをサーバーに保持しない設定が可能です。しかし、Cursorに限らず生成AIの業務利用には慎重な調査が必要です。

所感

今回の検証を通じて、生成AIのコード生成能力が向上していることを実感しました。特に、テストデータやDDLの生成は、開発の効率を大幅に向上させる可能性があります。アジャイル開発で迅速なモックアップの作成が求められる場面では、生成AIの効果的な活用が期待できそうです。一方で、既存システムの小規模な改修には、効果が限定的であるという印象を受けました。

この記事を作成したきっかけは、ObjectScriptの初学者向け演習問題を生成AIで作成した際、その問題と解答の品質が非常に高かったことです。業務での活用も十分可能であると思い、今回の検証を行いました。生成AIは、工夫次第でさらなる幅広い活用が期待できるツールだと感じています。

参考資料

0
0 249
記事 Tomoko Furuzono · 6月 6, 2024 2m read

これは、InterSystems FAQサイトの記事です。

IRIS/IRIS for Health2024.1以降のバージョンのSQLで、JSON_TABLE関数がサポートされています。
【ドキュメント】JSON_TABLE(SQL)

これを使用することにより、JSON形式データを表形式で取得することが出来ます。
【例】郵便番号情報を外部から取得し、表形式にマッピングする。
(取得データ(JSON))

{
	"message": null,
	"results": [
		{
			"address1": "東京都",
			"address2": "新宿区",
			"address3": "西新宿",
			"kana1": "トウキョウト",
			"kana2": "シンジュクク",
			"kana3": "ニシシンジュク",
			"prefcode": "13",
			"zipcode": "1600023"
		}
	],
	"status": 200
}

(SQL例)
 ※下記の実行には、SSL構成が必要になります。事前に構成を作成して、その構成名を使用してください。

0
0 122
記事 Toshihiko Minamoto · 4月 18, 2024 13m read

我々には、Redditユーザーが書いた、おいしいレシピデータセット がありますが, 情報のほとんどは投稿されたタイトルや説明といったフリーテキストです。埋め込みPythonLangchainフレームワークにあるOpenAIの大規模言語モデルの機能を使い、このデータセットを簡単にロードし、特徴を抽出、分析する方法を紹介しましょう。

データセットのロード

まず最初に、データセットをロードするかデータセットに接続する必要があります。

これを実現するにはさまざまな方法があります。たとえばCSVレコードマッパーを相互運用性プロダクションで使用したり csvgenのようなすばらしい OpenExchange アプリケーションを使用することもできます。

今回、外部テーブルを使用します。これは物理的に別の場所に保存されているデータをIRIS SQLで統合する非常に便利な機能です。

まずは外部サーバ(Foreign Server)を作成します。

CREATE FOREIGN SERVER dataset FOREIGN DATA WRAPPER CSV HOST '/app/data/'

その上でCSVファイルに接続する外部テーブルを作成します。

CREATE FOREIGN TABLE dataset.Recipes (
  CREATEDDATE DATE,
  NUMCOMMENTS INTEGER,
  TITLE VARCHAR,
  USERNAME VARCHAR,
  COMMENT VARCHAR,
  NUMCHAR INTEGER
) SERVER dataset FILE 'Recipes.csv' USING
{
  "from": {
    "file": {
       "skip": 1
    }
  }
}

以上です。すぐに「dataset.Recipes」にSQLクエリを実行できます。 image

## どんなデータが必要?

データセットは興味深く、直ぐに処理したいと思うのですが、調理のレシピを決めたいのであれば、分析に使える情報がもう少し必要です。 2つの永続化クラス(テーブル)を使用します。

  • yummy.data.Recipe抽出分析したいレシピのタイトルと説明、他のプロパティが入ったクラス (例: スコア、難易度、材料、調理タイプ、準備時間)
  • yummy.data.RecipeHistory レシピのログを取るためのシンプルなクラス

これで 「yummy.data*」 テーブルにデータセットの内容をロードすることができます。

do ##class(yummy.Utils).LoadDataset()

一見良さそうに見えますが、スコア、難易度、材料、準備時間、調理時間フィールドのデータをどのように生成するのかを見つける必要があります。

## レシピの分析 各レシピのタイトルと説明を処理します

  • 難易度, 材料, 調理タイプなどの抽出
  • 何を作りたいか決められるよう、基準に基づいて独自のスコアを構築

以下を使用します

  • より多くの分析を構築したい場合に再利用できる一般的な分析構造

LLM(大規模言語モデル)は自然言語を処理するための本当に素晴らしいツールです。

LangChainはPythonで動くようになっているので、Embedded Pythonを使ってInterSystems IRISで直接使うことができます。 LangChain is ready to work in Python, so we can use it directly in InterSystems IRIS using Embedded Python.

完全な SimpleOpenAI クラスは以下のようになります。

/// レシピ向けのシンプルな OpenAI 分析
Class yummy.analysis.SimpleOpenAI Extends Analysis
{

Property CuisineType As %String;

Property PreparationTime As %Integer;

Property Difficulty As %String;

Property Ingredients As %String;

/// 実行
/// ターミナルから実行できます。
/// set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8))
/// do a.Run()
/// zwrite a
Method Run()
{
    try {
        do ..RunPythonAnalysis()

        set reasons = ""

        // 好きな調理タイプ
        if "spanish,french,portuguese,italian,korean,japanese"[..CuisineType {
            set ..Score = ..Score + 2
            set reasons = reasons_$lb("It seems to be a "_..CuisineType_" recipe!")
        }

        // 丸一日調理に費やしたくない :)
        if (+..PreparationTime < 120) {
            set ..Score = ..Score + 1
            set reasons = reasons_$lb("You don't need too much time to prepare it") 
        }
        
        // 好きな材料ボーナス
        set favIngredients = $listbuild("kimchi", "truffle", "squid")
        for i=1:1:$listlength(favIngredients) {
            set favIngred = $listget(favIngredients, i)
            if ..Ingredients[favIngred {
                set ..Score = ..Score + 1
                set reasons = reasons_$lb("Favourite ingredient found: "_favIngred)
            }
        }

        set ..Reason = $listtostring(reasons, ". ")

    } catch ex {
        throw ex
    }
}

/// 分析結果でレシピを更新する
Method UpdateRecipe()
{
    try {
        // 親クラスの処理を先に呼び出す
        do ##super()

        // 個別のOpenAI 解析結果を追加
        set ..Recipe.Ingredients = ..Ingredients
        set ..Recipe.PreparationTime = ..PreparationTime
        set ..Recipe.Difficulty = ..Difficulty
        set ..Recipe.CuisineType = ..CuisineType

    } catch ex {
        throw ex
    }
}

/// 埋め込み Python + Langchain で分析を実行
/// do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8)).RunPythonAnalysis(1)
Method RunPythonAnalysis(debug As %Boolean = 0) [ Language = python ]
{
    # load OpenAI APIKEY from env
    import os
    from dotenv import load_dotenv, find_dotenv
    _ = load_dotenv('/app/.env')

    # account for deprecation of LLM model
    import datetime
    current_date = datetime.datetime.now().date()
    # date after which the model should be set to "gpt-3.5-turbo"
    target_date = datetime.date(2024, 6, 12)
    # set the model depending on the current date
    if current_date > target_date:
        llm_model = "gpt-3.5-turbo"
    else:
        llm_model = "gpt-3.5-turbo-0301"

    from langchain.chat_models import ChatOpenAI
    from langchain.prompts import ChatPromptTemplate
    from langchain.chains import LLMChain

    from langchain.output_parsers import ResponseSchema
    from langchain.output_parsers import StructuredOutputParser

    # init llm model
    llm = ChatOpenAI(temperature=0.0, model=llm_model)

    # prepare the responses we need
    cuisine_type_schema = ResponseSchema(
        name="cuisine_type",
        description="What is the cuisine type for the recipe? \
                     Answer in 1 word max in lowercase"
    )
    preparation_time_schema = ResponseSchema(
        name="preparation_time",
        description="How much time in minutes do I need to prepare the recipe?\
                     Anwer with an integer number, or null if unknown",
        type="integer",
    )
    difficulty_schema = ResponseSchema(
        name="difficulty",
        description="How difficult is this recipe?\
                     Answer with one of these values: easy, normal, hard, very-hard"
    )
    ingredients_schema = ResponseSchema(
        name="ingredients",
        description="Give me a comma separated list of ingredients in lowercase or empty if unknown"
    )
    response_schemas = [cuisine_type_schema, preparation_time_schema, difficulty_schema, ingredients_schema]

    # get format instructions from responses
    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
    format_instructions = output_parser.get_format_instructions()
    
    analysis_template = """\
    Interprete and evaluate a recipe which title is: {title}
    and the description is: {description}
    
    {format_instructions}
    """
    prompt = ChatPromptTemplate.from_template(template=analysis_template)

    messages = prompt.format_messages(title=self.Recipe.Title, description=self.Recipe.Description, format_instructions=format_instructions)
    response = llm(messages)

    if debug:
        print("======ACTUAL PROMPT")
        print(messages[0].content)
        print("======RESPONSE")
        print(response.content)

    # populate analysis with results
    output_dict = output_parser.parse(response.content)
    self.CuisineType = output_dict['cuisine_type']
    self.Difficulty = output_dict['difficulty']
    self.Ingredients = output_dict['ingredients']
    if type(output_dict['preparation_time']) == int:
        self.PreparationTime = output_dict['preparation_time']

    return 1
}

}

「RunPythonAnalysis」メソッドがOpenAIが詰め込むところです :)  ターミナルから直接実行してレシピを受け取れます。

do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)

以下のような出力を受け取れます。

USER>do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)
======実際の課題
                    レシピタイトルを解釈、評価: 巻きずし - アラスカロール
                    説明: 寿司がたべたいのに巻きすがない? 代わりに簡単なバージョンを試してみてください。超簡単なのに、同じようにおいしい!
[Video Recipe](https://www.youtube.com/watch?v=1LJPS1lOHSM)
# 材料
提供量:  \~サンドイッチ5枚分
* 米1カップ
* 水 3/4 カップ + 大さじ 2 1/2
* 昆布 小口切り 1枚
* 米酢 大さじ2
* さとう 大さじ1
* 塩 小さじ1
* アボカド 2個
* カニカマ 6個
* 和風マヨ 大さじ2
* サーモン 1/2 ポンド  
# レシピ     
* 酢飯1合をボウルに入れ、2回以上、または水が透明になるまで米を洗う。炊飯器に米を移し、昆布の小口切り1枚と水3/4カップ+大さじ2と1/2杯を加える。炊飯器の指示に従って炊く。
* 米酢大さじ2、砂糖大さじ1、塩小さじ1を中くらいのボウルに入れる。全体がよく混ざるまで混ぜる。
* 炊き上がったら昆布を取り除き、すぐに酢を入れた中ボウルに米をすべてすくい入れ、飯ベラを使ってよく混ぜる。米をつぶさないように、切るように混ぜること。炊きあがったら、キッチンタオルをかけて室温まで冷ます。
* アボカド1個の上部を切り、アボカドの中央に切り込みを入れ、ナイフに沿って回転させる。次にアボカドを半分ずつ取り、ひねる。その後、ピットのある側を取り、慎重にピットに切り込みを入れ、ひねって取り除く。その後、手で皮をむく。この手順をもう片方のアボカドでも繰り返す。作業スペースを確保するため、作業台を片付けるのを忘れずに。次に、アボカドを下向きに置き、薄くスライスする。スライスしたら、ゆっくりと広げていく。それが終わったら、脇に置いておく。
* カニカマから包みをはずす。カニカマを縦にむいていく。すべてのカニカマを剥いたら、横に回転させながら細かく刻み、和風マヨ(大さじ2)とともにボウルに入れ、全体がよく混ざるまで混ぜる。
* 鋭利なナイフを斜めに入れ、木目に逆らって薄くスライスする。切り口の厚さは好みによる。ただ、すべてのピースが同じような厚さになるようにする。
* 海苔巻きラップを手に取る。キッチンバサミを使い、海苔巻きラップの半分の位置から切り始め、ラップの中心を少し過ぎるまで切る。ラップを垂直に回転させ、作り始める。すし飯を握るために、手に水をつけておく。酢飯を手に取り、海苔巻きの左上の四辺に広げる。次に、右上にサーモンを2切れ並べる。右下にアボカドを2切れのせる。最後に左下にカニサラダを小さじ2杯ほどのせる。次に、右上の四つ角を右下の四つ角に折り込み、さらに左下の四つ角に折り込む。最後に、左上の四つ角をサンドイッチの残りの部分に折り込む。その後、ラップを上に置き、半分に切って、生姜とわさびを2、3枚添えれば出来上がり。
                    
                    出力は、先頭と末尾の"``json "と"``"を含む、以下のスキーマでフォーマットされたマークダウンのコードスニペットでなければなりません:
json
{
        "cuisine_type": string  // レシピの調理タイプは?                                  小文字の1単語で回答
        "preparation_time": integer  // レシピの準備に必要な時間(分)は? 整数で回答(不明な場合はnull)
        "difficulty": string  // レシピの難易度は?                               「容易」「標準」「難しい」「とても難しい」のうちから1つを回答
        "ingredients": string  // 小文字のカンマ区切りの材料リスト、不明な場合は空
}

                    
======応答
json
{
        "cuisine_type": "japanese",
        "preparation_time": 30,
        "difficulty": "easy",
        "ingredients": "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
}

良さそうです。OpenAIのプロンプトは有用な情報を返してくれるようです。ターミナルから分析クラス全体を実行してみましょう:

set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12))
do a.Run()
zwrite a
USER>zwrite a
a=37@yummy.analysis.SimpleOpenAI  ; <OREF>
+----------------- general information ---------------
|      oref value: 37
|      class name: yummy.analysis.SimpleOpenAI
| reference count: 2
+----------------- attribute values ------------------
|        CuisineType = "japanese"
|         Difficulty = "easy"
|        Ingredients = "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
|    PreparationTime = 30
|             Reason = "It seems to be a japanese recipe!. You don't need too much time to prepare it"
|              Score = 3
+----------------- swizzled references ---------------
|           i%Recipe = ""
|           r%Recipe = "30@yummy.data.Recipe"
+-----------------------------------------------------

## 全レシピを解析する!

当然、読み込んだすべてのレシピで分析を実行したいでしょう。 この方法でレシピ ID の範囲を分析することができます

USER>do ##class(yummy.Utils).AnalyzeRange(1,10)
> Recipe 1 (1.755185s)
> Recipe 2 (2.559526s)
> Recipe 3 (1.556895s)
> Recipe 4 (1.720246s)
> Recipe 5 (1.689123s)
> Recipe 6 (2.404745s)
> Recipe 7 (1.538208s)
> Recipe 8 (1.33001s)
> Recipe 9 (1.49972s)
> Recipe 10 (1.425612s)

その後、レシピテーブルを再度表示させ、結果をチェックします。

select * from yummy_data.Recipe

image

どんぐりカボチャのピザか、豚肉入り韓国風豆腐キムチを試してみます:) いずれにせよ、家で再確認する必要がありますね :)

最後に

サンプルソースは全て https://github.com/isc-afuentes/recipe-inspector にあります。

この簡単な例で、InterSystems IRIS で LLM テクニックを使用して機能を追加したり、データの一部を分析する方法を学びました。

これを起点に以下のことが考えられます

  • InterSystems BIを使い、キューブやダッシュボードでデータの検索やナビゲートをおこなう。
  • Webアプリを作成し、UIを提供する(例:Angular)RESTForms2のようなパッケージを活用することで、永続クラスへのREST APIを自動的に生成することができます。 *レシピが好きか嫌いかを保存し、新しいレシピが好きかどうかを判断するのはいかがでしょうか。IntegratedMLアプローチ、あるいはLLMアプローチでいくつかの例データを提供し、RAG(Retrieval Augmented Generation)ユースケースを構築してみるのも良いでしょう。

他にどんなことが試せそうでしょうか?ご意見をお聞かせください!

0
0 458
記事 Toshihiko Minamoto · 3月 28, 2024 12m read

ローコードへの挑戦

こんな状況を思い浮かべてください。「ウィジェットダイレクト」というウィジェットとウィジェットアクセサリーを販売する一流のネットショップで楽しく勤務しています。先日、上司から一部の顧客がウィジェット商品にあまり満足していないという残念な話を聞き、苦情を追跡するヘルプデスクアプリケーションが必要となりました。さらに面白いことに、上司はコードのフットプリントを最小限に抑えることを希望しており、InterSystems IRIS を使って 150 行未満のコードでアプリケーションを提供するという課題をあなたに与えました。これは実際に可能なのでしょうか?

免責事項: この記事は、非常に基本的なアプリケーションの構築を記すものであり、簡潔さを維持するために、セキュリティやエラー処理などの重要な部分は省略されています。このアプリケーションは参考としてのみ使用し、本番アプリケーションには使用しないようにしてください。この記事ではデータプラットフォームとして IRIS 2023.1 を使用していますが、それ以前のバージョンでは記載されているすべての機能が提供されているとは限りません。

ステップ 1 - データモデルの定義

クリーンなネームスペースを新規に定義することから始めましょう。CODE と DATA データベースを使用します。 すべてを 1 つのデータベースで賄うことはできますが、分割してデータのリフレッシュを実行できるようにすると便利です。

このヘルプデスクシステムには 3 つの基本クラスが必要です。スタッフアドバイザーユーザーアカウント(UserAccount) と顧客連絡先ユーザーアカウント(UserAccount)間のやり取りを文書化するアクション(Actions)を含められる Ticket オブジェクトです。

19 行のコードで完全なデータモデルができました!2 つのクラスはデータベースに保存できるように Persistent(永続)に設定し、%JSON.Adapter を継承しています。この継承によって、JSON フォーマットでのオブジェクトのインポートとエクスポートを非常に簡単に行うことができます。テストとして、最初のユーザーをターミナルにセットアップし、保存してから JSONExport が正しく動作することを確認します。

すべてうまくいったようです。上司から、スタッフと顧客のリストが含まれる csv ファイルを渡されました。これを解析して読み込むコードを書くことは可能ですが、簡単に行う方法はないものでしょうか?

ステップ 2 - データの読み込み

InterSystems IRIS には、CSV ファイルから簡単にデータを読み込み、ヘッダーの解析やフィールド名の変更のオプションも使用できる SQL のLOAD DATA ステートメントが備わっています。使い方も単純です。では、それを使用してユーザーテーブルを読み込んでみましょう。

ヘッダーラベルを使ってこのデータを抽出し、データベースに読み込みます。

1 つのコマンドで全 300 行のデータがインポートされました。この 4 行のコードを加えると、合計 23 行のコードが記述したことになります。これらのレコードが正しいことを、基本的な SQL の SELECT を使って素早く確認してみましょう。

最初のデータを得られました。では、フロントエンドを接続できる基本的な API を作成しましょう。この API は、JSON を送受信する REST サービスとして作成します。

ステップ 3 - REST API の作成

InterSystems IRIS には、%CSP.REST クラスの継承を通じてネイティブの RESTサポートが備わっています。そこで、REST.Dispatch クラスを作成して %CSP.REST を継承することにします。このクラスは、URL と動詞をメソッドにマッピングする XData UrlMap と、これらの URL から呼び出されるメソッドの 2 つのセクションで構成されます。

ここで作成する実用最低限のツールには、スタッフまたは顧客のユーザーリストの取得、発行された最近のチケットの取得、ID 番号による単一のチケットの取得、および新しいチケットの作成の 4 つの演算が必要です。ここで使用する動詞を定義してからメソッドを定義します。

GetUserList は、直接 JSON にデータを出力する基本的な埋め込み SQL カーソルです。その後で、ネイティブの JSON 機能を使ってこれを解析し、JSON 配列にプッシュして、レスポンス本文として送信することができます。URL から直接クエリにスタッフ変数を渡して、データのコンテキストを変更します。

TicketSummary はほぼ同じですが、クエリは TICKET テーブルにアクセスします。

TicketSummary は最もシンプルなサービスです。ID でオブジェクトを開き、組み込みの %JSONExport を出力に書き込みます。オブジェクトが読み込みに失敗する場合、エラーパケットを出力します。

最後に、UploadTicket は最も複雑なメソッドです。 リクエストオブジェクトからペイロードを読み取り、JSON に解析してから %JSONImport を使って Ticket の新しいインスタンスに適用します。また、入力を待つ代わりに現在の時間から OpenDate と OpenTime も設定します。正しく保存できたら、オブジェクトの JSON 表現を呼び出すか、読み込み失敗する場合はエラーを返します。

このサービスにより、さらに 60 行のコードが全体に追加されました。これで、このアプリケーションの合計コード行数は、89 行になりました。

次は、セキュリティ > アプリケーションに Web アプリケーションを作成する必要があります。これには REST タイプのアプリケーションに設定し、クラス名を今作成したディスパッチクラスとして設定する必要があります(このアプリケーションがコードとデータにアクセスできるように適切なセキュリティロールを付与する必要があることに注意してください)。保存すると、REST サービスを定義した URL から呼び出せるようになります。

UserList を呼び出して確認してみましょう。

これで、データを作成する準備ができました。REST クライアントを使用して、ペイロードをチケット作成サービスに送信してみましょう。Keyword、Description、Advisor、および Contact を提供すると、作成したチケットの JSON が OpenDate と TicketId と共に返されます。

これで実用最低限のツールが完成しました。任意のフロントエンドフォームビルダーを使用して、REST サービス経由でチケット情報を送受信できるようになりました。

ステップ 4 - 相互運用性の要件

たった 89 行のコードで基本的なチケット管理アプリケーションが完成しました。上司も絶対に驚いたのでは?確かにそうですが、上司から悪い知らせを受けました。要件を逃しているというのです。ウィジェットダイレクトにはフランス語圏での特別な契約があり、フランス語によるチケットはすべて初回レビューを行う Mme Bettie Francis(ベティー・フランシス)を通過しなければならないのです。幸いにも、Robert Luman の Python 自然言語サポートに関する優れた記事を読み、テキストサンプルを受け入れて言語を識別できる REST サービスを作成したことのある同僚がいました。InterSystems IRIS 相互運用性を使用してこのサービスを呼び出し、テキストがフランス語であればアドバイザーを自動的にフランシスさんに更新するようにすることはできるでしょうか?

まずは、リクエストを送受信できるように、メッセージクラスを作成することから始めましょう。チケット ID とサンプルテキストを格納するリクエストと、言語コードと説明を返すレスポンスが必要です。 それぞれ Ens.Request と Ens.Response を継承します。

さて、さらに 6 行が追加され、合計 95 行のコードとなりました。次に演算を作成する必要があります。同僚のサービスにリクエストを送信して回答を取得する演算です。Outbount Operation を Server と URL のプロパティで定義し、これらを SETTINGS パラメーターに含めてユーザー構成に公開します。こうすることで、サーバーのパスが変更した場合に簡単にリクエストを更新できます。HTTPRequest をセットアップするヘルパーメソッドを作成してから、それを使用してサービスを呼び出し、レスポンスを作成します。

27 行のコードが追加され 100 行を超え、合計 122 行となりました。次に、このクラスを Ensemble 本番環境内にセットアップする必要があります。相互運用性の本番構成に移動し、「演算」ヘッダーの下にある「追加」を押します。クラス名と表示名で演算をセットアップしましょう。

次に、それをクリックして設定を開き、サーバー名と URL を入力して演算を有効にします。

次に、チケット ID を取り、入力されたユーザーアカウント ID をアドバイザーに設定する 2 つ目の演算が必要です。メッセージと演算クラスが必要ですが、この場合レスポンスは返さず、演算はフィードバックなしでタスクを実行します。

さらに 12 行が追加され、合計 134 行になりました。言語サービスを追加した方法でこの演算を本番環境に追加しますが、この場合、構成を設定する必要はありません。

次に、サービスを呼び出し、レスポンスを評価し、オプションとしてフランス語アドバイザーの演算を呼び出すルーターが必要です。「相互運用性」>「ビルド」>「ビジネスプロセス」に移動し、ビジュアルルールビルダーを開きます。リクエストとレスポンスのコンテキストを設定してから、呼び出しの項目を追加します。作成したメッセージクラスに入力と出力を設定したら、リクエストビルダーを使って入力をマッピングします。「非同期」フラグのチェックがオフになっていることを確認してください。レスポンスを待ってから処理を進めるためです。

次に「If」項目を追加して、返される言語コードを評価します。「fr」であれば、FrenchAdvisor 演算を呼び出すようにします。

フランシスさんのユーザー ID は 11 であるため、AdvisorUpdate メッセージを FrenchAdvisor サービスに供給するように呼び出しオブジェクトを設定し、ビルダーを使って、TicketID と固定値 11 を Advisor パラメーターに渡します。

「プロセス」ヘッダーの下にある「追加」をクリックし、クラスを選択して表示名「FrenchRouter」を指定したら、本番にこれを追加します。

これでルーティングが設定されました。 後は、新しいチケットをスキャンして、ルーターでの処理に送信するサービスが必要です。SQL アダプターに従ってサービスクラスを定義します(さらに 8 行のコードが追加されます)。

次に、演算オブジェクトとプロセスオブジェクトと同じようにして、これを本番に追加します。SQL アダプターのセットアップが必要です。ODBC DSN を介してローカルデータベースへの接続情報を提供し、CallInterval 設定に設定されたタイマーでチケットをクエリするためにサービスが使用する基本的な SQL クエリを指定します。このクエリはクエリに一意のキーを定義し、送信済みのレコードが再送信されないようにする「キーフィールド名」設定と組み合わされます。

この設定により、新しいチケットをスキャンし、テキストを外部サービスに渡して言語を解析し、オプションとしてこのレスポンスに応じてアドバイザーをリセットする処理が完成しました。では試してみましょう!英語のリクエストを送信してみましょう。これは TicketID 70 で返されます。数秒待ってから、GetTicket REST サービスを介してこのレコードにアクセスすると、アドバイザーは元のリクエストから変わっていないのがわかります。

では、フランス語のテキストを使ってみましょう。

チケット ID 71 をリクエストすると、期待どおり、アドバイザーはフランシスさんに変更されています! これは相互運用性内で、ビジュアルトレースでメッセージの場所を探し、演算が期待どおりに呼び出されたことを確認すればわかります。

この時点でのコード行数は 142 行であり、データを永続させ、LOAD DATA でデータを読み込み、データの表示と編集を行える基本的な REST API と、外部 REST サービスへの呼び出しに基づいて意思決定サポートを提供する高度な統合エンジンを備えた InterSystems IRIS アプリケーションが出来上がりました。これ以上のことを求める人はきっといないですよね?

ステップ 5 - 更なる要求: 分析

このアプリケーションは大成功をおさめ、データもさらに増えています。この貴重データを利用するには専門知識が必要であり、ウィジェットダイレクトの経営陣はインサイトを希望しています。データへの対話型アクセスを提供できるでしょうか?

InterSystems IRIS Analytics を使用すると、高度なデータ操作ツールを素早く簡単に利用できます。まずは、内部 Web アプリケーションに対し、Analytics サポートを有効化する必要があります。

これにより、ネームスペースの分析セクションを使用できるようになります。「分析」>「アーキテクト」を開きましょう。「新規」を選択してフォームに入力し、Ticket クラスの分析キューブを作成します。

次に、ビジュアルビルダーを使って、次元と、基本的なドリルダウンのリストをセットアップします。このビューはドラッグアンドドロップで操作します。リストは、ユーザーがデータポイントを調査する際に表示されるコンテンツをカスタマイズするためのビジュアルエディターででも作成できます。

基本のセットアップが完了したら、キューブを保存、コンパイル、ビルドできます。これによりすべてのインデックスがセットアップされ、アナライザーでの分析用にキューブが有効化されます。アナライザーを開いてデータを操作してみましょう。例では、アドバイザーを年と四半期の階層に対して比較されており、保存されている連絡先でフィルターされています。セルをクリックすると双眼鏡のアイコンが表示されるため、それをクリックすると作成したドリルダウンリストが呼び出されます。それを使用してさらに分析し、エクスポートすることができます。

まとめ

たった 142 行のコードで、基本的ではありながらも機能性のあるモダンなバックエンドアプリケーションを作成しました。アプリケーション間の通信と高度な分析が可能なツールもサポートされています。これは過剰に単純化した実装であり、IRIS でデータベースアプリケーションを構築するために必要最低限の例としてのみ使用してください。この記事の冒頭で述べたように、このコードは本番対応かつ実用できるものではありません。開発者は InterSystems IRIS ドキュメントとベストプラクティスを参考にしながら、自分のコードを堅牢かつ安全で、スケーラブルにしてください(このコードベースはどれにも該当しません)。それでは、コーディングをお楽しみください!

1
0 131
記事 Toshihiko Minamoto · 9月 15, 2023 5m read

DeepSee BI ソリューションのユーザーインターフェース(UI)を配布するにはいくつかのオプションがあります。 最も一般的には以下の手法があります。

  • ネイティブの DeepSee ダッシュボードを使用し、Zen で Web UI を取得して、Web アプリに配布する。
  • DeepSee REST API を使用して、独自の UI ウィジェットとダッシュボードを取得・構築する。

最初の手法はコーディングを行わずに比較的素早く BI ダッシュボードを構築できるためお勧めですが、事前設定のウィジェットライブラリに限られます。これを拡張することはできますが、大きな開発の手間がかかります。

2 つ目の手法には、任意の総合 js フレームワーク(D3,Highcharts など)を使用して DeepSee データを可視化する手段がありますが、ウィジェットとダッシュボードを独自にコーディングする必要があります。

今日は、上の 2 つを組み合わせて Angular ベースの DeepSee ダッシュボード用 Web UI を提供するもう 1 つの手法をご紹介します。DeepSee Web ライブラリです。

詳しい説明

DeepSee Web(DSW)は、DeepSee ダッシュボードを表示する Angular.js Web アプリで、Highcharts.js、OpenStreetMap、および一部の自作 js ウィジェットを使用する特定のネームスペースで使用できます。

仕組み

DSW は、MDX2JSON ライブラリを使用するネームスペースで使用できるダッシュボードとウィジェットのメタデータをリクエストします。 ダッシュボードを可視化するために、DSW はウィジェットとそのタイプのリストを読み取り、ほぼすべての DeepSee ウィジェットに実装されている js-widget アナログを使用します。 ダッシュボードにリストされる js ウィジェットをインスタンス化し、DeepSee REST API と MDX2JSON を使用してウィジェットのデータソースに従ってデータをリクエストし、JSON 形式でデータを取得して可視化を実行します。

そのメリットは?

DSW は以下の理由により優れています。

  • 標準のエディターを使用して DeepSee ダッシュボードを作成し、コーディングを一切行わずに Angular で UI を使用できます。
  • 任意の js ライブラリまたは自作ウィジェットで簡単にウィジェットのライブラリを拡張できます。
  • DSW カバーにスマートタイルを追加できます。
  • 地図にデータを表示できます。

モバイルデバイスは?

DSW は、モバイルブラウザ(Safari、Chrome)で非常によく動作し、スタンドアロンの DeepSight iOS アプリもあります。以下はそのスクリーンショットです。

いくらですか?

無料です。 最新リリースのダウンロード、課題の送信、フォークの作成、修正や新機能を含むプルリクエストを自由に行えます。

プロダクションでの使用に適していますか?

はい。 DSW は 2014 年にリリースされ、今日まで 60 回リリースされてきました。 DSW は数十社の DeepSee ソリューションで正常に使用されています。

たとえば、開発者コミュニティでも DSW を使用しています。

ただし、DSW ライセンス(MIT ライセンス)に従い、ご自身のリスクで使用していただきます。

 

サポートされていますか?

コミュニティによってサポートされています。 ぜひ課題を開き、フォークを作成して、修正と新機能が含まれるプルリクエストを送信してください。

主要貢献者は [@Anton Gnibeda​]、[@Nikita Savchenko]、[@Eduard Lebedyuk] です。 

インストール方法は?

簡単です。 まず、MDX2JSON をインストールします。 リリースから最新のインストーラーをダウンロードして、USER ネームスペースで installer.xml をインポート/コンパイルし、以下を実行します。

USER> D ##class(MDX2JSON.Installer).setup()

GitHub から最新のリリースがダウンロードされ、MDX2JSON データベースとネームスペースの作成と %All へのマッピングが行われ、/MDX2JSON Web アプリが作成されます。

すべてがうまくインストールされたことを確認するには、localhost:57772/MDX2JSON/Test を開きます。  すべてが正常である場合は、以下のように返されます。

{
"DefaultApp":"\/dsw",
"Mappings": {
"Mapped":["%SYS","MDX2JSON","SAMPLES","USER"
],
"Unmapped":["DOCBOOK"
]
},
"Parent":"18872dc518d8009bdc105425f541953d2c9d461e",
"ParentTS":"2017-05-26 16:20:17.307",
"Status":"OK",
"User":"",
"Version":2.2
}​

 

次に DSW をインストールします。 最新の release xml をダウンロードします。

それを USER ネームスペースにインポート/コンパイルします。 以下を実行してください。

USER> Do ##class(DSW.Installer).setup()

/dsw アプリが作成され、すべての js ファイルが /csp/dsw フォルダにインストールされます。

localhost:57772/dsw/index.html#!/?ns=SAMPLES を開き、動作することを確認してください。

既知の問題:

一部のシステムでは、CSP ゲートウェイで CSP 以外のファイルに UTF8 サポートを設定する必要があります。 ターミナルで以下を実行してオンにしてください。

USER> set ^%SYS("CSP","DefaultFileCharset")="utf-8"

 

いつインストールすべきですか?

今すぐインストールしましょう! )

続きにご期待ください!

0
0 177
記事 Toshihiko Minamoto · 9月 30, 2021 16m read

はじめに

Caché 2016.2のフィールドテストはかなり前から利用可能ですので、このバージョンで新しく追加されたドキュメントデータモデルという重要な機能に焦点を当てたいと思います。 このモデルは、オブジェクト、テーブル、および多次元配列など、データ処理をサポートするさまざまな方法として自然に追加されました。 プラットフォームがより柔軟になるため、さらに多くのユースケースに適したものになります。

いくつかのコンテキストから始めましょう。 NoSQLムーブメントの傘下にあるデータベースシステムを少なくとも1つは知っているかもしれません。 これにはかなりたくさんのデータベースがあり、いくつかのカテゴリにグループ化することができます。 Key/Valueは非常に単純なデータモデルです。 値をデータベースに格納し、それにキーを関連付けることができます。 値を取得する場合は、キーを介してそれにアクセスする必要があります。 適切なキーを選択によってソートが決まり、キーの一部であるものでグループ化する場合に単純な集計に使用できるようになるため、キーの選択が重要な鍵となります。 ただし、値は値にすぎません。 値内の特定のサブ要素にアクセスしたり、それらにインデックスを作成したりすることはできません。 値をさらに活用するには、アプリケーションロジックを書く必要があります。 Key/Valueは、大規模なデータセットと非常に単純な値を操作する必要がある場合に最適ですが、より複雑なレコードを扱う場合には価値が劣ります。

ドキュメントデータモデルはKey/Valueにとてもよく似ていますが、値はより複雑です。 値はキーに関連付けられたままになりますが、さらに、値のサブ要素にアクセスして特定の要素にインデックスを作成することができます。 つまり、いくつかのサブ要素が制限を満たす特定のドキュメントを検索することもできるということです。 明らかに、NoSQLの世界にはGraphのような他のモデルもさらに存在しますが、ここでは、ドキュメントに焦点を置くことにします。

 

そもそもドキュメントとは?

一部の読者を混乱させる傾向があるため、まず最初に、1つ明確にしておきましょう。この記事で「ドキュメント」と言った場合、PDFファイルやWordドキュメントといった物理的なドキュメントを指してはいません。
この文脈でのドキュメントとは、サブ値を特定のパスと関連付けることのできる構造を指しています。 ドキュメントを記述できるシリアル化形式には、JSONやXMLなどのよく知られたものが様々あります。 通常こういった形式には、共通して次のような構造とデータ型があります。

  1. 順序付けされていないKey/Valueペアの構造
  2. 順序付けされた値のリスト
  3. スカラー値

1つ目は、XMLの属性要素とJSONのオブジェクトにマッピングされます。 2つ目の構造は、XMLのサブ要素とJSONの配列を使ったリストによって導入されています。 3つ目は、単に、文字列、数値、ブール値といったネイティブのデータ型を利用できるようしています。

JSONのようなシリアル化された形式でドキュメントを視覚化することは一般的ですが、これは、ドキュメントを表現できる一方法にすぎないことに注意してください。 この記事では、JSONを主なシリアル化形式として使用することにします。JSONサポートの改善機能をうまく利用できるでしょう。この改善についてまだ読んでいない方はこちらをご覧ください。

ドキュメントはコレクションにグループ化されます。 セマンティックと潜在的に共通の構造を持つドキュメントは同じコレクションに保存する必要があります。 コレクションはその場で作成できるため、事前にスキーマ情報を用意しておく必要はありません。

コレクションにアクセスするには、データベースハンドルを最初に取得しておく必要があります。 データベースハンドルはサーバーへの接続として機能し、コレクションへの単純なアクセスを提供しますが、分散環境の場合にはさらに複雑なシナリオを処理することもできます。

 

基本

まず、Caché Object Scriptで単純なドキュメントを挿入する方法を見てみましょう。

USER>set db = ##class(%DataModel.Document.Database).$getDatabase()

USER>set superheroes = db.$getCollection("superheroes")

USER>set hero1 = {"name":"Superman","specialPower":"laser eyes"}

USER>set hero2 = {"name":"Hulk","specialPower":"super strong"}

USER>do superheroes.$insert(hero1)

USER>do superheroes.$insert(hero2)

USER>write superheroes.$size()
2

上記のコードサンプルでは、まずデータベースハンドルが取得されて、「superheroes」というコレクションが取得されます。 コレクションは明示的に作成されるため、事前に設定する必要はありません。 新しいコレクションにアクセスできるようになったら、ヒーローのSupermanとHulkを表す非常に単純なドキュメントを2つ作成します。 これらは$insert(<document>)への呼び出しでコレクションに保存され、コレクションサイズの最終チェックで、2つのドキュメントを報告します。これは、以前にコレクションが存在していなかったためです。

$insert()呼び出しは、成功すると、挿入されたドキュメントを返します。 このため、ドキュメントの操作を続行する場合に、自動的に割り当てられたIDを取得することができます。 また、チェーンメソッドも可能になります。

USER>set hero3 = {"name":"AntMan","specialPower":"can shrink and become super strong"}

USER>write superheroes.$insert(hero3).$getDocumentID()
3

このコードスニペットは、別のヒーローオブジェクトを作成し、superheroesコレクションに永続させます。 今回は、メソッド呼び出しの$getDocumentID()$insert()呼び出しに連鎖させ、システムがこのドキュメントに割り当てたIDを取得します。 $insert()は必ず自動的にIDを割り当てます。 独自のIDを割り当てる必要がある場合は、$insertAt(<User-ID>,<document>)呼び出しを利用できます。

特定のIDでドキュメントを取得する場合は、コレクションに対して$get(<ID>)メソッドを呼び出すことができます。

USER>set antMan = superHeroes.$get(3)

USER>write antMan.$toJSON()
{"name":"AntMan","specialPower":"can shrink and become super strong"}

Supermanとそのhometownを表すドキュメントを更新するとしましょう。 この場合、$upsert(<ID>,<document>)呼び出しを使用して、既存のドキュメントを簡単に更新することができます。

USER>set hero1.hometown = "Metropolis"

USER>do superheroes.$upsert(1,hero1)

USER>write superheroes.$get(1).$toJSON()
{"name":"Superman","specialPower":"laser eyes","hometown":"Metropolis"}

$upsert()は、IDがまだほかに使用されていない場合にドキュメントを挿入するか、そうでない場合に既存のドキュメントを更新します。
もちろん、$toJSON()を呼び出すだけで、コレクションの全コンテンツをJSONにシリアル化することも可能です。

USER>write superheroes.$toJSON()
[
{"documentID":1,"documentVersion":4,"content":{"name":"Superman","specialPower":"laser eyes","hometown":"Metropolis"}},
{"documentID":2,"documentVersion":2,"content":{"name":"Hulk","specialPower":"super strong"}},
{"documentID":3,"documentVersion":3,"content":{"name":"AntMan","specialPower":"can shrink and become super strong"}}
]

コレクションがドキュメントの配列として表されていることがわかります。 各ドキュメントは、ドキュメントIDとドキュメントバージョンでラップされており、同時実行を適切に処理するために使用されます。 実際のドキュメントコンテンツは、ラッパーのプロパティコンテンツに格納されます。 これは、コレクションの完全なスナップショットを取得して移動できるようにするために必要な表現です。
また、これはモデルの非常に重要な側面をカバーしており、特殊プロパティを予約してドキュメントデータに挿入することはありません。 ドキュメントを適切に処理するためのエンジンが必要とする情報は、ドキュメントの外部に保存されます。  さらに、ドキュメントはオブジェクトまたは配列のいずれかです。 その他の多くのドキュメントストアは、オブジェクトを最上位の要素としてのみ許可しています。

コレクションでドキュメントを変更するためのAPI呼び出しには他にもたくさんあり、基本的な演算をいくつか見てきました。 ドキュメントの挿入と変更は楽しい作業ですが、実際にデータセットを分析したり、特定の制限を満たすドキュメントを取得したりすると、さらに興味深くなります。 

 

クエリ

すべてのデータモデルが有用とみなされるには、何らかのクエリ機能が必要です。  動的スキーマを使用してドキュメントをクエリできるようにするには、2つの潜在的な方法があります。

  1. 動的なドキュメントの性質に対処できる独自のクエリ言語を設計して実装する
  2. 定着している構造化されたクエリ言語にクエリを統合する

この記事の後の方で説明するいくつかの理由により、コレクションをSQLエンジンに公開することにしました。 SQLの知識を引き続き活用できるというメリットがあります。また、クエリ方言の別のフレーバーを作成しているところでもあります。 実際、SQL ANSI委員会は、JSONの標準拡張機能を提案しており、それに準拠しています。 まとめれば、これらの拡張機能には、JSON関数の2つのカテゴリが含まれています。

  1. リレーションコンテンツからJSONコンテンツに公開するための関数セット
  2. 動的JSONコンテンツをクエリするための関数セット 

この記事の範囲では、2つ目のカテゴリである動的JSONコンテンツのクエリのみを取り上げ、結果をSQLで処理できるようにテーブルとして利用できるようにします。

動的コンテンツ(関連するスキーマのないコンテンツ)を公開し、事前に定義されたスキーマを使用してデータを操作するSQLで利用できるようにする魔法の関数は、JSON_TABLEです。 一般に、この関数は2つの引数を取ります。

  1. JSONデータソース
  2. 名前と型で列へのJSONパスのマッピングを指定する定義

骨に肉付けした例を見てみましょう。

SELECT name, power FROM JSON_TABLE(
  'superheroes',
  '$' COLUMNS(
    name VARCHAR(100) PATH '$.name',
    power VARCHAR(300) PATH '$.specialPower'
  )
)

name        power
---------   -------------------------------
Superman    laser eyes
Hulk        super strong
AntMan      can shrink and become super strong

JSON_TABLE関数の最初の引数は、それが作成する仮想テーブルのソースを定義します。 この場合、コレクション「superheroes」をクエリします。 このコレクションのドキュメントごとに行が作られます。
2つ目の引数は、ドキュメントの特定の値をテーブルの列として公開することを忘れないでください。 この引数は2つ部分で構成されています。最初のステップとして、次の式のコンテキストを設定します。 ドル記号'$'には特別な意味があり、ドキュメントのルートを指しています。 それ以降のすべての式はこのコンテキストを基準としています。
後に続くのは、COLUMNS句で、カンマ区切りのCOLUMN式のリストです。 COLUMN式ごとに、仮想テーブルの列が作成されます。 「name」と「power」という2つの列をクエリで公開しています。 列「name」はVARCHAR(100)型で定義されていますが、列「power」は300文字に制限されています。 PATH式は、JPL(JSONパス言語)式を使用してドキュメントの特定の値を列に関連付けています。 キー「name」の値は列「name」に公開されますが、キー「specialPower」の値は列「power」にマッピングされます。 JPL式は非常に表現力が高く強力ですが、これについては別のトピックで説明することにします。 このサンプルで使用した式は非常に基本的な式です。

この構文が初めてであれば、理解するのに少し時間が掛かるかもしれませんが、 JSON_TABLE関数を自然に読み取ると理解しやすいでしょう。 例として、上記のクエリを使います。 ここで表現しているのは、基本的に次のことです。

コレクション「superheroes」をクエリし、各ドキュメントのルートに式のコンテキストを設定します。 次の2つの列を公開します。

  1. 列「name」を型VARCHAR(100)で公開し、キー「name」の値を挿入します。
  2. 列「power」を型VARCHAR(300)で公開し、キー「specialPower」の値を挿入します。

前に述べた通り、JPL式は複雑になりがちであるか、たくさんの列を公開したいだけの場合もあります。 そのため、型定義を参照できる標準への拡張を組み込みました。これは基本的に、事前定義済みのCOLUMNS句です。 このようにして、上記のCOLUMNS句を登録することができます。

do db.$createType("heropower",{"columns":[{"column":"name","type":"VARCHAR(100)","path":"$.name"},{"column":"power","type":"VARCHAR(300)","path":"$.specialPower"}]})

型情報を登録したら、%TYPE式を使って、JSON_TABLE関数でそれを参照することができます。

SELECT name, power FROM JSON_TABLE(
  'superheroes',
  '$' %TYPE 'heropower'
)

これは明らかに、SQLクエリにドキュメントの一貫したビューを提供し、クエリそのものを大幅に簡略化する上で役立ちます。

高度な内容

ここまで説明したことのほぼすべてについて補足することはたくさんありますが、ここでは最も重要なことに焦点を当てたいと思います。 最後のセクションを読みながら、JSON_TABLE関数を非常に強力なポイントとしている手掛かりに気づいたかもしれません。 

  1. 仮想テーブルを作成する
  2. JSONのようなデータをソースデータとして消費できる

最初の項目はそれだけで重要なポイントです。コレクションを簡単にクエリして、別のJSON_TABLE呼び出しまたは正にテーブルと結合することができるからです。 コレクションをテーブルと結合できることは大きなメリットです。要件に応じて、データに完璧なデータモデルを選択できるのです。 
型安全、整合性チェックが必要であるのに、モデルがあまり進化していませんか? リレーショナルを使いましょう。 他のソースのデータを処理してそのデータを消費する必要があり、モデルが必ず急速に変化するか、アプリケーションユーザーの影響を受ける可能性のあるモデルを保存することを検討していますか? ドキュメントデータモデルを選択しましょう。 モデルはSQLで一緒にまとめることができるので安心です。

JSON_TABLE関数の2つ目のメリットは、実のところ基盤のデータモデルとは無関係です。 これまでに、JSON_TABLEでコレクションのクエリを説明してきました。 最初の引数は任意の有効なJSON入力にすることもできます。 次の例を考察しましょう。

SELECT name, power FROM JSON_TABLE(
  '[
    {"name":"Thor","specialPower":"smashing hammer"},
    {"name":"Aquaman","specialPower":"can breathe underwater"}
  ]',
  '$' %TYPE 'heropowers'
)

name        power
---------   -------------------------------
Thor        smashing hammer
Aquaman     can breathe underwater

入力は通常のJSON文字列で、オブジェクトの配列を表します。 構造がsuperheroesコレクションと一致するため、保存された型識別子「heropowers」を再利用することができます。 
これにより、強力なユースケースが可能になります。 実際にメモリ内のJSONデータをディスクに永続させずにクエリすることができます。 REST呼び出しでJSONデータをリクエストし、クエリを実行してコレクションまたはテーブルを結合できます。 この機能を使用すると、Twitterタイムライン、GitHubリポジトリの統計、株式情報、または単に天気予報のフィードをクエリできます。 この機能は非常に便利であるため、これについては、後日、専用の記事で取り上げたいと思います。

 

REST対応

ドキュメントデータモデルでは、初期状態でREST対応のインターフェースが備わっています。 すべてのCRUD(作成、読み取り、更新、削除)とクエリ機能はHTTP経由で利用できます。 完全なAPIについては説明しませんが、ネームスペース「USER」内の「superheroes」コレクションのすべてのドキュメントを取得するサンプルcURLを以下に示します。

curl -X GET -H "Accept: application/json" -H "Cache-Control: no-cache" http://localhost:57774/api/document/v1/user/superheroes

 

私のユースケースで使用できますか?

ドキュメントデータモデルは、InterSystemsのプラットフォームへの重要な追加機能です。 これは、オブジェクトとテーブルに続く、まったく新しいモデルです。 SQLとうまく統合できるため、既存のアプリケーションで簡単に利用することができます。 Caché 2016.1で導入された新しいJSON機能によって、CachéでのJSONの処理が楽しく簡単になります。

そうは言っても、これは新しいモデルです。 いつ、そしてなぜそれを使用するのかを理解する必要があります。 常に言っていることですが、特定のタスクにはそれに適したツールを選択してください。

このデータモデルは、動的データを処理する必要がある場合に優れています。 以下に、主な技術的メリットをまとめます。

  • 柔軟性と使いやすさ

スキーマを予め定義する必要がないため、データの作業環境を素早くセットアップし、データ構造の変更に簡単に適応させることができます。

  • スパース性

テーブルに300列があっても、各行が入力できるのはその内の15列であることを覚えていますか? これはスパースなデータセットであり、リレーショナルシステムではそれらを最適に処理にできません。 ドキュメントは設計上スパースであり、効率的に保存と処理を行えるようになっています。

  • 階層

配列やオブジェクトなどの構造化型は、任意の深さでネストすることができます。 つまり、ドキュメント内で関連するデータを保存することができるため、そのレコードにアクセスする必要がある場合の読み取りのI/Oを潜在的に縮小することができます。 データは非正規化して保存できますが、リレーショナルモデルではデータは正規化して保存されます。

  • 動的な型

特定のキーには、列のように固定されたデータ型がありません。 名前は、あるドキュメントでは文字列であっても、別のドキュメントでは複雑なオブジェクトである可能性があります。 単純なものは単純にしましょう。 複雑になることはありますが、そうなった場合はもう一度単純化しましょう。

上記の項目はそれぞれ重要であり、適切なユースケースには、少なくとも1つが必要ではありますが、すべての項目が一致することは珍しいことではありません。

モバイルアプリケーションのバックエンドを構築しているとしましょう。 クライアント(エンドユーザー)は自由に更新できるため、同時に複数のバージョンのインターフェースをサポートする必要があります。 WebServicesを使用するなど、契約によって開発すると、データインターフェースを素早く適応させる能力が低下する可能性があります(ただし、安定性が増す可能性はあります)。 ドキュメントデータモデルには柔軟性が備わっているため、スキーマを素早く進化させ、特定のレコード型の複数のバージョンを処理し、それでもクエリで相関させることができます。

 

その他のリソース

この魅力的な新機能についてさらに詳しく知りたい方は、利用可能なフィールドテストバージョン2016.2または2016.3を入手してください。

『ドキュメントデータモデル学習パス』を必ず確認してください。
https://beta.learning.intersystems.com/course/view.php?id=9

今年のグローバルサミットのセッションをお見逃しなく。 「データモデリングリソースガイド」には、関連するすべてのセッションが集められています。
https://beta.learning.intersystems.com/course/view.php?id=106

最後に、開発者コミュニティに参加し、質問を投稿しましょう。また、フィードバックもお待ちしています。

0
0 200
記事 Mihoko Iijima · 9月 21, 2021 3m read

これは InterSystems FAQ サイトの記事です。

Excelのマクロ機能を使ってVBAからREST/JSON経由でアクセスすることが可能です。

REST/JSONを使用したInterSystems製品との連携の簡単なサンプルを以下のGitHubサイトから入手できます。

VBAマクロサンプル

マクロサンプルは Solution/activityreport.xlsm をご覧ください。

サンプルは、コンテナを利用しています。READMEに記載の方法でコンテナを開始し、Solution/activityreport.xlsm の「データ取得」ボタンをクリックすると以下の結果が得られます。

マクロの中では、以下のGET要求を実行しています。

以下の図例は、RESTクライアントでGET要求を行った結果です(​​​ Basic 認証でアクセスしています)。


​​​上記GET要求により、ベースURL(/pm)の指定で、RESTディスパッチクラス(PM.Broker)が起動し、要求されたパス(例では /getactivities)から、PM.REST クラスの GetActivities() メソッドを実行しています。

0
0 355
記事 Toshihiko Minamoto · 8月 11, 2021 17m read

不在時に、セキュリティとプライバシーを維持しながら、コンピューターを相互に信頼させるにはどうすればよいでしょうか?

「ドライマルティーニを」と彼は言った。 「1 杯。 深いシャンパングラスで。」
「承知いたしました。」
「気が変わった。 ゴードンを 3、ヴォッカを 1、キナリレを半量。 キンキンに冷えるまでよくシェイクしてから、大きめの薄いレモンピールを 1 つ加えてくれ。 わかったかい?」
「お承りいたしました。」 バーテンダーはその考えが気に入ったようだった。
イアン・フレミング著『カジノ・ロワイヤル』(1953 年)より

OAuth は、ユーザーログイン情報を伴うサービスを「運用中」のデータベースから、物理的にも地理的にも分離する上で役立ちます。 このように分離すると、ID データの保護が強化され、必要であれば、諸国のデータ保護法の要件に準拠しやすくしてくれます。

OAuth を使用すると、ユーザーは、最小限の個人データをさまざまなサービスやアプリケーションに「公開」しながら、一度に複数のデバイスから安全に作業することができるようになります。 また、サービスのユーザーに関する「過剰な」データを操作しなくてよくなります(データはパーソナル化されていない形態で処理することができます)。

InterSystems IRIS を使用する場合、OAuth と OIDC サービスを自律的かつサードパーティのソフトウェア製品と連携してテストし、デプロイするための既成の完全なツールセットを利用できます。

OAuth 2.0 と Open ID Connect

OAuth と Open ID Connect(OIDC または OpenID)は、アクセスと識別をデリゲートするためにオープンプロトコルを汎用的に組み合わせたもので、21 世紀現在、人気を得ているようです。 大規模な使用において、これより優れたオプションはまだ誰も思いついていません。 HTTP(S) プロトコル上にとどまり、JWT(JSON Web Token)コンテナを使用するため、特にフロントエンドのエンジニアに人気があります。

OpenID は OAuth を使用して機能しています。実際、OpenID は OAuth のラッパーです。 OpenID を電子識別システムの認証と作成に使用するオープンスタンダードとして使用することは、開発者にとって目新しい事ではありません。 2019 年には、公開から 14 周年を迎えました(バージョン 3)。 Webとモバイル開発、そしてエンタープライズシステムで人気があります。

そのパートナーである OAuth オープンスタンダードはアクセスをデリゲートする役割を担っており、12 年目を迎えています。関連する RFC 5849 標準が登場してからは 9 年です。 この記事の目的により、プロトコルの最新バージョンである OAuth 2.0 と最新の RFC 6749 を使用することにしましょう。 (OAuth 2.0 は、その前身の OAuth 1.0 とは互換していません。)

厳密に言えば、OAuth はプロトコルではなく、ソフトウェアシステムにアクセス権制限アーキテクチャを実装する際に、ユーザー識別操作を分離して別のトラステッドサーバーに転送するための一連のルール(スキーム)です。

OAuth は特定のユーザーについて何も言及できないことに注意してください! ユーザーが誰であるか、ユーザーがどこにいるのか、またユーザーが現在コンピューターを使用しているかどうかさえも、知ることはできません。 ただし、OAuth を使用すれば、事前に発行されたアクセストークンを使用して、ユーザーが参加することなくシステムと対話することが可能であり、 これは重要なポイントです(詳細は、OAuth サイトにある「User Authentication with OAuth 2.0」をご覧ください)。

User-Managed Access(UMA)プロトコルも OAuth に基づくプロトコルです。 OAuth、OIDC、および UMA を合わせて使用することで、次のような分野で保護された ID とアクセス管理(IdM、IAM)システムを実装することができます。

API エコノミーの新しいアクセス制御ベン図
API エコノミーの新しいアクセス制御ベン図

何よりも、個人データをシステムのほかの部分と同じ場所に保存してはいけません。 認証と認可は物理的に分離する必要があります。 そして、ID と認証を各個人に与えることが理想と言えます。 自分で保管せずに、 所有者のデバイスを信頼するのです。

信頼と認証

ユーザーの個人データを自分のアプリや作業データベースと組み合わさったストレージ場所に保存するのはベストプラクティスではありません。 言い換えれば、このサービスを提供できる信頼のある人を選ぶようにする必要があります。

このサービスは、次の項目で構成されます。

  • ユーザー
  • クライアントアプリ
  • 識別サービス
  • リソースサーバー

アクションは、ユーザーのコンピューターの Web ブラウザで実行されます。 ユーザーには識別サービスが備わったアカウントがあり、 クライアントアプリは、識別サービスと相互インターフェースとの契約に署名済みです。 リソースサーバーは、識別サービスを信頼して、識別できた人にアクセスキーを発行します。

ユーザーはクライアント Web アプリを実行して、リソースを要求します。 クライアントアプリは、アクセス権が必要なそのリソースへのキーを提示する必要があります。
ユーザーにキーがない場合、クライアントアプリはリソースサーバーへのキーを発行するために契約している識別サービスに接続します(ユーザーを識別サービスに転送します)。

識別サービスは、どのようなキーが必要かを問い合わせます。

ユーザーは、リソースにアクセスするためのパスワードを入力します。 この時点でユーザー認証が行われ、ユーザーの身元が確認されると、リソースへのキーが提供され(ユーザーをクライアントアプリに戻します)、ユーザーがリソースを利用できるようになります。

認可サービスの実装

InterSystems IRIS プラットフォームでは、必要に応じてさまざまなプラットフォームからのサービスをアセンブルできます。 次はその例です。

  1. デモクライアントが登録された OAuth サーバーを構成して起動します。
  2. デモ OAuth クライアントを OAuth サーバーと Web リソースに関連付けて構成します。
  3. OAuth を使用できるクライアントアプリを開発します。 Java、Python、C#、または Node JS を使用できます。 以下の方に、ObjectScript でのアプリケーションコードの例を示しています。

OAuth にはさまざまな設定があるため、チェックリストが役立ちます。 例を見ていきましょう。 IRIS 管理ポータルに移動し、[システム管理]>[セキュリティ]>[OAuth 2.0]>[サーバー]の順に選択します。

各項目には設定行の名前とコロン、そして必要であればその後に例または説明が含まれます。 別の方法として、Daniel Kutac の 3 部構成になっている「InterSystems IRIS Open Authorization Framework (OAuth 2.0)の実装 - パート1」、パート2、そしてパート3 に記載されているスクリーンショットのヒントを参考にしてください。

次のスクリーンショットはすべて、例として提示されています。 独自のアプリケーションを作成する際は、別のオプションを選択する必要があるでしょう。

[一般設定]タブで、次のように設定してください。

  • 説明: 構成の説明を入力します。「認証サーバー」など。
  • ジェネレーターのエンドポイント(以降「EPG」)のホスト名: サーバーの DNS 名。
  • サポートされている許可の種類(少なくとも 1 つを選択):
    • 認可コード
    • 暗黙
    • アカウントの詳細: リソース、所有者、パスワード
    • クライアントアカウントの詳細
  • SSL/TLS 構成: oauthserver

[スコープ]タブで、次を設定します。

  • サポートされているスコープを追加: この例では「scope1」です。

[間隔]タブで、次を設定します。

  • アクセスキー間隔: 3600
  • 認可コードの間隔: 60
  • キー更新の間隔: 86400
  • セッション中断間隔: 86400
  • クライアントキー(クライアントシークレット)の有効期間: 0

[JWT 設定]タブで、次を設定します。

  • 入力アルゴリズム: RS512
  • キー管理アルゴリズム: RSA-OAEP
  • コンテンツ暗号化アルゴリズム: A256CBC-HS512

[カスタマイズ]タブで、次を設定します。

  • 識別クラス: %OAuth2.Server.Authenticate
  • ユーザークラスの確認: %OAuth2.Server.Validate
  • セッションサービスクラス: OAuth2.Server.Session
  • キーの生成クラス: %OAuth2.Server.JWT
  • カスタムネームスペース: %SYS
  • カスタマイズロール(少なくとも 1 つ選択): %DB_IRISSYS および %Manager

では、変更内容を保存します。

次のステップでは、OAuth サーバーにクライアントを登録します。 [顧客の説明]ボタンをクリックして、[顧客の説明を作成]をクリックします。

[一般設定]タブで、次の情報を入力します。

  • 名前: OAuthClient
  • 説明: 簡単な説明を入力します。
  • クライアントタイプ: 機密
  • リダイレクト URL: oauthclient から識別した後に、アプリに戻るポイントのアドレス。
  • サポートされている付与の種類:
    • 認可コード: はい
    • 暗黙
    • アカウントの詳細: リソース、所有者、パスワード
    • クライアントアカウントの詳細
    • JWT 認可
  • サポートされているレスポンスタイプ: 次のすべてを選択してください。
    • コード
    • id_token
    • id_token キー
    • トークン
  • 認可タイプ: シンプル

[クライアントアカウントの詳細]タブは自動的に入力されますが、クライアントの正しい情報であるかを確認してください。
[クライアント情報] タブには次の項目があります。

  • 認可画面:
    • クライアント名
    • ロゴの URL
    • クライアントのホームページ URL
    • ポリシーの URL
    • 利用規約の URL

では、[システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]の順に移動して、OAuth サーバークライアントにバインディングを構成します。

サーバーの説明の作成:

  • ジェネレーターのエンドポイント: 一般的なサーバーのパラメーターから取得されます(上記を参照)。
  • SSL/TLS 構成: 事前構成済みのリストから選択します。
  • 認可サーバー:
    • 認可エンドポイント: EPG + /authorize
    • キーエンドポイント: EPG + /token
    • ユーザーエンドポイント: EPG + /userinfo
    • キーのセルフテストエンドポイント: EPG + /revocation
    • キーの終了エンドポイント: EPG + /introspection
  • JSON Web Token(JWT)設定:
    • 動的登録以外のほかのソース: URL から JWKS を選択します。
    • URL: EPG + /jwks

このリストから、たとえばサーバーが OAuth-client にユーザーに関するさまざまな情報を提供できることがわかります(scopes_supported および claims_supported)。 また、アプリケーションを実装するときは、共有する準備ができているデータが何であるかをユーザーに尋ねても何の価値もありません。 以下の例では、scope1 の許可のみを要求します。

では、構成を保存しましょう。

SSL 構成に関するエラーがある場合は、[設定]>[システム管理]>[セキュリティ]>[SSL/TSL 構成]に移動して、構成を削除してください。

これで OAuth クライアントをセットアップする準備が整いました。
[システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]>[クライアント構成]>[クライアント構成を作成]に移動します。 [一般]タブで、次を設定します。

  • アプリケーション名: OAuthClient
  • クライアント名: OAuthClient
  • 説明: 説明を入力します。
  • 有効: はい
  • クライアントタイプ: 機密
  • SSL/TCL 構成: oauthclient を選択します。
  • クライアントリダイレクト URL: サーバーの DNS 名
  • 必要な許可の種類:
    • 認可コード: はい
    • 暗黙
    • アカウントの詳細: リソース、所有者、パスワード
    • クライアントアカウントの詳細
    • JWT 認可
  • 認可タイプ: シンプル

[クライアント情報]タブで、次を設定します。

  • 認可画面:
    • ロゴの URL
    • クライアントのホームページ URL
    • ポリシーの URL
    • 利用規約の URL
  • デフォルトのボリューム: サーバーに以前に指定したものが取得されます(scope1 など)。
  • 連絡先メールアドレス: カンマ区切りでアドレスを入力します。
  • デフォルトの最大経過時間(分): 最大認可経過時間または省略できます。

[JWT 設定]タブで、次を設定します。

  • JSON Web Token(JWT)設定
  • X509 アカウントの詳細から JWT 設定を作成する
  • IDToken アルゴリズム:
    • 署名: RS256
    • 暗号化: A256CBC
    • キー: RSA-OAEP
  • Userinfo アルゴリズム
  • アクセストークンアルゴリズム
  • クエリアルゴリズム

[クライアントログイン情報]タブで、次を設定します。

  • クライアント ID: クライアントがサーバーに登録された際に発行された ID(上記を参照)。
  • 発効されたクライアント ID: 入力されません
  • クライアントシークレット: クライアントがサーバーに登録された際に発行されたシークレット(上記を参照)。
  • クライアントシークレットの有効期限: 入力されません
  • クライアント登録 URI: 入力されません

構成を保存しましょう。

OAuth 認可を使用した Web アプリ

OAuth は、インタラクション参加体(サーバー、クライアント、Web アプリケーション、ユーザーのブラウザ、リソースサーバー)間の通信チャネルが何らかの形で保護されていることに依存しています。 この役割は SSL/TLS プロトコルが果たしているのがほとんどですが、 OAuth は、保護されていないチャネルでも機能します。 そのため、たとえばサーバー Keycloak はデフォルトで HTTP プロトコルを使用して、保護なしで実行します。 調整と調整時のデバッグが単純化されます。 サービスを実際に使用する際、OAuth のチャネル保護は、厳重な要件に含まれるべきであり、Keycloak ドキュメントに記述されている必要があります。 InterSystems IRIS の開発者は、OAuth に関するより厳密なアプローチに従っており、SSL/TSL の使用を要件としています。 単純化できる唯一の方法は、自己署名証明書を使用するか、組み込みの IRIS サービス PKI([システム管理]>>[セキュリティ]>>[公開鍵システム]を利用することです。

ユーザーの認可の検証は、UAuth サーバーに登録されているアプリケーションの名前と OAuth クライアントスコープの 2 つのパラメータを明示的に示すことで行えます。

Parameter OAUTH2APPNAME = "OAuthClient";
set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized(
..#OAUTH2APPNAME,
.sessionId,
"scope1",
.accessToken,
.idtoken,
.responseProperties,
.error)

認可がない場合に備え、ユーザー ID をリクエストして、アプリケーションを操作する許可を取得するためのリンクを準備しておきます。 ここでは、OAuth サーバーに登録されているアプリケーションの名前を指定して、OAuth クライアントと要求されるボリューム(スコープ)を入力するだけでなく、ユーザーを返す Web アプリケーションのポイントへのバックリンクも指定する必要があります。

Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/"
set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
..#OAUTH2APPNAME,
"scope1",
..#OAUTH2CLIENTREDIRECTURI,
.properties,
.isAuthorized,
.sc)

 

IRIS を使用して、ユーザーを IRIS OAuth サーバーに登録しましょう。 たとえば、ユーザーに名前とパスワードを設定するだけで十分です。
受信した参照の下でユーザーを転送すると、サーバーはユーザーを識別する手続きを実行し、Web アプリケーションのアカウントデータによって、操作許可が照会されます。また、%SYS フィールドのグローバル OAuth2.Server.Session で自身に結果を保持します。

  1. 認可されたユーザーのデータを示します。 手続きが正常に完了したら、アクセストークンなどがあります。 それを取得しましょう。

    set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT( .#OAUTH2APPNAME, accessToken, "scope1", .aud, .JWTJsonObject, .securityParameters, .sc )

以下に、完全に動作する OAuth の例のコードを示します。

Class OAuthClient.REST Extends %CSP.REST
{
Parameter OAUTH2APPNAME = "OAuthClient";
Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/";
// to keep sessionId
Parameter UseSession As Integer = 1;
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
    &lt;Routes>
        &lt;Route Method="GET" Url = "/" Call = "Do" />
    &lt;/Routes>
}
ClassMethod Do() As %Status
{
    // Check for accessToken
    set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized(
        ..#OAUTH2APPNAME,
        .sessionId,
        "scope1",
        .accessToken,
        .idtoken,
        .responseProperties,
        .error)
    // to show accessToken
    if isAuthorized {
     set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT(
         ..#OAUTH2APPNAME,
         accessToken,
         "scope1",
         .aud,
         .JWTJsonObject,
         .securityParameters,
         .sc
     )
     &html&lt; Hello!&lt;br> >
         w "You access token = ", JWTJsonObject.%ToJSON()
     &html&lt; &lt;/html> >
     quit $$$OK
    }
    // perform the process of user and client identification and get accessToken
    set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
        ..#OAUTH2APPNAME,
        "scope1",
        ..#OAUTH2CLIENTREDIRECTURI,
        .properties,
        .isAuthorized,
        .sc)
    if $$$ISERR(sc) {
         w "error handling here"
         quit $$$OK
    }
    // url magic correction: change slashes in the query parameter to its code
    set urlBase = $PIECE(url, "?")
    set urlQuery = $PIECE(url, "?", 2)
    set urlQuery = $REPLACE(urlQuery, "/", "%2F")
    set url = urlBase _ "?" _ urlQuery
    &html&lt;
     &lt;html>
         &lt;h1>Authorization in IRIS via OAuth2&lt;/h1>
         &lt;a href = "#(url)#">Authorization in &lt;b>IRIS&lt;/b>&lt;/a>
     &lt;/html>
    >
    quit $$$OK
}
}

コードの作業コピーは、InterSystems GitHub リポジトリ(https://github.com/intersystems-community/iris-oauth-example)にもあります。

必要に応じて、OAuth サーバーと OAuth クライアントに高度なデバッグメッセージモードを有効にしてください。これらは、%SYS エリアの ISCLOG グローバルに記述されます。

set ^%ISCLOG = 5
set ^%ISCLOG("Category", "OAuth2") = 5
set ^%ISCLOG("Category", "OAuth2Server") = 5

詳細については、「IRIS、OAuth 2.0 と OpenID Connect の使用」ドキュメントをご覧ください。

まとめ

これまで見てきたように、すべての OAuth 機能には簡単にアクセスでき、完全に使用できる状態になっています。 必要に応じて、ハンドラークラスとユーザーインターフェースを独自のものに置き換えることができます。 OAuth サーバーとクライアントの設定は、管理ポータルを使う代わりに、構成ファイルで構成することも可能です。

0
0 417
記事 Toshihiko Minamoto · 8月 9, 2021 4m read

1 年ほど前、私のチーム(多数の社内アプリケーションの構築と管理、および他の部署のアプリケーションで使用するツールやベストプラクティスの提供を担う InterSystems のアプリケーションサービス部門)は、Angular/REST ベースのユーザーインターフェースを元々 CSP や Zen を使って構築された既存のアプリケーションに作りこむ作業を開始しました。 この道のりには、皆さんも経験したことがあるかもしれない興味深いチャレンジがありました。既存のデータモデルとビジネスロジックに新しい REST API を構築するというチャレンジです。

このプロセスの一環として、REST API 用に新しいフレームワークを構築しました。あまりにも便利であるため、自分たちだけに取っておくわけにはいきません。 そこで、Open Exchange の https://openexchange.intersystems.com/package/apps-rest で公開することにしました。 今後数週間または数か月の間に、これに関する記事がいくつか掲載される予定です。それまでは、GitHub のプロジェクトドキュメント)https://github.com/intersystems/apps-rest)に用意されたチュートリアルをご利用ください。

はじめに、設計の目標と意図についていくつか以下に示します。 すべての目標が実現したわけではありませんが、順調に進んでいます!

高速開発とデプロイ

REST アプローチは、Zen と同じアプリケーション開発のクイックスタートを提供し、一般的な問題を解決しながら、アプリケーション固有の特殊なユースケースに柔軟性を提供する必要があります。

  • REST アクセスへの新しいリソースの公開は、Zen DataModel と同じくらい簡単である必要があります。
  • REST リソースの追加/変更には、アクセスされているレベルでの変更が必要です。
  • REST による永続クラスの公開は、継承と最小限のオーバーライドで行えるべきですが、ハンドコーディング相当の機能もサポートする必要があります。 (これは、%ZEN.DataModel.Adaptor と %ZEN.DataModel.ObjectDataModel に似ています。)
  • エラー処理/レポート、シリアル化/逆シリアル化、検証などに関する一般的なパターンは、各アプリケーションの各リソースに対して再実装する必要があってはいけません。
  • SQL クエリ、フィルタリング、順序付け、および高度な検索機能とページネーションのサポートは、各アプリケーションに再実装するのではなく、組み込みである必要があります。
  • REST API はオブジェクトレベル(CRUD)でだけでなく、既存の API/ライブラリのクラスメソッドとクラスクエリに対しても簡単に構築できます。

セキュリティ

セキュリティは、付け足しで行うものではなく、設計/実装時に断定的に決定するものです。

  • REST 機能がクラス継承によって取得される場合、開発者が能動的にアクセスが必要な人とアクセス条件を指定するまでリソースにアクセスできない動作をデフォルトとします。
  • SQL 関連機能の実装を標準化することで、SQL インジェクション攻撃の標的を最小にします。
  • 設計には OWASP API トップ 10(参照: https://owasp.org/www-project-api-security)を考慮する必要があります。

持続可能性

アプリケーション設計の統一性はエンタープライズアプリケーションエコシステムにおいて強力なツールです。

  • 多様なハンドコーディングされた REST API と実装を蓄積するのではなく、一連のポートフォリオを通じて外観が似通った REST API を作成しなければなりません。 この統一性により、次が達成されなければなりません。
    • 共通のデバッグ手法
    • 共通のテスト手法
    • REST API に接続するための共通の UI 手法
    • 複数の API にアクセスする複合アプリケーションの開発の簡便性
  • REST を介して提供または受け入れられるオブジェクト表現のエンドポイントと形式は、エンドポイントに基づいて API ドキュメント(Swagger/OpenAPI)を自動的に生成できるほど十分に定義されている必要があります。
  • 業界標準の API ドキュメントに基づき、サードパーティまたは業界標準のツールを使用して、クライアントコードの一部(REST 表現に対応する typescript クラスなど)を生成できるようにします。
0
0 240
記事 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月 15, 2021 11m read

MonCaché — InterSystems Caché での MongoDB API 実装

免責事項:この記事は筆者の私見を反映したものであり、InterSystemsの公式見解とは関係ありません。

構想

プロジェクトの構想は、クライアント側のコードを変更せずに MongoDB の代わりに InterSystems Caché を使用できるように、ドキュメントを検索、保存、更新、および削除するための基本的な MongoDB(v2.4.9) API 機能を実装するところにあります。

動機

おそらく、MongoDB に基づくインターフェースを使って、データストレージに InterSystems Caché を使用すると、パフォーマンスが向上するのではないか。 このプロジェクトは、学士号を取得するための研究プロジェクトとして始まりました。

ダメなわけないよね?! ¯\(ツ)

制限

この研究プロジェクトを進める過程で、いくつかの簡略化が行われました。

  • プリミティブ型のデータのみを使用する: nullbooleannumberstringarrayobjectObjectId
  • クライアント側のコードは MongoDB ドライバーを使って MongoDB と連携する。
  • クライアント側のコードでは、MongoDB Node.js ドライバーを使用する。
  • クライアント側のコードでは、MongoDB API の基本的な機能のみを使用する:
    • findfindOne — ドキュメントの検索
    • saveinsert — ドキュメントの保存
    • update — ドキュメントの更新
    • remove — ドキュメントの削除
    • count — ドキュメント数のカウント

実装

タスクは最終的に次のサブタスクに分割されました。

  • 選択した基本機能ように、MongoDB Node.js ドライバーのインターフェースを再作成する。
  • このインターフェースを InterSystems Caché を使用してデータストレージ用に実装する。
    • Caché でデータベースの表現スキームを設計する。
    • Caché でコレクションの表現スキームを設計する。
    • Caché でドキュメントの表現スキームを設計する。
    • Node.js を使って Caché と対話するためのスキームを設計する。
    • 設計したスキームを実装して、少しテストする。 :)

実装の詳細

最初のサブタスクは問題ではなかったので、中間の説明を省略して、インターフェースの実装部分を説明します。

MongoDB は、データベースをコレクションの物理的なコンテナとして定義します。 コレクションはドキュメントのセットで、ドキュメントはデータのセットです。 ドキュメントは JSON ドキュメントのようですが、大量の許容型を持っています。つまり BSON です。

InterSystems Caché では、すべてのデータはグローバルに保存されます。 簡単に言えば、階層データ構造として考えることができます。

このプロジェクトでは、すべてのデータは単一のグローバル(^MonCache )に保存されます。

そのため、階層データ構造を使用して、データベース、コレクション、およびドキュメントの表現スキームを設計する必要があります。

Caché におけるデータベースの表現スキーム

実装したグローバルレイアウトは、数多くの潜在的なレイアウトの 1 つに過ぎず、これらのレイアウトには、それぞれにメリットと制限があります。

MongoDB では、1 つのインスタンスに複数のデータベースが存在する可能性があるため、複数の分離されたデータベースを格納できるようにする表現スキームを設計する必要があります。 MongoDB はコレクションをまったく含まないデータベースをサポートしていることに注意しておきましょう(これらを「空」のデータベースと呼ぶことにします)。

私はこの問題に対し、一番単純で一番明白なソリューションを選択しました。 データベースは、^MonCache グローバルの第 1 レベルノードとして表されます。 さらに、そのようなノードは、「空」のデータベースのサポートを有効にするために ”” 値を取得します。 問題は、これを行わずに子ノードを追加するだけの場合、それらを削除すると親ノードも削除されるということです(これがグローバルの動作です)。

したがって、各データベースは Caché 内で次のように表されます。

^MonCache(<db>) = ""

たとえば、「my_database」データベースの表現は次のようになります。

^MonCache("my_database") = ""

Caché におけるコレクションの表現スキーム

MongoDB は、コレクションをデータベースの要素として定義します。 単一のデータベース内にあるすべてのコレクションには、正確なコレクションの識別に使用できる一意の名前があります。 このことが、グローバルでコレクションを表現する単純な方法を見つけ、第 2 レベルのノードを使用する上で役立ちました。 次に、2 つの小さな問題を解決する必要があります。 1 つ目の問題は、コレクションがデータベースと同じように空であることができるということです。 2 つ目は、コレクションが一連のドキュメントであるということです。 そして、これらのドキュメントはすべて、互いに分離している必要があります。 正直なところ、コレクションノードの値として自動的に増分する値のようなカウンターを使う以外に良いアイデアが浮かびませんでした。 すべてのドキュメントには一意の番号があります。 新しいドキュメントが挿入されると、現在のカウンターの値と同じ名前が付いた新しいノードが作成され、カウンターの値が 1 つ増加するというアイデアです。

したがって、各 Caché コレクションは、次のように表されます。

^MonCache(<db>) = ""
^MonCache(<db>, <collection>) = 0

たとえば、「 my_database」データベース内の「my_collection」コレクションは次のように表されます。

^MonCache("my_database") = ""
^MonCache("my_database", "my_collection") = 0

Caché におけるドキュメントの表現スキーム

このプロジェクトでは、ドキュメントは追加の ObjectID という型で拡張された JSON ドキュメントです。 ドキュメントの表現スキームは、階層データ構造で設計する必要がありました。 いくつかの驚きに遭遇したのはここです。 まず、Caché では「ネイティブ」の null がサポートされていなかったために、使用できなかったことです。 もう 1 つは、ブール値が 0 と 1 の定数で実装されているということです。 つまり、1 が true で 0 が false ということなのです。 一番予想していた問題は、ObjectId を格納する方法を考え出す必要があるということでした。 結局、これらの問題はすべて最も簡単な方法でうまく解決されたのです。というか、解決されたと思いました。 以下では、各データ型とその表現について説明します。

Caché のインタラクションスキーム

Node.js ドライバーの選択は、InterSystems Caché と連携する上で論理的かつ単純な決定であるように思われました(ドキュメントサイトには、Caché と対話するためのドライバーがほかにも掲載されています)。 ただし、ドライバーの機能では不十分です。 私が行いたかったのは、1 回のトランザクションで複数の挿入を実行することだったので、 Caché 側で MongoDB API をエミュレートするために使用される一連の Caché ObjectScript クラスを開発することにしました。

Caché Node.js ドライバーは、Caché クラスにアクセスできませんでしたが、Caché からプロフラム呼び出しを行うことができました。 このことから、小さなツールが作成されました。ドライバーと Caché クラスを繋ぐ、一種のブリッジです。

結局、スキームは次のようになりました。

プロジェクトの作業を進めながら、NSNJSON(Not So Normal JSON: あんまり普通じゃない JSON)と名付けた、ドライバーを介して ObjectId、null、true、および false を Caché に「密輸」する特別な形式を作成しました。 この形式の詳細については、GitHub の対応するページ(NSNJSON)をご覧ください。

MONCACHÉ の機能

ドキュメント検索には、次の基準を使用できます。

  • $eq — 等号
  • $ne — 不等号
  • $not — 否定
  • $lt — より小さい(未満)
  • $gt — より大きい
  • $exists — 有無、存在

ドキュメントの更新操作には、次の演算子を使用できます。

  • $set — 値の設定
  • $inc — 指定された数による増分
  • $mul — 指定された数による乗算
  • $unset — 値の削除
  • $rename — 値の名前変更

以下のコードは、私がドライバーの公式ページから取得して少し修正したコードです。

var insertDocuments = function(db, callback) {
 var collection = db.collection('documents');
 collection.insertOne({ site: 'Habrahabr.ru', topic: 276391 }, function(err, result) {
     assert.equal(err, null);
     console.log("Inserted 1 document into the document collection");
     callback(result);
 });
}
var MongoClient = require('mongodb').MongoClient
  , assert = require('assert');
var url = 'mongodb://localhost:27017/myproject';
MongoClient.connect(url, function(err, db) {
    assert.equal(null, err);
    console.log("Successfully connected to the server");
    insertDocument(db, function() {
         db.close();
     });
});

このコードは、MonCaché と互換性を持つように簡単に変更することができます。

ドライバーを変更するだけです!

// var MongoClient = require('mongodb').MongoClient
var MongoClient = require('moncache-driver').MongoClient

このコードが実行されると、^MonCache グローバルは次のようになります。

^MonCache("myproject","documents")=1
^MonCache("myproject","documents",1,"_id","t")="objectid"
^MonCache("myproject","documents",1,"_id","v")="b18cd934860c8b26be50ba34"
^MonCache("myproject","documents",1,"site","t")="string"
^MonCache("myproject","documents",1,"site","v")="Habrahabr.ru"
^MonCache("myproject","documents",1,"topic","t")="number"
^MonCache("myproject","documents",1,"topic","v")=267391

デモ

ほかのすべてとは別に、小型のデモアプリケーションソースコード)を公開しました。また、サーバーの再起動とソースコードの変更を行わないで、MongoDB Node.js から MonCaché Node.js へのドライバーの変更を実演するために、Node.js を使って実装しました。 アプリケーションは、CRUD 演算を製品やオフィスで実行するための小さなツールであり、構成を変更(ドライバーを変更)するためのインア―フェースでもあります。

サーバーでは、構成で選択されたストレージ(Caché または MongoDB)に保存される製品オフィスを作成できます。

Orders]タブには、注文のリストが含まれています。 レコードは作成されていますが、フォームは未完成です。 プロジェクトの援護を歓迎しています(ソースコード)。

構成は、Configuration ページで変更できます。 このページには、MongoDB と MonCache の 2 つのボタンがあります。 対応するボタンをクリックすると、希望する構成を選択できます。 構成が変更されると、クライアントアプリケーションはデータソースに再接続します(実際に使用されているドライバーからアプリケーションを分離する概念)。

まとめ

結論を出すために、根本的な質問に答えさせてください。 そうです! 基本的な操作におけるパフォーマンスを向上させることができました。

MonCaché プロジェクトは GitHub に公開されており、MIT ラインセンスの下に提供されています。

簡易マニュアル

  • Caché のインストール
  • 必要な MonCaché コンポーネントを Caché に読み込む
  • Caché で MONCACHE 領域を作成する
  • Caché で、ユーザー名が「moncache」でパスワードが「ehcacnom」(moncache」の逆)のユーザーを作成する
  • 環境変数 MONCACHE_USERNAME = moncache を作成する
  • 環境変数 MONCACHE_PASSWORD = ehcacnom を作成する
  • 環境変数 MONCACHE_NAMESPACE = MONCACHE を作成する
  • プロジェクトで、依存関係を 'mongodb' から 'moncache-driver' に変更する
  • プロジェクトを始動! :-)

InterSystems 教育プログラム

InterSystems テクノロジーに基づく独自の研究プロジェクトを始めたい方は、InterSystems 教育プログラム専用の特設サイトをご覧ください。

0
0 147
記事 Mihoko Iijima · 3月 9, 2021 1m read

これは InterSystems FAQ サイトの記事です。

ダイナミックオブジェクトから JSON 文字列を生成するときに使用する %ToJSON() の引数にストリームオブジェクトを指定することでエラーを回避できます。

コード例は以下の通りです。

USER>set temp=##class(%Stream.TmpCharacter).%New()

USER>set jsonobj={}

USER>set jsonobj.pro1=["a","b","c","d"]

USER>set jsonobj.pro2=["あ","い","う","え"]

USER>do jsonobj.%ToJSON(temp)

USER>write temp.Size
51
USER>write temp.Read()
{"pro1":["a","b","c","d"],"pro2":["あ","い","う","え"]}

 

詳細はドキュメントもご参照下さい。

【IRIS】大きいダイナミック・エンティティからストリームへのシリアル化

大きいダイナミック・エンティティからストリームへのシリアル化

3
0 544
記事 Megumi Kakechi · 2月 18, 2021 3m read

jQuery($.getJSON と $.ajax)を使用した InterSystems IRIS データのJSON形式での取得方法をご紹介します。

以下にサンプルをご用意しました。

https://github.com/Intersystems-jp/REST_JSON_sample


サンプルには、次の内容が含まれます。

・REST + JSON
・REST + CORS

※それぞれ、$.getJSON と $.ajax で取得する方法を記載しています。

※サーバ側ではSelect文の実行結果をJSON_OBJECT関数を使用しJSON文字列で出力しています。
 関数については以下のドキュメントをご覧ください。
 JSON_OBJECT関数


使用手順は以下になります。

0
0 583
記事 Toshihiko Minamoto · 12月 21, 2020 9m read

$LIST のフォーマットと%DynamicArray、%DynamicObject クラス

IRIS には、様々なデータ値を含むシーケンスを作成する方法がいくつかあります (以前は Cache にもありました)。  長年に渡り使用されているデータシーケンスの 1 つに $LIST の文字列があります。  より最近のデータシーケンスには %DynamicArray クラスと %DynamicObject クラスがあり、両者ともに JSON の文字列表現に対応する IRIS サポートの一部となっています。  これら 2 つのシーケンスにはそれぞれ非常に異なるトレードオフがあります。

$LIST の文字列形式

$LIST 形式は、かつてメモリアドレスのスペースが小さいだけでなく、ディスクドライブも小さく、読み取り速度が遅かった時代に考案されました。  $LIST の形式は、複数の異なるデータ型で構成されるシーケンスをバイト数を可能な限り抑えながら 8 ビットの一般的な文字列にパッキングするためにデザインされました。

$LIST のシーケンスは、ObjectScript の $LISTBUILD 関数を使って作成します。 

$LIST の文字列の最も重要な機能は、データを 8 ビットの値で構成される最小のシーケンスにぎっしりパッキングできるという点です。  $LIST の文字列には、ObjectScript の様々なデータ型の複数の異なる表現を含めることができます。  それらのデータ型には、文字列型 (ObjectScript では引用符で囲んだ文字列リテラルを使って作成)、10 進浮動小数点型 (ObjectScript では数値リテラルを使って作成)、および IEEE 規格の 2 進浮動小数点型 (ObjectScript では $DOUBLE 関数を数値式に適用して作成) が含まれます。  ObjectScript の oref 型は $LIST の文字列によってサポートされていません。  $LIST の要素が作成されるとき、これらの値の内部表現に対しバイナリとバイトの非常にシンプルな圧縮化が行われます。  

$LIST の文字列の 2 つ目の重要な機能は、これらの 8 ビットの値を IRIS インスタンスまたは転送メディアのエンディアン特性 (ビッグなのかリトルなのか) を気にせずに、両立させながら同インスタンス間を送信できるという点です。  特に明記すべきは、ビッグエンディアンを使うデータベースとリトルエンディアンを使うデータベースの間で $LIST の文字列を移動させる際に、$LIST のコンポーネントの値が変更されないという点です。

データのパッキングと転送機能に次いで重要なのがパフォーマンスです。  すべての $LIST オペレーション ($LISTVALID を除く) は、実行するマシンインストラクションの数を最小限に抑えようとします。  $LIST の構造が無効なために、セグメントフォールトや他のシステム例外が起り得る場合を除き、$LIST オペレーションは $LIST のデータ構造が有効であるという想定の基に実行されます。  $LIST オペレーションは、それを有効性を確認するためのインストラクションは実行しません。  

空の $LIST は空の文字列です。  $LIST の文字列を連結するには、文字列の一般的な連結方法を用います。

$LIST の文字列をタイトにパッキングするということは、$LIST の i 個目の要素 ($LIST(ListString,i)) に直接ジャンプするのに効果的な情報はなく、先行する $LIST の要素をまず最初にスキャンする必要があることを意味します。  $LIST のすべての要素をスキャンする場合は、以下のコードを使ってはいけません     

Set N=$LISTLENGTH(ListString)
For i=1:1:N {
   Write $LIST(ListString,i),!
}

なぜなら、上の ObjectScript コードは、$LIST の値を O(N**2) の時間計算量でループするからです。  代わりに以下を使います。

Set P=0
While $LISTNEXT(ListString,P,value) {
    Write value,!
}

このコードは $LIST の値を O(N) の時間計算量でループします。  

%DynamicArray クラス

最近では、%DynamicArray クラス (および %DynamicObject クラス) が作成されています。  今は、はるかに大きなメモリを使用できるようになり (現在のメモリは $LIST の文字列がデザインされた時代のディスクドライブよりも大きくなっている)、ディスクドライブも大幅にサイズアップしています。  今は構造化されたデータを異なるシステム間で送信するための標準的な形式があります。  これらには、XML や JSON が含まれます。  %DynamicArray クラス (および %DynamicObject クラス) には、JSON の値と ObjectScript の値 (oref 型の値も含む) を正確に表現する機能があります。  JSON の値と ObjectScript の値は非常によく似ている上に、1 つの種類の値が別の種類の値に変換されても、形式はほとんど変わりません (例外は、ObjectScript の oref 型の値で、JSON 特有の表現には変換できません)。

JSON の仕様では、JSON 配列リテラルは角括弧で囲まれると説明されています。  例えば、 [0.1,"One tenth",2.99792E8,"speed of light in m/s"] のように囲みます。  JSON のオブジェクトリテラルは、中括弧で囲まれます。  例えば、 {"Name":"IRIS", "Size":64} のように囲みます。  ObjectScript 言語では、JSON の配列リテラルとオブジェクトリテラルを使用できる他、JSON 配列または JSON オブジェクトが持つ要素の値を丸括弧で囲み、ObjectScript のランタイム式として使用できるという拡張機能が 1 つあります。  例えば、 [(.1),("One " _ "tenth"),(2.99793*1000)] のようにできます。  丸括弧の中では、JSON の構文の代わりに ObjectScript の構文が使用されることに注意してください。

$LIST の文字列と %DynamicArray オブジェクトにはいくつか相違点があります。 (%DynamicObject オブジェクトのプロパティは %DynamicArray オブジェクトのプロパティと似ているので、以下のディスカッションで %DynamicObjectについて毎回言及するのは控えます。)

%DynamicArray の最初の要素は、インデックス 0 である一方で、$LIST の最初の要素はインデックス 1 となります。

%DynamicArray 要素は、ObjectScript の式で評価されるか、JSON 文字列に変換されるまでは、それが持つ元々の JSON の値または元々の ObjectScript の値を正確に表現でき、変換オペレーションにより小さな変化が生じることは一切ありません。

$LIST の文字列の最大サイズ (結果的にその要素のサイズ) は、現時点で 3641144 文字というObjectScript 文字列の最大長により制限されています。  %DynamicArray の最大サイズ (結果的にその要素のサイズ) はIRIS プロセスのメモリ空間のサイズにのみ制限されます。  これを踏まえ、多数の大きな %DynamicArray オブジェクトを同時に持つことは避けてください。これは、仮想アドレス空間を無制限で使用すると、過度のメモリーページングを引き起こす可能性があり、システムのパフォーマンスに影響するためです。

$LIST の文字列は、ObjectScript の文字列を格納できる場所であれば、どこにでも格納できます。 これは、文字列の長さが 3641144 文字をオーバーしないローカル変数やグローバル変数を含んでいます。  %DynamicArray オブジェクトをグローバル変数、ストリーム、またはファイルに移動する前には、%DynamicArray を他のデータ型 (通常は、JSON の表現を使用する文字列) に変換する必要があります。  JSON 文字列を持つ ObjectScript の文字列を返すには、引数なしの %ToJSON() メソッドを使用できます。  しかし、%DynamicArrays が大きいと、ObjectScript のグローバル変数や ObjectScript 式の中に収まりきらないほど長い JSON の文字列が生成される場合があります。  この場合、%ToJSON(output) メソッドを実行すると、%DynamicArray が JSON 文字列に変換され、%ToJSON メソッドの引数によって指定される %Stream もしくはファイルに送付されます。

新しい %DynamicArray を効率よく作成するには、ObjectScript コンストラクタを使ったり、%FromJSON(input) メソッドを呼び出したりすると良いでしょう。  %DynamicArray のコンポーネントを調べる際に %Get メソッドを使用するのも効率の良い方法です。  特に、$LIST(ListVar,i) を評価する場合とは違い、DynArray.%Get(i) の評価にかかる時間が 'i' の値や DynArray.%Size() の値に左右されない点に効率の良さが伺えます。  例えば、ObjectScript を使った次のループを実行すると、

Set N=DynArray.%Size()
For i=O:1:N-1 {
    Write DynArray.%Get(i),!
}

%DynamicArray に含まれるすべての要素が O(N) の時間計算量で出力され、$LIST(ListVar,i) メソッドを使いアクセスした要素が出力されるループを実行した場合に発生する O(N**2) の時間計算量は避けることができます。

次のようなループを書くこともできます。

Set iter = DynObject.%GetIterator()
While iter.%GetNext(.key , .value ) {
    Write "key = "_key_" , value = "_value,!
}

このループは、'DynObject' の未定義の要素をスキップする上に、%DynamicObject の要素と %DynamicArray の要素を出力するのに便利でもあります。

しかし、既に作成済みの %DynamicArray の内部要素を変更するのに 'Do DynArray.%Set(i,newvalue)' を使用すると、DynArray に割り当てられたメモリの圧縮がある程度必要になる可能性があるほか、様々な内部インデックスの変更が必要になると思われるため、処理に時間がかかる可能性があります。  配列要素を大幅に変更する場合は、ObjectScript の多次元配列変数を使ってデータを表す方が無難と言えます。それは、ObjectScript の多次元配列は、配列要素の変更、挿入、削除に必要な時間を最小限に短縮できるようデザインされているためです。

IRIS 2019.1 では、%Get(key,default,type) メソッドと %Set(key,value,type) メソッドの機能が拡張されています。  引数 'default' と 'type' は省略可能です。  'default' 引数には、指定された 'key' を持つ要素が未定義である場合に、DynObject.%Get(key,default) が返す値が入ります。  'type' パラメータとして使用できる値に、"stream" があります。これを使うことで、文字列値要素が ObjectScript の文字列に収まりきらないほど大きい場合に、%DynamicArray/%DynamicObject 要素の値を %Stream オブジェクトとして取得することができます。  'type' パラメータには、"json" という値も使用できます。これは、ObjectScript の値に使用される表現への変換を防止する JSON 仕様に従ってフォーマットされた文字列にアクセスします。 詳細は、クラスリファレンスのウェブページをご覧ください。  サポートされる 'type' の値は、IRIS の今後のリリースでさらに追加されると思われます。 

0
0 976
記事 Toshihiko Minamoto · 10月 22, 2020 7m read

InterSystems IRIS 2019.1は公開されてからしばらく経ちますが、気づかれていない可能性のある、JSONの処理の強化機能について説明したいと思います。 最新のアプリケーションを構築する際、特にRESTエンドポイントを操作する際は、JSONをシリアル化形式として扱うことが重要です。

JSONの書式

まず、JSONに書式設定を適用すると、人の目で読みやすくなります。 コードをデバックする際に、特定のサイズでJSONコンテンツを確認する場合に非常に役立ちます。 構造が単純化されていれば、ざっと目を通すことが容易にはなりますが、ネストされている複数の要素に遭遇すると、あっという間に読みづらくなります。 以下に簡単な例を示します。

{"name":"Gobi","type":"desert","location":{"continent":"Asia","countries":["China","Mongolia"]},"dimensions":{"length":1500,"length_unit":"km","width":800,"width_unit":"km"}}

人の目でより読みやすい形式を適用すると、コンテンツの構造を調べやすくなります。 適切な改行やインデントを使用した同一のJSON構造を見てみましょう。

{
  "name":"Gobi",
  "type":"desert",
  "location":{
    "continent":"Asia",
    "countries":[
      "China",
      "Mongolia"
    ]
  },
  "dimensions":{
    "length":1500,
    "length_unit":"km",
    "width":800,
    "width_unit":"km"
  }
}

この単純な例だけでも、出力がかなり大きくなるため、多くのシステムでこの書式がデフォルト設定となっていない理由は明確でしょう。 ただし、この詳細な書式により、基礎構造を簡単に読み取れるようになり、何かが間違っているかどうかを見つけやすくなります。

InterSystems IRIS 2019.1では、%JSONという名前のパッケージが導入されました。 パッケージには、上記で示したとおり、動的なオブジェクトと配列、そしてJSON文字列をより読みやすくできる整形ツールなど、いくつかの便利なユーティリティがあります。 %JSON.Formatterは非常に単純なインターフェースを持つクラスです。 すべてのメソッドはインスタンスメソッドであるため、必ずインスタンスを取得するところから始めます。

USER>set formatter = ##class(%JSON.Formatter).%New()

この選択の背景には、インデント(空白またはタブなど)特定の文字や行末記号の特定の文字を使えるように整形ツールを1回構成し、それ以降、必要な個所に使用できるようになるという理由があります。

Format()メソッドは、動的なオブジェクト化配列またはJSON文字列を取ります。 では、動的なオブジェクトを使用した簡単な例を見てみましょう。

USER>do formatter.Format({"type":"string"})
{
  "type":"string"
}

そして、以下は、同じJSONコンテンツでJSON文字列を使った例です。

USER>do formatter.Format("{""type"":""string""}")
{
  "type":"string"
}

Format()メソッドは、整形された文字列を現在のデバイスに出力しますが、直接変数に出力する場合のFormatToString()FormatToStream() も表示されます。

変速を変える

上記は素晴らしい機能ですが、それだけでは記事にする価値はないかもしれません。 InterSystems IRIS 2019.1では、永続オブジェクトと一時オブジェクトをJSONとの間でシリアル化する便利な方法も導入されています。 ここで調べるクラスは %JSON.Adaptorです。 概念が%XML.Adaptorに非常に似ているため、その名前が付けられています。 JSONとの間でシリアル化するクラスは、%JSON.Adaptorをサブクラス化する必要があります。 クラスは、いくつかの便利なメソッドを継承しますが、中でも%JSONImport()%JSONExport()の継承は非常に役立ちます。 これについては例で示すのが一番良いでしょう。 次のクラスがあったとします。

Class Model.Event Extends (%Persistent, %JSON.Adaptor)
{
 Property Name As %String;
 Property Location As Model.Location;
}

および

Class Model.Location Extends (%Persistent, %JSON.Adaptor)
{
 Property City As %String;
 Property Country As %String;
}

ご覧の通り、永続的なイベントクラスがあり、ロケーションにリンクしています。 両方のクラスは%JSON.Adaptorから継承されています。 このため、オブジェクトグラフを作成し、それをJSON文字列として直接エクスポートすることができます。

USER>set event = ##class(Model.Event).%New()
 
USER>set event.Name = "Global Summit"
 
USER>set location = ##class(Model.Location).%New()
 
USER>set location.City = "Boston"
 
USER>set location.Country = "United States of America"
 
USER>set event.Location = location
 
USER>do event.%JSONExport()
{"Name":"Global Summit","Location":{"City":"Boston","Country":"United States of America"}}

もちろん、%JSONImport()を使用して、逆の方向にエクスポートすることもできます。

USER>set jsonEvent = {"Name":"Global Summit","Location":{"City":"Boston","Country":"United States of America"}}
 
USER>set event = ##class(Model.Event).%New()
 
USER>do event.%JSONImport(jsonEvent)
 
USER>write event.Name
Global Summit
USER>write event.Location.City
Boston

インポートメソッドとエクスポートメソッドは、任意のネストされた構造で機能します。 %XML.Adaptorと同様に、対応するパラメーターを設定して、個々のプロパティのマッピングロジックを指定できます。 Model.Eventクラスを次の定義に変更してみましょう。

Class Model.Event Extends (%Persistent, %JSON.Adaptor)
{
 Property Name As %String(%JSONFIELDNAME = "eventName");
 Property Location As Model.Location(%JSONINCLUDE = "INPUTONLY");
}

上記の例とオブジェクト構造が変数eventに割り当てられていると仮定した場合、%JSONExport()を呼び出すと、次の結果が返されます。

USER>do event.%JSONExport()
{"eventName":"Global Summit"}

Nameプロパティは、eventNameフィールド名にマッピングされ、Locationプロパティは %JSONExport()呼び出しから除外されますが、存在する場合は%JSONImport()呼び出し中にJSONコンテンツに入力されます。 マッピングを調整するには、次のようなパラメーターを使用できます。

  • %JSONFIELDNAME: JSONコンテンツのフィールド名に対応します。
  • %JSONIGNORENULL: 開発者が文字列プロパティの空の文字列のデフォルト処理をオーバーライドできるようにします。
  • %JSONINCLUDE : このプロパティがJSON出力/入力に含まれるかどうかを制御します。
  • %JSONNULL: これがtrue(=1)である場合、未指定のプロパティはnull値としてエクスポートされます。 そうでない場合は、プロパティに対応するフィールドはエクスポート中に省略されます。
  • %JSONREFERENCE: オブジェクト参照の処理方法を指定します。 デフォルトは「OBJECT」で、参照先クラスのプロパティが参照先オブジェクトを表すために使用されることを示します。 その他のオプションは「ID」、「OID」、および「GUID」です。

これにより高度な制御が可能になり、非常に便利です。 オブジェクトを手動でJSONにマッピングする時代は終わりました。

あともう一つ

マッピングパラメーターをプロパティレベルで設定する代わりに、XDataブロックにJSONマッピングを定義することも可能です。 次に示すOnlyLowercaseTopLevelという名前のXDataブロックには、上記のeventクラスと同じ設定が行われています。

Class Model.Event Extends (%Persistent, %JSON.Adaptor)
{
 Property Name As %String;
 Property Location As Model.Location;
 XData OnlyLowercaseTopLevel
 {
 <Mapping xmlns="http://www.intersystems.com/jsonmapping">
     <Property Name="Name" FieldName="eventName"/>
     <Property Name="Location" Include="INPUTONLY"/>
 </Mapping>
 }
}

重要な違いが1つあります。それは、XDataブロックのJSONマッピングはデフォルトの動作を変更しないが、対応する%JSONImport()%JSONExport()の呼び出しで最後の引数として参照する必要があるということです。例を示します。

USER>do event.%JSONExport("OnlyLowercaseTopLevel")
{"eventName":"Global Summit"}

指定された名前のXDataブロックが存在しない場合、デフォルトのマッピングが使用されます。 このアプローチを使用すると、複数のマッピングを構成し、各呼び出しに必要なマッピングを個別に参照することができます。そのため、マッピングをより柔軟で再利用可能にしながら、より高い制御性を得ることができます。

これらの機能強化によって作業が楽になることを願っています。皆さんからのフィードバックを楽しみしています。 ぜひコメントを残してください。

0
0 607