sifue's blog

プログラマな二児の父の日常 ポートフォリオは www.soichiro.org

エリック・エヴァンスのドメイン駆動設計に沿ってSymfony2でユーザー管理アプリを作ってみた

あけましておめでとうございます。
去年の暮からエリック・エヴァンスドメイン駆動設計という5200円、500ページもする本を購入して読み始めた自分です。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

あまりに勿体無かったので試しにこのドメイン駆動設計の設計思想にそって、簡単なアプリをSymfony2で作ってみました。


実際に作られたサイトは、
http://www.soichiro.org/sf
こんな感じです。

  • id: test1@test.com
  • pass: test1

でログインできます。(ユーザー作るだけならadmin/adminpassでもOK)


画面イメージは





こんな感じです。UIにはtwitterのbootstrapを使っています。


ソースコードは、
https://github.com/sifue/UserManagement
からチェックアウトできます。無論MITライセンスです。サンプルとして使えそうな所があったら好きなように改変してご自由にご利用ください。


ドメイン駆動設計はすごく簡単に言うと、システムを作る時って業務モデル(実際のビジネスを抽象化した概念、例えばUMLのクラス図で書いたようなもの)の共有ってすごく重要だよねって話から始まって、そのモデルとシステムをいかに近づけるかって話と、実際のシステムを実装するときには、

に分けて実装すれば良いよっていうところから、更に深く実際のいろいろなプラクティスが書かれています。


かなりじっくり読まなきゃいけないタイプの本で、読めば読むほど考えさせられる所もあって読み進まないタイプの本でした。オブジェクト指向大規模アプリ設計の教科書と言っても過言ではないのでしょうか。


読み進んでビックリしたのは、ここに出てくる概念がほとんどSymfony2で実装されているというところでした。たぶんこの本に相当影響を受けてるんだと思います。例えで言うと、

  • サービス : Symfony2ではサービスコンテナというDIコンテナ
  • エンティティ: Symfony2ではエンティティというデータクラス
  • ファクトリ: Symfony2ではサービスにインスタンスを作るファクトリを登録したり、サービスを作るファクトリを設定できる
  • リポジトリ: Symfony2ではリポジトリというORMとの接続処理実装集約クラス
  • モジュール: Symfony2ではバンドル、OSGiのバンドルのように拡張ポイントもある

とかがありました。
残念ながらなかった概念は、値オブジェクト。Javaenumのように不変でstaticなインスタンスを作る仕組みがPHPにはないのでやむなしというところなのかもしれません。


実際にアプリ作るにあたってエリック・エヴァンスドメイン駆動設計によるとドメイン層はできるだけ集約して一つにして高い凝集にしたほうがよいということだったので、実際に作ったこのユーザー管理システムのバンドル設計、クラス図はこんな風になりました。

Symfony2の思想としては、ドメイン層とアプリ層とUI層を一つのバンドル内で作ってしまうように作られていたのですが、よりドメイン層に全てのビジネスロジックを集約する意図を持たせるために、DomainBundleというものを作りました。
赤がインフラ層、オレンジがドメイン層、緑がアプリ層、青がUI層をあらわしています。UI層のテンプレートファイルやルーティング設定ファイル類は割愛しています。


確かにこの設計で作ってみると非常にアプリの作りがいいように感じます。あと、クラスそれぞれが単一責務になっている。


以下に実際にUserFactoryの実装を書きますが、ここではUserのインスタンスができる際に守らせなければならない、インスタンス作成時は有効になっていることや、ランダムなハッシュ用のsaltの設定などを守らせることができます。

<?php
namespace Sifue\Bundle\DomainBundle\Factory;
use Sifue\Bundle\DomainBundle\Entity\User;

/**
 * Userインスタンスの作成とインスタンスが守らなければいけない整合性を保つ責務を持つクラス
 */
class UserFactory
{
    /**
     * ユーザーのインスタンスを取得する
     * @return Sifue\Bundle\DomainBundle\Entity\User
     */
    public function get()
    {
        $user = new User();
        $user->setIsActive(true);
        $user->setSalt(base_convert(sha1(uniqid(mt_rand(), true)), 16, 36));
        return $user;
    }
}

いい具合に責務が分離されています。
これなら大規模開発で拡張、リファクタリングを続けていっても耐えられる構成なのではないかなという確信を得ることができました。


とは言えまだドメイン駆動設計、ぶっちゃけ業務でいろいろやってみないとわからないことも多そうなので、いろいろなテクニックを取り入れながら少しずつ試して行けたらななんて考えています。まだ初心者な所もあるので、ご指摘などあればコメント下さい。


ちなみにこのアプリ。Symfony2のインストールのためのPHP環境、MySQL環境を揃えた後、

$ git clone git@github.com:sifue/UserManagement.git
$ vim app/config/parameters.yml
parameters:
    database_driver:   pdo_mysql
    database_host:     127.0.0.1
    database_port:     ~
    database_name:     symfony
    database_user:     root
    database_password: password

    mailer_transport:  smtp
    mailer_host:       127.0.0.1
    mailer_user:       ~
    mailer_password:   ~

    locale:            ja
    secret:            ThisTokenIsNotSoSecretChangeIt
$ php app/console doctrine:database:create
$ php app/console doctrine:schema:update --force

とするだけで、UserManagement/webをウェブサーバーに公開すれば動かすことができるようになっています。


テストもひと通り実装してあり、PHPUnitがインストールしてあれば、

$ phpunit -c app

で全てのテストが実施可能です。
Symfony2はPHPUnitをいい具合に拡張してあって、WebのUIをエミュレーションするテストやサービスコンテナのテストができるところが便利です。


ぜひ試してみて下さい。


追伸
ちなみに自分はPHPなんてオブジェクト指向言語の風上にも置けない言語だと思っていたのですが、Symfony2に触れて全然そんなことはないな、普通に戦えるフレームワークだなと思いました。PHPはひどい言語だけど、捨てたもんじゃない!きっとw

恐ろしく高い編集能を持つマルチカーソルエディタとしてのSublimeText2

最近、開発環境の導入コストの低いIDEの導入を広めようと思って、VimからSublimeText2にメインエディタに変更した自分ですが、SublimeText2のすごさはやっぱりマルチカーソルにあるなと思ったのでエントリーを書くことにしました。

Sublime Text2 は

http://www.sublimetext.com/

以上のURLからMac, Windows, Linuxマルチプラットフォームでダウンロード、利用できます。(たまに保存時にダイアログは出ますが無料でずっと使い続けられます)


無論、Vimにもマルチカーソルに近い感じの複数行編集モードは存在しています。
vimで複数行の行頭、行末に一気に文字を挿入する方法 - プログラマ 福重 伸太朗 〜基本へ帰ろう〜
などで紹介されている。Ctrl+vで矩形選択モードで縦1列を選択して、IまたはAで行頭または行末に入力してEscで抜けると全ての行に反映されているというこの動きです。


SublimeText2でも基本的に同じことができるのですが、矩形選択による複数行編集と、複数選択のあとのマルチカーソル選択は根本的に違うものだということがわかりました。



このスクリーンショットのように、SublimeText2では、CommandまたはCtrlを押しながら領域を複数選択し、Command + dを押すことで複数のカーソルを置くことができます。このマルチカーソルのすごいところは、この全てのカーソルを上下左右に動かすことが出来、一度に編集できることです。つまり矩形選択の枠に縛られない。



そして、この次のスクリーンショットでは、通常の複数行選択をCommand + Shift + l で複数の選択に分割してマルチカーソルを設置し、全ての行頭、行末に編集を加えたり、改行を消去したりしています。マルチカーソルはなんと行を超えてカーソルを移動することができるので、改行を消すことも追加することもできるわけです!


すごいよ、すごすぎるよSublimeText2。


このマルチカーソルとしての機能があることで、個人的にはVimを超えた概念を持つエディタが登場したなと感じました。これからも、SublimeText2での編集技術を磨いて行こうと思います。


ぜひ一度は、このすごい編集能を持つSublimeText2でマルチカーソルエディタのすごさを体験して見ることをオススメします。

JavaでノンブロッキングIOを使ったネットワークアプリを学ぶのに最適なNetty 3.5系のGetting Startedを日本語訳しました

Nettyと言えばJavaのノンブロッキングIOのAPIであるNIOをラップしたフレームワークとして、TwitterのFinagleなどで分散ネットワークアプリケーションシステムで使わていて高速で実績のあるライブラリとして有名ですが、ノンブロッキングIOでイベント駆動のサーバークライアントのネットワークアプリケーションを知るのに非常に良い題材ですので、素人翻訳ですがその日本語訳を公開することにしました。


ちなみにNettyがどれぐらいパフォーマンスに優れているのかというと、Herokuの仮想インスタンスを利用した実験の結果が参考になります。Scala(Finagle)がNettyの実装を利用したものになりますが、秒間6000リクエスト時の1dyno(APサーバー)の応答が秒間4000レスポンスで、C(Accept)、Java(Jetty)、Java(Tomcat)、Js(Node)、Python(Bottle)、Ruby(Sinatra)と比べて最も応答性がよく、接続エラーが少ないという結果が出ています。


つまり、Nettyは今ある有名なネットワークアプリを作るフレームワークの中でかなり良いパフォーマンスと安定性を持つ選択肢として考えることができると思います。
Nettyすごい。
NettyはTCP/IPを使った通信のフレームワークを自由に実装できますが、Codec framework、SSL/TLS、HTTP、WebSockets、Google Protocol Bufferなどに関しては予め実装があり、利用することができます。


といってもこういうものを実務で利用しなくては行けない方というのは少ないのではないかと思いますが、TCP/IPの通信ってこんな感じなんだ、Webサーバーの中身の実装ってこうなってるんだとか、リソース管理ってこうするんだというノウハウがこのGetting Startedを通じて学べますので非常に価値があると思います。


元のドキュメントは、
http://static.netty.io/3.5/guide/
となります。素人の意訳なのでご利用は自己責任でお願いします。


なお、ソースコードGitHub
https://github.com/sifue/nettystudy
にて公開しておりますので、動かす場合はチェックアウトして、mavenでビルドしてライブラリを落としてご利用下さい。

The Netty Project 3.x User Guide

高速なネットワークアプリケーション開発のための折り紙つきのアプローチ


はじめに

1. 問題
2. 解決法

1. 問題

今日、わたちたちは一般的に相互通信をさせるためにアプリケーションやライブラリを使います。例えばウェブサーバーから情報を取ったり、ウェブサーバーのリモートプロシージャを実行するのにいつもHTTPクライアントライブラリを利用します。


しかし、一般的なプロトコルやその実装はよくスケールしません。 それは巨大なファイルやeメール、ほぼリアルタイムの金融情報ややマルチプレイヤーのゲームにの通信にHTTPサーバーを使わないというようなことです。特別な目的を果たすための高く最適化されたプロトコルの実装には何が必要なのでしょうか。例えば、AJAXベースのチャットアプリや、メディアストリーミング、大きなファイルの転送にはHTTPサーバーは最適化されたHTTPサーバーの実装が必要になるでしょう。更に正確にあなたの要求を満たすならば、全く新しいプロトコルをデザイン、実装をしたいかもしれません。

2. 解決法

Nettyプロジェクトは、非同期のイベント駆動ネットワークアプリケーションフレームワークの提供に注力し、メンテナンス可能なハイパフォーマンスでハイスケーラビリティのサーバーとクライアントのプロトコルの高速な開発を整備します。


言い換えれば、Nettyは、高速に簡単に、プロトコルサーバークライアントのようなネットワークアプリを開発することを可能にするNIOクライアントサーバーフレームワークです。NettyはTCPUDPソケットサーバー開発のようなネットワークプログラミングを簡単にし、合理化します。


"高速で簡単"はメンテナンス性やパフォーマンスの問題に見舞われることを意味しているのではありません。Nettyは、FTPSMTP、HTTPや様々なバイナリやテキストベースの巨大な古いプロトコルのような多くのプロトコルの実装から得られた経験に基づき、とても注意深く設計されました。結果として、Nettyは簡単な開発、パフォーマンス、安定性、柔軟性を、妥協なしに達成する方法を見つけることに成功しました。


あるユーザーは、既に似たような利点を持つ他のネットワークアプリケーションフレームワークを既に見つけていたり、Nettyがそれらとどう違うのかが聞きたいでしょう。答は哲学的なところです(難訳)。NettyはあなたにAPIの内容とその実装という最も快適な経験をその日から与えるでしょう。ただそれは、あなたがこのガイドを読んで実際にNettyで遊んで得られる哲学のように、触れないものなのです。

Chapter 1. Getting Started

1. はじめる前に
2. ディスカードサーバーを書く
3. 受け取ったデータを見る
4. ECHOサーバーを書く
5. TIMEサーバーを書く
6. TIMEクライアントを書く
7. ストリームベースの通信を扱う
7.1. ソケットバッファーに関する一つの小さな警告
7.2. 第一解決策
7.3. 第二解決策
8. ChannelBufferの代わりのPOJOを使って通信する
9. アプリケーションをシャットダウンする
10. まとめ


この章は、あなたが素早くはじめるための簡単な例を用いたNettyの中心的構造のツアーです。この章の最後についた時、あなたはNetty上でクライアントとサーバーが書けるようになっているでしょう。


もしあなたが何かを学ぶのにトップダウンのアプローチの方が好きなら、Chapter 2, Architectural Overview を先に読んでそれから戻ってきてください。

1. はじめる前に

この章の例を動かすのに最低限のものが2つあります。NettyとJDK1.5以上です。Nettyの最新バージョンはプロジェクトのdownloadのページから利用できます。正しいバージョンのJDKに関しては、好きなJDKのベンダーのサイトを参照して下さい。


読む時に、この章で沢山の導入されたクラスについて沢山の疑問を持つでしょう。もしあなたがもっと知りたいとおもったのであれば、APIリファレンスを参照して下さい。このドキュメントの全てのクラス名はAPIリファレンスにリンクしています。同様に、Nettyプロジェクトのコミュニティに連絡を取るのをためらわないでください。不正確な情報、文法間違い、ドキュメント改善に関する良いアイディがあったら教えて下さい。

2. ディスカードサーバーを書く

最もシンプルなプロトコルはこの世界に置いてはハローワールドではなくDISCARDです。このプロトコルは、何かしらのデータを受け取ったら何かのレスポンスをすることなく、無視します。


DISCARDプロトコルを実装するためにはただ、受け取ったデータを無視することを考えればよいだけです。ではNettyによって生成されたIOイベントを操作をハンドラーの実装を始めましょう。

package org.soichiro.nettystudy.example.discard;

import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class DiscardServerHandler extends SimpleChannelHandler {

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
		e.getCause().printStackTrace();

		Channel ch = e.getChannel();
		ch.close();
	}
}

1. SimpleChannelHandlerを継承したDiscardServerHandlerはChannelHandlerの実装です。SimpleChannelHandlerはあなたがオーバーライドすべき様々なイベントハンドラメソッドを提供します。ここではハンドラを実装しなくても、SimpleChannelHandlerを継承するだけで十分です。
2. messageReceivedイベントハンドラメソッドをここではオーバーライドしています。このメソッドは、 MessageEventと共にコールされ、クライアントから受け取る新しいデータが含まれています。この例では、DISCARDプロトコルの実装のために、受け取ったデータは何もしないことによって無視します。
3. exceptionCaughtは IO例外がイベント処理の中で投げられた時に、ExceptionEventと共に呼ばれます。大抵の場合、キャッチされた例外はログにはかれ、関連するチャンネルをここで閉じます、例外のシチュエーションへの対応実装をどうしたいかは異なるにも関わらずです。例えば、あなたはコネクションが閉じる前にエラーコードとレスポンスメッセージ返したいだろう。


今のところ順調です。もうDISCARDサーバーの半分を実装しました。DiscardServerHandlerを使いサーバーをスタートさせるmainメソッドが残っています。

package org.soichiro.nettystudy.example.discard;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class DiscardServer {

  public static void main(String[] args) throws Exception {
      ChannelFactory factory =
          new NioServerSocketChannelFactory(
                  Executors.newCachedThreadPool(),
                  Executors.newCachedThreadPool());

      ServerBootstrap bootstrap = new ServerBootstrap(factory);

      bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
          public ChannelPipeline getPipeline() {
              return Channels.pipeline(new DiscardServerHandler());
          }
      });

      bootstrap.setOption("child.tcpNoDelay", true);
      bootstrap.setOption("child.keepAlive", true);

      bootstrap.bind(new InetSocketAddress(8080));
  }
}

4. ChannelFactoryは、 Channelとそれに関わるリソースを作るファクトリーです。全てのIOリクエストを処理し、ChannelEventの生成のためのIOを実行します。Nettyは様々なChannelFactoryの実装を提供しています。この例ではサーバー再度のアプリケーションを実装しています。それはつまり、NioServerSocketChannelFactoryが使われます。別なことを書くと、これ自身はIOスレッドを作りません。あなたがコンストラクタで設定したスレッドプールを使ってスレッドを作成します。そしてセキュリティーマネージャの用いられサーバーのようなアプリケーション実行環境でのスレッドがどのように管理されるべきかという制御をあなたに与えます。
5. ServerBootstrap はサーバーをセットアップするヘルパークラスです。あなたは、Channelを直接設定することができます。しかし、それは殆どの場合あなたに不必要な退屈なプロセスです。
6. ここでChannelPipelineFactoryを設定します。新しい接続がサーバーにアクセプトサれるときはいつでも、新しいChannelPipelineがChannelPipelineFactoryに特徴づけられて作成されます。この新しいパイプラインはDiscardServerHandlerを含みます。もし複雑化してきたときは、もっとハンドラをパイプラインに追加して、匿名クラスをトップレベルクラスに随時抽出しましょう。
7. Channelにパラメータを渡すこともできます。私たちはTCP/IPのサーバーを書いています。だから、tcpNoDelayとkeepAliveをソケットオプションとして設定できます。"child."とプレフィックスを付け加えていることについて書きます。これは、ServerSocketChannelのオプションの代わりに、Channelのオプションとして適用されます。ServerSocketChannelのオプションは以下のようにします。

bootstrap.setOption("reuseAddress", true);

8. 今行く準備ができました。あとは、ポートをバインドしてサーバーをスタートさせるだけです。ここにこのマシンのすべてのNICの8080にバインドしました。違うアドレスならば何度でもバインドすることができます。


おめでとうございます!これであなたはあなたの最初のサーバーをNetty上に構築し終えました。

3. 受け取ったデータを見る

丁度いまわたしたちの最初のサーバーを書きました。本当に動いているかどうか確かめなくてはいけません。最もテストする簡単な方法はtelnetコマンドを使うことです。例えば、"telnet localhost 8080"とコマンドラインで入力して、さらに何かタイプしてみてください。(MacLinuxならすぐtelnetが使えます。Windowsはバージョンによって準備が必要かもしれません...)


しかしながら、サーバーはうまく動いているといっていいのでしょうか?ディスカードサーバーであるためわかりません。何のレスポンスも得られないでしょう。本当にうまく行っているか検査するために、何か受け取ったらプリントしてみるように修正しましょう。


私たちは、既にイベントを受け取るとMessageEventが生成され、messageReceivedが呼び出されることを知っています。ちょっとしたコードを、DiscardServerHandlerのmessageReceivedメソッドに書きましょう。

	 @Override
	 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
	     ChannelBuffer buf = (ChannelBuffer) e.getMessage();
	     while(buf.readable()) {
	         System.out.println((char) buf.readByte());
	         System.out.flush();
	     }
	 }

9. ソケット通信におけるメッセージの型が常にChannelBufferだと推定するのは安全です。ChannelBufferはNettyにおいてバイトシーケンスを保存する基本的なデータ構造です。これは、NIOのByteBufferに似ています。しかし、より簡単で柔軟性があります。例えば、Nettyは不要なメモリコピーを減らしたChannelBufferを複数組み合わせたChannelBufferなどを作ることもできます。
多くの部分がByteBufferに似ているにていますが、APIリファレンスを参照することを強く薦めます。ChannelBufferを正確に学ぶことは難なくNettyを使うために致命的に重要なステップです。


telnetのコマンドをもう一度叩いてみて下さい。サーバーが受け取ったものがprintされるのがわかります。
DISCOARDサーバーの全ソースは、配布物のorg.jboss.netty.example.discardの中にあります。

4. ECHOサーバーを書く

ここまでで、全くレスポンスはせずデータを消費するだけでした。しかしながら、サーバーは通常リクエストに反応します。次は与えられたデータを返すECHOプロトコルを実装して、メッセージをクライアントに返す実装の仕方を学びましょう。
DISCARDサーバーとの唯一の違いは、受け取ったデータをコンソールに出す代わりにクライアントに送り返すところです。つまり、messageReceivedを以下のように修正し直すだけで十分です。

	 @Override
	 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
	     Channel ch = e.getChannel();
	     ch.write(e.getMessage());
	 }

10. ChannelEventオブジェクトは関連するChannelへの参照をもっています。つまり、Channelは受け取ったMessageEventの接続を表しています。Channelを取得しwriteメソッドでリモートの相手に対して何かを戻すことができます。


telnetコマンドをもう一度実行してみましょう。何を送っても返してくれると思います。
この全体のソースコードは、配布物のorg.jboss.netty.example.echoに含まれています。

5. TIMEサーバーを書く

この項ではTIMEプロトコルを実装します。前の例と違うのは、何も受け取らず32bitの整数を含むメッセージを送り、送った後にすぐに切断するところです。この例では、メッセージの構築の仕方と完全にコネクションを閉じることを学びます。
どんなデータを受け取っても無視するためコネクションが確立した瞬間にメッセージを送ります。この度はmessageReceivedは使えません。代わりに、channelConnectedメソッドを使います。以下のような実装になります。

package org.soichiro.nettystudy.example.time;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class TimeServerHandler extends SimpleChannelHandler {

    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
        Channel ch = e.getChannel();
        
        ChannelBuffer time = ChannelBuffers.buffer(4);
        time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
        
        ChannelFuture f = ch.write(time);
        
        f.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) {
                Channel ch = future.getChannel();
                ch.close();
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
        e.getCause().printStackTrace();
        e.getChannel().close();
    }
}

11. 説明すると、channelConnectedメソッドは接続が確立した時に呼ばれます。そして、現在時間の秒数を表す32bitのintegerを書きます。
12. 新しいメッセージを送るため、メッセージを含む新しいバッファが必要です。32bitのintegerを書くので、ChannelBufferのキャパシティは4バイトです(4 x 8bit)。ChannelBuffersは新しいバッファを確保するためのヘルパークラスです。bufferメソッドに加え、多くの便利な関連するメソッドを持っています。もっと知るために、APIリファレンスを参照して下さい。
一方で、ChannelBuffersをstaticインポートすると、以下のように書けます。

 import static org.jboss.netty.buffer.ChannelBuffers.*;
 ...
 ChannelBuffer  dynamicBuf = dynamicBuffer(256);
 ChannelBuffer ordinaryBuf = buffer(1024);

13. 普段は、構築されたメッセージを書きますがちょっとまってください、filpメソッドはどこでしょうか?私たちは、NIOにメッセージを送る際にByteBuffer.flip()を読んでいません。ChannelBufferはそのようなメソッドをもっていません。なぜならそこには2つのポインタしかないからです。一つは読む操作のためのもの、もうひとつは書く操作のためのものです。(ちなみにNIOのByteBufferのposition, limit, capacityの話は、http://kimama2index.info/coolJava/channel/buffer.html の日本語の資料が参考になる) writer indexはChannelBufferに書き込みを行った場合に増加します。その場合reader indexは増加しない。reader indexとwriter indexはそれぞれ、メッセージの最初と終わりをそれぞえ表しています。
対照的に、NIO のバッファはflipメソッドを除いてメッセージの最初と終わりを表す明確な方法を提供していません。flipし忘れて不正なデータを送ったり送れなかったりするトラブルに見舞わるでしょう。Nettyの場合そんなエラーは発生しません。なぜなら、操作の異なる違うポインターを用意しているためです。あなたはflipをしない生活がとても簡単だということにきづくでしょう。
writeメソッドが返すChannelFutureの別な点を記述します。ChannelFutureはまだ起こっていないIOの操作を表すものです。つまり、まだ実行されていないリクエストです。なぜならば、Nettyの全ての操作は非同期だからです。例えば、以下のコードではメッセージを送るまえに 接続を閉じてしまいます。

Channel ch = ...;
ch.write(message);
ch.close();

つまり、closeメソッドは、ChannelFutureでwriteメソッドの結果が返されてその結果が通知された後実行される必要があります。同様にclose処理もすぐに行われるわけではなく、ChannelFutureを返します。
14. どのようにwriteリクエスト終了したかをきづくのでしょうか。これはシンプルでChannelFutureListenerをChannelFutureに追加することで出来ます。私たちは、オペレーションが行われたあとで、Channelを閉じる非同期のChannelFutureListenerを追加しました。
別に言い換えれば、あなたは元々定義されたリスナーを使って以下のようにシンプルにも書けます。

f.addListener(ChannelFutureListener.CLOSE);

このサーバーが正しく動いているかテストするために、UNIXのrdateコマンドを使えます。

$ rdate -o <port> -p <host>

ポートはmainメソッドで設定されたもので、hostはいつもどおりlocalhostです。(Macにはrdateコマンドを入れる方法がありませんでした、残念。次にTIMEクライアントを書くのでそこで検証します。)


なおTimeServerの実装は、例が配布物に含まれていませんが以下のとおり。

package org.soichiro.nettystudy.example.time;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class TimeServer {

  public static void main(String[] args) throws Exception {
      ChannelFactory factory =
          new NioServerSocketChannelFactory(
                  Executors.newCachedThreadPool(),
                  Executors.newCachedThreadPool());

      ServerBootstrap bootstrap = new ServerBootstrap(factory);

      bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
          public ChannelPipeline getPipeline() {
              return Channels.pipeline(new TimeServerHandler());
          }
      });

      bootstrap.setOption("child.tcpNoDelay", true);
      bootstrap.setOption("child.keepAlive", true);

      bootstrap.bind(new InetSocketAddress(8080));
  }
}
6. TIMEクライアントを書く

DISCARDやECHOサーバーと異なり、TIMEプロトコルはクライアントが必要です。なぜなら、32bitのバイナリデータをカレンダー上の日付に人間は訳せないからです。
この項では、どうやってサーバーが正しく動いているか確かめる方法を議論し、Nettyでクライアントを書きます。


Nettyにおけるサーバーとクライアントの最も大きい唯一の違いは、違うBootstrapとChannelFactoryが必要なことです。以下のコードを見て下さい。

package org.soichiro.nettystudy.example.time;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

public class TimeClient {

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int port = Integer.parseInt("8080");

        ChannelFactory factory =
            new NioClientSocketChannelFactory(
                    Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool());

        ClientBootstrap bootstrap = new ClientBootstrap(factory);

        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                return Channels.pipeline(new TimeClientHandler());
            }
        });
        
        bootstrap.setOption("tcpNoDelay", true);
        bootstrap.setOption("keepAlive", true);

        bootstrap.connect(new InetSocketAddress(host, port));
    }
}

15. NioServerSocketChannelFactoryの代わりにNioClientSocketChannelFactoryをクライアントサイドのChannelを作るために使います
16. ClientBootstrapがServerBootstrapのクライアントサイドに相当するものです。
17. child.というプレフィックスは不要です。SocketChannelはクライアントサイドでは親を持っていません。
18. bindメソッドの代わりにconnectメソッドを使う必要があります。


見てみるとサーバーサイドのスタートアップとの違いは内容に見えます。ChannelHandlerの方はどうでしょうか?32bitのintegerをサーバーから受け取り、人間が読めるフォーマットに変換し、出力して接続を閉じなくてはいけません。

package org.soichiro.nettystudy.example.time;

import java.util.Date;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class TimeClientHandler extends SimpleChannelHandler {

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
        ChannelBuffer buf = (ChannelBuffer) e.getMessage();
        long currentTimeMillis = buf.readInt() * 1000L;
        System.out.println(new Date(currentTimeMillis));
        e.getChannel().close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
        e.getCause().printStackTrace();
        e.getChannel().close();
    }
}

とてもシンプルでサーバーサイドの例とは違って見えると思います。しかしながら、このハンドラはIndexOutOfBoundsExceptionを生じて動かないでしょう。なぜこのようなことが起きたのかは次の項で議論します。

7. ストリームベースの通信を扱う
7.1. ソケットバッファーに関する一つの小さな警告

TCP/IPのようなストリームベースの通信では、受け取られたデータはソケットのrecive bufferで受け取られる。不幸なことに、ストリームペースの通信のバッファは、パケットのqueueではなく、byteのqueueとなっています。これは、2つのメッセージを異なる独立したパケットとして送った場合、オペレーションシステムはこれらを2つのメッセージとしては扱わず、ただバイトの束として扱うということです。つまり、リモートで書かれたものが正確に読まれるという保証がないのです。例えば、OSのTCP/IPが3つのパケットを受け取ったと想定します。

                                    • +
ABC DEF GHI
                                    • +

ストリームベースのプロトコルの一般的な性質のため、あなたのアプリケーション上では以下の様な断片で受け取る可能性が高い。

                                          • +
AB CDEFG H I
                                          • +

つまり、サーバーサイドかクライアントサイドかによらず受け取りの部分では、受け取った一つまたは複数のフレームを結合し、アプリケーションのロジックが簡単に理解できるようにしなくてはならない。上の例で言うならば、データは以下のようにフレーム分けされないといけません。

                                    • +
ABC DEF GHI
                                    • +
7.2. 第一解決策

TIMEクライアントの例に戻ろう。わたちたちは同じ問題に面しています。32bitのintegerはとても小さなデータです。そして、よく断片化しそうな幹事ではない。しかし、実際には断片化しています。断片化の可能性はトラフィックが増えるほど増す。
最も簡単な解決案は、内部的な累積バッファを作り、全ての4バイトを内部バッファないに受け取るまで待つことです。以下は、そのようにしてTimeClientHandlerを実装して問題を修正した例です。

package org.soichiro.nettystudy.example.time;

import static org.jboss.netty.buffer.ChannelBuffers.dynamicBuffer;

import java.util.Date;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
 
 public class TimeClientHandler extends SimpleChannelHandler {
 
     private final ChannelBuffer buf = dynamicBuffer();
 
     @Override
     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
         ChannelBuffer m = (ChannelBuffer) e.getMessage();
         buf.writeBytes(m);
         
         if (buf.readableBytes() >= 4) {
             long currentTimeMillis = buf.readInt() * 1000L;
             System.out.println(new Date(currentTimeMillis));
             e.getChannel().close();
         }
     }
 
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
         e.getCause().printStackTrace();
         e.getChannel().close();
     }
 }

19. 必要に応じて容量を増加させるChannelBufferは動的バッファであります。メッセージの長さがわからない時にはとても便利であります。
20. 最初に、全てのデータがbufの中に蓄積されます。
21. そして、bufの長さが十分かどうかチェックし、この例では4バイトに達した時に実施のビジネスロジックを実行します。一方で、もしもっとデータが到着した場合、NettyはmessageReceivedをもう一度呼び出し、最終的に4バイトずつ累積します。

7.3. 第二解決策

第一解決策でTIMEクライアントの問題を解決したが、修正したハンドラはクリーンには見えない。もっと複雑なプロトコルを想像しよう。様々なフィールドを複数持つようなものです。あなたのChannelHandlerはメンテナンス不可だが速いというものになるだろう。
あなたが多くに気を払う時、あなたは一つ以上のChannelHandlerをChannelPipelineに足すことができる。つまり、あなたのアプリケーションの複雑性を減らすために複数のモジュールに巨大なひとつのChannelHandlerを分割することができる。例えば、TimeClientHandlerは2つのハンドラに分割できる。

  • 断片化問題を扱うTimeDecoder
  • 最初のシンプルなTimeClientHandler

幸運なことに、Nettyは最初の物を箱の外に書くような拡張クラスを提供しています。

package org.soichiro.nettystudy.example.time;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

public class TimeDecoder extends FrameDecoder{
	 
    @Override
    protected Object decode(
            ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
            
        if (buffer.readableBytes() < 4) {
            return null; 
        }
        
        return buffer.readBytes(4);
    }
}

22. FrameDecoderはおの断片化問題を簡単に扱うChannelHandlerの実装です。
23. FrameDecoderはdecodeメソッドをメンテナンスされた累積バッファを用いて呼ぶ。
24. nullが返った場合、まだデータが十分ではないということです。FrameDecoderは十分の量のデータが来た時にもう一度呼ばれる。
25. もしnullではないデータが帰った場合、decodeメソッドが成功したということであります。FrameDecoderは読まれた部分を内部バッファから破棄します。複数のメッセージをデコードする必要が無いことを思い出して欲しい。FrameDecoderはdecorderメソッドをnullの間呼び続ける。

なお、TimeClientのパイプラインの部分は以下の様な実装となる。

        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                return Channels.pipeline(
                		new TimeDecoder(),
                		new TimeClientHandler());
            }
        });


もしあなたが一歩進んだ人なら、decorderをもっとシンプルにするReplayingDecoderを試してみたいだろう。もしもっと情報が欲しい場合にはAPIリファレンスをもっと読む必要があります。

package org.soichiro.nettystudy.example.time;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.jboss.netty.handler.codec.replay.VoidEnum;

public class TimeDecoder extends ReplayingDecoder<VoidEnum> {
	 
    @Override
    protected Object decode(
            ChannelHandlerContext ctx, Channel channel,
            ChannelBuffer buffer, VoidEnum state) {
            
        return buffer.readBytes(4);
    }
}

加えて、Nettyはもっとプロトコルを簡単にし、巨大なメンテナンスできないハンドラの実装を避けることを助けるout-of-the-boxのデコーダーを提供します。以下のパッケージの例を参照して欲しい。

8. ChannelBufferの代わりのPOJOを使って通信する

いま見てきた全ての例において、プロトコルのメッセージとしてずっとChannelBufferが使われてきました。この項では、TIMEプロトコルクライアント、サーバーをChannelBufferの代わりにPOJO(Plain Old Java Object)を使うように改善します。
POJOをあなたのChannelHandlerで使う利点は、明白性にあります。あなたのハンドラはよりメンテナンスできるようになりChannelBufferから情報を抽出することでコードが分割されて再利用性が高まります。このTIMEクライアント、サーバーの例では、32bitのintegerのみを読むので、ChannelBufferを直接読む問題はありません。しかし、実装と現地つ世界のプロトコルを分割することが重要なことを理解してもらいます。
まず最初に、UnixTypeという型を定義しましょう。

package org.soichiro.nettystudy.example.time;

import java.util.Date;

public class UnixTime {
	private final int value;

	public UnixTime(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}

	@Override
	public String toString() {
		return new Date(value * 1000L).toString();
	}
}

TimeDecoderをUnixTImeをChannelBufferの代わりに返すように改定します。

	 @Override
	 protected Object decode(
	         ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
	     if (buffer.readableBytes() < 4) {
	         return null;
	     }
	 
	     return new UnixTime(buffer.readInt());
	 }

26. FrameDecoderとReplayingDecoderはどんな型のオブジェクトでも返せるようになっています。もしChannelBufferだけを返すように制限したい場合は、ChannelBufferをUnixTimeに変更する別なChannelHandlerを追加しなくてはいけません。


このdecoderの更新で、TimeClientHandlerもうChannelBufferを使えなくなりました。

	 @Override
	 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
	     UnixTime m = (UnixTime) e.getMessage();
	     System.out.println(m);
	     e.getChannel().close();
	 }

よりシンプルでエレガントではないでしょうか?同じテクニックをサーバーサイドにも適用します。まずはTimeServerHandler。

	 @Override
	 public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
	     UnixTime time = new UnixTime((int)System.currentTimeMillis() / 1000);
	     ChannelFuture f = e.getChannel().write(time);
	     f.addListener(ChannelFutureListener.CLOSE);
	 }

今、かけている要素としてはエンコーダががあるが、それはUnixTimeをChannelBufferに翻訳するChannelHandlerの実装です。パケットの断片化の問題とアッセンブルのに配慮する必要がないためとてもシンプルに書ける。

package org.soichiro.nettystudy.example.time;

import static org.jboss.netty.buffer.ChannelBuffers.buffer;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class TimeEncoder extends SimpleChannelHandler {

    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) {
        UnixTime time = (UnixTime) e.getMessage();
        
        ChannelBuffer buf = buffer(4);
        buf.writeInt(time.getValue());
        
        Channels.write(ctx, e.getFuture(), buf);
    }
}

27. エンコーダーは書き込みリクエストに割り込むためにwriteRequestedメソッドをオーバーライドします。MessageEventパラメーターに注目してください。messageReceivedで設定されたものと同じタイプですが、異なる割り込みをします。ChannelEventはアップストリームかダウンストリームかのどちらかのイベントで、イベントの流れに依存します。例えば、MessageEventは、messageReceivedで呼ばれるときはアップストリームでり、writeRequestedで呼ばれるときはダウンストリームとなります。アップストリームイベントとダウンストリームイベントの差をもっと知りたい場合は、リファレンスを参照下さい。
28. POJOをChannelBufferに一度変換したら、新しいバッファを前のChannelDownstreamHandlerにChannelPipelineで転送しなくてはいけません。ChannelsはChannelEventを生成したり送ったりする様々なヘルパーメソッドを提供しています。この例では、Channels.write(...) メソッドは新しいMessageEventが作られ、ChannelPipelineの中でそれを前のChannelDownstreamHandlerに送っています。
一方で、Channelsのstaticインポートを使うとこうも書けます。

import static org.jboss.netty.channel.Channels.*;
...
ChannelPipeline pipeline = pipeline();
write(ctx, e.getFuture(), buf);
fireChannelDisconnected(ctx);

最後のタスクはTimeEndocerをサーバーサイドのChannelPipelineに挿入し、あとは取るに足らないエクササイズが残るのみとなります。

   bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
          public ChannelPipeline getPipeline() {
              return Channels.pipeline(new TimeServerHandler(),
            		  new TimeEncoder());
          }
      });
9. アプリケーションをシャットダウンする

TimeClientを起動すると、アプリケーションが何もせずに起動し続けていることにきづくだろう。全てのスタックトレースから見ると、2つのIOのスレッドが動き続けていることわかる。これらのIOスレッドをシャットダウンし、上品にアプリケーションを終了させるには、ChannelFactoryのリソースを開放する必要があります。
ネットワークアプリケーションの形式的なシャットダウンのプロセスは、以下の3つで構成されます。

  1. なんであれ、全部サーバーのソケットを閉じる
  2. なんであれ、サーバーではない全部のソケットを閉じる(クライアントソケットやアクセプトソケットなど)
  3. ChannelFactoryが持つ全てのリソースを開放する


この3つのステップをTimeClientに適用すると、TimeClient.main() は自身を上品にシャットダウンするため、唯一のクライアントコネクションを閉じて、ChannelFactoryの全リソースを開放します。

package org.soichiro.nettystudy.example.time;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

public class TimeClient {

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int port = Integer.parseInt("8080");

        ChannelFactory factory =
            new NioClientSocketChannelFactory(
                    Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool());

        ClientBootstrap bootstrap = new ClientBootstrap(factory);

        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                return Channels.pipeline(
                		new TimeDecoder(),
                		new TimeClientHandler());
            }
        });
        
        bootstrap.setOption("tcpNoDelay", true);
        bootstrap.setOption("keepAlive", true);

        ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
        future.awaitUninterruptibly();
        if (!future.isSuccess()) {
            future.getCause().printStackTrace();
        }
        future.getChannel().getCloseFuture().awaitUninterruptibly();
        factory.releaseExternalResources();

    }
}

29. ClientBootstrapのconnectメソッドはChannelFutureを返す。これは接続が成功または失敗した場合に通知します。同様に接続に関連するChannelへの参照を持つ。
30. ChannelFutureは、接続が成功したかしてないかが判定されるまでを待ちます。
31. もし失敗した場合、なぜ失敗したかをプリントします。ChannelFutureのgetCause()メソッドは接続が成功もキャンセルもサれなかった場合、どのような失敗が起こったのかを返す。
32. 接続への試みが終了した場合、ChannelのcloseFutreで接続が閉じられるのを待つ必要があります。全てのChannelは自身のcloseFutreを持っており、閉じられた時に実行され通知を行う。
たとえ接続への試みが失敗してもcloseFutreは呼ばれる。なぜなら、Channelは接続が失敗したら自動的に閉じられるためであります。
33. 全てのコネクションはこの時点で閉じられています。残ったタスクはChannelFactoryの全てのリソースを開放することであります。やり方は、releaseExternalResources()メソッドをよぶだけであります。NIO Selectorsやスレッドプールを含む全てのリソースがシャットダウンし、自動的に終わらせられる。


クライアントのシャットダウンはとても簡単です。しかし、サーバーはどうだろうか?まずポートをアンバインドし、全部のacceptedコネクションを閉じる。これをやるために、アクティブな接続のリストのトラックを維持するデータ構造が必要になるだろう。そして、これは平凡な仕事ではない。幸運なことにそれには解決策があります。ChannelGroupです。


ChannelGroupはJava Collections APIの特別な拡張です。これは開いているChannelのセットを表す。もしChannelがChannelGroupに加えられた場合、そして、Channelが閉じられた場合、自動的にChannelGroupから取り除かれる。あなたはただ同じグループの全てのチャンネルに一つの操作を実行すれば良い。例えば、サーバーにおいて全てのChannelGroupのChannelを閉じれば良い。


開いたソケットのトラックを維持するために、TimeServerHandlerでChannelが開いた際にグローバルのChannelGroupにChannelを追加しよう。TimeServer.allChannelsだと

 @Override
 public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
     TimeServer.allChannels.add(e.getChannel());
 }

34. もちろんChannelGroupはスレッドセーフです。

今全てのアクティブなチャンネルは自動的にメンテナンスされています。サーバーをシャットダウンするは、クライアントをシャットダウンするのと同じように簡単です。

package org.soichiro.nettystudy.example.time;

import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class TimeServer {

  final static ChannelGroup allChannels = new DefaultChannelGroup("time-server");
  
  public static void main(String[] args) throws Exception {
	  
      ChannelFactory factory =
          new NioServerSocketChannelFactory(
                  Executors.newCachedThreadPool(),
                  Executors.newCachedThreadPool());

      ServerBootstrap bootstrap = new ServerBootstrap(factory);

      bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
          public ChannelPipeline getPipeline() {
              return Channels.pipeline(new TimeServerHandler(),
            		  new TimeEncoder());
          }
      });

      bootstrap.setOption("child.tcpNoDelay", true);
      bootstrap.setOption("child.keepAlive", true);

      Channel channel = bootstrap.bind(new InetSocketAddress(8080));
      allChannels.add(channel);
      waitForShutdownCommand();
      ChannelGroupFuture future = allChannels.close();
      future.awaitUninterruptibly();
      factory.releaseExternalResources();
  }
  
  private static final CountDownLatch shutdownSignal = new CountDownLatch(1);
  private static void waitForShutdownCommand() {
	  Thread shutdown = new Thread() {
	      public void run() { 
	    	  shutdownSignal.countDown();
	      }
	  };
	  Runtime.getRuntime().addShutdownHook(shutdown);
	  try {
		shutdownSignal.await();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
  }
}

35. DefaultChannelGroupはグループ名を引数として必要とします。グループ名は単独で他のグループと区別が付く名前です。
36. ServerBootstrapのbindメソッドは、ローカルアドレスが設定されたChannelを返します。close()メソッドが呼ばれるとChannelはローカルアドレスをアンバインドします。
37. サーバーサイド、クライアントサイド、アクセプテッドに限らずどんなタイプのChannelもChannelGroupに追加することができます。つまりアクセプテッドなチャンネルを一回で、Channel領域まるまるサーバーをしゃっとだうんですることができます。
38. waitForShutdownCommand()はサーバーのシャットダウンを待つ仮想のメソッドです。特権的なクライアントのからのメッセージやJVMのシャットダウンをフックするように実装してください。
39. 同じChannelGroupに置いてすべてのチャンネルが同じオペレーションが実行されます。この場合、全てのチャンネルを閉じます。これは、サーバーサイドのチャンネル領域を意味し、これらはアンバウンドされて全てのアクセプテッドなコネクションは非同期的に閉じられます。全てのコネクションが閉じたれたことを通知するため、ChannelGroupもChannelFutureを返します。

10. まとめ

このチャプターでは、ネットワーク上で動くNetty上のアプリケーションを書くデモン捨てレーションを交えてNettyのクイックツアーを行いました。
Nettyに関する詳細は、今後の章で紹介します。あと、 org.jboss.netty.example package以下にある他の例も見て見ることをおすすめします。
あと、Nettyのコミュニティはいつも、あなたの質問とあなたを助ける質問を待ち、それらのフィードバックを得てNettyを改良し続けていることを知っていて下さい。



以上となります。お疲れ様でした。

ScalaでEclipseデバック可能な実行jarを作るまでの手順 (例:特定のハッシュタグを含むツイートをファイルに保存し続けるアプリ)

最近Scalaでちょっとしたjarアプリを作ることが多いのでその手順をまとめておきます。まだ発展途上とは言え2.0.2になったことでScala IDE for Eclipseも実用に耐えうるところまできているように思います。あとは、リファクタリング機能とクイックフィックス機能がもう少し良くなると良いですが、コンパイルエラーが出た際に、どうするべきかということがポップアップメニューで出るのでその点で助かっています。
ちなみに今回この記事を書こうと思ったのは、今までSbt0.11.2まで利用できた実行jarを作るproguardというプラグインが利用できなくなってしまったこともあり、sbt-assemblyを利用したものを再度書くことにしました。

環境確認

まず、環境情報の確認です。Macのパッケージ管理システムで利用できる2012/07/28現在でできる限り最新安定版を利用しています。


Javaは、Macの場合は

$ java -version

と実行するとインストールされ、バージョンが確認できます。ソースコード付きのJDKを入れたい方は、この記事を参照。
Homebrewはリンク先を参照してインストールの事。XCodeとCommand line toolsのインストールも必要です。
Scalaとsbtのインストールは

$ brew install scala
$ brew install sbt

で完了です。バージョンアップの際には、

$ brew update
$ brew upgrade scala
$ brew upgrade sbt

を実行します。Eclipse 3.6.2とScala IDE for Eclipse 2.0.2のインストールはリンク先のインストール方法に従って下さい。


というわけで、環境は整ったということでお題であるEclipseデバッグ可能な外部jarライブラリを内包する実行可能jarを作ってきます。お題は、


特定ハッシュタグを含むツイートをファイルに保存し続けるアプリ」


です。すでに実装したものが、GitHubの方にMITライセンスでpushしてありますので、ソースコードだけが欲しい場合はこちらをcloneしてお使いください。

プロジェクトの作成

まず、プロジェクトの作成です。ここではtweet_recorderとします。有りそうな名前ですねw

$ mkdir tweet_recorder
$ cd tweet_recorder

次にjarを作るまでに雛形を作成します。なお基本的にこのtweet_recorderディレクトリから操作していきます。

$ mkdir -p src/main/scala
$ echo 'object TweetRecorder { def main(args: Array[String]) = println("Hi!") }' > src/main/scala/TweetRecorder.scala
$ vim build.sbt

build.sbtを編集

name := "tweet_recorder"

version := "1.0"

scalaVersion := "2.9.2"

次に念のためにsbtもバージョン指定しておきます。

$ mkdir project
$ vim project/build.properties

project/build.propertiesを編集

sbt.version=0.11.3

とここまでで、プロジェクトのひな形は完成。



というわけで早速sbtでビルド、実行してみます。

$ sbt
> compile
[success] Total time: 15 s, completed 2012/07/29 9:00:22
> run
Hi!
[success] Total time: 1 s, completed 2012/07/29 9:00:36
> exit

これで大丈夫なことを確かめます。

実行jarの作成

次にjar化できるようにします。他にもjar化させるものはいくつかありますがsbt-assemblyが非常に良い出来だったので、これを使います。

$ vim project/plugins.sbt

project/plugins.sbtを編集

resolvers += Resolver.url("artifactory", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.8.3")
$ vim build.sbt

build.sbtを編集

import AssemblyKeys._ // put this at the top of the file

assemblySettings

name := "tweet_recorder"

version := "1.0"

scalaVersion := "2.9.2"

mainClass in assembly := Some("TweetRecorder")

他にも設定が、github内に書いてありますが、ここではmainメソッドのあるクラスの定義だけ設定します。
そして早速jarを作ってみます。assemblyコマンドでjar作成とのことです。
早速、jarを作成、実行してみましょう。

$ sbt
> assembly
[info] Done packaging.
[success] Total time: 107 s, completed 2012/07/29 9:14:19
> exit
$ java -jar target/tweet_recorder-assembly-1.0.jar
Hi!

これでOKそうですね。

実行jarへの3rdパーティjarライブラリの梱包

ちなみにもうライブラリとしてtwitter4jを使うことがわかっているので、twitter4jの必要なライブラリとソースをプロジェクトにコピーしてビルドしてみましょう。tmpディレクトリに一度ダウンロードし、削除します。

$ mkdir lib
$ mkdir lib-src
$ mkdir tmp
$ cd tmp
$ curl -O http://twitter4j.org/en/twitter4j-2.2.6.zip
$ unzip twitter4j-2.2.6.zip
$ cp lib/twitter4j-core-2.2.6.jar ../lib
$ cp lib/twitter4j-stream-2.2.6.jar ../lib 
$ cp twitter4j-core/twitter4j-core-2.2.6-sources.jar ../lib-src 
$ cp twitter4j-stream/twitter4j-stream-2.2.6-sources.jar ../lib-src
$ cd ..
$ rm -rf tmp
$ sbt
> assembly
[info] Including twitter4j-core-2.2.6.jar
[info] Including twitter4j-stream-2.2.6.jar
[success] Total time: 1 s, completed 2012/07/29 9:32:14
> exit
$ java -jar target/tweet_recorder-assembly-1.0.jar
Hi!

これでライブラリの実行jarへの内包も大丈夫そうです。


Eclipseへのsbtプロジェクトのインポート

次に、Eclipseにインポートできるようにします。sbteclipseを使います。

$ vim project/plugins.sbt

project/plugins.sbtを編集して、末尾に追記します。

resolvers += Resolver.url("artifactory", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.8.3")

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.0")

編集後、eclipseコマンドを実行します。

$ sbt
> eclipse
[info] Successfully created Eclipse project files for project(s):
[info] tweet_recorder
> assembly
[info] Including twitter4j-core-2.2.6.jar
[info] Including twitter4j-stream-2.2.6.jar
[success] Total time: 1 s, completed 2012/07/29 9:32:14
> exit
$ java -jar target/tweet_recorder-assembly-1.0.jar
Hi!

eclipseプロジェクトの作成も、実行jarの作成もOKそうです。


次に、Eclipseへのインポートです。ScalaパースペクティブのPackage Explorerビューの右クリックメニューにて。Import > General > Existing Projects into Workspaceを選択して、Nextを押します。


Select root directoryで、プロジェクトのフォルダを選択して、Finishボタンを押します。この際に、copyをせずにそのままそのフォルダを利用します。これは今後sbtでビルドを利用するためです。

Twitter4jのStreamingAPIを使った実装とビルド

これでプロジェクトのインポートが終わったので、早速ちょっとコードを書いてデバッグで止めていましょう。
早速サンプルコードを書いてみます。

TweetRecorder.scalaを編集して

import twitter4j.conf.ConfigurationBuilder
import twitter4j.TwitterStreamFactory
import twitter4j.FilterQuery
import twitter4j.StatusListener
import twitter4j.Status
import twitter4j.StatusDeletionNotice
import java.io.PrintWriter
import java.io.FileWriter
object TweetRecorder { 
  def main(args: Array[String]) = {
    val conf = new ConfigurationBuilder().setUser(args(0)).setPassword(args(1)).build
    val twitterStream = new TwitterStreamFactory(conf).getInstance()
    twitterStream.addListener(new Listener)
    twitterStream.filter(new FilterQuery().track(args.slice(2, args.length)))
  }
  class Listener extends StatusListener {
    override def onStatus(status: Status) = {
      val tweet = status.getCreatedAt().toLocaleString() +
      	"," + status.getUser().getScreenName() + 
      	"," + status.getText() 
      val writer = new PrintWriter(new FileWriter("tweet_recoder.csv", true))
      writer.println(tweet)
      writer.close
      println(tweet)
    } 
    override def onDeletionNotice(statusDeletionNotice: StatusDeletionNotice) = {}
    override def onTrackLimitationNotice(numberOfLimitedStatuses: Int) = {}
    override def onException(ex: Exception) = {}
    override def onScrubGeo(userId: Long, upToStatusId: Long) = {}
  }
}

これでOK。起動する時に第一引数がtwitterのアカウントのID、第二引数がtwitterのアカウントのパスワード、第三引数以降は追跡したいキーワード(英単語か#ではじまるハッシュタグのみ) を半角スペース区切りで追加という感じになります。右クリックでDebag As > Scala Applicationを選択してデバッグ実行で引数が無いという例外が投げられて失敗したあと。
虫アイコンのドロップダウンメニューのDebug ConfigurationのArgumentsタグでそれぞれの引数を設定して実行します。


今回は、単語をすごい量のツイートが流れていそうなhttpに設定して実行します。コンソールに

2012/07/29 11:06:30,Vasilisa_Anime,http://t.co/nKcvZJw7
2012/07/29 11:06:30,Neylianavera,esteeeen; x_x @Georgejsm. http://t.co/Ap5vLa1R

のように表示されればOK。今回は日付、ユーザーID、ツイートをカンマ区切りで表示してます。


無論ブレークポイントを貼ってデバッグすることもできます。


最後にビルドして実際に動くか確かめて見ましょう。
今回は、2chまとめのタグ#MT2とニコ動の#nicovideoで検索してみます。

$ sbt
> assembly
[info] Including twitter4j-core-2.2.6.jar
[info] Including twitter4j-stream-2.2.6.jar
[success] Total time: 1 s, completed 2012/07/29 9:32:14
> exit
$ java -jar target/tweet_recorder-assembly-1.0.jar sifue password \#MT2 \#nicovideo
2012/07/29 11:35:08,nicorank_bot,毎時ランキング第1位:じょしらく 第三席「無情風呂」「浅草参り」「真田小ZOO」  http://t.co/0U9SZFnQ #nicovideo #so18447313 2012/07/29 11:35 現在
2012/07/29 11:35:09,2chtitle,「あっ、こいつインド人だな」って思う仕草 インド人とのつきあい方?インドの常識とビジネスの奥義 を Amazon でチェック!  http://t.co/Lf2sWYQ5 #MT2 #まとめ
2012/07/29 11:35:12,zyazya11,RT @Mell_Ta: ニコニコ有名歌い手がポケモン割れ発覚 http://t.co/qQh9eCvL #MT2 今ぐるたみんが熱い

以上のように表示され無事利用できるようです。これで好きなハッシュタグのツイートなどを保存して解析したりするのに使えますね。
ちゃんとtweet_recoder.csvというファイルを確かめても保存できているようです。


以上でScala2.9.2でsbt-assemblyを使ってEclipseでデバック可能な3rdパーティjarを含む実行jarを作るまでの手順を終わりとします。お疲れ様でした。

イングリッシュ記法(名前に情報を追加する)は、誰もが行うべき重要な変数命名法

リーダブルコードを読み終えました。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

Java, JavaScript, PHPがメインな自分としては、過去に読んだEffective JavaJavaScript: The Good Parts、Clean Code、リファクタリング、プログラミング作法と基本的に同じ情報ではあったのですが、ここ最近のトレンドを取り込み、薄い本としてまとめているというところに非常に価値のある本でした。


その中でも一番のお気に入りは、「2.4 名前に情報を追加する」の項で書かれているハンガリアン記法と区別してイングリッシュ記法を提唱している以下の部分です。

これってハンガリアン記法なの?

ハンガリアン記法というのは、Microsoft社で広く使われていた命名規則だ。すべての変数名の接頭辞に「型」をつける。例えばこんな感じだ。

変数名 意味
pLast あるデータ構造の最後の要素を指すポインタ(p)
pszBuffer ゼロ終端(z)の文字列(s)バッファを指すポインタ(p)
cch 文字(ch)のカウント(c)
mpcopx カラーポインタ(pco)からX軸のポインタ(px)を指すマップ(m)

これも「名前に属性を追加」しているけれど、もっと厳密で規律のあるシステムだ。
一方、僕達が提唱しているのは、もっと大まかで規律のゆるいシステムだ。必要な時にだけ変数の大切な属性を見つけ出して、それを読みやすくして名前に追加する。これをハンガリアンならぬ「イングリッシュ記法」と呼んでもいいだろう。


この項では、変数名を

var start = (new Date()).getTime();

ではなく

var start_ms = (new Date()).getTime();

と書いたりして、変数名に属性を追加しわかりやすくしようということを言っています。


載っていた例としては

状況 変数名 改善後
passwordはプレインテキストなので、処理をする前に暗号化すべきである。 password plaintext_password
ユーザーが入力したcommentは表示する前にエスケープする必要がある。 comment unescaped_comment
htmlの文字コードUTF-8に変えた。 html html_utf8
入力されたdataをURLエンコードした。 data data_urlenc

といったものです。


このイングリッシュ記法は本当に重要で、変数をわかりやすくする他、変数名にあえて属性を入れることで変数の意図しない使い回しを避けさせる効果もあります。

var start = new Date();
if('ms' === type){
    start = start.getTime();
}else if('array' === type){
    start = [start];
}else if('sec' === type){
    start = start / 1000;
}else if('string' === type){
    start = "" + start;
}

// ... この数百行程度の別なコード

// TODO 1年後、startを使った処理を他人が書く。バグらないだろうか?

こういうコードが本当に多いのも難点ですが...、もしこのstartという変数がstart_msやstart_arrayだとすれば、さすがにミリ秒や配列以外のものを代入しようとは思いませんし、また後でこの変数を利用する人が余計な型チェックや状態チェック、特にsecとmsの判定などをせずに、安心して変数をつかうことができます。


このようにイングリッシュ記法には、余計なコードを排除し、わかりやすくなってバグを減らせるという素晴らしい効果があります。しかもエディタの入力補完があるので、総コーディングタイプ数はそんなに変わりません。


あとこれは前職の先輩に教えてもらったテクニックなのですが、選別に使いたい属性を先にしておくと入力補完機能のあるエディタのあるEclipsevimemacsでは非常に良い副作用があります。

var buttonTitle = null;
var buttonTextArea = null;
var buttonCombobox = null;
var labalTitle = null;
var labalTextArea = null;
var labalCombobox = null;
var titles = null;
var mapCombboxContent = null;

大抵コーディングする場合には、そこに書くべき変数の型というのは決まっていることが多いです。 この場合はtitを押して入力補完を実行するとすぐにタイトルの配列titlesが取れます。
これが、もしボタンやラベルまでtitleが最初に付く変数になっていると、後でボタンなのかラベルなのか配列なのかを候補の中から選ばなくてはいけなくなってしまいます。


実装の内容にもよりますが、属性をつけるのを前にするか後にするかでコーディングスピードを変えることができる、というのもこのイングリッシュ記法のメリットだと思います。この辺も考えながら実装するとより、楽な実装ができるのではないかと思います。
ぜひ、イングリッシュ記法を実践してみてください。では楽しい開発を!

// 2012/07/21 変更 最後の例がシステムハンガリアンを強制するような内容であったのでJavaScriptで書き直し

Mac OSXのEclipseにJavaのソース付きのJDKを設定する方法

MacJDKをインストール新たにインストールしたり、今回みたいにFlashback対策のようなセキュリティアップデートがされた際に、JDKを再インストールするわけですが、いつものソース付きJDKインストール、設定の仕方を忘れるのでまとめました。

1. MacのアップデートでJavaのバージョンアップが走ったことを確認する

2. Apple Developerのダウンロードで最新のJava for Mac OS X 2012-01 Developer Packageをダウンロードする

3. EclipseJREデフォルトのものから変更する

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home

のものが登録されているが、新しいJREを以下のホームのものを登録する

/Library/Java/JavaVirtualMachines/1.6.0_31-b04-413.jdk/Contents/Home

デフォルトのものは、勝手に読み込まれる事もあるので除去しても良い。

4. Javaプロジェクト内でStringなどの型を参照して、ソースを添付する

/Library/Java/JavaVirtualMachines/1.6.0_31-b04-413.jdk/Contents/Home/src.jar

これがJavaソースコードになっているので添付する。
以上



これでJavaのソースのコードが実際に読めるようになり、Java自体のパフォーマンスを観たり、Javadocを実装を観ながら確認したりできます。幸せ。

Scala+sbt+EclipseでUTF-8文字とUIを含む実行jarのアプリケーション作成、開発方法

sbt0.11.3以上をお使いの方はこちら


全国のScalaファンのみなさん、こんにちわ!
最近よくScalaをいじっていることもあって、Scalaを使ってUTF-8の文字を表示しつつ、SwingのUIなんかを表示させる実行jarを作ったりすることがあり、いろいろと開発環境の設定が面倒くさい所もあるのでその手順をまとめておきました。
Scalaを使って日本語を使ったUIを表示させることのできる実行jarをビルドするHelloWorld!と考えてもらえれば良いです。無論、UTF-8文字やUIを含まなくてもこの方法でビルドできます。

まず使っている使っている環境です。

となります。


というわけで、早速はじめていきます。プロジェクト名をhelloswingにしてにしてありますが、その辺は適宜変更して使って下さい。ちなみに、自分のワークスペースは、

 $ cd ~/Dropbox/Dev/sbt

というパスから開始してやっていますが、その辺も適宜調整お願いします。

sbtのプロジェクトの作成

 $ mkdir helloswing
 $ cd helloswing
 $ mkdir -p src/main/scala
 $ mkdir -p src/test/scala
 $ echo 'import javax.swing.JOptionPane; object Helloswing { def main(args: Array[String]) = JOptionPane.showMessageDialog(null, "こんにちわ!世界!") }' > src/main/scala/Helloswing.scala
 $ vim build.sbt

ここまでで、Mavenでよく使われているフォルダ構成の作成とMainにてダイアログを表示させる処理を実装しました。最後にbuild.sbtを編集します。別にvimでなくてもお好きなエディタで以下のような内容にします。

name := "helloswing"

version := "1.0"

scalaVersion := "2.9.1"

これで、プロジェクトは完成です。
ビルド後、実行してみましょう。

$ sbt
  > run


以上のようなメッセージダイアログが出ればプログラムは完成です!
Ctrl+Dでsbtは終了させることができます。

実行jarのビルド

というわけで今度はここから実行jarを作成できるようにします。そのためにprogaurdというsbtのプラグインをインストールしていきます。
siasia/xsbt-proguard-plugin · GitHub
以上の本家を参考にしていますが、sbt 0.11.1とsbt0.11.2でフォルダ構成が違うので要注意です。では進めていきます。

 $ mkdir project
 $ vim project/plugins.sbt

場合によっては、projectフォルダは既にあるかもしれません。次に以下を追記します。

libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-proguard-plugin" % (v+"-0.1.1"))

これがプラグインの依存関係を追加する設定です。

 $ mkdir project/project
 $ vim project/project/Build.scala

次に以下を追記します。

import sbt._
object PluginDef extends Build {
	override def projects = Seq(root)
	lazy val root = Project("plugins", file(".")) dependsOn(proguard)
	lazy val proguard = uri("git://github.com/siasia/xsbt-proguard-plugin.git")
}

これは依存関係がなかったらGitHubからダウンロードしてくるという設定です。

 $ vim build.sbt

以上を編集して、以下の用に処理を開始するprogaurdの設定の読み込みと、Mainのobjectの設定を追記します。

name := "helloswing"

version := "1.0"

scalaVersion := "2.9.1"

seq(ProguardPlugin.proguardSettings :_*)

proguardOptions += keepMain("Helloswing")

これでOK。
最後に、scala-liblary.jarをlibフォルダに同梱して、実行jarをビルドしてみましょう。

 $ mkdir lib
 $ cp /usr/local/Cellar/scala/2.9.1-1/libexec/lib/scala-library.jar lib
 $ sbt
   ..
   > update
   ..
   > proguard
   ..
   [success] Total time: 30 s, completed 2012/04/14 17:08:37

これで実行jarの作成完了です。


実行jarの場所は、以上のスクリーンショットのように

target//.min.jar

という形式になります。minがついていないものはscala-library.jarが含まれていないものです。


というわけで、helloswing_2.9.1-1.0.min.jarをダブルクリックしてみましょう。以下のように表示されれば、実行jarビルドは成功です。

Eclipseへのインポート及びデバッグ実行

さて、これを今度はEclispseで動かしてみたいと思います。そのためにtypesafehub/sbteclipse · GitHubというプラグインを使います。このサイトの手順にしたがって、

 $ vim project/plugins.sbt

以上のファイルを編集し、依存関係を追加します。

libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-proguard-plugin" % (v+"-0.1.1"))

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0")

最後の一行を追加して最終的にこのようになります。Eclipseに読み込む設定をします。

 $ sbt
   > eclipse
   [info] About to create Eclipse project files for your project(s).
   [info] Successfully created Eclipse project files for project(s): helloswing

これで完了です。とは以下のようにScala IDEをインストールしたEclipseに読み込みます。


プロジェクトエクスプローラーの右クリック > Import > General > Existing projects into workspaceから、以下のようにコピーをせずにインポートしましょう。(でないと同期してくれなくなります)


じつは、ここからが肝で、エラーが出るのでそれを解消します。まず、プロジェクトエクスプローラーで、helloswingのルートノードを選択して、右クリック > Propertiesから。



Java Build Pathに重複している、Scala Libraryの取り除きを行います。



次に、ResourceからText file encodingがSJISになっているので、UTF-8に変更します。

UTF-8



最後に、Scala Compilerで、プロジェクトの設定を使うようにして、追加コマンドライン引数に-encoding UTF-8加えます。

-encoding UTF-8



これでOKです。早速Helloswing.scalaを開いて、右クリック > Debug As .. > Scala Applicationsからデバッグしてみましょう。



これで無事起動出来ればOKです。無論、ブレークポイントを貼ってデバッグすることもできますので、あとはお好きに。
実行jarの作成は、sbtから行いますので sbt→updatem→proguardという手順でビルドできます。あとはJenckinsにsbtのプラグインを入れてデイリービルドなり、デイリーテストなりをやってみてください。


あと、sbtで依存ライブラリを追加する際には、再度eclipseコマンドを実施してインポートの手順を踏むことでうまくいきます。


以上です。お疲れ様でした。