Category Archives: Uncategorized

JavaScriptのasync/awaitを再理解してみた

半年ぶりの投稿になりますが、表題のことをやってみました。ちょっと時間の余裕ができたので、改めて勉強しようとしていろんなサイトを見たのですが、「async関数はPromiseを返す」ということが前提だけに、まずはPromiseを勉強するという体裁になっています。一方、「async/awaitは結局のところ、非同期呼び出しを同期呼び出しのように書いてよし」というシンプルに考えていいのかどうか、非常に迷うところでした。ということで、忙しい世の中ですので先に言ってしまうと、そこまでシンプルには残念ながら行かないことがわかります。INTER-Mediator Ver.6の実装で結構苦労したことが、本当にその苦労が必要だったのかということの検証でもあるのですが、これは個人的なところですね。

まず、次のようなプログラムがあるとします。以下は、適当な.jsファイルに入れて、nodeコマンドで実行します。ただし、「npm install xmlhttprequest」あるいはsudo付きで、XMLHttpRequestモジュールのインストールをお忘れなく。ブラウザで適当に動くようにされているのなら不要です。単にWebサイトのデータをAJAXで取り出すプログラムですが、どんな順序で実行されたかわかるように、コンソールへ出力を残しています。

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

async function aFunc() {
  console.log('start aFunc');
  const r = new XMLHttpRequest();
  r.open('GET', 'https://msyk.net')
  r.onreadystatechange = () => {
    if(r.readyState == 4) {
      console.log('receive aFunc');
    }
  }
  console.log('setup aFunc');
  r.send()
  console.log('send aFunc');
}

console.log('before aFunc');
let a = aFunc(); // async関数を普通に呼び出してみた
a.then(console.log('act1')) // thenが呼び出せるということはPromiseが返っている
console.log('after aFunc');

これを実行すると、次のようにコンソールに出てきます。ポイントは「after aFunc」より後に「recieve aFunc」が出るということです。AJAXによるダウンロード処理は非同期で行われているので、関数aFunc内に記述したコードが実行し終わる前にaFuncを呼び出しているステートメントの次に行くのです。これが非同期呼び出しの動作です。JavaScriptは「プログラムとして書いたコード」は、並列では動きませんが、「書いた順序」では実行しないというよくあるプログラムです。thenについてはPromiseを勉強すれば必ず説明されていますので、省略します。

before aFunc
start aFunc
setup aFunc
send aFunc
act1
after aFunc
receive aFunc

さて、ここで、やりたいことは「通信が終わったら次に行く」と言う動作です。要するに、「aFuncを同期的に呼び出したいのでしょ。だったら、awaitを入れればいいじゃん」と思うところですね。やってみましょう。ここで、単にaFunc()の前にawaitをつけると文法エラーになります。awaitはasync関数内部でしか使えません。グローバルエリアはasyncではないので、エラーになるので、新たにbFunc関数を作り、そこで実行します。

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

async function aFunc() {
  console.log('start aFunc');
  const r = new XMLHttpRequest();
  r.open('GET', 'https://msyk.net')
  r.onreadystatechange = () => {
    if(r.readyState == 4) {
      console.log('receive aFunc');
    }
  }
  console.log('setup aFunc');
  r.send()
  console.log('send aFunc');
}

async function bFunc() {
  console.log('before aFunc');
  let a = await aFunc(); // async関数を普通に呼び出してみた
  //a.then(console.log('act1')) // thenはエラーになり呼び出せない
  console.log('after aFunc');
}

bFunc()

おっと、thenの呼び出しでエラーになるので、コメントします。そして、コンソールの出力結果を見ると、次のようになっていました。前と変わりません。after aFuncより後にreceive aFuncがあるということは、aFuncのコールが終わってから実行したコードよりも後に、aFuncの中身が処理されています。つまり、async/awaitを使っても同期呼び出しされていないということです。おやおや、おかしいですね。

before aFunc
start aFunc
setup aFunc
send aFunc
after aFunc
receive aFunc

ここで、thenの呼び出しでエラーを出してみると、This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().などと書かれています。はい、ここでPromiseの知識が必要です。Promiseでは、thenやcatchメソッドを呼び出すことで、「先に進める」的な動作をするというか、それを期待しているとも言えます。thenメソッドの2つの引数にresolveやrejectメソッド(もちろん、その名前でなくてもいいのですが、たぶん、皆さんは常にそう書いていると思います)が乗ってくるので、そちらの方がお馴染みかもしれません。ともかく、Promiseを書かないで、asyncにしたからと言って、そのままawaitできるのかというと、この例で示すようにできないということになります。

では、どう書けばいいかというと、こういうことです。非同期処理部分をPromiseで包みます。既存の処理をPromise化するのに適した方法として、コンストラクターの引数に、処理を記述する方法です。処理が終わったらresolve()を呼び出します。reject()は省略します。

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

async function aFunc() {
  console.log('start aFunc');
  const p = new Promise((resolve, reject) => {
    const r = new XMLHttpRequest();
    r.open('GET', 'https://msyk.net')
    r.onreadystatechange = () => {
      if(r.readyState == 4) {
        console.log('receive aFunc');
        resolve()
        console.log('resolve aFunc');
      }
    }
    console.log('setup aFunc');
    r.send()
    console.log('send aFunc');
  })
}

async function bFunc() {
  console.log('start bFunc');
  await aFunc()
  console.log('end bFunc');
}

bFunc()

結果はこのようになります。おや?receive aFuncが、end bFuncより後にあります。これじゃあ、全然同期的に呼び出しているんじゃないですね。なんででしょうね?どこが間違っているのでしょう? これは難しいクイズですね。実はブログを執筆しながら、偶然発見してしまったトラブルです。

start bFunc
start aFunc
setup aFunc
send aFunc
end bFunc
receive aFunc
resolve aFunc

はい、答えは、aFunc関数の最後に「return p」つまり、生成したPromiseを返すというステートメントが必要になります。async内にPromise生成を記述しないと、自動的に生成されるけど、書いてしまったら、ちゃんと自分で返すようにしないといけないと言うことですね。そうすれば、コンソールには次のように出力されます。無事、非同期通信処理の結果を受け取ってから、await aFunc()の次のステートメントを実行しています。つまり、aFunc()は非同期処理が記述されているけども関数呼び出しは同期的に処理が行われたと言えます。

start bFunc
start aFunc
setup aFunc
send aFunc
receive aFunc
resolve aFunc
end bFunc

ところで、前のプログラムで、bFunc()の部分の次にconsoleを記述したら、どの順序で実行されるでしょう。一番最後? それは違いますね。receive aFuncの前にbFunc()の次に記述したステートメントが実行されます。つまり、bFunc()によって呼び出されるのは非同期関数なのです。当たり前ですね、asyncと頭に書いているから。なので、さらにbFunc()呼び出しの部分を同期的にやりたいとなると、ちょっとしつこいですが、こんな風に書かないといけません。

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

async function aFunc() {
  console.log('start aFunc');
  const p = new Promise((resolve, reject) => {
    const r = new XMLHttpRequest();
    r.open('GET', 'https://msyk.net')
    r.onreadystatechange = () => {
      if(r.readyState == 4) {
        console.log('receive aFunc');
        resolve()
        console.log('resolve aFunc');
      }
    }
    console.log('setup aFunc');
    r.send()
    console.log('send aFunc');
  })
  return p
}

async function bFunc() {
  console.log('start bFunc');
  await aFunc()
  console.log('end bFunc');
}

async function cFunc() {
  console.log('# before bFunc');
  await bFunc()
  console.log('# after bFunc');
}

cFunc()

コンソールへの出力結果は次のようになり、期待通り、awaitで非同期処理が同期処理として呼び出されています。

# before bFunc
start bFunc
start aFunc
setup aFunc
send aFunc
receive aFunc
resolve aFunc
end bFunc
# after bFunc

最終的に、ここでは、cFunc()という非同期呼び出しがありますが、他の部分のコードは全部、同期的に動いていると言える状態かと思います。ですが、全部の関数にasyncがつけられていて、非同期に動く関数であることを明確に示しています。INTER-Mediatorでの実装では、通信処理は何段階か呼び出した低いレイヤーで行なっています。そこをasync/await対応にしたら、その1つ上をやっぱりasync/await対応にしてということになり、要するに全階層がasync/await対応、つまり、全レイヤーが非同期処理するという状況にまずはなりました。asyncで非同期にして、awaitで同期的に呼び出すと言うことをあちらこちらに適用しないといけなくなりました。なんだか、非同期処理を同期的に記述してプログラムしやすくするという目的があるのに、全体的に非同期処理になってしまうではないかというジレンマに陥りました。結局、全体的に見直しが必要になったのは言うまでもありませんが、AJAXを非同期にするために、なんとか頑張ったのでした。実は、それから数年経過し、その頑張りに意味があったのか、ちょっと検証したくなったので、今日、こんなテストをやっているうちに、この記事を書こうと考えた次第です。まあ、方針としては間違いなかったのかと思います。お付き合いありがとうございます。

Amazon LightsailにFileMaker Server 19をインストール

FileMaker Server 19 for Linuxが登場したときに、VM上で動かす方法をブログに記載しました。今回は、Lightsail上で動かす方法をまとめておきましょう。Amazon Lightsailは簡単に言えば、Amazon版のVPSサービスです。コア数やメモリなどの設定が何段階かあるのですけど、FileMaker Serverの場合はメモリは4GB以上は欲しいところです。2コアで4GBメモリ、80GB SSDの設定で毎月20ドルです。EC2の同等なものがt3.mediumくらいだとすると、月に30ドルくらいになるので、それよりも安いということと、管理やセットアップが楽というのがLightsailでのマシン利用のメリットでしょう。なお、FileMaker Serverは8GB以上がメモリ推奨値です。4GBで動く保証はもちろんできませんが、大体、2GBだとサーバーは動くけどWeb系が怪しい感じ、4GBだとWebDirectも含めてとりあえず全機能は動くけど負荷増大には弱そう…という印象です。数人で使うサーバーなので、4GBで運用することにしました。

Lightsailによるクラウドコンピューターを用意

実際のセットアップを追っていきましょう。まず、Amazon Web Servicesのコンソールに入り、Lightsailのコンソールに移動します。そして、インスタンスを作成します。以下は、通常は画面を見ながら作業するところですので、設定のポイントだけを説明します。まず、ロケーションは東京を選択してあります。イメージは、Linux/Unixのうち「OSのみ」の「CentOS」を選択します。アプリが入っているものでない方が良いでしょう。

さらにスクロールしてプランを選択します。インスタンス名については、以下は既定値通りにしておきます。これで、画面下にある「インスタンスの作成」ボタンをクリックするだけです。

作成できれば、インスタンスのところにグレーのボックスで表示されます。そのボックスの右上にある点々の部分をクリックすると、ポップアップメニューが出てくるので、そこで「接続」を選択すると、新たなウインドウにコンソールが出てきます。コマンドはあとでまとめて紹介しましょう。ここで、スタティックなIPと、ファイアウォールの設定をしたいので、「管理」を選択します。

静的IPつまりは固定したIPアドレスは、いくつかは無料です。「管理」を選択した後にはそのコンピューターの設定がでできます。「ネットワーキング」のところに静的IPの設定があるので、それをクリックし、どのコンピュータのIPなのかを指定すれば、基本的にIPは割り当てられます。

また、ファイアウォールについても、同様に「ネットワーキング」のところにあるIPv4 Firewallのところで設定します。初期値は22と80番ポートだけが開いています。「ルールを追加」の部分をクリックして、TCPの443、5003、16000を指定して、ルールを追加しておきます。

ターミナル等のSSHから接続したい場合は、ログインのためのキーが必要です。こちらについては、画面上部の「アカウント」と書かれた部分をクリックして、ポップアップメニューからさらに「アカウント」を選択します。そしてページ中央のナビゲーション部分で「SSHキー」を選択します。ここでキーを指定したりもできますが、デフォルトで作られているキーをダウンロードして利用するのも良いでしょう。なお、ダウンロードした場合、そのファイルのアクセス権設定ではグループや全員に対しては何もできなくなっている必要があります。sshコマンドでは、-iオプションで、キーファイルを指定できます。ログインする時のユーザ名は「centos」です。このユーザーはパスワードなしでsudoが可能です。

コンピューターの設定は以上です。

FileMaker Serverのインストール

続いて、FileMaker Serverをインストールします。まずは、OSのアップデートを済ませておきます。

sudo yum update -y

FileMaker Serverのインストールに必要なコマンドのうち、最初から入っていないのはunzipです。unzipコマンドもインストールしておきます。

sudo yum install -y unzip

FileMaker Server 19.2.1のセットアップでは、http24というパッケージに依存しますが、こちらも最初は入っていません。以下のコマンドでインストールをします。これは、ClarisのKnowlege Baseにも記載されています。このcentos-release-sclはいろいろなソフトウエアが含まれています。その中に含まれているApacheとSSLモジュールを利用する模様です。

sudo yum install centos-release-scl -y

これで準備は完了です。あとはダウンロードしてインストールです。ダウンロードのURLは、ライセンスのページからコピペしてください。以下、XXXで一部を隠しますが、ライセンスを所有されていればわかるはずです。このまま以下のコマンドをコピペしてもダウンロードはされません。その後、unzipで展開しておきます。

curl -O https://downloads.claris.com/XXX/fms_19.2.1.23 .zip
unzip fms_19.2.1.23.zip

なお、この方法での入手だと、ダウンロードが途中で止まる場合もあります。その場合は再度トライする、あるいは一度PC/Macにダウンロードして、scp等でクラウドのコンピューターに転送しましょう。

rpmファイルが展開されれば、それを指定してyumでインストールをします。こちらの詳細は、VM上で動かす方法をご覧ください。

sudo yum install -y filemaker_server-19.2.1-23.x86_64.rpm

証明書を取得してインポート

続いて、Let’s Encryptで証明書をインストールします。設定方法は、こちらの記事を参考にさせてもらいました。このコンピューターのIPアドレスには、fms.msyk.netというFQDNと関連づけてありますので、以下はこの名前をそのまま使います。

まずはcertbotのインストールですが、いきなりはインストールできず、次の2つのコマンドを入れます。apacheプラグインは不要かもしれませんが、一応入れておきます。

sudo yum install epel-release
sudo yum install certbot python-certbot-apache

続いて証明書を発行します。

sudo certbot certonly --manual -d fms.msyk.net --agree-tos

途中で処理が止まって入力待ちになります。以下のように、通知が送られるメールアドレスを指定します。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): msyk@msyk.net
Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org

メールアドレスを共有するかどうかを、YかNで指定します。

Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom.

(Y)es/(N)o: Y
Account registered.

続いて、チャレンジ、つまり一種の認証設定を行います。以下のようにターミナルで出ている状態になったら、リターンキーを押さずに、別のウインドウでログインします。

別のウインドウで、同じコンピュータにログインしたら、前述のメッセージのように、Web公開された場所に指定された名前のファイルを作り、その内容を指定通りにします。まず、FileMaker Serverは、/opt/FileMaker/FileMaker Serverディレクトリにあります。そこにある、HTTPServer/htdocsが、Webサーバーのドキュメントルートです。例えば、以下のように作業を行います。途中でエディタで作業をするのが手軽だと思うので、最初にnanoを入れていますが、適当なエディタを使ってOKです。パスを掘り、URLの最後の長い名前のファイルを作ります。もちろん、ファイル名は、ウインドウからコピペしてください。

sudo yum install nano -y
cd /opt/FileMaker/FileMaker\ Server/HTTPServer/htdocs
sudo mkdir -r .well-known/acme-challenge
cd .well-known/acme-challenge
sudo nano q3mLUhDwx-9PFQ4PihyvUJfQAlQ-PVqE0SV3KBTxx4g 

エディタでは、ターミナルに見えていた「Create a file containing just this data:」の次の行の文字列(q3mLUhDwx-9P…Vnu6zbvyJgTsの文字列)を入れてファイルとして保存します。このファイルの中身も、もちろん、ウインドウからコピペします。上記のような方法で作ったファイルはrootユーザー&rootグループになりますが、読み込み権限だけがあればいいので、全員に対するrが効いて問題なく処理できます。

ここまで準備ができれば、ターミナルの「Press Enter to Continue」と見えているウインドウに戻り、リターンキーなどを押して先に進めます。これで、通信とチャレンジが行われて、証明書が作成されます。以下のようにメッセージが出ますが、Congratulations!と出ていれば成功でしょう。その次の行以降に生成された証明書のパスが見えています。

 Waiting for verification...
 Cleaning up challenges
 Subscribe to the EFF mailing list (email: msyk@msyk.net).
 Starting new HTTPS connection (1): supporters.eff.org
 

 IMPORTANT NOTES:
  - Congratulations! Your certificate and chain have been saved at:
    /etc/letsencrypt/live/fms.msyk.net/fullchain.pem
    Your key file has been saved at:
    /etc/letsencrypt/live/fms.msyk.net/privkey.pem
    Your cert will expire on 2021-03-30. To obtain a new or tweaked
    version of this certificate in the future, simply run certbot
    again. To non-interactively renew *all* of your certificates, run
    "certbot renew"
  - If you like Certbot, please consider supporting our work by:
 

    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
    Donating to EFF:                    https://eff.org/donate-le 

証明書をFileMaker Serverに読み込みます。ここで生成した証明書へのパスの途中にある/etc/letsencrypt/liveがrootだけに読み書きができるディレクトリなので、このままだとFileMaker Serverはファイルの読み込みができません。そこで、以下のように、FileMaker Serverの中のCStoreディレクトリに一度証明書ファイルをコピーしてfmsadminコマンドで読み込むことにします。

cd /opt/FileMaker/FileMaker\ Server/CStore
sudo cp /etc/letsencrypt/live/fms.msyk.net/*.pem .

これで、証明書などのファイル4つ(cert.pem、chain.pem、fullchain.pem、privkey.pem)がCStoreに読み込まれます。通常は同名のファイルは最初からはありませんので、コピーして問題はないでしょう。また、ファイルの所有者とグループはrootになりますが、全員に対して読み込みができるので、アクセス権については問題ありません。そして、CStoreディレクトリがカレントであることを確認して、以下のコマンドで、FileMaker Serverに証明書を読み込みます。以下のコマンドは1行です。

fmsadmin certificate import cert.pem  --intermediateCA chain.pem --keyfile privkey.pem

これで証明書がセットアップされました。3ヶ月後に失効しますが、その時に対処を考えるとして、お疲れ様でした。

FileMaker Server 19のWebテクノロジーベンチマーク

以前からやろうやろうと思っていたベンチマークを必要に迫られてやらないと行けなくなったので、やりました。昔、2008年や2009年にFileMakerのイベントでお話しさせていただいた通り、当時のFMSのカスタムWebは同時にアクセスすると、なぜか倍以上の時間がかかるみたいな状態だったのですが、そんな実装はいつの間にかなくなってそこそこちゃんと動くようになっています。今の焦点は、Custom WebとData APIの違いがどの程度なのかというところかと思います。CloudやLinux版ではCustom Webがなくなっているため、いずれData APIに移行するのか、はたまたFileMakerを諦めるのかはそろそろ心算が必要な時期ではないでしょうか?

では先に結果を書きます。評価条件などは後から記載します。以下のグラフは、レコードの検索にかかる時間を100回行った場合の平均値です。横軸は1回の検索で取り出すレコード数で、それぞれのレコード数の検索を100回ずつ行った結果です。FileMaker Serverはこの作業以外は行わない状態にしました。FXはCustom WebのXML共有を使ったものです。APIはFileMaker APIを意味します。いずれも、INTER-MediatorのサーバーサイドのAPIを使って、検索を行いました。ベンチマークというかPHPで作ったプログラムがあるMacで動いていて、別のMacでFileMaker Server 19が動いているという状況での計測値です。

レコード数が少ない状況では、圧倒的にFXが早いです。200レコードの場合でもAPIの方は3.5倍も時間がかかっています。1つには、FXが1回のネットワークアクセスで処理が終わるのに対して、APIは認証、検索、ログアウトの3つのアクセスがあるので、ある程度時間がかかるのは仕方ないと思われます。しかしながら、ほとんどデータのやりとりがない1レコードの検索でも、200msほどかかっており、これがAPIのベースの処理に必要な時間と思って良いかと思います。100レコードでも253msとあまり変わらずです。ちなみに1レコードの場合は、FXは24msほどでした。

ところが、レコードの件数が2000件を超えると、FXよりAPIの方が処理が早く終わります。つまりデータが増えても処理時間の伸びは抑えられています。大量のデータに向くかどうかはさておき、FXよりもAPIの方が、比較の上ではデータが増えてもパフォーマンスの低下は少ないと言えるでしょう。同じデータを散布図で描くと次のように、かなり直線上に乗る感じです。傾きが違うのが見て取れるかと思います。

FXとAPIの違いの原因はなんでしょうか? もちろん、アルゴリズムが違うのは違うのでしょうけど、FXはXML、APIはJSONであり、これらの処理ライブラリを考えれば、JSONの方がパフォーマンスは上げやすいのではないかと考えられます(竹内さん、アドバイスありがとうございます)。

一方、FileMaker Serverに対して並列にアクセスをかけた場合の結果を以下に示します。500件のレコードを検索する処理を100回順番に実行するのが1つのタスクとします。並行実行処理数が1の場合は、そのタスクが1つだけ動く場合なので、前述のグラフの500の場合と同じ測定をしています。並行実行処理数が2、4、8は、そのタスクを同時にこの数字だけ稼働して、全てのタスクが終わるまでの時間を測定しました。従って、2の場合は検索数は200回行っているということになります。

Custom WebのFXは、並列数が1も2も時間に違いがありません。しかし、4、8となると線形的に増加しています。どうやら、2つくらいの並列処理くらいまでは現実に並列に処理をしているのではないかと思われます。サーバー側のプロセスであるfmdcwpcは1タスクだけだとCPUの利用率は30%くらいなのに、2タスク以上だと80〜90%と本当に仕事を頑張っている様子です。ところが、APIは2並列で稼働すると時間が2倍かかっています。つまり、サーバー側で並行的に効率良く処理する仕組みが稼働してはないかと思われます。APIの処理プロセスはfmwipdのようで1タスクだと3-〜50%くらいですが、タスクを増やしてもあまりCPUの利用効率は上がりませんでした。

また、次のグラフは、前述の時間を検索数で割ったものです。FXは、このように1の場合の半分の検索時間の値が2以上8まで推移しているので、どうやら1検索の2倍のパフォーマンスを上限として、順次処理を進めることができる模様です。ところが、APIは1検索のパフォーマンス以上は出せていません。その結果、2並列では2倍の時間がかかるということが分かります。

以上のことから、APIはやはりWebアプリケーションに使うとしてもアクセスが集中しないような用途に限定すべきでしょう。FXでもある意味そうなのですが、まだ、性能が高い面があります。また、Webアプリケーションでは小さなレコード数のアクセスが多いだけにFXで運用する方が有利ではないでしょうか。FileMaker Data APIはもちろん汎用的に使えるのですが、レコード数の増大に強い面があるとしたら、やはり大量のデータ交換に耐えうる設計がなされていると見るべきでしょう。

ちなみにこの並列処理の実験で気付いたのですが、FXは、8つのプロセスであってもほぼ同時に8つのプロセスが終了します。ところが、APIは、事実上、最初に入ったプロセスの検索を終えてから、2つ目の処理に移行しています。同時にプロセスは上がっているのですが、どうやら片方が優先的に処理されるようです。キープしたコネクションを優先的に使用するのかもしれません。実際の測定値は、1つ目のプロセスが34.5秒で終わり、2つ目は69.8秒で終わります。この場合、測定値は、69.8秒としました。同時に開始し、最後に終わったプロセスの経過時間を採用しました。

さて、なぜ、APIは4や8の測定値がないのでしょう。並列度をそこまで上げれば、812のエラー(Exceed host’s capacity)が出ます。ちなみに、Developer版なので、3ユーザですね。Data APIはデータ量の制限はあるのは知っていますが、ユーザ数による並列処理制限があるのでしょうか? これは始めて知りました。ちなみに、2並列ではエラーは出ないのですが、3並列ではうち1つの並列プロセスで812のエラーになってしまうため、測定不可能としました。ちなみに、この一連のベンチマークのために、3.3Gの転送量が計上されていました。

ベンチマーク測定においては、FileMaker Server 19側は特に設定は変更していません。コマンドで、XML共有をオンにしただけです。ベンチマークのプログラムは別のPHP 7.4が稼働するMacで動かしました。その2つのMac間は、Gigabit Ethernetなので、ネットワークのパフォーマンスは非常に高い状態です。なお、並列処理を行うために、php -Sによるサーバープロセスを最大8つまで異なるポート番号で起動して、ブラウザからそれぞれのポートのphpによるWebサーバーに接続し、ほぼ並列に動く状態を作りました。このマックはQuad CoreのCore i7です。ベンチマークのプログラムはこちらです。lib/src以下にINTER-Mediatorをクローンし、INTER-Mediatorをカレントにしてcomposer updateを行った後に、dist-docs/buildup.shを稼働させて(3)を選択して、lib/INTER-Mediatorを生成します。なお、INTER-Mediatorは、Pull Reuqest #1487が含まれたものである必要があります。fms-benchのディレクトリをルートにして「php -S localhost:9000」などで起動して、「http://localhost/do_bench.php」でベンチマークを動かしました。

ここでのベンチマークの検索プログラムは、レコード数12万件余りの郵便番号データベースを使っています。検索ごとに、乱数を使って異なるスタートポジションを指定して、そこから決められた数のレコードを取り出しています。フィールド検索には入っていませんが、データベース内部をランダムに探る必要がある点からデータベース自体の入出力により近い結果が得られることを期待しています。検索やリレーションシップが絡むと、データそのものによる要因が発生するので、今回のベンチマークではスタート位置のランダム化で測定を行いました。

いずれ機会があれば、更新系の処理もやってみたいですが、機会があるかどうか定かではありません。

FileMaker Server 19 Linux版のインストール手順

2020/10/28に、これまでプレビュー版だったFileMaker Server 19 Linux版の正式版が出ました。インストール方法や、インストール結果などをまとめておきます。

原則として、FileMakerのなんらかのライセンスがあることと、Linuxを稼働する環境があることを前提としています。以下の手順はVirtual Boxで、CentOS 7.8をインストールして動かすところから説明していますが、クラウド、オンプロミスにしても、原則同一かと思います。それぞれの細かい点は違っていると思うので、適時読み替えてください。CentOSは、こちらのサイトから「7 (2003)」のタブを選択してダウンロードします。Virtual BoxでMacあるいはWindowsだと、x86_64のISOイメージをダウンロードすれば良いでしょう。MacではもうすぐARM64を選ぶ場合も出てきそうです。その後、サーバーのリストが出るので適当なサーバーに移動し、「CentOS-7-x86_64-Minimal-2003.iso」というファイルを選択しました。サーバーなのでミニマルを利用します。こちらをダウンロードして利用しました。

Virtual PCでの準備

Virtual PCで、ツールバーの「新規」をクリックするなどして、新たにVMを追加します。名前などは適当につけますが、最初の選択肢は次のように、タイプは「Linux」、バージョンは「Red Hat (64-bit)」を選択しておきます。FAQですが、「CentOS」という選択肢はなく、内容はLed Hatと基本部分が同じなので、『CentOSはRed Hadを選ぶ』ことで大丈夫です。

メモリを最初2GB、ディスクは8GBにしていたのですが、2GBだと、Webパブリッシングが動きませんので、4GB以上にするのが良いようです。FileMakerの推奨環境だとメモリは8GBですので、負荷が多いあるいは安定性が必要なら8GBは確保しましょう。

VM作成後、左側のリストに項目が出てくるので、それを選択して、「設定」をクリックして設定パネルを出します。ここで、メモリなどの変更はできますが、ネットワークの設定を次のようにしておきます。つまり、アダプター1を有効化して割り当てはNAT、アダプター2も有効化してこちらは「ホストオンリーアダプター」にします。おそらく、vboxnet0が選択されていると思いますが、ここの設定がいくつもある方は適切に選択してください。アダプター2は、VMの外部からのネットワーク接続の確保ですので、「ブリッジアダプター」でも構いません。

ここで先の設定のために、ホストオンリーアダプタで選択したvboxnet0の設定を確認しておきます。左上の「ツール」をクリックすると右側に表示されます。通常はvboxnet0が自動的に作成されており、192.168.56.1/24のIPアドレスになっています。この後、VMをこの範囲の固定IPに設定しますので、この設定と、後から設定するIPは矛盾がないようにする必要があります。

続いて設定の「ストレージ」を参照します。ここで、コントローラー:IDEのしたの「空」を選択し、右側の属性にある光学ドライブの右の、CDマークをクリックしてメニューを表示して、「ディスクファイルを選択」を選択します。この後、ダイアログボックスが出てくるので、ダウンロードしておいた「CentOS-7-x86_64-Minimal-2003.iso」ファイルを選択します。すると、左側で「空」の部分がファイル名に置き換わります。これで、通常はCentOSのインストールディスクから起動するようになります。

OKボタンをクリックして、設定を確定します。左側で該当する項目が選択されているのを確認して、ツールバーの起動ボタンをクリックすると、起動が始まります。(以下のVMの設定は変更前のものです)

CentOSのインストール作業

ここからはCentOSのインストール作業です。画面はVirtual Boxのものですが、他の環境でも基本、同じだと思います。なお、AWSなどのクラウド環境では事実上、インストール作業は不要ですし、サクラVPSなどのVPS環境ではそのサービスでのインストール方法がサポートページにありますので、そちらをチェックしましょう。

インストーラの最初画面では、このままリターンキーを押します。実際には「Install CentOS 7」が選択された状態がデフォルトなので、それを選択することになります。

インストーラの画面が出てきます。ここで、デフォルトは英語なのでそのままでも良い方はそのまま右下のボタンで進めます。日本語にしたい方は、左下の検索枠のjapなどと入れれば、自動的に日本語が選択された状態になるので、「続行」をクリックします。

次は「インストールの概要」が出ます。ここは少し待つと、次の図のようになり、「インストール先」に黄色いアイコンが見えていて、これはここの設定がなされていないことを示しています。ここでネットワーク名とホスト名を設定してもいいのですが、ネットワークは後からコマンドで設定することにします。「インストール先」をクリックします。なお、下まで見えない場合は、右端の部分にスクロールバーが出るので、それをドラッグして表示範囲を変更します。画面上のポインタがウインドウの外に出ないのでパニックになるかもしれませんが、ウインドウの右下に、ポインタを外に出すキー操作(この場合は左側のコマンドキー)が書かれています。

インストール先の設定画面では、「ローカルの標準ディスク」の1つの項目を選択すればOKです。2回クリックが必要な気がしますが、ともかく選択して、左上の「完了」をクリックすればOKです。

もとの「インストール概要」の画面に戻り「インストールの開始」ボタンをクリックして、インストールを進めます。

次にこのような画面になります。右側の「ユーザーの作成」をクリックします。「ROOTパスワード」の方は放置で構いません。ルートのパスワードを設定しない運用方法が現状では安全と思われるので、管理者ユーザーを作ってルートはログイン不可能にしておきます。

ユーザーの作成では、自由にユーザーを定義してください。なお、ユーザー名とパスワードは絶対に忘れないようにしてください。そして、「このユーザーを管理者にする」のチェックには絶対に忘れないようにしてください。入力後「完了」ボタンをクリックすると設定されます。

設定されました。画面下に見えているように、インストールはその間もどんどんと続きます。

「再起動」ボタンが見えればインストールの完了です。

CentOSの最初起動時にネットワーク設定する

しばらく待つと起動します。login: が見えるまで待ちます。これで起動しました。ここで、インストール時に作成したユーザー名とパスワードを入力してログインをします。このコンソールでの作業はやりにくいので最低限にしたいのですが、ネットワーク設定まではここでやってしまうのが良いと思います。

ログイン後、以下コマンドで、ネットワークの状況をみてみます。この2つのコマンドにより、enp0s3とenp0s8の2つのネットワークアダプタがあることが分かります。Virtual Boxの場合は前者がアダプター1で、後者がアダプター2です。ちなみに、NATつまりアダプター1は、このVMがクライアントになってインターネット接続するために設定されたものです。アダプター2は前に説明したように、VMへの接続ができるようにするためのものです。なお、いずれのコマンドを見ても、IPv4のアドレスは見えず、まだネットワーク接続されていない状態になっています。

ip a
nmcli connection

次のようにコマンド入力をして、NAT側(アダプター1)はDHCPによるIP設定、ホストオンリーアダプター側(アダプター2)は固定IPに設定します。最初の4行は固定IPの設定です。コマンドはややこしいですが、何を設定しているのかは容易に想像できると思います。ここでは、192.168.56.19を固定IPにしています。6, 7は、起動時に自動的にアクティブになるようにするための設定です。なお、DHCP設定は何も指定しない場合にその方法でIPアドレスが設定されます。最後は、ホスト名の設定です。これはsudoが必要ですので、自分のパスワードで認証して続けます。ホスト名の確認はhostnameコマンドを利用します。

nmcli connection modify enp0s8 ipv4.address 192.168.56.19
nmcli connection modify enp0s8 ipv4.gateway 192.168.56.1
nmcli connection modify enp0s8 ipv4.method manual
nmcli connection modify enp0s8 ipv4.dns 8.8.8.8
nmcli connection up enp0s8
nmcli connection modify enp0s3 connection.autoconnect yes
nmcli connection modify enp0s8 connection.autoconnect yes
sudo nmcli general hostname centos.msyk.net
hostname

これでネットワーク設定ができたので「sudo reboot」コマンドで再起動します。

再起動後は、Macだと普通にターミナルで接続します。その方が、画面が見やすいなど作業効率が良いからです。要するにターミナルのウインドウでsshコマンドで接続するのですが、例えば「ssh-copy-id msyk@192.168.56.19」で、デフォルトの鍵ファイルをサーバーに登録すれば、以後は「ssh msyk@192.168.56.19」でパスワードを入れなくても接続は可能です。もちろん、ユーザー名とIPは指定したものです。ssh-copy-idコマンドの実行時にはサーバーのフィンガープリントの登録確認や、アカウントのパスワード入力も必要になります。この辺りの情報は他のサイトをご覧ください。以下は、ターミナルで通常通り接続できた状態であるとします。

ログインできれば、IPアドレスを確認しておきます。このコマンドはいろんな意味で覚えやすいのですが、出力結果はコンソールではもう少し見やすハズです。enp0s3とenp0s8に、それぞれ10.0.2.15、192.168.56.19が設定されていることが分かります。

$ ip a
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s3: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:50:61:b7 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic enp0s3
valid_lft 83583sec preferred_lft 83583sec
inet6 fe80::ca47:2b2f:8c68:439b/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: enp0s8: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:a4:c7:ae brd ff:ff:ff:ff:ff:ff
inet 192.168.56.19/32 brd 192.168.56.19 scope global noprefixroute enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::20fd:6331:59b8:1ec0/64 scope link noprefixroute
valid_lft forever preferred_lft forever

また、以下のコマンドを入れて、CentOSのアップデートをしておきましょう。少し時間がかかります。

sudo yum update -y

FileMaker Serverのインストール

FileMakerのドキュメントでは、wgetとunzipのインストールをせよと書かれています。unzipは確かに存在しないので、以下のようなコマンドでインストールしますが、wgetについては既存のcurlコマンドが使えるので必ずしも必要ではありません。

sudo yum install -y unzip

Apache2のインストールについては、あとで結果を書きますが、FileMaker Serverのインストール時に自動的にインストールされます。ここでは、CentOSのMinimal版だからかもしれませんが、「sudo yum list installed|grep httpd」とコマンドを入れても何も出力されず、Apache2は入っていません。従ってそのまま進めます。もし、「systemctl status httpd」により、httpdプロセスが既にアクティブになっている場合には、それは止める必要があると考えられます。「sudo systemctl stop httpd」で停止できますし、起動時に自動的に起動していたら「sudo systemctl disable httpd」で自動起動できないようにしておきます。Apache2自体は使うのですが、サービスの起動はFileMaker Serverに任せないと、動かない機能が出るのはプレビュー版で経験したことがあります。

では、FileMaker Serverダウンロードです。私はFDS会員なので以下のようなページが供給されていますが、そこにあるFileMaker Server 19のCentoOS Linuxの部分でコンテキストメニューをだし、「リンクアドレスをコピー」を選んで、URLをクリップボードにコピーします。

そして、「curl -O 」と手入力し、そしてペーストすることで以下のようなコマンドになるので、これで、カレントディレクトにダウンロードすることができます。URLは汎用的なもののように思えるのですが、一部は「XXX」に変えてあります。ここでは「fms_19.1.2.234.zip」というファイルがダウンロードされるので、unzipコマンドで展開すると、同じディレクトリにドキュメントやインストーラファイルが展開されます。ドキュメントは、設定支援インストール構成ファイル「Assisted Install.txt」を利用する場合には一読しましょう。そうでない場合は、追加の情報はありません。

$ curl -O https://downloads.claris.com/XXX/fms_19.1.2.234.zip
$ unzip fms_19.1.2.234.zip
$ ls -l
合計 682052
-rw-rw-r--. 1 msyk msyk 155 10月 17 10:04 Assisted Install.txt
-rw-rw-r--. 1 msyk msyk 24950 10月 17 10:44 FMS License (English).rtf
-rw-rw-r--. 1 msyk msyk 30689 10月 17 10:44 FMS License (French).rtf
-rw-rw-r--. 1 msyk msyk 26969 10月 17 10:44 FMS License (German).rtf
-rw-rw-r--. 1 msyk msyk 28616 10月 17 10:44 FMS License (Italian).rtf
-rw-rw-r--. 1 msyk msyk 79026 10月 17 10:44 FMS License (Japanese).rtf
-rw-rw-r--. 1 msyk msyk 27934 10月 17 10:44 FMS License (Spanish).rtf
-rw-rw-r--. 1 msyk msyk 5398 10月 17 10:04 README_Installation_English.txt
-rw-rw-r--. 1 msyk msyk 6225 10月 17 10:04 README_Installation_French.txt
-rw-rw-r--. 1 msyk msyk 6062 10月 17 10:04 README_Installation_German.txt
-rw-rw-r--. 1 msyk msyk 5906 10月 17 10:04 README_Installation_Italian.txt
-rw-rw-r--. 1 msyk msyk 6327 10月 17 10:04 README_Installation_Japanese.txt
-rw-rw-r--. 1 msyk msyk 6074 10月 17 10:04 README_Installation_Spanish.txt
-rw-rw-r--. 1 msyk msyk 350402832 10月 17 10:50 filemaker_server-19.1.2-234.x86_64.rpm
-rw-rw-r--. 1 msyk msyk 347731241 10月 30 10:06 fms_19.1.2.234.zip

続いて、ディレクトリにあるrpmファイルを特定し、以下のようにインストーラのコマンドを入力してインストールを行います。そこそこ時間がかかります。「Perform pre-installation…」と出てくるまで待ちます。このメッセージが出れば、質問に答える必要が出てきます。

$ sudo yum install -y filemaker_server-19.1.2-234.x86_64.rpm
[sudo] msyk のパスワード: xxxxxxx
読み込んだプラグイン:fastestmirror
filemaker_server-19.1.2-234.x86_64.rpm を調べています: filemaker_server-19.1.2-234.x86_64
filemaker_server-19.1.2-234.x86_64.rpm をインストール済みとして設定しています
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ filemaker_server.x86_64 0:19.1.2-234 を インストール
:
インストール中 : 1:java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64 115/141
インストール中 : ImageMagick-6.9.10.68-3.el7.x86_64 116/141
インストール中 : libfontenc-1.1.3-3.el7.x86_64 117/141
=== Perform pre-installation…

最初の質問は、使用許諾に従うかどうかです。使用許諾はzipファイルに入っていますが、lここでは「y」を選択するしかないでしょう。

I confirm that I have read and agree to the terms of the Claris FileMaker Server Software License Agreement included with the software.
Agree (y) Decline (n) [y/n] y

続いては、FileMaker Serverとして起動するか、WebDirect Workerとして起動するかを選択します。最初は前者なので、「0」と入力します。

0 ) Claris FileMaker Server
1 ) Claris FileMaker WebDirect Worker
Choose 0 to install Claris FileMaker Server or 1 to install Claris FileMaker WebDirect Worker. [0/1] 0

続いて、管理者のユーザー名、パスワード、パスワードリセット用の4桁数字のPINナンバーをそれぞれ入力して、リターンを押します。メッセージをよく見ると、fmsadminグループが作成され、現在ログインしているユーザーがそのグループのメンバーに登録されています。

Perform installation for Claris FileMaker Server…
Set up the Claris FileMaker Server Admin Console account for Claris FileMaker Server.
Use this account when you sign into Claris FileMaker Server Admin Console.
Enter User Name: admin
Create a password to sign into Claris FileMaker Server Admin Console.
Enter password:
Confirm password:
Create a 4-digit PIN needed to reset Claris FileMaker Server Admin Console account password via the command line interface.
Enter PIN:
Confirm PIN:
Set Claris FileMaker Server Admin Console account information.
Claris FileMaker Server is being installed by msyk to run as fmserver of fmsadmin group…
Create fmsadmin group…
Create fmserver user in fmsadmin group…
Add msyk user to fmsadmin group…

その後、インストール作業が再開されて、いろんなメッセージが見えますが、ここはしばらく傍観します。

インストール中 : filemaker_server-19.1.2-234.x86_64 118/141
=== Perform post-installation…
Set up core dump location at /var/crash…
Deployment type: Claris FileMaker Server
Retrieved Claris FileMaker Server Admin Console account information from cache.
Install default license certificate.
Create a default Claris FileMaker Server configuration with Japanese locale.
Open HTTP connection port 80…
Open HTTPS connection port 443…
Open Claris FileMaker Server connection port 5003…
Open ODBC connection port 2399…
Open Claris FileMaker Server Admin Console connection port 16000…
Enable and start HTTP server service…
Enable Claris FileMaker Server service…
Reload system daemons…
Check for Avahi daemon…
Avahi daemon has not started yet, wait for 2 seconds…
Avahi daemon has not started yet, wait for 2 seconds…
Avahi daemon has not started yet, wait for 2 seconds…
Avahi daemon has not started yet, wait for 2 seconds…
Avahi daemon has not started yet, wait for 2 seconds…
Start Claris FileMaker Server service…
Claris FileMaker Server service has started…
Waiting for connection session…
Sending Claris FileMaker Server Admin Console account information to Claris FileMaker Server…
Claris FileMaker Server Admin Console account is set up successfully.
HTTP Server has not started yet, wait for 2 seconds…
HTTP Server has not started yet, wait for 2 seconds…
HTTP Server has not started yet, wait for 2 seconds…
HTTP Server has not started yet, wait for 2 seconds…
HTTP Server has not started yet, wait for 2 seconds…
Warning! Failed to start HTTP server, please reboot the system.
インストール中 : 1:xorg-x11-font-utils-7.5-21.el7.x86_64 119/141
インストール中 : 1:cups-libs-1.6.3-43.el7.x86_64 120/141
インストール中 : libtiff-4.0.3-32.el7.x86_64 121/141
:

次のインストール項目に移る前に「Warning! Failed to start HTTP server, please reboot the system.」と見えています。これはインストール作業直後に「sudo reboot」をしなさいということです。しばらく待ってプロンプトが」出れば、「sudo reboot」とコマンドを打ち込んで再起動します。

インストール後のFileMakerサーバーのセットアップ

VMが再起動したら、ブラウザから、以下のURLで接続をします。もちろん、IPアドレスは実際のIPアドレスにします。そして、httpsであること、16000であることを確実に設定してください。これで、Admin Consoleが出てきて、あとはGUIでの設定になります。もちろん、ログインアカウントは、FileMaker Serverのインストール中に指定したユーザー名とパスワードになります。なお、おそらくは自己署名証明書で当初は運用するので、ブラウザはすんなりと画面は出してくれないと思いますが、いくつかの操作をすればページは開きます。

https://192.168.56.19:16000

セットアップ状態の確認

まず、httpdつまりApache2がインストールされたかを見てみると、このように元々入っていなくても、FileMaker Serverのインストーラによって自動的にセットアップされていることが確認できました。

$ yum list installed | grep http
httpd.x86_64 2.4.6-93.el7.centos @base
httpd-tools.x86_64 2.4.6-93.el7.centos @base

ですが、以下のコマンドの結果のように、httpdサービスはinactiveになっています。これは、httpdサービスとしては起動していないことを意味します。httpdデーモンの起動はFileMaker Serverが行います。

$ systemctl status httpd
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
Active: inactive (dead)
Docs: man:httpd(8)
man:apachectl(8)

ファイアウォールの設定も自動的に行われています。以下のコマンドで確認できますが、portsに設定が見えています。servicesじゃないのかよと思ってしまいますが、ともかくポートは開いています。

$ sudo firewall-cmd --list-all --zone=public
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3 enp0s8
sources:
services: dhcpv6-client ssh
ports: 80/tcp 443/tcp 5003/tcp 2399/tcp 16000/tcp
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

Systemd配下で動くFileMaker Serverってどうなっているのだろうと思って/etc/systemdを探ったら、例えば、以下のようにすることで、サービスとして動いていることの確認はできそうです。psコマンドよりかは分かりやすい気がします。ただ、「sudo systemctl stop com.filemaker.httpd.start」とやって止まるかと言えば、おかしなメッセージが出てきて、startでは元に戻らない状況になってしまったので、起動や停止は、fmsadminコマンドなどを利用する方が確実かと思います。

$ sudo systemctl status com.filemaker.httpd.start
● com.filemaker.httpd.start.service - Filemaker.com monitor system and start httpd
Loaded: loaded (/etc/systemd/system/com.filemaker.httpd.start.service; enabled; vendor preset: disabled)
Active: active (running) since 金 2020-10-30 10:35:40 JST; 19min ago
Process: 1207 ExecStart=/usr/bin/env /opt/FileMaker/FileMaker Server/HTTPServer/bin/httpdctl start -d (code=exited, status=0/SUCCESS)
Main PID: 1417 (httpd)
CGroup: /system.slice/com.filemaker.httpd.start.service
├─1417 /usr/sbin/httpd -k start -D FILEMAKER -f /opt/FileMaker/FileMaker Server/HTTPServer/conf/httpd.conf
├─1420 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/error_log.%Y-%m-%d-%H_%M_%S 10M
├─1421 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/fmsadminserver_error_log.%Y-%m-%d-%H_…
├─1422 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/ssl_error_log.%Y-%m-%d-%H_%M_%S 10M
├─1423 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/access_log.%Y-%m-%d-%H_%M_%S 10M
├─1424 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/fmsadminserver_access_log.%Y-%m-%d-%H…
├─1425 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/fmsadminserver_ssl_request_log.%Y-%m-…
├─1426 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/ssl_access_log.%Y-%m-%d-%H_%M_%S 10M
├─1427 /usr/sbin/rotatelogs /opt/FileMaker/FileMaker Server/HTTPServer/logs/ssl_request_log.%Y-%m-%d-%H_%M_%S 10M…
├─1431 /usr/sbin/httpd -k start -D FILEMAKER -f /opt/FileMaker/FileMaker Server/HTTPServer/conf/httpd.conf
├─1432 /usr/sbin/httpd -k start -D FILEMAKER -f /opt/FileMaker/FileMaker Server/HTTPServer/conf/httpd.conf
├─1433 /usr/sbin/httpd -k start -D FILEMAKER -f /opt/FileMaker/FileMaker Server/HTTPServer/conf/httpd.conf
└─2131 /usr/sbin/httpd -k start -D FILEMAKER -f /opt/FileMaker/FileMaker Server/HTTPServer/conf/httpd.conf

10月 30 10:35:39 centos.msyk.net systemd[1]: Starting Filemaker.com monitor system and start httpd…
10月 30 10:35:39 centos.msyk.net env[1207]: [Fri Oct 30 10:35:39.773253 2020] [proxy:warn] [pid 1257:tid 139647227140…haring
10月 30 10:35:40 centos.msyk.net systemd[1]: Can't open PID file /var/run/httpd.pid (yet?) after start: No such file …ectory
10月 30 10:35:40 centos.msyk.net systemd[1]: Started Filemaker.com monitor system and start httpd.
Hint: Some lines were ellipsized, use -l to show in full.

カスタムWebパブリッシングは、リリースノートにあるように、非サポートとなっています。SETコマンドで、CWPCONFIGの属性であるENABLEXMLへの書き込みはできませんし、そもそも以下のようにCWPCONFIGの設定の読み取りすらできません。機能がないので、設定がないのは当然ということになりますね。

$ fmsadmin GET CWPCONFIG
username (msyk):admin
password:
Error: 21 (Not Supported)

とりあえず、正式版初インストールのレポートです。何かあれば、書きたします。

[続開発プロセス#14] エンクロージャー/リピーターの制約

前回の記事で、HTMLのテンプレートの設計、つまりページファイルの設計を出すときに、非常に根深いところにある制約の話が必要であることに気付きました。ですが、前の記事が長いだけに、別途記述するということで、連投となりました。

話を少しすっ飛ばしたのは、ページのモックアップにある左側の、カテゴリ一覧の設計です。まず、オブジェクト指向的に正しくモデリングをするということを進めます。以下の図は、ともかく見えるオブジェクトとをクラスとして記述して、全体をまとめるであろうCategoryBox配下に並べたところです。オレンジ色のメモに記述したように、ここには矛盾があります。ここでは、「小分類は必ず何かの大分類に所属している」前提があるとしているので、このように、大分類に所属しない小分類が存在しえるモデルは、モデル自体が間違っているので、正しい設計とは言えません。

ここで、小分類は大分類に所属することをモデルとして表現してみます。小分類の名前とボタンがあり、それぞれ、MinorCategoryLabel、MinorCategoryButtonクラスですが、既に存在する大分類の名前であるMajorCategoryLabelに関連づけてみました。これだと、小分類の存在は大分類の存在を前提になっている状況は示しています。しかしながら、オレンジ色のメモに記述したように、小分類の中でラベルとボタンがバラバラであり、ラベルが4つでボタン3つでも成り立ちそうなモデルです。ここで、1つの小分類は、何らかの1つの対象が保持する形にであるべきではないかと考えます。

そこで、次のように、小分類の項目をまとめるMinorCaterogyBoxクラスを間に入れてみました。この辺りで分類そのものをデータベースのテーブルに入れていることを考えれば、MinorCategoryLabelなどフィールドに相当するものが、レコードに相当するMinorCategoryBoxにまとめられて、それが1つの大分類に所属するという状況をだいぶんと正確に示してきていることが伺えます。ここで、改めて大分類に目を向けます。ここでの大分類の名前を示すMajorCategoryLabelは大分類の1項目の中の1つのフィールドであり、属性の1つにすぎません。これ自体が大分類項目1つを代表するのはちょっと無理があるのではと考えられますし、小分類の類するから、「大分類の1つの項目をまとめるものがあれば良い」ことが考えられます。

そして、次の図のように、大分類の1レコードに対応するMajorCategoryBoxが存在し、大分類の1つのフィールドは、このMajorCategoryBoxから1対1で表現しています。また、1つの大分類から複数の小分類が関連づけられることも、MajorCategoryBoxとMinorCategoryBoxの1対多の関係として表現できています。ここで、エンクロージャー/リピーターの関係を1対多の関係に持ち込み、データベースのデータをバインドしたいと考えます。これはINTER-Mediatorの肝になる機能です。

ここで改めて、エンクロージャー/リピーターをモデルとして示します。HTMLのテンプレートは何がクラスで何がオブジェクトかというのは視点あるいは場面によって変わると思われますが、HTMLの記述可能な範囲において、エンクロージャーがリピーターを含み、リピーターにはターゲットノードが含まれる階層構造になっています。オブジェクト図で記述すると明白なように、これらはクエリー結果のリレーション/レコード/フィールドと言った階層に一致しています。

INTER-MediatorはDOMの領域でテンプレート処理を行っています。テキストのレベルで行うより高い粒度になりますが、一方でそのための制約が発生しています。このリレーションとテンプレートを合成するアルゴリズムでは、テンプレートにあるリピーターを一度複製を取って削除します。複製は所属するノードも含めるので、複製はテンプレートそのままにターゲットノードを含めて保存されています。そして、リレーションの中にレコードがあればその保存したリピーターを複製して、エンクロージャーの子要素とします。この作業を繰り返します。この流れによってリピーターがレコードの数だけ繰り返されて、一覧表示が完成します。

このとき、必須ではないのですが、エンクロージャーの子要素は全てリピーターである方が最終結果は予測付きやすいです。仮にエンクロージャーにリピーターでない子要素がある場合、現状は結果的にリピーターが繰り返す前に集まってしまいます。それでいいのかもしれませんが、そうなら、エンクロージャーの前に記述すればいいことなので、リピーター以外はエンクロージャーの子要素にはしないというルールは問題ありません。ところが、前要素→リピーター→後要素のように並んでいた場合、気持ちは前要素→展開したリピーター→後要素のようになって欲しいかもしれませんが、そうなりません。そこで、header、footerとなるリピーターやseparatorとなるリピーターも定義しています。とにかく、テンプレートで並べたリピーター以外の要素が展開後どうなるかを意識しなくてもいいように、リピーター以外は子要素にないのが良いと考えます。これは、INTER-Mediatorの大きな制約なります。

ここで、エンクロージャーとリピーターの展開は、1対多の箇所に対応づけられるのがわかったので、以下の図のように、ステレオタイプで、enclosure/repeater/tareget nodeを割り当てました。しかしながら、ここで、MinorCategoryBoxがリピーターなのですから、その上位であるMajorCategoryBoxがエンクロージャーになる必要があります。もちろん、そうすると、MajorCategoryBoxはエンクロージャー件、さらに上位のコンテキストに対するリピーターでもあります。この状態でINTER-Mediatorは稼働するのではありますが、こうなると、MajorCategoryBoxの子要素に、MinorCategoryBoxとMajorCategoryLabelの2つの要素が割り当てられます。前者はリピーターですが、後者はリピーターではなく、INTER-Mediatorの制約に反します。そこでどうするかを考えないといけません。

ここで、新たに、MinorCategoryConteinerクラスを導入します。つまり、レコードの数に応じて複数存在するMinorCategoryBoxクラスのオブジェクトをまとめる存在のものを用意します。HTMLではこの記述は、汎用タグのdivやspanで簡単にできます。すると、MajorCategoryBoxはMajorCategoryLabelとMinorCategoryContainerを含み、リピーターを含まないことになります。(なお、AllSelectButtonは本来はエンクロージャーであるCategoryBoxの外に出すべきです。この要素は、リピーターではないからです。)

このような、エンクロージャーとリピーターの間の制約があり、そのために、ここでは4段階のdivタグで囲まれるようなHTMLコード例を示したわけです。

この制約は一般には考えにくい事実かもしれません。HTMLは独特の柔軟性を持っていることを利用しているということと、やはりデータベースの検索結果であるリレーションという表形式のデータを自動的に展開するという仕組みが微妙にコンフリクトする場面でもあります。リレーションの結果を調整するのはもちろんですが、一方で、HTMLのテンプレートも、リレーションをマッピングできるように調整しないといけないということになります。

元々、INTER-Mediatorは、trタグの要素がtbodyタグ要素の子要素として繰り返すことで、レコードの展開を「繰り返し」の拡張命令的な処理をしなくても実現できることを発見したのが始まりです。tbodyタグの子要素にはtrタグ要素しか登録できません。それ以外のものはテーブルの外にはみ出ますし、定義上、trしか存在できないはずです。また、同時にselectとoptionについても同様な関係があるとして、これらの2組は自動的にエンクロージャー/リピータとして認識するようにしました。前述のカテゴリ一覧も、tableの中にtableを作る方法でできると説明してしまえが一言で終わってしまい、あとは試行錯誤してコンテキスト定義を行えば動くでしょう。しかしながら、data-im-control=”enclosure” / data-im-control=”repeater”により、どんなタグでもエンクロージャーやリピーターになりうるようにしたのですが、その結果、明示的にエンクロージャーとリピーターのタグを記述しないといけなくなってしまっています。デザイン的には不要でも、INTER-Mediatorのアルゴリズムに合わせるために、ここでのMinorCategoryContainerのような要素が必要になります。

[続開発プロセス#8] 設計作業の実例(ドメイン分析とUI設計)

ここからは開発プロセスに従って、実際にどのように設計を進められるかを検証しながら、問題点やポイントを把握して行くことにします。サンプル開発のテーマですが、あまり難しすぎると把握が大変で、プロセスそのものとは関係ない情報を大量に捌くことになりかねないので、なるべくシンプルなものにしました。テーマは「1行メモ」です。もちろん、ものすごく簡単なので、すぐに作れるという方は多いとは思います。とはいえ、小さな開発でも突き詰めればきりがありません。要求をしっかり掘り下げるといくらでも細かな点は抽出できます。過剰に複雑にならないように、とは言え、ステークホルダーの要求に答えるというプロセスを考えるためには、一見するとすごく簡単なテーマがいいのではないかと思います。

以下、まずはINTER-Mediatorで構築することを中心に考えます。モックアップも、INTER-Mediatorを使って作りますが、データベース定義などしないで、モックは単に見栄えのチェックのためのものだけを作ります。

まずは、最初の要求が発生するとして、次のように要求が出てきたとします。

  • [R1] 1行メモを複数記録でき、一覧で見ることができる
  • [R2] メモ作成あるいは変更時の日時を記録することができる
  • [R3] 1つ1つのメモは、大分類と小分類を設定することができる
  • [R4] それぞれの分類は、予め定義されたものから選択するようにできる
  • [R5] 小分類の項目は1つの大分類項目に所属する形式にする(大分類と小分類は階層的な関係になっている)
  • [RX-1] 利用者、1人1人の1行メモを管理でき、他人のメモを見たり修正できたりはしない
  • [RX-2] 大分類、小分類の項目を編集する機能が必要

項目にコードを割り振っています。RはRequirementのつもりですが、RXの番号があるものは、要求としてはあるけども、設計作業を単純にするために、以後は無視するものとします。思いつく全ての要求を入れてしまうと、プロセスよりも状況の把握の方が膨大な情報量になるので、その点はご容赦ください。もちろん、無視した要求が重要なこともあるかと思いますが、ここでは、プロセスを通すことをまずは説明したいと思います。

上記のような要求がある場合、ざっくりとモックアップを作ってみるとこんな感じでしょうか? INTER-Mediatorを読み込んでいるので、テーマが適用されていますが、あとは単にinputやselectタグを適当に並べているだけで、特にデータベース処理などはしていません。要するに、tableタグにtrタグが3つ含まれていて、「複数のメモがある」感じを出しているだけです。

こういうのを作ると、もう、ここでいろんな要求が増えてきます。ちなみに、「メモ」あるいは「リマインダー」って、多くの人は「いいアプリがない」とお悩みなようで、どうやら、一人一人が要求するスペックが多岐に渡っていて、落とし所が見つかりにくいテーマなようです。ということで、以下、要求が出てくるとは言っても私の好みという色彩は強いので、メモアプリケーションとして評価はしないでください。あくまで、プロセスとしてこういう流れがありうるのかどうかということです。以下のような要求が追加されました。

  • [R6] 一覧に表示するメモは、分類に応じて絞り込みができるようにする。
  • [R7] 分類に関係なく全部のメモを表示したい。
  • [R8] メモがたくさん出てくる可能性がある。だとしたら、10ずつ表示するなど、ページネーションが必要になる。
  • [R9] 日付の入力はキータイプだけではなく、JavaScriptのコンポーネントを利用したい。

これらを含めたモックアップとして、次のように発展させたとします。絞り込みのため、左側に分類名のボタンを用意します。ページネーションは「とりあえず配置する」ということを示す赤い文字列を示すに留めています。一定以上の労力がかかる作り込みは、モックアップでは避けた方が良いでしょう。その代わり、例えば、このように赤字でコメントを書くということを行うことで、コミュニケーションを図ります。

この辺りまで進んでくると、おそらくディテールについて、いろいろな意見が飛び交うでしょう。まず、前の図だと、分類やメモそのものはテキストフィールドにありますが、最初に書き込んだ文言から変更しないことが多いのだから、テキストフィールドである必要はないと言えます。また、文字数が長くなると、欠ける可能性もあるので、メモの一覧には編集機能を付けないのがいいのではないかということになります。

であれば、入力や編集はどうするか? 1つの方法としては、以下の図にあるように、メモの一覧の下などに、メモの入力用エリアを設けて、ここで最初の入力ができるようにすることになるでしょう。他にも方法がありそうですが、ここでは別領域を設けることで合意したとします。

  • [R10] メモの一覧は、表示のみにする
  • [R11] 新しいメモを入力するためのUIを、一覧とは別に用意するタイプにする

左側の絞り込みを見てみると、小分類での絞り込みはできそうですが、指定した大分類だけの絞り込みもしたいのではないかということになりました。要求についてはR6が詳細化されたとみることができるでしょう。

  • [R6-m1] 一覧に表示するメモは、分類に応じて絞り込みができるようにする。この時、小分類だけでなく、大分類を指定しての絞り込みができるようにする

入力と表示を分けましたが、このままでは、メモの編集ができません。書き損じた場合はどうするのということもありますし、分類を変えたい場合も手段がありません。要するに、CRUDの考慮が必要になるということになりました。項目の削除は、一覧に「削除」ボタンを作る。そして、項目の内容を変更することは、どちらかと言えば主要な操作ではないので、リストをクリックするか、あるいはボタンを押すなどの方法で、別のページに遷移して修正ができるようにしようということになりました。ただし、この別ページ部分は今回の流れでは追わないことにしましょう。

  • [R12] 一覧にあるメモを「削除」ボタンで削除できるようにする
  • [RX-3] 既存のメモの内容を変更するための編集ページを用意し、遷移して修正できるようにする

他にはどうでしょうか? リストや分類項目の並べ方が確定していません。これについてもやはりいろいろな意見があるでしょうが、次のようにしましょう。

  • [R-13] メモの一覧では、作成修正日時の逆順で表示する。
  • [RX-4] 絞り込みの部分での項目の順序は、分類項目の編集時に指定できるようにする。

ちょっとしたアプリケーションのつもりでも、結構、項目は出てくるものです。ここまでのところでは、要求に関して、UI設計を進めることで、要求に不足する情報を補うことを意図した作業を進めてみました。メモについてはドメインと呼べるほどの知識はないのかもしれませんが、アプリケーションによってはそのドメイン部分の理解を何かの方法で進めながら、要求とUIの部分を進めました。次回は続いて、要件定義からモデル化を進める部分に進みましょう。

設計作業のサンプルで用いたファイルは、こちらで共有します。

Catalinaで使えなくなったRealtekのEthernetのドライバが出たけどダメだった話

私は、MacBookにNOVOOと書かれたEthernet端子付きのアダプタを自宅で使っているのですが、CatalinaになってEthernetが使えなくなりました。ドライバの問題なのは明らです。「システム情報」ではドライバそのものを認識しているのに対して、「ネットワーク」システム環境設定では赤いボールが見えていて、接続できません。もちろん、ケーブルはちゃんと接続してあります。

それで、どうやらCatalina対応版のドライバが出た模様です。Ver.1.0.20だそうです。とは言え、どうやらインストールが簡単ではないようです。そもそも、ドライバが動かなくなった理由は、おそらくはmacOSのセキュリティ要件が厳しくなったからと言うのもあると思われますが、その結果、機能拡張系は簡単にはインストールできない状態になったと思われます。そう言うわけでいろんな関門を潜り抜ける必要があるようです。

インストーラーの中のpkgファイルをダブルクリックして、まずはいきなり起動できません。署名はしてあり有効なもののようなのに、これが出るんですね。システム環境設定の「セキュリティとプライバシー」で「このまま開く」にしたら、もちろん起動してインストールはできます。

延々と待たされます。すると、インストーラーから「許可が必要になった」みたいなメッセージが出てきて、やはり「システム環境設定」のセキュリティとプライバシーを見ると、カギを解除しないといけませんが、みたこともない「いくつかのシステムソフトウェアの読み込みがブロックされました。」といったメッセージが出ています。「許可」をクリックします。

すると、ブロックされたソフトウェアがリストアップされるので、チェックを入れてOKをクリックしました。

この後待たされたのですが、最後の最後でエラーでインストールできないと言われました。えー。もう一度やったら成功したので再起動したのですが、やはり認識してくれません。「システム情報」上は以前と同じのようです。アップデートされていないとしか思えないです。

次に試したのはアンインストールです。ドライバのインストーラに、.commandファイルであるので、説明に従ってインストールして動かし、再起動してインストールしたのですが、再起動後にドライバがもはや見えない状態になっていました。

インストーラの文書を見ると、それでもダメならSIPをオフにしろと書いてあります。手順に従って、Command+R起動、csrutil disable、再起動してインストールしましたが、再起動後、「システム情報」にドライバ情報が登場しません。やはりダメです。

と言うことで、ダメでした。ググったのですが、できたと言う人もいればダメだと言う人もいると言う状況です。何か手順がいるのかも知れませんけど、macOSは順調にシステムに手を入れることを困難にしてセキュリティ確保を画策している模様です。

Apple IDの2要素認証をいつまでも求められる場合の対処

ちょっと話題的には古いのかもしれませんが、Apple DeveloperのApple IDは2要素認証が必須になり、そのような警告メッセージが出ました。で、ある時から、2要素認証を「しないといけない」状況になりました。

そこで、appleid.apple.comで、2要素認証をオンにしてみたのですが、XcodeやDeveloperサイトでの状況は変わりませんでした。Xcodeでは署名のたびにパスワードを求められるのですが、それでも認証が通らなくなってきてなんとかしないといけなくなりました。appleid.apple.comで設定すればいいだけではないというのがポイントです。

で、どうすればいいかといえば、macOSのシステム環境設定にあるiCloudで、そのApple IDを入力します。しばらくすると、警告アイコンが出てきて、更新するみたいな内容のボタンが出てくるので、それをクリックしてしばらくすると、アカウントの情報が更新された模様です。そこまでくると、Xcodeでの認証が通るようになります。

多分、何かの具合で、appleid.apple.comでは更新ができない状態なのでしょうけど、iCloudで普段使っていないアカウントの場合、入力し直しになり、データが消えるみたいなメッセージに対処しないといけないわけですが、データはクラウドにあるので原則問題ないと思います。しかしながら、システム環境設定を変えるのは色々面倒と後の影響がきになるところですが、appleid.apple.comでダメだったら仕方ないので、iCloudでセットアップして、更新処理をmacOS側からやってください。

composer.jsonファイル内でのスクリプトのパスをUNIX/Windows互換にする

 PHPでのモジュール管理ツールであるcomposerでは、通常「composer update」と入力することでインストール作業が始まるが、インストール後に実行するスクリプトを記載することもできる。INTER-Mediator Ver.6では、composerの仕組みによりnode.jsをインストールして、「composer update」に引き続いて「npm install」を自動的に動かすようにしている。composer.jsonファイルの主要部分はこんな感じだ。package.jsonはすでに用意されている。composerはvendorというディレクトリを作り、そこにモジュールをコピーし、加えてネームスペースを元にしたファイル検索の仕組みが組み込まれる。単にコマンドを入れてみれば、nodeやnpmコマンドがvendor/binにコピーされているので、相対パスの記述が適切だと考えた。

{
"name": "inter-mediator/inter-mediator",
"version": "6-dev",
"time": "2019-03-07",
:
"require": {
"php": "^7",
:
"mouf/nodejs-installer": "*",
:
"scripts": {
:
"post-update-cmd": [
"./vendor/bin/npm --save-dev install"
],
:

 これで、macOS、Linuxでは正しくnpmコマンドが稼働するのではあるが、Windowsではエラーが出てnpmが動かない。PowerShellでは次のようにでる。最後の行は赤い背景になる。コマンドプロンプトでもほぼ同様だ。

> ./vendor/bin/npm --save-dev install
'.' is not recognized as an internal or external command,
operable program or batch file.
Script ./vendor/bin/npm --save-dev install handling the post-update-cmd event returned with error code 1

 やっぱり/だろうかと思ってディレクトリセパレータを¥にしてみた。もちろん、JSONなので¥¥とタイプするが、やはり同じだ。./を無くしても同じである。

 あれこれ調べて分かった。composerのドキュメントのNoteの所に書いてある。要するに、./vendor/bin(あるいは.¥vendor¥bin)へは、composerによってパスが通っているらしい。composerによってインストールされるコマンドの場合は、以下のように相対パスを書かなくても、npmコマンドの実行はできるということである。この記述だと、UNIXでもWindowsでも同一の記述でnpmコマンド実行ができる。

 :
"post-update-cmd": [
"npm --save-dev install"
],
:

要求は対話を通じて次第に明確化するもの

 ソフトウェア開発では、実際に動くようにする「実装」という作業が必要である。どのように実装するかは「要件定義」という記述を元にして検討され、その要件定義は顧客の「要求」を元に作られるというのが原則だ。本来は要求が先にあり、「〜をしたい」「〜を実現したい」という形式で記述できるものが基本である。「○秒以内で処理が完了する」と言った非機能要求も記述される。しかしながら、この要求による記述内容だけでは、その実現方法が多岐に渡る事になる。そこで、実装を円滑に進めるために「〜ができるようにする」という形式で記述できる要件を定義し、実装側は要件の実現に注力する。容易に分かるのはこうすれば実装の分業がやりやすいことだ。開発業務は元請けから下請けや複数の開発者に至る階層化された業務フローが一般的であり、要件定義は分業の基礎としてビジネス構造にもマッチする。この方法の是非はあるのだが、長年この流れをベースにやっているだけに、なかなか変えられれないというのが実情だとして、この枠内で開発における問題点を考える。

 うまく分業され開発が進むかと思うと、現実には顧客が望んでいたものと違ったものが開発されてしまうという問題がある。笑い話になりそうな、全く違うものができてしまうこともある。ちょっと意図的に強調した例として次のようなものがある(もちろん、かなり単純化している)。要求として「ポイントシステムを作りたい」として、要件としていくつか作られた項目の中に「ユーザー管理機能」というものがあるとして、その要件だけを知らされて実装したとしよう。すると、単に住所録みたいなものが出来上がり、管理番号がないとか、期限の管理はどうするのよ?みたいな実装が上がってくるかもしれない。もちろん、通常は自身が実装する要件以外も目を通すので、優秀なプログラマーたちの忖度機能が発動され、次第に顧客の要求に近づくということになる。なんだ、うまく行っているということではないのか?と思われるかもしれないが、うまくいく前提の1つとして、要求も要件も記述されている、つまりは文書化されているという前提がある。もちろん、100%文書化できるものではないのは確かだが、一定以上の割合の文書化がされていないとおそらく忖度ベースの開発は成り立たない。そして、経験上、一定以上の割合が満たされることはほとんどないと感じている。

 ソフトウェア開発の形態は実に様々あり、個別の事情も多いのだが、ここでは単純化して、顧客とコントラクターとデベロッパー(開発者)の3つの存在があるとして考えよう。下請け構造については「開発者」の一言で片付ける。ざっくりと言えば、SIerという人たちは「コントラクター」であるが、いわゆる「ゼネコン」の意味でのコントラクターであり、顧客から業務を請負、遂行する中心人物である。このような状況で開発をしている場合、開発者にとっては顧客の要求が極めてわかりにくいことが多々ある。好意的に見えればコントラクターが開発者にわかりやすいように解釈して作業に分割してくれている結果とも言えるのだが、コントラクターが顧客の要求を本当に理解しているのか疑問に持つような要件を投げてくることもある。たぶん、誰も悪意はない。それでも、比較的大きめの開発をしていると、要件に矛盾が目立つようになることが出てきて、疑問は急激に膨らむのである。要件は解釈がぶれる余地のない文章に一般的にはなっていることもあり、開発者は要件は書いてある通りに解釈できるものの要求が断片的にしか分からないと強く感じるようになる。コントラクターとの関係が悪くなると、責任の押し付け合いにもなる。

 何とかならないのだろうか? だからこそアジャイルなのだろうか? 手法に至る前にまず、問題の本質をどこに求めるかを特定したい。開発者が要件が分からないと感じる時、実はコントラクターも要件はよく分かっていないことが多い。開発者がコントラクターに問い合わせた場合、開き直って「私にもわかりません」と言われても困るのだが、もっと困るのは分かっているふりをすることで、顧客の要求と異なる回答をしてしまうこともある。実は、開発者と同じ程度にコントラクターも顧客の要求が分かっていないと考えるべきだ。そうなると、コントラクターがもっとしっかりすべきなのだろうか? それでは、よくある「営業 vs 実行部隊」の局面になってしまい責任の押し付け合いに等しく、ここから何か生み出されたことを見た事はない。開発者はコントラクターを責めてはいけない。その理由は、実際のところ、顧客自身が実はコントラクター以上に要求を理解していないからである。顧客に何か要求が明確にあるのかというとそうでもない事が普通にあるというのが現実だ。正直な顧客は「モヤモヤした話で恐縮ですが」と切り出して、構想を語る。不誠実な顧客は「お前らプロなのにそんなんことも分からないのか」と訝る。分からないから聞いているのである。私たちが超能力者でないことを説明する気力は普通は出ない。何れにしても、要求が文書化されているかと言えば、極めて限定的になってしまうのが通常である。

 そこで、顧客は要求を理解していないという前提に立つ必要がある。理解していないというのは色々な側面がある。何か構想はあるけど言語化されていない場合もあれば、上司に言われてシステム化の取りまとめをしており経営的な意味は全然説明されずに担当者になっているような場合もある。この点は掘り下げたいところだが、ここでは文書化あるいは言語化されていないという状況としてまとめておこう。このような状況は、次のように考えて見よう。顧客の頭の中はファンタジーであり、顧客の発する言葉はポエムなのである。ソフトウェア開発において、コントラクターはそのファンタジーを実現すべく奔走する人であり、開発者はポエムを開発言語に翻訳する人たちである。それじゃあやってられん!と言う前にこう考えよう。ソフトウェア開発者は、要するにスターウォーズのような素晴らしいSF作品を制作しているスタッフのような存在なのだと。若干無理やりかつ恣意的なポジティブ化ではあるが、ここで前を向かないとどうしようもないのである。

 ここであっさり「要求なんぞはないものだ、幻想だ」と言い切るのも無理がある。現在の多くの開発手法は要求が出発点にあるから。要求がないのではなく、開発者は要求を何らかの方法で明確化をする必要があるという前提に立ちたい。筆者は開発者として関わることがあるが、一人で全部行うコントラクター兼開発者の場合もある。後者の場合の問題点はかなり別のことが絡むので、ここでは顧客/コントラクター/開発者モデル場合だけに限定する。短絡的な考えとしては、開発者として関わっても、顧客と直接話をしたらなんとかなるのではないかとも考えた。だが、ビジネスライン的な意味で、普通はコントラクターは嫌がるのと、対顧客に対する原点的な意味での要求獲得は別の難しい問題があるので、開発者と顧客を会わせるというのはよほど限られた状況でしか成立しない。少なくとも、開発者が顧客対応可能なSE能力を持つような場合などである。ということで、顧客/コントラクター/開発者ラインを保って要求を明確化する方法はないかと試行錯誤をした結果、1つの方法がうまくやれば良い方向に行くのではないかという感触を得た。確証を得るほどの事例に当たった訳ではないが、提案はできる段階に来たと感じている。

 その方法は、コミュニケーションにおいて、要求の断片を話題に含めて、その要求あるいは会社が正しいかを検証することである。例えば、要件定義として「顧客管理番号は7桁である」と書かれているとしよう。これは少なくとも、「123」でいいのか、「0000123」のようにするか悩む定義である。単に「この要件は管理番号の頭を0で埋めるという意味でしょうか?」などと問い合わせるのが普通だと思われる。このコミュニケーションの中において、要求に近い文章を紛れ込ませる。例えば、「管理番号は、ポイントカードに印刷されるので、頭を0で埋める7桁なのでしょうか?」と聞いてみる。ここで「ポイントカードは印刷してお客様に渡したい」「ポイントカードには管理番号が印刷されていて、システム問い合わせで利用したい」と言った要求を言い換えて、質問に含ませている。このような問い合わせに対して、正解と言われるかもしれないが、違っているかもしれない。「管理番号はポイントカードには印刷しません。単にバックエンドのシステムが7桁で切られているから、それを気をつけて欲しいという意味です」のように答えが返るかもしれない。どっちにしても、ほんの少しだが、要求に近い情報が得られた。もちろん、それは小さな積み重ねだが、全くないより大きな進歩があるはずだ。なんとなくそうなのかなと思っていたことが明確になることも小さな進歩だろう。前記の例だと、「実は顧客情報はバックエンドのシステムにも入れられる」という新しい発見があったのかもしれない。仮にポイントカードに印刷するための0埋めだったとしたら、それをどこでやるのかという議論に発展するかもしれない。

 開発者からコントラクターに、要求が分からないから示して欲しいと言っても、コントラクターは開発者と同程度、下手をするとさらに下回るレベルでしか要求は理解していないと考えるべきだ。しかも、コントラクターはその点を意識しているかどうかも怪しい。無理やり文書化をお願いしたとしても、出てくるものには新しい情報はない。顧客に依頼するというのも同様だ。しかしながら、要件の中から要求を含めた、あるいは要求に至る内容を込めたコミュニケーションをすることで、不明瞭な要求群に対して逆算的に少しずつ隙間を埋めるような効果が期待できる。その結果、要求が次第に明確化し、気づいていない関連性が見えて来ることも期待できる。開発者にとって知る事も重要だが、コントラクターやさらには顧客に対しても気づく機会を作っていることも重要である。開発に関わる全ての人の意識を要求のさらなる明確化に向けるためのコミュニケーションが活発になることで、開発現場の様々な側面で要求が次第に固まるということを期待できるのではないかと考える。

 この手法で一番難しいのは作文である。コントラクターや顧客との関係上、どこまで言っていいのかなど、ドメインや個別の事情も絡む。要件に絡めるというのが1つの成立しやすい手法だが、普段の会話で投げてみるというのもあるかもしれない。しかしながら、同じようなことを何度も聞く、突拍子もない質問になってしまうといったようなことは避けたほうがいい。バカ者かと思われてしまうのは不利な状況を作りかねない。それでもうまい問いかけを繰り返し辛抱強く進め、成果物に対する着目点を顧客/コントラクター/開発者でなるべく多く共有できれば、より良い結果になることは十分に期待できる。

 実装する開発者が何人もいるような開発で、頼りになるように見える人は必ずいる。その人は、発言が多く、かつ、発言の中で、念の為にという意味合いで色々な角度で言葉を繰り出す発言が目立つ。そうしたやりとりが、開発の中で自身の実装部分に対しても非常に参考になったという経験もある。確認のために、条件を言い換えてみたり、その要件の目的をたずねてみると言った行動が、他の開発者の助けになるということもあった。そういう人はコミュニケーションスキルが高いという事で終わらせがちだが、スキルには細かなポイントがたくさんある。その1つが要求の明確化であり、それがコミュニケーションから可能ではないかということも、様々な人たちの行動や自身のプラクティスからの結論である。一方、この方法がうまく行かない事例として、コントラクターが開発者の発言を抱えてしまって顧客に流さないタイプの人である場合だ。コントラクターがしっかりしていればいいとも言えなくはないが、前記の通り、開発者が要求が分からないときは得てしてコントラクターも分かっていない。顧客に確認もしないというのは、単なる顧客に対するイエスマンかもしれないが、要求に対する問題意識がないのかもしれない。こういう現場では、とにかく時間がかかり、作業方針も立ちにくく、開発者の間で何かがぐるぐる回っているだけになる。このようにアンチパターンはある程度は見えている部分もあるが、まずはプラクティスとして問いたいと考える。