見出し画像

BridgePoint で作成した概念モデルから DTDL 定義を自動生成する

本ドキュメントの利用は、https://github.com/kae-made/kae-made/blob/main/contents-license.md に記載のライセンスに従ってご利用ください。

https://github.com/kae-made/kae-made/blob/main/contents-license.md

はじめに

本稿は、BridgePoint で作成した概念モデルから、Azure Digital Twins で利用可能な DTDL による Onthology 定義スキーマを生成する方法を、概念モデルを使った自動生成に関する各種 Tips を交えながら解説します。
概念モデルについては、「Art of Conceptual Modeling」の解説を参照してください。また、サンプルとして、「概念モデリングチュートリアル ~ ホテルのコインランドリ」で作成した概念モデルを使用します。

何故、Azure Ditigal Twins の DTDL を生成するのか?

「Art of Conceptual Modeling」の、「概念情報モデルを IT システムに組み込む」の章でも解説していますが、復習を兼ねて、何故、Azure Digital Twins の DTDL を、概念モデルから自動生成するのか、その理由について解説しておきます。
概念モデルは、ビジネスで扱う様々なモノやコト等を抽出して、それらを特徴づける変数をまとめて概念クラスとして、加えて、それらの間の関係、役割を Relationship として定義した概念情報モデルと、それを基盤として、ビジネスで発生しうる事象と各事象発生に伴う振舞を概念振舞モデルとして定義し、ビジネスの対象とそれらに潜むルールを明確化します。概念情報モデルで定義された概念クラスのインスタンス群、及び、Relationship で制約されたインスタンス間のリンク群は、実際のビジネスが進行する過程で存在しうる現実世界の状況を正確に表現できるので、Digital Twins を実現する上で不可欠の要素であり、概念モデリングは、最近流行りの”デジタルトランスフォーメーション”や”メタバース”を実装するために必須のスキルセットであるといえます。
概念モデルを必要とビジネスを支援する IT システムを構築する場合、ビジネスで発生しうる様々なデータ、情報を保持、共有、加工するためには、何らかのデータストレージが必要であり、現実世界の情報をモデル化した概念モデルとそのデータストレージのスキーマは無矛盾でなければなりません。
IT システムで最も広く使われているのは、リレーショナルセオリーを元に作られた ”リレーショナルデータベース(RDB)”を使ったデータストレージです。

昨今のビッグデータを扱うのに使われる No SQL 系のデータストレージが一般的ではないかと考える読者もいるかもしれませんが、ビジネスで扱うデータに対して何も考慮せずに適当なフォーマットで保存したビッグデータに対して効率の良いクエリーを実行することは不可能です。No SQL系データストレージは単に、”リレーショナルセオリーに従ったテーブルとリレーションシップを事前に定義しなくてよい”というだけで、扱うデータのスキーマを定義しなくてよいという事ではありません。No SQL の場合も RDB と同様、概念モデリングは有効です。

概念モデルの教本でも説明している通り、概念情報モデルから RDB のスキーマを自動生成することも論理的に可能であり、本稿と関連するドキュメントを熟読して概念モデリングと変換による自動生成の技術をマスターすれば、誰にでも自動生成ツールが開発できるでしょう。実際にチャレンジする奇特な技術者の出現を筆者は待っています。
余談はこれぐらいにしておいて、主題に戻ります。
概念情報モデルと RDB のスキーマの違いは、前者が、Super Sub Relationship というコンセプトを持っていて、後者にはないということです。また、元々のリレーショナルセオリーでは(実際に使われている RDB では使えるのですが)Attribute の型はプリミティブ(つまり複合型はNG)だけであるという違いがあります。
著者が解説する概念情報モデルもリレーショナルセオリーをベースに定義されているので、まぁ、これらの違いは、ちょっと工夫すれば如何様にもできるギャップではあるのですが、変換による自動生成のソースと成果物の間のギャップが大きければ大きいほど、一般的には、自動生成環境の開発が困難になります。
一方で Azure Digital Twins がサポートする DTDL によるスキーマ定義は、上に挙げた相違がなく、細かな違いがあるにせよ、比較的簡単に自動生成環境が構築できるということで、本稿では、DTDL への変換をテーマに選んでいます。加えて、概念モデルから DTDL に変換できれば、IoT ソリューションの場合は、kae-made/dtdl-iot-app-generator: This application generates IoT application framework which can collaborate with Azure IoT Hub from IoT PnP DTDL file (github.com) を使って、IoT 機器側のアプリの多くの部分を自動生成することも可能です。

ここで、一旦立ち止まって、自動生成ではなく、旧来の人手による開発の場合を脳内実験していましょう。人手で開発を行う場合は、BridgePoint の様な概念モデリングの専用ツールを使った厳密な概念モデルの構築は必要ありません。他人が理解できる程度の詳細度と正確さを持った、絵と文章だけで十分です。一歩譲って、概念モデルを構築しない場合でも、それに代替する何か別のソフトウェア工学的な手段に基づいた要件定義は必ず行っているはずです。いずれにせよ、その段階では、IT システムが扱うビジネス要件にフォーカスした作業になり、その作業が一段落ついたところから、具体的にどんな実装技術を使って IT システムを開発するかという設計を行い、全体アーキテクチャや個々の要件をどのように実装するかに関する方針などを定義して、必要なプログラムやスクリプト、スキーマの作成、テスト、…が続きます。要件定義の成果物は、変換による自動生成のソースに対応し、プログラムやスクリプト、スキーマ、テストケース、…等は、自動生成の成果物に対応します。人手による開発と、変換による自動生成のどちらを採用した場合でも、必ず前述の”ソースと成果物のギャップ”は存在します。ギャップが大きければ大きいほど、人手による開発においても、設計、実装の難易度が高まるのは容易に想像がつくでしょう。人手による開発の場合は、ギャップが大きければ大きいほど、実装に必要な工数が増大し、それに関わるソフトウェア開発者への開発スキルのレベルは上がります。

変換ルールの定義(設計)

まず、DTDL の仕様を熟読し、理解します。このサイトから公開されているサンプルを実際に動かしてみるのも、理解するのに非常に役立つので是非試してみましょう。
さて、ざっくりと基本的な変換ルールを考えてみると、以下のルールで実現できそうです。

  • 概念クラス を DTDL の interface として定義する

  • 概念クラスの特徴値(BridgePointでは Attribute)を、DTDL の property として定義する

  • 概念クラスのオペレーションを、DTDL の command として定義する

これらは、概念モデル、DTDL 双方の定義から自然な対応付けと思われます。
次に、DTDL の telemetry を考えてみます。telemetry は、現実世界で刻一刻と発生する情報の抽象化なので、概念クラスが状態モデルを持つ場合の、その状態モデルで定義されたイベントを対応付けると意味的に間違っていないと思われるので、そのように対応付けます。

DTDL では、property、telemetry、command の request と response でデータ型を schema として定義します。BridgePoint で使える基本データ型と DTDL の shcema で使える基本データ型は異なるので、以下の様な対応付けを考えます。前者が BridgePoint、後者が DTDL と読んでください。

  • unique_id -> string

  • boolean -> boolean

  • integer -> integer

  • real -> double

  • timestamp -> dateTime

  • string -> string

  • state<State_Model> -> string

 BridgePoint で作成する概念モデルでは、モデル化対象の主題領域で意味のあるデータ型をユーザー定義データ型として定義でき、Attributeや、イベントの引数パラメータの型として利用できます。ユーザー定義型では、複数の下位変数から成る複合型と、有限の列挙子を持つ列挙型が定義できます。これらは、DTDL のデータ型を定義する schema で利用可能な、Object と Enum に対応付けることができます。
telemetry のデータ型は、そのイベントが発生した時点とし、イベントが引数パラメータを伴っている場合には、発生した時点を示す変数を加えて複合型とします。

次に、概念モデルの Relationship を考えます。概念情報モデルでは、Relationship は二つ以上の概念クラスを紐づける独立したコンセプトとして定義されるのに対し、DTDL では、interface の下位要素として一つの interface に従属する形で定義するようになっています。(筆者の個人的見解としては、DTDL においても Relationship は複数の interface が関与する定義なので、interface とは独立した単体での定義が、より本質的な定義ができるのではないかと考えている)ここでは、Relationship を Formalize した時に referential attribute が追加された側の概念クラスから変換される interface に DTDL の relationship を定義することにします。

Relationship の変換については、もう一つ問題が残っています。それは、Super Sub Relationship をどのように扱うかという問題です。オブジェクト指向プログラミングに頭が慣れ切っている読者は、自然と、「DTDL の extends を使えばよいのでは?」と思う事でしょう。しかし、概念情報モデルの Super Sub Relationship は、意味的には、extends ではないので、自然な決定とは考えられないので、合理的な判断が別途必要です。BridgePoint で描かれた概念モデルを注意深く観察すると、Formalize された Super Sub Relationship の Sub 側の概念クラスは、その Relationship の referential attribute を持っている事が見て取れます。結果的に、Relationship に関する前述の変換ルールで解決できるので、とりあえずは、それで作業を進めることにします。

以上、変換ルールを解説していきましたが、これらは全て著者の恣意的な決まり事です。「Art of Conceptual Modeling」の「ドメインと IT システム構築」で解説している通り、DTDL 定義は、一個の独立したドメインと考えられます。変換による自動生成のソースとなる概念モデルは、そのドメインとは無関係のドメインのモデルです。ドメインの定義から、この二つは意味的・概念的に全くの無関係です。全く無関係なものを対応付けるので、必然的に、全てのルールは恣意的なものになります。

一通り、変換ルールが決まったので、それに従って、BridgePoint
の概念モデルから適当にどれか一つ概念クラスを選択して、手書きで実際に、DTDL の JSON ファイルを書いてみます。ここでは、"Washing Machine" をサンプルとして書き下しています。

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:com:kae_made:LaundromatInHotel:WashingMachine;1",
  "@type": "Interface",
  "comment": "auto generated - generator version=0.0.1",
  "displayName": "Washing Machine",
  "contents": [
    {
      "@type": "Property",
      "name": "MachineID",
      "schema": "string",
      "comment": "@I0",
      "description": "..."
    },
    {
      "@type": "Property",
      "name": "FriendryName",
      "schema": "string",
      "description": "..."
    },
    {
      "@type": "Property",
      "name": "IsBusy",
      "schema": "boolean"
    },
    {
      "@type": "Property",
      "name": "RoomID",
      "schema": "string",
      "comment": "@R2"
    },
    {
      "@type": "Property",
      "name": "DoorID",
      "schema": "string",
      "comment": "@R14"
    },
    {
      "@type": "Property",
      "name": "Status",
      "schema": {
        "@type": "Enum",
        "valueSchema": "string",
        "displayName": "WashingMachineStatus",
        "enumValues": [
          {
            "name": "Ready",
            "enumValue": "Ready"
          },
          {
            "name": "Washing",
            "enumValue": "Washing"
          },
          {
            "name": "Drying",
            "enumValue": "Drying"
          },
          {
            "name": "WaitForTaking",
            "enumValue": "WaitForTaking"
          },
          {
            "name": "Reserved",
            "enumValue": "Reserved"
          },
          {
            "name": "Interupted",
            "enumValue": "Interupted"
          }
        ]
      }
    },
    {
      "@type": "Property",
      "name": "current_state",
      "schema": "string"
    },
    {
      "@type": "Telemetry",
      "name": "AssignedGuest",
      "displayName": "WashingMachine1:Assigned guest",
      "schema": "dateTime"
    },
    {
      "@type": "Telemetry",
      "name": "DoneWashing",
      "displayName": "WashingMachine3:Done washing",
      "schema": "dateTime"
    },
    {
      "@type": "Telemetry",
      "name": "DoneDrying",
      "displayName": "WashingMachine5:Done drying",
      "schema": "dateTime"
    },
    {
      "@type": "Telemetry",
      "name": "StartTaking",
      "displayName": "WashingMachine6:Start taking",
      "schema": "dateTime"
    },
    {
      "@type": "Telemetry",
      "name": "TakenOut",
      "displayName": "WashingMachine7:Taken out",
      "schema": "dateTime",
      "description": "...'"
    },
    {
      "@type": "Telemetry",
      "name": "Interupted",
      "displayName": "WashingMachine8:Interupted",
      "schema": "dateTime"
    },
    {
      "@type": "Telemetry",
      "name": "InteruptActionCompleted",
      "displayName": "WashingMachine9:Interupt action completed",
      "schema": "dateTime"
    },
    {
      "@type": "Command",
      "name": "IsAvailable",
      "response": {
        "name": "payload",
        "schema": {
          "@type": "Object",
          "fields": [
            {
              "name": "result",
              "schema": "boolean"
            },
            {
              "name": "status",
              "schema": "integer"
            }
          ]
        }
      },
      "displayName": "IsAvailable"
    },
    {
      "@type": "Command",
      "name": "StopExecution",
      "response": {
        "name": "status",
        "schema": "integer",
        "description": "HTTP response status code."
      },
      "displayName": "StopExecution"
    },
    {
      "@type": "Command",
      "name": "StartWashing",
      "request": {
        "name": "payload",
        "schema": {
          "@type": "Object",
          "fields": [
            {
              "name": "timeInMinutes",
              "schema": "integer"
            }
          ]
        }
      },
      "response": {
        "name": "status",
        "schema": "integer",
        "description": "HTTP response status code."
      },
      "displayName": "StartWashing"
    },
    {
      "@type": "Command",
      "name": "StartDrying",
      "request": {
        "name": "payload",
        "schema": {
          "@type": "Object",
          "fields": [
            {
              "name": "timeInMinutes",
              "schema": "integer"
            }
          ]
        }
      },
      "response": {
        "name": "status",
        "schema": "integer",
        "description": "HTTP response status code."
      },
      "displayName": "StartDrying"
    },
    {
      "@type": "Relationship",
      "@id": "dtmi:com:kae_made:LaundromatInHotel:R2_IsSetUpAt;1",
      "name": "R2_IsSetUpAt_LaundromatRoom",
      "maxMultiplicity": 1,
      "target": "dtmi:com:kae_made:LaundromatInHotel:LaundromatRoom;1"
    },
    {
      "@type": "Relationship",
      "@id": "dtmi:com:kae_made:LaundromatInHotel:R14_FrontDoor;1",
      "name": "R14_FrontDoor_DoorwithLock",
      "maxMultiplicity": 1,
      "target": "dtmi:com:kae_made:LaundromatInHotel:DoorwithLock;1"
    }
  ]
}

書いている内に、色々と足りないルールが見つかるので、逐次定義していきます。追加したルールを以下に列記します。

  • @id を interface、relationship に付与

    • interface の場合、"dtmi:org:domainname:classname;version"

    • relationship の場合、"dtmi:org:domainname:Rnumb_meaning;version"

    • 太字の部分は固定、その他は、変換するソースの属性に置き換える

  • BridgePoint のモデル側で説明記述がある場合、description 属性として追加

  • property が Identity や referetial の場合、comment で追加する

    • ※ 自動生成のロジック検証用

  • Relationship の相手方の多重度が、1、または、1c の場合、"maxMultiplicity:1" を加える。

概念モデルから幾つか概念情報モデルをピックアップ(2、3個で十分)して、定義した変換ルールに基づいて手書きをしてみます。作成したファイルは、実際に Azure Digital Twins にモデルアップロードして、エラーが無いか検証します。

以上で、変換ルールのドラフトが完成しました。次は変換ルールを自動生成ツールに組み込んでいきます。

自動生成環境作成

変換による自動生成環境作成方法については、「Art of Software Development Deliverables Generation」 で解説しているので、基本はそちらで学習していただきたい。本稿の流れも、それに沿って進めています。
この教本で説明している通り、情報ソースから DTDL を生成するルールは、何かと使い勝手の良い、T4 Template を使って記述します。作成した生成ルールは、dtdl-schema-generator/DTDLSchemaGeneration/Kae.XTUML.Tools.Generator.DTDL/template at main · kae-made/dtdl-schema-generator (github.com) から公開しているので参考にしてください。
さて、DTDL は、BridgePoint で作成した概念モデルから以下の流れで生成します。

  1. OOA of OOA を読み込み、概念モデルの概念情報モデルの定義をメモリ上に作成

  2. 作成した概念モデル情報の取り込み

  3. 取り込んだ概念モデル情報から一つづつ、概念クラスを取り出し、DTDL に変換

流れの中の、1. と 2. は、kae-made/xtuml-ooa-of-ooa-library (github.com) から公開している、Kae.XTUML.Tools.CIMModelResolver  ライブラリを使っています。このライブラリで、BridgePoint をインストールすると、インストールしたフォルダーの下に、org.xtuml.bp.product-win32.win32.x86_64\BridgePoint\tools\mc\schema\sql\xtumlmc_schema.sql  というファイルが使えるようになるので、これをOOA of OOA(概念モデルの概念情報モデルを詳細に定義したもの)として読み込みます。
また、BridgePoint でドメインの概念モデルを作成すると、workspace の直下にドメインの名前のフォルダーが作られ、その下に xtuml という拡張子を持つファイル(SQL的な文法でモデルの定義が保持されている)群に分散して、モデルの定義内容が保持されているので、このフォルダー以下のファイル群を丸ごと読み込みます。前述の BridgePoint で利用可能なデータ型は、rg.xtuml.bp.product-win32.win32.x86_64\BridgePoint\tools\mc\schema に、Globals.xtuml で定義されているので、各自が作成したドメインのモデルを読み込む前に読み込んでおきます。

string OOAofOOAModelFilePath = "...\\tools\\mc\\schema\\sql\\xtumlmc_schema.sql";
var vmodelResolver = new CIModelResolver.ConceptualInformationModelResolver();
modelResolver.LoadOOAofOOA(null, OOAofOOAModelFilePath);
logger.LogInfo($"Loaded ${OOAofOOAModelFilePath} as OOA of OOA model schmea");

string BaseDataTypeDefFilePath = "...\\tools\\mc\\schema\\Globals.xtuml";
string DomainModelFilePath = "...\\workspace\\DomainName";
string[] domainModels = { BaseDataTypeDefFilePath, DomainModelFilePath };
modelResolver.LoadCIInstances(domainModels);
logger.LogInfo($"Loaded Domain Models.");

一連の処理の実行で、xtumlmc_schema.sql で定義されたメタモデル(概念モデルの概念情報モデル)で定義された概念クラス、Relationship に従って、作成したモデルの全ての内容を取り出すことができます。
より具体的には、xtuml-ooa-of-ooa-library/Kae.CIM.MetaModel.CIMofCIM at main · kae-made/xtuml-ooa-of-ooa-library (github.com) で公開している、Kae.CIM.MetaModel.CIMofCIM ライブラリの CIClassDefs で定義された C# のクラス群のインスタンスとして保持され、CIModelRepository から、メタモデルの概念クラスの名前を指定して概念インスタンス群を取得可能になっています。
使用するメタモデルでは、各自が作成した概念モデルの概念クラスは、O_OBJ という名前の概念クラスのインスタンスとして取り出せます。

var modelRepository = modelResolver.ModelRepository;
var defs = modelRepository.GetCIInstances(CIMOOAofOOADomainName, "O_OBJ");
foreach (var def in defs)
{
    var objDef = (CIMClassO_OBJ)def;
}

上記のコードの様に、GetCIInstances() メソッドで、メタモデルの概念クラスの名前を指定して、ドメインモデル内の定義された概念クラスが取り出せます。'Laundromant in Hotel' を読み込んだ場合、

Laundromant in Hotel の概念情報モデル

概念クラスは15個定義されているので、defs には15個のインスタンスが入っています。このメソッドから取り出した時点の型は、CIClassDef というinterface型です。Kae.CIM.MetaModel.CIMofCIM  には、xtumlmc_schema.sql に定義された全ての概念クラスに対し、CIClassDef interface を realize した class が定義(詳細は、xtuml-ooa-of-ooa-library/README.md at main · kae-made/xtuml-ooa-of-ooa-library (github.com)を参照のこと)されており、それぞれの概念クラスで定義された特徴値や Relationship を元にした traverse を行う場合は、その class にキャストして使います。概念クラス(O_OBJ)には、CIMClassO_OBJ という名前の class が用意されています。メタモデルの他の概念クラスは、O_OBJの名前をそれぞれの名前に変えた class が用意されています。
CIMClassO_OBJの場合は、

  • Attr_Obj_ID - 概念クラスの一意の識別子

  • Attr_Name - 概念クラスの名前

  • Attr_Key_Lett - 概念クラスのプログラミング言語でも使えるキーレター

  • Attr_Descrip - BridgePoint 上で記述した説明文

といった、C# のプロパティが定義されて、それぞれ参照できます。
加えて、メタモデル上で定義された、O_OBJ が参加する Relationship を辿って関連するインスタンスを取り出すためのメソッドが用意されています。

  • R102 -> O_ATTR の集合

    • O_ATTR は概念クラスの特徴値。概念クラスが特徴値を複数持っている事を意味する。

  • R115 -> O_TFR の集合

    • O_TFR は概念クラスのオペレーション。概念クラスがオペレーションを複数持っている事を意味する。

  • R518 -> SM_ISM

    • SM_ISM は状態モデル。概念クラスは状態モデルを一つ持っているか、または、持っていない事を意味する。

これらのメソッドを利用することにより、ある概念クラスのインスタンスから R102 を辿れば、DTDL の property を定義するための情報ソースが得られ、R115 を辿れば、command を、R518 を辿れば SM_ISM のインスタンスが取り出せ、更に、CIMClassSM_ISM から、R517 -> SM_SM -> R502 で、SM_EVT(イベント)を取り出すことにより、telemetry のソースを取得可能です。
以上、メタモデルの定義に従って都度都度必要な情報ソースを取り出し、T4 Template に供給し、DTDL に変換していきます。

Relationship の場合は、ちょっと判り難いので、図で書いておきます。

メタモデルの Relationshipに関わる部分の一部を抽出

 DTDL の各 interface で、Relationship を定義する時の必要な情報の取得方法は、

  1. O_ATTR(特徴値)インスタンス(型はCIMClassO_ATTR)から R106 を辿って、Sub クラスが、O_RATTR(referential attribute)の場合に DTDL のrelationship を生成する。

  2. O_RATTR から R108 -> O_REF -> R111 -> R_RGO と辿り、R_RGO の Sub クラスを調べて(図は二項関係の場合のみ記載)、R_FORM(二項関係の片一方の端)を見つけ(赤い線のルート)、テキストフレーズや多重度を取得

  3. R_FORM から R208 -> R_SIMP -> R206 -> R_REL と辿って、概念情報モデルの Relationship の番号を取得し、前のステップのテキストフレーズも使って、DTDL の relationship 用の id 文字列を生成

  4. O_ATTR インスタンスから R105 -> O_OIDA -> R105 -> O_ID -> R104-> O_OBJ と辿って、関係付いている相手の概念クラス(O_OBJ)を見つける

といった流れになっています。

多分(いや確実に)ここでの説明は何を言っているのかさっぱりわからない珍紛漢紛なものだと思います。「Art of Conceptual Modeling」を完全に理解している読者なら xtumlmc_schema.sql を読み解いて、メタモデルの概念情報モデルを頭に思い描くことも可能なはずですが、それができるのは、日本では人の指の数より少ないのが現状です。有償での支援とさせていただきますが、マスターしたい方は、master@kae-made.jp までご連絡ください。

以上、くどくど説明するより、実物の T4 Template とそのヘルパークラスを眺めた方が良いと思うので、説明はここまでにしておきます。

  • DTDLjson.tt

    • 概念クラス毎(O_OBJのインスタンス)に、DTDL の定義を生成する。必要に応じて、PropertyDef.tt、TelemetryDef.tt、CommandDef.tt、RelationshipDef.tt  を使って、それぞれ property、telemetry、command、relationship の定義を生成する

  • PropertyDef.tt

    • 概念クラスの特徴値(O_ATTRのインスタンス)を元に、DTDL の property の定義を生成する

  • TelemetryDef.tt

    • 概念クラスの状態マシンのイベント(SM_EVTのインスタンス)を元に、DTDL の telemetry の定義を生成する

  • CommandDef.tt

    • 概念クラスのオペレーション(O_TFRのインスタンス)を元に、DTDL の command の定義を生成する

  • RelationshipDef.tt

    • 概念情報モデルの特徴値(O_RATTR)を元に、DTDL の relationship の定義を生成する

  • SchemaDef.tt

    • 特徴値(O_ATTR)やイベントの引数パラメータ(SM_EVTDI)、オペレーション(O_TFR)の戻り値型、引数の型の定義(S_DT)を元に、DTDL の schema の定義を生成する

  • EnumDef.tt

    • 列挙型(S_EDT)を元に、DTDL の Enum 型の schema 定義を生成する

  • ObjectDef.tt

    • 複合型(S_CDT)を元に、DTDL の Object 型の schema 定義を生成する

T4 Template の分割の目安は、生成するテキストの塊単位、生成するファイルの単位などで分割するとよいでしょう。ヘルパークラスは、それぞれのファイル名の本体に Code を付けたファイルで定義されています。

変換適用中の変換ルール変更

変換する種類によっては、変換実施中に、ある条件の時だけ、適用する変換ルールを変えたい場合があります。そのような条件付けのことを、xtUML(eXecutable and Translatable UML)コミュニティの流儀に従ってカラーリング(色付け)と呼ぶことにします。
Github から公開している自動生成ライブラリはその機能を持っています。
Super Sub Relationship の DTDL への変換は、他の二項関係と同様のルールで、DTDL の relationship に変換すると前の方で解説しました。実は、上に挙げた DTDLjson.tt、RelationshipDef.tt、PropertyDef.tt  では、色付けによって、extends を使った形式での変換もできるようになっています。その場合の追加ルールは以下の様になっています。

Super Sub Relationship の Sub 側の概念クラスの場合において、

  • Super 側の概念クラス(O_OBJ)を見つけ出し、extends で使う id を生成

  • referential attribute が Super Sub Relationship のみを Formalize している場合は、DTDL の relationship は生成しない

  • DTDL を生成中の 概念クラス(O_OBJ)と Super 側の概念クラスが共に状態モデルを持っている場合、BridgePoint の機能上、current_state という同名の attribute を持つので、Sub 側(つまり、DTDL の生成対象)の current_state の名前を、current_state_of_ObjKeyLetter に変更する

というルールを適用します。
relationship 形式で生成した DTDL 一式extends 形式で生成した DTDL 一式をそれぞれ Github から公開しているので、比較してみてください。
カラーリングの詳細については、現在作成予定の「自動生成における色付け」にご期待ください。

アプリケーションで利用可能なライブラリに仕立てる

本稿で解説した方法で開発した自動生成ライブラリは、kae-made/dtdl-schema-generator (github.com) から公開しています。このライブラリは、NuGet のパッケージ(Kae.XTUML.Tools.Generator.DTDL)としても公開しているので、この、難解なドキュメントを理解した方は、是非、使ってみてください。
ライブラリ化するにあたり、

  • 様々なアプリケーションに組み込んで使える事

  • Kae.XTUML.Tools.CIMModelResolver  を使った他の Generator 系ライブラリと共存できること

  • WPF 等を使った GUI アプリケーションが、一定のルールに従って GUI による入力項目を自動的に構築可能なこと

を考慮しています。これらを満たすため、kae-made/generator-framework (github.com) から公開中(NuGet でも、Kae.Tools.Generator として公開中)のフレームワークに従ってライブラリ化しています。
IGenerator interface の ContextParams を使うと、GUI アプリは、どんな入力情報を用意すればよいか推察できるようになっています。
この interface は、GenFolder というプロパティで、自動生成したファイル群の格納先が必要なことや、

  • ResolveContext()

    • ContextParams に設定した情報を解釈する

  • LoadMetaModel()

    • ContextParams に設定した情報を元にメタモデル定義をロードする

  • LoadDomainModels()

    • ContextParams に設定した情報を元にドメインモデルをロードする

  • Generate()

    • 成果物を生成する

という機能が必要なことを規定しています。
ライブラリ化した Generator のサンプルを dtdl-schema-generator/DTDLSchemaGeneration/ConsoleAppDTDLGenerator at main · kae-made/dtdl-schema-generator (github.com) から公開しています。使い方は、

ConsoleAppDTDLGenerator --metamodel metamodel_file [--meta-datatype meta_data_type_file] --base-datatype base_data_type_file --domainmodel domainmodel_folder --dtdlns namespace --dtdlver version --gen-folder folder [--colors color_file]
  • --metamodel metamodel_file

    • 必須:xtumlmc_schema.sql のファイルのパスを指定

  • --meta-datatype meta_data_type_file

    • オプショナル:メタモデルで使われている基本データ型の C# のデータ型への対応を定義したYAMLファイルのパスを指定
      ※ 特段の理由がない限り指定の必要なし

  • --base-datatype base_data_type_file

    • 必須:globals.xtuml のファイルのパスを指定

  • --domainmodel domainmodel_folder

    • 必須:BridgePoint の workspace の DTDL を生成したいドメインのフォルダーのパス

  • --dtdlns namespace

    • 必須: DTDL の id で使用する名前空間の指定

  • --dtdlver version

    • 必須: DTDL の id で使用するモデルのバージョン指定

  • --gen-folder folder

    • 必須: 生成ファイルの格納先のフォルダーのパスを指定

  • --colors color_file

    • オプショナル: カラーリング定義ファイルのパスを指定

BridgePoint をインストールして、artifacts-laundromat-in-hotel-tutorial/model/LaundromatInHotel at main · kae-made/artifacts-laundromat-in-hotel-tutorial (github.com) から公開しているサンプルドメイン概念モデルを使えば、お試しいただけます。

紹介したアプリケーション実行用のオプションを眺めると、

  • 概念モデル、及び、概念モデルを解釈するために必要な情報

  • 自動生成アプリとして最低限必要な情報

  • 自動生成時に生成ファイルに埋め込まれる付加情報

に分類できます。最後の分類は、DTDL の名前空間やモデルバージョンです。この情報は、DTDL が属しているドメインの世界の情報であり、変換による自動生成のソースであるドメイン概念モデルには含まれていないので、自動生成時に追加が必要なわけです。
変換による自動生成”は、言ってみれば、情報ソースと変換ルールの掛け算が基本であり、生成するターゲット側のドメインに属する情報は、自動生成時に都度都度注入する仕組みが必要です。

テスト

自動生成のライブラリと、そのライブラリをテストするコード一式が出来上がったら、自動生成した成果物、本稿では、DTDLのJSONファイルを Azure Digital Twins に実際にアプロードしてテストを行います。
アップロードは、Azure Digital Twins Explorer でも、Azure Digital Twins の SDK を使ったプリケーションでも構いません。生成した DTDL ファイルに問題があれば、エラーが発生します。発生したエラーの内容を見ながら、地道に、変換ルールを修正していきます。
本稿で解説する流れで作成した変換ルールのテストでは、15の障害が発生しました。そのうちの13の障害はいずれも単純なスペルミスでした。この様な障害は、比較的簡単に修正が可能なので、見つけたらつぶす、をひたすら繰り返します。個人的な感想ですが、T4 Template のエディタがもう少し高機能なら、三分の二ぐらいは変換ルール作成時点で排除できる気がしています。ここは、マイクロソフトさんにひと踏ん張りしてほしいところです。
以下、スペルミス以外の障害を参考までに紹介しておきます。
先ず、timestamp、inst_ref<Timer> 等の一見基本データ型と思えるデータ型が、実は基本型ではなく、ユーザー定義型(S_UDT)だったために発生した障害です。これはやってみないと判らない類のもので、発見したら対応方法を考えて対処するという形になります。
次が、BridgePoint で記述するモデル要素への説明文の DTDL の description プロパティへの変換です。BridgePoint側では、複数行にわたる文章を記述できます。これをそのまま description の値にすると、

   "description": "<#= elemDef.Attr_Descrip #>"

当然、JSONでは許容されない改行ありの記述になってしまいます。

    "description": "ホテルの洗濯機。
予め設定されている選択可能な洗濯時間、乾燥時間を…",

こんな感じですね。URL Encodingするなどいろいろやってみましたが、文字列が長くなると、Azure Digital Twins の 512文字制約に引っかかってしまい、うまくいきません。512文字以上は捨てるルールも試してみましたが、一部だけしか変換されないのもなんとなく変です。
このルールは、BridgePoint側で説明文が記述されている時だけ生成されるルールなので、最終的には、「概念モデル側で説明がありますよ」という意味で、どこに記載があるかを示す文面を生成することに落ちつきました。

また、概念情報モデル側で、Super Sub Relationship が使われている Sub 側の概念情報モデルで、Super 側の概念情報モデルを指し示す DTDL の relationship の id が重複するという障害も発生しました。具体的には、Laundromant in Hotel サンプルでは、NonReservableWashingMachine とReservableWashingMashine の R15 の関係が、その問題を引き起こしていました。この障害を解決するため、relationship の id は、前述のルールに加えて、最後に、"_subClassName"と、それぞれの概念クラスの名前を追加することで解決しています。
この障害は、変換ルールの定義の際に気が付きそうな問題ですが、見逃してしまったということです。これが手書きによる人力作成だったら大変ですが、変換による自動生成の場合は、変換ルールを修正して再生成すれば、同様な問題を抱えた DTDL は全て修正されるので気が楽です。

以上、実際のテスト過程を振り返りましたが、変換による自動生成のテストでは、変換ルールで想定している全てのケースが使われている概念モデルを準備することが重要です。

最後に

以上で、本稿は終了です。
変換ルールの定義において、サンプルとして Laundromant in Hotel の概念モデルを使ってはいますが、全ての変換ルールが、メタモデルの定義だけを対象にして定義されていて、Laundromant in Hotel で定義された内容には一切依存していない事に気づいていただけたでしょうか?
これの意味するところは、本稿で紹介した 「DTDL Generator は、BridgePoint で作成可能な、様々なドメインを対象とした概念モデルであれば、それをソースとして DTDL のファイル群を生成できる」ということです。
これは、概念情報モデルの概念情報モデルであるメタモデルを使った変換による実装を活用した Generator が持つ特徴の一つです。

本稿では、DTDL の生成を紹介しましたが、変換による自動生成で生成できるものは、読者のアイデア次第です。

さて、本稿で紹介した DTDL Generator を使って BridgePoint から DTDL が生成でき、Azure Digital Twins 上でモデル定義を行えることは理解できたかと思います。
実際にビジネス向けの IT システムとして成り立たせるためには、このストレージをベースとし、概念情報モデルで定義された制約、例えば、予約を行うには、Guest Stay と R5 でリンクされた Guest や R6 でリンクされた Card Keyが無ければならない、Washing Machine のインスタンスを作成するには、R15 でリンクされた Non Reservation Washing Machine、または、Reservable Washing Machine のどちらかのインスタンスがなければならない、といった制約を満たすための実装ロジックや、概念モデルの振舞モデルを実装したコードが最低限必要になります。これらのロジックの基となる定義が概念モデル内で定義されていれば、その定義を元に、実装で使用するサービスや技術を使った、実際に動かすことが可能なプログラムコード群一式を自動生成する環境を、本稿で解説した変換による自動生成技術を使って 開発することも可能です。
この Topic は、後日、「Azure Digital Twins、Azure Functions で動く、概念モデルからのコード一式自動生成」として公開予定です。ご期待ください。

この記事が気に入ったらサポートをしてみませんか?