Technote

by sizuhiko

BambooのChatWork通知プラグインを作成しました

現在作業支援している現場では Atlassian JIRA,Bitbucket,Bambooと、コミュニケーションツールとしてChatWorkを使っています。 CIツールであるBambooは通知機能としてEmail,HipChat,IMに対応しているのですが、汎用的な通知機能は持っていないので、ChatWorkプラグインを自作してみました。

Bamboo ChatWork Plugin

ここではBambooのプラグインを作る手順と、そのときにハマったポイントなどを解説します。

はじめに

BambooのプラグインをAtlassian SDKをインストールします。 Set up the Atlassian Plugin SDK and Build a ProjectからプラットフォームにあったSDKをインストールします。

SDKをインストールしたらStep 3: Try an atlas commandのとおり以下のコマンドを実行します。

mkdir 作業ディレクトリ名
cd 作業ディレクトリ名
atlas-run-standalone --product bamboo

atlas-run-standaloneを実行すると、大量のmaven installが動きます。 かなり時間がかかるので、ネットワークが速い環境と、時間にゆとりがあるときに実行した方が良いです。

プラグインのスケルトンを生成する

以下のコマンドを実行してください。 sh atlas-create-bamboo-plugin 以下の表の入力を求められるので、適切に入力します。

Define value for groupId作成するプラグインのパッケージパスを指定
Define value for artifactId作成するプラグインの名前を指定
Define value for version作成するプラグインのバージョンを指定
Define value for packagegroupIdとartifactIdを結合した値を指定
入力した内容はpom.xmlに反映されるだけなので、後でpom.xmlを編集すれば大丈夫です。

プラグイン名のディレクトリにpom.xmlsrc/main フォルダに以下のファイルが自動生成されます。

  • {Define value for package}のパス/MyPluginComponent.java
  • {Define value for package}のパス/MyPluginComponentImpl.java
  • /resources/atlassian-plugin.xml

正直、自動生成されたコードは役に立たないので、削除してしまって良いでしょう。 atlassian-plugin.xmlはデフォルト値になっているので、適切に編集します(これは後で大丈夫です)。

以下のコマンドを入力して、プラグイン環境を実行します。

cd プラグインの名前
atlas-run

atlas-runを実行すると、大量のmaven installが動きます。 かなり時間がかかるので、ネットワークが速い環境と、時間にゆとりがあるときに実行した方が良いです(2度目)。

プラグインを実装する

Bambooの通知プラグインとして、最初からインストールされているものはHipChat通知プラグインです。 とりあえずプラグインを作るための情報があまりに少ないので(Wikiを見て進めても肝心な箇所ほどJavaDocを見ろになってしまう。だがJavaDocを見てもわかるはずがない)、通知プラグインを作りたいと思った人はHitChatプラグインまたは、私の作ったChatWorkプラグインをコピー&ペーストするのがお勧めです。

私もまずHipChatプラグインのソースコードを参考にしました。

HipChatプラグインやChatWorkプラグインのコードを見るとわかりますが、実装するファイルはわずかです。 src/mainディレクトリに以下のファイルを配備します。

  • AbstractNotificationRecipientを継承した通知プラグインの設定画面コントローラ
  • NotificationTransportを実装した通知プラグインの通知コントローラ
  • resources/atlassian-plugin.xml(プラグインの設定ファイル)
  • Freemaker形式のテンプレートファイル(設定入力画面、設定表示画面、通知メッセージなど)

実装する量は多くなく、コツさえ掴めば通知プラグインを作るのは難しくありません。コツさえ掴めば…ですが。

通知プラグインの設定画面コントローラを作成する

ChatWorkプラグインでは、ChatworkNotificationRecipient.javaというクラスを作成しました。 作成したというよりはHipChatプラグインのHipchatNotificationRecipient.javaを丸々コピーして微修正した程度です。

実際に差分を見るとわかりますが、クラス名など変更した程度です。

このコントローラに対応する画面はatlassian-plugin.xmlで指定します。

<notificationRecipient key="recipient.chatwork" name="Chatwork Recipient" class="jp.tokyo.open.bamboo.plugin.chatwork.ChatworkNotificationRecipient" weight="10">
    <description>ChatWork</description>
    <resource type="freemarker" name="edit" location="templates/plugins/notifications/chatwork/editNotification.ftl"/>
    <resource type="freemarker" name="view" location="templates/plugins/notifications/chatwork/viewNotification.ftl"/>
</notificationRecipient>

resourcenameeditである場合、設定編集画面のファイルパスを指定します。 viewの場合は、導入済み通知プラグインが設定一覧に表示される画面部品のファイルパスを指定します。

実際の画面を見てみましょう

[@ww.textfield labelKey="chatwork.api.token" name="chatWorkApiToken" value="${chatWorkApiToken!}" required='true'/]
[@ww.textfield labelKey="chatwork.room" name="chatWorkRoom" value="${chatWorkRoom!}" required='true'/]
[@ww.checkbox labelKey="chatwork.notify" name="chatWorkNotifyUsers" value="${chatWorkNotifyUsers!?string}"/]

これはFreemakerというテンプレートエンジンを使っているのですが、なんとなく想像できるレベルです。 HTMLと似ています。labelKeyはリソースファイルに定義した内容をバインドするので国際化対応できます。

ここでコツ1

Freemakerのテンプレート上のname属性と、コントローラクラスの設定入力画面の項目名 がマッピングされています。

この画面はBambooで通知設定を入力するときに以下のようなHTMLに変換されます。

<input type="text" name="chatWorkApiToken" value="xxxxx" required>
<input type="text" name="chatWorkRoom" value="oooooo" required>
<input type="check" name="chatWorkNotifyUsers" value="1" checked>

想像どおりですか? 注意しなければならないのは「設定画面にはすべてのプラグインのHTMLが並ぶ」ということです。 えっ?何を言っているかわからない?ではどのようになっているかというと以下のようなHTMLになるのです。

<div class="hipchat-plugin" style="display:none">
  <input type="text" name="apiToken" value="xxxxx" required>
  <input type="text" name="room" value="oooooo" required>
  <input type="check" name="notifyUsers" value="1" checked>
</div>
<div class="chatwork-plugin">
  <input type="text" name="chatWorkApiToken" value="xxxxx" required>
  <input type="text" name="chatWorkRoom" value="oooooo" required>
  <input type="check" name="chatWorkNotifyUsers" value="1" checked>
</div>

このようなフォームが生成されていて、選択した通知の部分だけが見えるようになるのです。 カンの良い人は気付いたかもしれません。 私は最初HipChatプラグインをコピペして、項目名を変更していなかったので、以下のようなHTMLが生成されていて、うまくフォームから値を取得できませんでした。

<div class="hipchat-plugin" style="display:none">
  <input type="text" name="apiToken" value="xxxxx" required>
  <input type="text" name="room" value="oooooo" required>
  <input type="check" name="notifyUsers" value="1" checked>
</div>
<div class="chatwork-plugin">
  <input type="text" name="apiToken" value="xxxxx" required>
  <input type="text" name="room" value="oooooo" required>
  <input type="check" name="notifyUsers" value="1" checked>
</div>

同一フォームに同じname属性を持つHTMLが生成されて、submitされるのでコントローラのpopulateメソッドのMapに正しく値が入らなくなっていました。 なのでなるべく重複しない名前を指定しておくことが重要です。 ところが標準の通知プラグインであるHipChatが一等地の名前を持っているのです。Bambooとしてどのような名前規則を推奨しているのかドキュメントには記載していないので、パッケージ名を付加するなどの工夫が必要です。

なお保存すると、保存完了メッセージが表示されるのですが、このメッセージのカスタマイズ方法はわかっていません。 そもそもカスタマイズできるのか調査中ですが、Bambooのコードはオープンソースではないので…(ry

通知プラグインの通知コントローラを作成してChatWorkに通知する

ChatWorkプラグインでは、ChatworkNotificationTransport.javaというクラスを作成しました。 作成したというよりはHipChatプラグインのHipchatNotificationTransport.javaを丸々コピーして修正しました。

まず通知には通知メッセージが必要ですよね。 通知メッセージをFreemakerのテンプレートで記述したいのですが、ここで圧倒的なドキュメント不足に遭遇します。 ほぼ自力での解決はムリなので、Bambooの標準テンプレートをコピペして作業した方が良いでしょう。

HipChatプラグインのソースコードを見ると、特に通知クラスを作ったり、通知のテンプレートを指定することはしていないようです。 Bambooの通知機能はHipChatに連携することに依存していて、HipChatはBamboo標準のテンプレートを使っています。 通知プラグインを作るためのドキュメントを確認します。 Building a Notification Pluginを読むと、カスタムリスナーを登録する手順が書いてありますが、独自の通知タイプを作らない限りこのとおりやらなくても大丈夫です。 実際、私もカスタムリスナーを作成してドキュメント通りやってみたのですが、うまく動作しませんでした。atlassian-plugin.xmlにリスナー定義を追加してやってみたりしたのですが….

ここでコツ2

そこでChatWorkプラグインでは、ChatworkNotificationRecipientからChatworkNotificationTransportのインスタンスを作成するときに、TemplateRendererというFreemakerのテンプレートを操作できるインスタンスを渡すようにしました。ChatworkNotificationTransportで独自に取得することはできないようです。 ChatworkNotificationRecipientではDIによって値がセットされるようになっているようです(ドキュメントなし)。

ChatworkNotificationTransport::sendNotification()メソッドが送信指示として呼び出されるので、TemplateRendererを使って独自テンプレートを呼び出して文字列に変換します。 文字列変換したメッセージをChatWork APIを使って指定したルームに送信するだけです。

以下の部分がChatWork用に実装したコードです。

    static String getChatworkApiURL(String room) {
        return CHATWORK_API_URL.replaceAll("\\{room_id\\}", room);
    }


    private String getChatworkContent() {
        String templateLocation = "templates/plugins/notifications/chatwork/BuildCompleted.ftl";
        return templateRenderer.render(templateLocation, populateContext());
    }

    private Map<String, Object> populateContext()
    {
        Map<String, Object> context = Maps.newHashMap();
        context.put("build", plan);
        context.put("buildSummary", resultsSummary);
        return context;
    }

getChatworkContent()を使ってFreemakerテンプレートからメッセージを取得します。 実際のテンプレートファイルは以下のよな内容です。

[#-- @ftlvariable name="build" type="com.atlassian.bamboo.build.Buildable" --]
[#-- @ftlvariable name="buildSummary" type="com.atlassian.bamboo.resultsummary.BuildResultsSummary" --]
[#include "/notification-templates/notificationCommons.ftl"]
[#include "/notification-templates/notificationCommonsText.ftl" ]
[#assign authors = buildSummary.uniqueAuthors/]

[#if buildSummary.successful][#lt]
[info][title][@buildNotificationTitleText build buildSummary/] was SUCCESSFUL[/title]
[@showRestartCount buildSummary/]
[#if buildSummary.testResultsSummary.totalTestCaseCount >0] [@showTestSummary buildSummary.testResultsSummary/][/#if].
[#if authors?has_content] [@showAuthorSummary authors/][/#if][#lt]
${baseUrl}/browse/${buildSummary.planResultKey}/
[/info]
[#else][#lt]
[info][title][@buildNotificationTitleText build buildSummary/] has FAILED[/title]
[@showRestartCount buildSummary/]
[#if buildSummary.testResultsSummary.totalTestCaseCount >0] [@showTestSummary buildSummary.testResultsSummary/][/#if].
[#if authors?has_content] [@showAuthorSummary authors/][/#if][#lt]
${baseUrl}/browse/${buildSummary.planResultKey}/
[/info]
[/#if][#lt]

先ほども書きましたが、これをJavaDocの情報を元に生成するのはほぼムリ、というか完全にムリです。 最初の手順でインストールしたSDKの中に実際のBambooがインストールされるので、Bambooのwarファイルが展開されたWEB-INF/classes/notification-templatesのBuildCompleted.ftlファイルの内容をコピペして、ChatWorkメッセージ記法に合わせて修正しています。

後はSDKで実行したBambooにプラグインをインストールして、デバッグしながら確認するのが近道です。

プラグインをビルドする

プラグインは管理画面からjarファイルとしてアップロードします。 そのためコンソールから以下のコマンドを実行します。

cd プラグインの名前
atlas-mvn clean package

atlas-mvnはmvnのラッパーのようなのですが、mvnを直接実行するのでなく、こちらのコマンドを使った方が良いみたいです。 あとは通常のmavenのビルドと一緒ですが、初回はまた大量のmvn installが動くのでネットワークと時間には余裕を持って挑みましょう。

ビルドが終わるとtargetディレクトリにjarファイルが生成されます。

さいごに

ChatWork通知プラグインとHipChat通知プラグインの差分はほんとうにわずかで、それほど難しいものではありません。 Bambooの通知プラグインモジュールで得た経験は以下のようなところです。

  • ドキュメント読めばなんとなくわかるけど、「詳しくはJavaDocへ」でつまづく(心が折れる)
  • 入力画面など生成されたものはブラウザのデバッガを使って、名前空間がバッティングしていないか確認が必要
  • EclipseのJDなどを使って、Bamboo自体のソースコードをリバースして調べながら実装しないとムリ

プロジェクト都合じゃないとなかなかBambooとか使ったりする機会がないのですが、もしBamboo使うことになって通知プラグインを作りたいと思った方は参考にしていただければと思います。

長くなったので、このあたりで終わりにしますが、他にもいろいろわかったことはあるので、また気が向いたら書こうかなと思います。

Fabricateのversion2を作成しました

これまでCakePHP2用のデータジェネレータプラグインとして開発を続けてきたFabricateV2として各種ORMへ対応するようなコアモジュールへと変更しました。 またFabiricateリポジトリのmasterブランチへは統合されていませんが、CakePHP3のリリース時期を合わせて、V2ブランチを本流にする予定です。 これまでのCakePHP2用ライブラリはcakephp2ブランチでメンテナンスを続ける予定です。

データジェネレータって何?誰得?という方は、以下の投稿を一読していただけると理解が深まります。

FabricateはRubyのFabricationとFactory_Girlに影響された「fixtureはDRYではないので、Fixture replacement を使おう」という流れに乗ったPHPクローンです。

Version2を作成するきっかけ

このキッカケはいくつかあります。 1つには昨年の CakeFest2014 で Fabricate の LT をした後で、 CodeIgniter の開発者だったことでも知られるPhil Sturgeon から、「これいいね、他のフレームワークでも使えるようにしてよ」と言われたことでした。 他のフレームワークでも使えるようにしたいという思いは、少しあったのですがまだ確約できなかったので「maybe」と返しただけでした。(このときPhilはかなり酔っていたので、私に話した事を覚えているのかどうかも怪しいですがw)

さらにもう1つおおきなキッカケがCakePHP3でのPSR対応によって、namespaceなど外部ライブラリを使いやすくなり、さらに外部ライブラリからフレームワーク本体のコードへのアクセスしやすくなったということがあります。 CakePHP2ではフレームワークへアクセスするにはアプリケーションまたはプラグインでないと困難で、特にORMに依存したライブラリをフレームワークをまたいで作成するのは困難でした。これがPSRの対応でやりやすくなったということ、CakePHP3への対応に合わせてV2を作って、他のフレームワークにも対応できるようにしようと思ったのです。

Fabricate V2の主な変更点

Fabricateの利用方法はほとんど変わっていませんが、各ORM用にアダプターと呼ばれるORMの差分を吸収するクラスを準備する必要があります。 Fabricateの本体にはFabricate\Adaptor\FabricateArrayAdaptorという連想配列構造を返すサンプル用のアダプターを用意しています。これを参考にアダプターを実装すればDoctrinePropelYiiCodeIgniterでも利用可能になります。

アダプターの実装方法

とはいえ実際にORMに接続するアダプターもないと、ということでCakePHP3用のアダプターCakePHP Fabricate Adaptorも作成しました。

ここではCakePHP3のアダプター実装を例に、アダプターで何を実装しなくてはいけないのかを解説したいと思います。

作成するクラス

アダプタークラスはFabricate\Adaptor\AbstractFabricateAdaptorクラスを継承します。

<?php
use Fabricate\Adaptor\AbstractFabricateAdaptor;

class CakeFabricateAdaptor extends AbstractFabricateAdaptor
{
    ...
}

実装するメソッド

アダプターとして実装する必要があるメソッドは以下の3つです。

  • getModel
  • create
  • build

それぞれのメソッドの実装を確認しましょう。

getModel

getModelメソッドは各ORMの差分を吸収するためのモデルインスタンスを返却するデータジェネレータの定義部分です。 データジェネレータとしては最も重要な機能です。

<?php
use Fabricate\Model\FabricateModel;

public function getModel($modelName)
{
    $model = new FabricateModel($modelName);
    $table = TableRegistry::get($modelName);
    $schema = $table->schema();
    foreach ($schema->columns() as $name) {
        if ($this->filterKey($table, $name)) {
            continue;
        }
        $attrs = $schema->column($name);
        $options = [];
        if (array_key_exists('length', $attrs)) {
            $options['limit'] = $attrs['length'];
        }
        if (array_key_exists('null', $attrs)) {
            $options['null'] = $attrs['null'];
        }
        $model->addColumn($name, $attrs['type'], $options);
    }
    foreach ($table->associations()->keys() as $key) {
        $association = $table->associations()->get($key);
        $target = $association->target();
        $className = get_class($target);
        $alias = $target->alias();
        switch ($association->type()) {
            case Association::ONE_TO_ONE:
                $model->hasOne($alias, $association->foreignKey(), $className);
                break;
            case Association::ONE_TO_MANY:
                $model->hasMany($alias, $association->foreignKey(), $className);
                break;
            case Association::MANY_TO_ONE:
                $model->belongsTo($alias, $association->foreignKey(), $className);
                break;
        }
    }
    return $model;
}

getModelはFabricateModelクラスのインスタンスを生成する責務があります。 FabricateModelはPHPのマイグレーションツールであるPhinxに影響を受けていて、スキーマの定義方法が似ています。

<?php
// Phinx
$users = $this->table('users');
$users->addColumn('username', 'string', array('limit' => 20))
      ->addColumn('password', 'string', array('limit' => 40))
      ->addColumn('password_salt', 'string', array('limit' => 40))
      ->addColumn('email', 'string', array('limit' => 100))
      ->addColumn('first_name', 'string', array('limit' => 30))
      ->addColumn('last_name', 'string', array('limit' => 30))
      ->addColumn('created', 'datetime')
      ->addColumn('updated', 'datetime', array('null' => true))

// FabricateModel
$users = new FabricateModel('users');
$users->addColumn('username', 'string', array('limit' => 20))
      ->addColumn('password', 'string', array('limit' => 40))
      ->addColumn('password_salt', 'string', array('limit' => 40))
      ->addColumn('email', 'string', array('limit' => 100))
      ->addColumn('first_name', 'string', array('limit' => 30))
      ->addColumn('last_name', 'string', array('limit' => 30))
      ->addColumn('created', 'datetime')
      ->addColumn('updated', 'datetime', array('null' => true))

利用できるカラム型はPhinxと同様で、オプションについて現時点ではlimit(長さ)のみ対応しています。

CakePHP3では

<?php

$table = TableRegistry::get($modelName);
$schema = $table->schema();

という記述でスキーマ情報が連想配列で取得できるので、それを繰り返してaddColumnを呼び出しています。

スキーマ定義を作成したら、次にモデルの関連を定義します。 モデルの関連定義は、以下のようにassociationを使って関連構造を一度に作成する場合に必要となります。

<?php
Fabricate::create('Users', function($data, $world) {
    return [
        'username' => 'taro',
        'posts' => $world->association('Posts', 3),
    ];
});

この例のように関連を設定するには、以下のようにFabricateModel::hasMany()やbelongsTo()を使います。

<?php
$users->hasMany('posts', 'post_id', 'Posts');
$posts->belongsTo('users', 'post_id', 'Users');

パラメータの指定方法は、hasManyもhasOneもbelongsToも同じで、(別名、外部キーカラム名、モデル名)を指定します。

create

createメソッドは、Fabricateによって生成された連想配列構造のデータを、ORMを使ってDBに保存する機能を実装します。

<?php
public function create($modelName, $attributes, $recordCount)
{
    $table = TableRegistry::get($modelName);
    $entities = $table->newEntities($attributes, ['validate' => $this->options[self::OPTION_VALIDATE]]);
    $table->connection()->transactional(function () use ($table, $entities) {
        foreach ($entities as $entity) {
            $ret = $table->save($entity);
            if (!$ret) {
                return false;
            }
        }
        return true;
    });
    return $entities;
}

$modelNameにはFabricate::create('Users',と記述した場合の、Usersが渡ります。 $attributesには生成された連想配列が渡ります。例えば以下のとおりです。

<?php
array (
  0 => 
  array (
    'title' => 'Lorem ipsum dolor sit amet',
    'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
    'created' => '2013-10-09 12:40:28',
    'updated' => '2013-10-09 12:40:28',
  ),
  1 => 
  array (
  ....

$recordCountは生成する件数ですが、この値はcount($attributes)の値と一致します。 CakePHP3では以下の流れでDBへ保存しています。

  • TableRegistry::get()でテーブルインスタンスを取得
  • Table::newEntities()で連想配列からエンティティを生成
  • Table::save()でエンティティをDBに保存
build

buildメソッドは、Fabricateによって生成された連想配列構造のデータから生成したエンティティを返却する機能を実装します。

<?php
public function build($modelName, $data)
{
    $table = TableRegistry::get($modelName);
    $entity = $table->newEntity($data, ['validate' => $this->options[self::OPTION_VALIDATE]]);
    return $entity;
}

$modelNameにはFabricate::create('Users',と記述した場合の、Usersが渡ります。 $dataには生成された連想配列が1インスタンス分だけ渡ります。例えば以下のとおりです。

<?php
array (
  'title' => 'Lorem ipsum dolor sit amet',
  'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
  'created' => '2013-10-09 12:40:28',
  'updated' => '2013-10-09 12:40:28',
)

さいごに

現在CakePHP3用のアダプターしかないので「xxxx ORMについてアダプター実装したよ!」という連絡を待っています。 Fabricateのcomposer.jsonのsuggestに追加してPull Requestをもらえるととても助かります。

    "suggest": {
        "sizuhiko/cake_fabricate": "for integration with CakePHP3"
        // ここに追加したPRをお待ちしています
    }

皆様のPull Requestをお待ちしております!!

2014年ふりかえり

この投稿は2014年参加/発表したり、運営に携わったイベント、やっていた仕事の内容などについてふりかえるポストです。

1月

2013年から年末の忘年会が少なめで、2014年から新年会が異常に多いという現象になりました。 ちなみにこれは2015年も同様で新年会がいっぱいです。新年から1年をなかったこと(忘れる)にできるぐらいです。

まだタイトルもなく、何も中身も決まっていなかった CakePHPで学ぶ継続的インテグレーション 本(以降 CI本 )が動き出したのでした。

当時のブログサイト My Opera Blogがもうすぐ終わりだ、やべーと言いながら、Co-Edoで何にしたら良いかなーと頭を悩ませていたのでした。

2月

非常に忙しくCakePHPの案件支援をしており、毎日ピリピリしていた記憶が….。仕事では人材の流動性の高まりを感じでいたのでした。

バグフィックスに対応した Fabricate 1.1.1 をリリースしました。

Bddプラグインのインストールをしようと思ったら、php-object-freezerが見つからないエラーで失敗するように なんとPHPUnitプロジェクトにあったリポジトリがpearからもgithubからも、世の中の公開リポジトリから一切いなくなったのでした。 これはforkして使っているSpec for PHPの依存ライブラリなので、しかたなくコードをlibの下に展開してSpec for PHPに同梱するようにしました。

CI本は主にチャットワークで作業が進み、目次を考えていました(まだ決まってはいない)。

いよいよ My Operaの終了までカウントダウンということで、ブログをMiddlemanに移行しました。 記念すべきMiddlemanでの一号記事 Hello Middleman!

3月

3/12 Web生誕25周年でした。 またそれに関連して「HTML5 Japan Cup 2014」が開催されることになり、ボランティアスタッフとしてハッカソンや審査などに関わるようになりました。

3/8 Co-Edoにてたまっていたブログネタを一気に3本投入しました

3/18 テスト駆動web開発勉強会 Vol.1で発表しました。 そういえば、Vol.2が開催されないなーとか思うのでした…

Middlemanに移行してブログ書くぞーと熱が上がっていたので、さらに一本 Composerのautoloadを使いこなす を投下。

3/25 第76回 PHP勉強会 に参加しました。確かHTML5 Japan Cup2014の告知をしたと記憶しています。

CI本は大筋が決まって、いざ書くぞーというモードになるのでした。

4月

4/4 今も続くCakePHP3もくもく会#3に参加しました(ブログ記事)。このとき初めてCakePHP3に触れたのでした。

4/9 はもちろん4q!カンファレンス。今年も盛り上がりましたー

4/12 Test the Web Forward Meetup (仮), Tokyo #2 に参加しました(ブログ記事)。私はShadow DOMに興味があったので、そのテストコードを書くチームに参加しました。帰宅後もいくつか作業をすすめ本家にプルリクエストが取り込まれるなど、Contributeしたぞーという気持ちが高まったのですが、その後あまり作業できていません。HTML5の仕様テストに興味はあるので、今年も少しずつ取り組みたいなーと思っています。

5jCupはスタッフ顔合わせがあったあと、すぐに第一回のアイデアソン/ハッカソンがあり、どちらもスタッフとして参加しました。

全然活動と関係ないのですが、その頃ちょうどいつも聴いているJ-Waveで流れていたY.I.Mのkonsaiという曲にはまって何日も頭をリピートしていたのでした。今でもたまに聴いてなごみますw

CI本は5jCupの繁忙期と重ならないように、なるべく早めにと思いちゃくちゃくと原稿を進めるのでした。

4/28 第77回 PHP勉強会に参加し、もくもく会でCakePHP3に触った話をしました。

5月

5/19 CakePHP もくもく会 に参加しました。

5/21 CakeDC/Migration プラグインがアップデートされ、generateコマンドでいろいろごにょごにょできるようになり、とても便利になったのでした。すぐにプロジェクトでもアップデートして記法を取り入れました。

後はひたすら仕事が忙しく、平日夜中や土日はほぼCI本の原稿を書いていた記憶が….

6月

イベントにたくさん参加しました。

  • 6/12 - 6/13は毎年恒例のInterop
  • 6/16 CakePHP 3.0.0 もくもく会(勉強会) #5 (ブログ記事
  • 6/23 【祝9周年】第79回 PHP勉強会
  • 6/28 PHPカンファレンス関西

Fabricate1.2をリリースしました。主な機能追加は

  • configで主キーを割り振らないようにするオプション
  • Fabricate::define() のクローン
  • Fabricate::association() のクローン
  • Fabricate::trait() のクローン

これらでだいぶFabricator/FactoryGirl の実装に近づきました。

CI本は書きたい事は書ききって、全体の流れを整える調整が..

7月

5jCupはいよいよ審査モード。スタッフが集まって一次審査を行いました。大量に集められた審査用のガジェット(機器)など、作品も沢山寄せられてあっという間に時間が過ぎました。 そして、7/26 には表彰式が行われ、今年も前半戦イベントの幕が閉じたのでした。

7/29 CakePHP 3.0.0 もくもく会(勉強会) #6に参加。

いくつかのバグフィックスをしたFabricate1.2.1をリリースしました。

CI本は大大リファクタリング大会で、毎週末とても大変だったなぁ〜

8月

今年もCakeFestに参加しました(ブログ記事)。初スペイン(まぁだいたいどこへ行っても初なのですがw)。

Fabricateを遅まきながらTravis.ci対応したので、CakePHPのプラグインをCIする方法についてブログ記事を書きました。

CI本は校正をしながらいよいよ大詰め!

9月

原稿からも解放され、たくさんのイベントに参加しました。

  • 9/4 CakePHP 3.0.0 もくもく会(勉強会) #7(ブログ記事
  • 9/6 俺のXP祭り
  • 9/19 CakeFest報告会
  • 9/25 HTML5とか勉強会
  • 9/29 第82回 PHP勉強会

そしてついに、9/19 CakePHPで学ぶ継続的インテグレーションが発売されました(ブログ記事)。

10月

先月同様にイベントに参加したり、発表したり楽しかったー

  • 10/9 10q!カンファレンス
  • 10/11 PHPカンファレンスで発表(ブログ記事
  • 10/25 俺聞け10(急遽空手形で発表、Should Beeをアピール)
  • 10/27 第83回 PHP勉強会
  • 10/30 HTML5 デバイスAPI勉強会

そして毎年恒例のアレ(カレンダー)のイラストが原稿終わった後にあるのでした..

11月

いよいよ渋谷の支援案件も残りわずかとなりました。

  • 11/17 CakePHP 3.0.0 もくもく会(勉強会) #9
  • 11/25 HTML5とか勉強会

カレンダーデザイン終わり!

12月

12月から半分はJavaの新規案件のお手伝いにも行っていました。

12/23 にCakePHP 3.0.0 感謝祭 & もくもく会(勉強会) #10と同時開催(フロア別)で、『CakePHPで学ぶ継続的インテグレーション』ハンズオンセミナー を開催しました(私はサポート役でしたが…)。

例のCI本のハンズオンですが、まだ2回目ということでちょっと練度が足りなくて、参加者の皆様は若干消化不足だったかもしれませんが、またリクエストもありますので、ぜひ今年もやりたいと思います。

12/24 に渋谷の支援を1日残していますが、感謝のLT(ブログ記事)をしました。

まとめ

たくさんブログ書くぞーと言っていたくせに、14本しか書けませんでした。 執筆とかイベントスタッフとかあると、なかなかなー。わかってはいるのですが。

今年はもう少し情報公開できるだろうか。CakePHP3の新しいネタとかイロイロ書けそうですしね。 そうそう今日CakePHP3はRC1になりましたね。CakeFestもちょっと早めの5月末ニューヨークだそうですよ。ぜひ行かねば。

直近でいうと1月末にHTML5カンファレンスがあって、今年もスタッフ参加しますので、会場でお会いできればと思います。

さらには1月末に伊良部大橋が開通するので宮古に行きたいとか(これは完全に趣味ですが)。

今年もイロイロなイベントに参加したり、橋見に行ったり、また機会に恵まれれば執筆したりと変わらず活動していきたいと思いますので、皆様よろしくお願いいたします。

僕とPHPxAgileの481日間

2014/12/24 に行われた【12/24 クリスマスイブ】第85回 PHP勉強会でLTをしました。

当日はもの凄く楽しく盛り上がったので、私が発表を開始する頃には会場の終了時間ギリギリになっていました。 このため高速LTで話していろいろ溢れた話などを記事にまとめます。

CakePHPを使った開発現場での481日間の支援

コンサル会社からご指名でお話をいただいたら、コミュニティの知り合いの会社でしたよ、というなんか遠回りな形で始まったお仕事でした。

まず参加して感じたのは、481日前のスライドにあるように、これはなかなか手強いなーという状況でした。 コンサルの方からイロイロ事情を聞いていたので、ある程度覚悟はしていったのですが、まずの印象はチーム開発ができていないなーという印象。ここでいうチームとは、誰かが指示して動くというよりは、全員の力で進んでいくアジャイルチームをイメージしています。 良い意味で言うとウォーターフォールのやり方としては、文化があってうまく進めている状況でした。

とはいえ最初に見た手順書とかコードとか、もっと優先してやらないといけなそうな事がたくさんあったので、スライドの順に対策を計画し、開発しながら改善活動をゆるやかに進めるということになりました。

できたこと と できなかったこと

かなり大きな案件だったため、複数のサブシステムがあり、スライドの対象は私が直接関わった2つについて支援した内容です。 ただ、施策のいくつかは全体として取り入れてもらったりして、現在も継続しています。

できたことについては、スライドに含んでいるので、ここではスライドに含められなかった、主にできなかったことについて書きたいと思います。

  • デプロイの自動化
  • 環境毎の設定切り替えの仕組み
  • 共通化されていたコードの改善
  • プロダクトオーナーとの関わり方
  • more チーム

まずデプロイについては、独自の方法で半自動化されていました。 ここで列挙したのは、デプロイにcapやjenkinsを使ったりは「しなかった」という意味です。このあたりの改善はすでにワークフローとして機能しているものを改善すべきか、どこまで文化に踏み込むかという判断が必要になると思います。 本案件ではスクリプトを実行するのは手動ながらも、それによる問題はなさそうだったので、そのまま継続しています。 ただCI、インテグレーション、ステージング、本番とある内のインテグレーションだけはJenkinsと連動して自動デプロイできるようにしても良かったかなーと今頃思っていますが、まだいるメンバーの方、これを読んだらTRYしてみてください。

環境毎の設定切り替えの仕組みはあったのですが、当初設定を追加するのにExcelファイルを編集しないといけなくて、ひどく苦労しました(Excelファイルだと差分がわからないし)。現在はテキストファイルを直接編集して良い方式となり、これも全サブシステムに影響する箇所で対策がしにくくそのまま継続利用しました。 CakePHPだとCI本でも解説した CakePHP Environmentsプラグイン を使えると良かったのですが。ここはいずれCakePHP3対応するのであればプラグインに切り替えられると良いなーと思います。

上記と同じような理由で、すでに全サブシステムで共通化されていたコードの改善もあまり手が付けられませんでした。サブプロジェクトによって進捗具合が異なり私が入った段階ですでに多くの機能ができあがっていたサブシステムもあり、多くの共通部分は未着手のままとなりました。 一方で、できたことのほとんどは、まだ実施されていない自動テストだったり、サブシステム単位で改善できる箇所からの着手となったため、このような結果になっています。

プロダクトオーナーとの関わり方については、これはどの現場でもそうですが、開発からのアプローチだけではなかなか改善が難しい部分です。文化を変えるには内側からの大きな力だったり、より大きな成功体験を求めるといったことが必要となるのではないかと思います。 もしこのあたりを改善したいという現場があれば、プロダクトオーナーを含めてふりかえりをやって、ProblemとTryを共有して改善していくと良いと思います。でも大規模システムでは… なかなかに難しいですね…

moreチームということでは、最初に関わったサブシステムのチームでは、見積もりゲームをやって、カンバンを導入し、よりチーム力で進めたと思います。そのまま継続してやってくれると良いなーと思っています。私が離れてからもメンバー間でPull Requestに活発にコメントされていたし、とてもうまく廻っているという印象がありました。

一方でその後に担当したサブシステムは、かなり複雑な事情で始まりました。チームの力を付けながら進むというよりは、とにかく完成を目指すような形でしたので、チーム力という意味ではまだ道半ばだったかもしれません。 あと、年末に様々なイベントが不運なタイミングで重なってしまったのも、多少心残りですが、残ったメンバーの力を合わせて進めていって欲しいです。

さいごに

私はいろいろなところで揉めるというか、正論でズバズバ行くタイプなので衝突することが多かったと思います。 今回支援させてもらった現場では、派遣の方が多く、そういう意味でチーム力で進めていくのはとても困難な状況であったと思います。 特に社員ではない働き方を選んでいる人の場合、もの凄く前のめりでやりたい人と、3歩後ろをついていきたい人に別れると、これまでの経験で感じます。私もそういう意味では前者になる訳ですが…

例えばチーム全員で要求を仕様にブレークダウンして見積もりゲームをやったり、相互にコードをレビューしたり、これはAgile開発の文化にない開発者にとって、全員が望んでいる方法でないというのは、気持ちでは理解できます。 が、これをやらずに属人性を排除し、チームとしてより良く開発を進めていく方法が現時点ではあまり思い当たりません。 特にWeb界隈では人の流動性も高いので、極端に生産性を落とさない為にも、コードや仕様を引き継ぐというために時間を取られるといった事はできれば避けたいはずです。

このプロジェクトを通じて、テストデータジェネレータ Fabricate も作ったし、多くの実践投入実績や経験も得ることができました。

まだ現場では開発が続いており CakePHPで継続的インテグレーションを導入したモダンで大規模な開発 を継続しているという、貴重な体験ができていると思います。 そういう意味で、誰か事例発表をすると良いなーと思いますので、今年のPHPカンファレンスなどで話が聞けるのを楽しみに待っています。

そして、PHP勉強会の2日後、2014/12/26をもって、作業支援から離れました。

最後にステキなお花と色紙をいただきました。

本当にありがとうございました。引き続き開発頑張ってください!!

PHP Conference 2014で「擬人化から始めるPHPerのためのオブジェクト指向超入門」を発表してきました

2014/10/11 に行われたPHPカンファレンス2014で発表をしてきました。

Photo by gihyo.jp

スライドは以下の内容です。

PHPカンファレンスでは公募セッションは最大30分のため、なぜ今回このようなタイトルを発表しようと思ったか、という背景については話ことができませんした。こちらのブログでは、スライドの共有と共に、その辺りの経緯について書いておきたいと思います。

なぜ今オブジェクト指向か?

オブジェクトデザインは原著から数えると、もう12年になろうとする訳です。これはセッションの中でも話したのですが、2003年当時はまだWebのフレームワークもまだ今日のようなものでなく、多くの会社は各社オレオレフレームワークを作っていた時代です。 オブジェクト指向が必要ないような使い捨てサイトならまだしも、受託やサービスなどソースコードを維持したり、機能アップをするような場面ではオブジェクト指向が必要だと考え、今のアジャイルムーブメントかそれ以上に語られていたはずです。

オレオレフレームワークを作ったことがある人は、オブジェクト指向やデザインパターンをよく勉強してそのあり方を議論したものです。 そのような人たちが、現在の入門プログラマーに「オブジェクト指向がわかってない」と言っても、そもそも通じないと思ったのがきっかけでです。

ここ4,5年の間にWebからプログラミングを始めた人(Webフレームワーク世代)は、すでに素晴らしいWebフレームワークがあり、その上でプログラミングをすることを要求されます。言語そのもの、たとえばPHPなら「イラストでよくわかるPHP はじめてのWebプログラミング入門」や「よくわかるPHPの教科書」あたりを読んでから、各フレームワークの本や公式チュートリアルを片手にアプリケーション開発を始めています。 つまりオレオレフレームワーク世代のオッサンが通過したオブジェクト指向やデザインパターンの経験に基づいてソースコードに手厳しいコメントを付けても、受けた方も辛いだけなのです。

今どのような弊害が起きているのか

で、このようなWebフレームワーク世代はCakePHPやその他のWebフレームワーク(MVC的なもの)を使って、MVCの枠に収めようとしてFatコントローラになったりしやすいのです。 なぜなら特定のディレクトリにフレームワークが指定した方法で書いていれば、良くわかんないけどうまく動いてしまうのです。フレームワークすげーな!という訳です。 それ以外の書き方は知らないし、どこにも書いてないのです。 結局、適切にクラス分割ができないため、結果として与えられた範囲の枠組みで問題はない、と考えているような状況を見かけるのです。 classってPHPの文法です、キリッ!とか言われたことはないですが、まぁ無くはないのかな…と。

じゃぁフレームワークの本に、その概念や考え方を書くべきだという声もあるかもしれません。私もCakePHP本の執筆に携わったことがあり、耳の痛い話です。しかしフレームワークの本では、すべてのフレームワークについての説明もページ数的に困難な中、OOPやMVCの概念のような内容を解説するのは困難なことも事実です。

そこで話してみようと思った

もちろん、オブジェクト指向ムーブメント世代のオッサンであってもオブジェクト指向をみんな理解しているかというと、そういう訳ではありません(まぁ少なくともみんな読書会なんかして勉強はしていたような気がします)。 今時のWebフレームワーク世代だって、しっかり勉強している意識高い人はいっぱいいるし、知っています。 ただ最近プログラミングを始めた人は、技術の移り変わりが激しいし、言語やフレームワークはどんどんバージョンアップするし、なかなか基礎的な事に対する勉強時間を作るのが難しいのではないかな、と思うのです。 しかもオブジェクト指向の勉強って難しいし「銀の弾丸」は無いわけです。最近そういった勉強会の話もあまり聞きません。 ではどのあたりから始めると良いだろう?という当たりを付けられるセッションをしてみたいと思ったのです。

私が技術支援で入った中で良く感じている問題はオブジェクト指向プログラミングというより、そもそものオブジェクト指向がうまく理解されていないと思ったので以下のポイントに絞った内容にしたいと思いました。

  • オブジェクトとは何なのか
  • オブジェクトを導出するにはどうしたら良いのか
  • 今回のPHPカンファレンスのテーマは「知りたい,があなたを変えていく」

これらの要件に合うのは、責務駆動設計の中心的原理とテクニックを解説した「オブジェクトデザイン」の紹介が最適で、30分という時間に適している(知りたい==何か持ち帰って始めることができる)と思ったのです。

どうだったか

セッションをやっている間、いつも盛り上がっていないと不安なので、小ネタを差し込むのですが、皆さん真面目に聞きに来ていただいたので、講演者から見るとダダ滑りでした(個人的な感想です)。ただjoind.inのコメントを読ませていただいたり、顔見知りの方に確認した範囲では何かしら体験を持ち帰っていただけたようで「知りたい」につながったのかな、と思っています。

もし私の講演を聴いていただいてコメントまだな方は、コチラに一言いただけると嬉しいです。joind.inは匿名でのコメントもできますので、ご安心ください。

また、今回の目玉?!セッションであった「ウェブエンジニアに必要なセキュリティスキルとは 徳丸浩 x 大垣靖男」の対談セッションでも思ったのですが、お二人の話の大きな相違点のベースである現実論理想論のケースはセキュリティだけではなく、オブジェクト指向についても同じだな、と思いました。 私はすべての人がオブジェクト指向を理解して進めるのが理想だと思うし、アジャイル原則の私たちはオブジェクト指向を理解しているプログラマーであるべき、と思っています。ただすべての人がそうでないというのも理解できるので、理解度はともかくオブジェクト指向と向き合って欲しいなと思っています。そこから何かが変わると良いな、と。

裏話

実は、セッションを応募するとき、PHPカンファレンスでは重複して申し込めると聞いていたので、オブジェクト指向超入門(上)と、オブジェクト指向超入門(下)の2セッションにして合わせて60分で申し込もうと思ったのですが、それは通らなそう(www)と思って自重しました。 またどこかで60分セッションやれる機会があれば、やってみたいなと思うのでした。