Mendix Platform SDKについて

# はじめに

[既存システムをMendixに移行する](https://qiita.com/puffy/items/536f962e0f0bd0cf28db) のように、既存のシステムをリバースエンジニアリングした結果をもとに新しいアプリを作成する場合、Excelのシートを作成して「スプレッドシートからMendixアプリを生成する」のが簡単な方法です。

より詳細にアプリを構成したい場合や、構成を繰り返し行いたい場合は、Mendix Platform SDKを使って自動化することができます。

# Mendix Platform SDKについて

Mendix Platform SDKを使うとAPI経由でリポジトリにアクセスすることができます。

![sdk-diagram](https://docs.mendix.com/apidocs-mxsdk/mxsdk/attachments/sdk-intro/sdk-diagram.png)

Mendix Platform SDKのドキュメントはこちらをご覧ください

- [Bendix Platform SDKのドキュメント](https://docs.mendix.com/apidocs-mxsdk/mxsdk/)
- [ビルドシステム社提供のMendix Platform SDK日本語ドキュメント](https://mendixdocs.buildsystem.jp/apidocs-mxsdk/mxsdk/)


# 開発環境準備

以下はMacを使用した例になります。 [Node.js](https://nodejs.org/) が必要です。Macの場合はインストーラーを使用する方法の他にHomebrewを使用してNode.jsのインストールする方法があります。

Node.js をインストールしたら、作業を行うディレクトリー（以下の例ではmy-app）を作成してからそのディレクトリーに移動し、以下のコマンドで開発環境を準備します。これにより最小限のMendix Platform SDKが準備できます。

```
mkdir my-app
cd my-app
npm init --yes
sudo npm install -g typescript
npm install mendixmodelsdk mendixplatformsdk when @types/when --save
tsc --init --target es2018
```

tsconfig.json は以下の設定が必要です。

```json:tsconfig.json
{
    "compilerOptions" : {
        "module" : "commonjs",
        "target" : "es2018",
        "strict": true
    },
    "files" : [
        "script.ts"
    ]
}
```

# 最初のアプリ作成

開発環境が準備できたことを確認するため、最初のアプリを検証してみます。Mendixに新しいアプリを作成するスクリプトです。

先ほどのディレクトリーに script.ts という名前のファイルを作成して、以下の内容を入力します。お好みに応じてVisual Studio Codeやviなどのエディタを使用します。

username は自分のMendixユーザーID、apikey はMendixの My Profile から歯車のマークの Setting を選んで、API Keys で Create new API key をクリックして作成します。

```typescript:script.ts
import { MendixSdkClient, OnlineWorkingCopy } from 'mendixplatformsdk';
import { domainmodels } from 'mendixmodelsdk';

const username = 'richard.ford51@example.com';
const apikey = '364fbe6d-c34d-4568-bb7c-1baa5ecdf9d1';
const client = new MendixSdkClient(username, apikey);

async function main() {
    const project = await client.platform().createNewApp(`NewApp-${Date.now()}`);
    const workingCopy = await project.createWorkingCopy();

    const domainModel = await loadDomainModel(workingCopy);
    const entity = domainmodels.Entity.createIn(domainModel);
    entity.name = `NewEntity_${Date.now()}`;
    entity.location = { x: 100, y: 100 };

    try {
        const revision = await workingCopy.commit();
        console.log(`Successfully committed revision: ${revision.num()}. Done.`)
    } catch (error) {
        console.error('Something went wrong:', error);
    }
}

function loadDomainModel(workingCopy: OnlineWorkingCopy): Promise<domainmodels.DomainModel> {
    const dm = workingCopy.model().allDomainModels().filter(dm => dm.containerAsModule.name === 'MyFirstModule')[0];

    return dm.load();
}

main();
```

tsk コマンドでビルドします。

```sh
tsc
```

生成されたJavaScriptを実行します。App-nnnnnnnn というアプリが作成されます。Mendixにログインして確認してください。

```sh
node script.js
Creating new project with name NewApp-[...] for user [...]
Project creation for user [...] underway with job id: [...]
Project created successfully for user [...] with id [...]
Creating new online working copy for project [...] : NewApp-[...]
Successfully created new online working copy [...] for project [...]: NewApp-[...]
Successfully opened new online working copy [...] for project [...]: NewApp-[...]
Closing connection to Model API...
Closed connection to Model API successfully.
Committing changes in online working copy [...] to team server project [...] branch null base revision -1
Successfully committed changes to team server: revision 3 on branch null
Successfully committed revision: 3. Done.
```

# Excel からモデル作成

[既存システムをMendixに移行する](https://qiita.com/puffy/items/536f962e0f0bd0cf28db) で作成したExcelのデータベース定義からMendixのモデルを作成しましょう。

新しいディレクトリーを作成してテンプレートを template.xlsx として保存します。

サンプルのテンプレートは [こちら](https://github.com/naofum/mendix-excel2entity/blob/master/template.xlsx?raw=true) から。

```sh
mkdir excel2entity
cd excel2entity
npm init --yes
npm install mendixmodelsdk mendixplatformsdk when @types/when --save
nom install xlsx
tsc --init --target es2018
```

tsconfig.json は以下の設定が必要です。

```json:tsconfig.json
{
    "compilerOptions" : {
        "module" : "commonjs",
        "target" : "es2018",
        "strict": true
    },
    "files" : [
        "script.ts"
    ]
}
```

作成するスクリプトのサンプルです (ES2018版)。旧版は [こちら](https://github.com/naofum/mendix-excel2entity)。
自分の環境に合わせてスクリプトを修正してください。

```typescript
const username = `{YOUR_USERNAME}`; // MendixユーザーID
const apikey = `{YOUR_API_KEY}`; // My Profile -> Setting -> API Keys
const projectName = `{YOUR_PROJECT_NAME}`; // アプリ名(API-nnnn)
const projectId = `{YOUR_PROJECT_ID}`; // APP ID
```

```typescript:excel2entity.ts
/*
    Copyright (C) 2017 Naofumi
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import { MendixSdkClient, OnlineWorkingCopy, Project, Revision, Branch, loadAsPromise } from "mendixplatformsdk";
import { ModelSdkClient, IModel, projects, domainmodels, microflows, pages, navigation, texts, security, IStructure, menus } from "mendixmodelsdk";
import when = require('when');
import XLSX = require('xlsx');
const utils = XLSX.utils;

const username = `{YOUR_USERNAME}`;
const apikey = `{YOUR_API_KEY}`;
const projectName = `{YOUR_PROJECT_NAME}`;
const projectId = `{YOUR_PROJECT_ID}`;
const moduleName = "MyFirstModule";
const revNo = -1; // -1 for latest
const branchName = null // null for mainline
const wc = null;
const client = new MendixSdkClient(username, apikey);
const project = new Project(client, projectId, projectName);
const revision = new Revision(revNo, new Branch(project, branchName));

async function main() {
  const workingCopy = await client.platform().createOnlineWorkingCopy(project, revision);
  const domainModel = await loadDomainModel(workingCopy);
  await createEntities(domainModel);

  try {
    const revision = await workingCopy.commit();
    console.log(`Successfully committed revision: ${revision.num()}. Done.`)
  } catch (error) {
    console.error('Something went wrong:', error);
  }
}

function loadDomainModel(workingCopy: OnlineWorkingCopy): Promise<domainmodels.DomainModel> {
    const dm = workingCopy.model().allDomainModels().filter(dm => dm.containerAsModule.name === moduleName)[0];

    return dm.load();
}

function createEntities(domainModel: domainmodels.DomainModel): when.Promise<void> {
    var workbook = XLSX.readFile('template.xlsx');
    var sheet_name_list = workbook.SheetNames;
    var xLoc = 100;
    var yLoc = 100;
    sheet_name_list.forEach(function (sname) {
      var worksheet = workbook.Sheets[sname];
      var cell_a1 = worksheet['A1'];
      if (((cell_a1 ? cell_a1.v : undefined) == 'Table name (logical name)') &&
          (sname != 'all attributes') &&
          (sname != 'Tables in no category') &&
          (sname != 'all tables') &&
          (worksheet['B2'].v == 'PARTY')) { // PARTY TABLE only
        var entity = domainmodels.Entity.createIn(domainModel);
        entity.name = camelCase((worksheet['B2'] ? worksheet['B2'].v : '').toLowerCase());
        entity.documentation = (worksheet['B1'] ? worksheet['B1'].v : '');
        entity.location = { x: xLoc, y: yLoc };
        var range = utils.decode_range(worksheet['!ref'] ? worksheet['!ref'] : 'A1:M99');
        for(var R = 7; R <= range.e.r; ++R) {
          if (!worksheet['A' + R]) {
            break;
          }
          var attr = domainmodels.Attribute.createIn(entity);
          var type = (worksheet['C' + R] ? worksheet['C' + R].v : '');
          var len = (worksheet['D' + R] ? worksheet['D' + R].v : '');
          attr.name = camelCase((worksheet['B' + R] ? worksheet['B' + R].v : '').toLowerCase());
          if (type.lastIndexOf('char', 0) === 0) {
            var stringAttributeType = domainmodels.StringAttributeType.createInAttributeUnderType(attr);
            stringAttributeType.length = len;
            attr.type = stringAttributeType;
          } else if (type.lastIndexOf('varchar', 0) === 0) {
            var stringAttributeType = domainmodels.StringAttributeType.createInAttributeUnderType(attr);
            stringAttributeType.length = len;
            attr.type = stringAttributeType;
          } else if (type.lastIndexOf('number', 0) === 0) {
            var decimalAttributeType = domainmodels.DecimalAttributeType.createInAttributeUnderType(attr);
            attr.type = decimalAttributeType;
          } else if (type.lastIndexOf('timestamp', 0) === 0) {
            var dateTimeAttributeType = domainmodels.DateTimeAttributeType.createInAttributeUnderType(attr);
            attr.type = dateTimeAttributeType;
          } else {
            var stringAttributeType = domainmodels.StringAttributeType.createInAttributeUnderType(attr);
            attr.type = stringAttributeType;
          }
          attr.documentation = (worksheet['A' + R] ? worksheet['A' + R].v : '');
        }
        xLoc += 50;
        yLoc += 50;
      }
    });
    return when.resolve();
}

function camelCase(str: string){
    str = str.charAt(0).toLowerCase() + str.slice(1);
    return str.replace(/[-_](.)/g, function(match, group1) {
        return group1.toUpperCase();
    });
}

main();
```

ビルド後実行します。

```sh
tsc
node excel2entity.js
```

Excelシートを読み込んでエンティティが作成されます。リポジトリ側が更新されるのでMendix Studio Proでプロジェクトを開いている場合は、Version ControlからUpdateして最新のリポジトリを取り込むと、モデルが更新されていることが確認できます。

# 応用

このように、Mendix Platform SDKを使用すると、

- レガシーコードのインポート

![import](https://docs.mendix.com/apidocs-mxsdk/mxsdk/attachments/16714511/16844116.png)

- アプリの分析

![analyze](https://docs.mendix.com/apidocs-mxsdk/mxsdk/attachments/16714511/16844117.png)

- アプリのエクスポート

![export](https://docs.mendix.com/apidocs-mxsdk/mxsdk/attachments/16714511/16844114.png)

- アプリの変更

![modify](https://docs.mendix.com/apidocs-mxsdk/mxsdk/attachments/16714511/16844115.png)

できます。