BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル ScalaとSpring:両世界のベストを一体化

ScalaとSpring:両世界のベストを一体化

原文(投稿日:2010/05/19)へのリンク

導入

Scalaは、簡潔で読みやすいシンタックスとJavaと完全互換で、オブジェクト指向と関数型プログラミングのパラダイムとのシームレスな融合を兼ね備えた、すばらしいプログラミング言語である。後者のお陰で、ScalaをJava開発者が馴染んだJavaAPIとフレームワークに一体化することが可能であった。そうすることによって、既存のJavaフレームワークの使用方法が改善され、単純化することができた。それに加えて、「Javaの良く知られた世界」と実に簡単に統合できることからScalaを学習する敷居も低くなった。

このアーティクルで、最も人気のあるフレームワークの一つである、 Springがいかに、Scalaによって強化されたかを紹介する。Springは、依存性の注入(DI)やアスペクト指向プログラミング(AOP)のような効果的なプログラミング パラダイムを提供しただけでなく、 JEE環境とHibernate, Toplinkなどのような人気のあるフレームワークへのたくさんの手軽な接続用のコードを提供している。特に後者によって、Scala は、その成功が掛かっているエンタプライズ分野でスムーズに使われることになった。

Scala がいかに Springにサービスするかを示すために、このアーティクルは、簡単なサンプルアプリケーションを基にしている。このアプリケーションは、以下のドメインモデルといっしょに Scala, SpringとHibernate/JPAスタックを使っている:

このドメインモデルは、ソーシャル ネットワーク アプリケーションの小さなバージョンに似ている。簡単に言うと、人々が他の人たちと繋がるようにできるのである。

Step 1

このドメインモデルに基づいて、まず第一に、このアーティクルでは、どのようにHibernate/JPAを使って、Scalaにおける Personエンティティ用の ジェネリックData Access Object (DAO) と具体的なDAOの実装方法を示す。その結果、 PersonDaoに、 CRUD操作が実行できるようになりる。例:

val p1 = new Person(“Rod Johnson”) 
val p2 = dao.findByName(“Martin Odersky”) 
p1.link(p2)
personDao.save(p1)

Step 2

それからこのアーティクルは、Person エンティティを「リッチな」ドメインオブジェクトに変換するのに、何が必要なのかを要約する。このオブジェクトは、内部で NotificationServiceを使って、linkメソッドが呼ばれたときに、追加ロジックを実行する。このサービスは、要求に応じて 「魔法のように 」に注入される。この段階で、以下のことが達成される:

val p1 = Person(“Martin Odersky”) //‘new’キーワードの省略は意図的
val p2 = dao.findByName(“Rod Johnson”) 
p1.link(p2) //魔法がここで起きる
personDao.save(p1) 

Step 3

最後に、このアーティクルでは、Springがいかに Scalaの進化した 概念である‘traits’から恩恵を受けている かを説明します。 traitsは、リッチな Personドメイン オブジェクトを本格的なOOクラスに変換できる。このクラスは、CRUD操作を含んで、可能な全ての責務を満足させることができる。例:

Person(“Martin Odersky”).save 

Step 1: Scala, Spring と Hibernate/JPAによる DAO

要件

当然、 DAOの設計は、Person エンティティに対するジェネリックDAOと具体的なDAOからできているべきである。ジェネリックDAOは、 save, remove, findById そして findAllのような基本的な CRUDメソッドを実行できるフィーチャを持つべきである。ジェネリックなので、エンティティの具体的な実装ではなく、型を扱うべきである。要するに、以下のインタフェース定義にこだわるべきである:

trait GenericDao[T] {
      def findAll():List[T]
      def save(entity:T):T 
      def remove(entity:T):Unit
      def findById(id:Serializable):T
}

Personエンティティ用の具象DAOクラスは、Personエンティティ固有のfinderメソッドを加えるべきである:

trait PersonDao extends GenericDao[Person] {
  def findByName(name:String):List[Person]
  //more finders here…
} 

Scalaが提供する生産的なフィーチャの恩恵を受けるために、もっと詳細な実装を考える必要がある:

  • collectionを考える: DAO interfaceは、Javaのcollection型 (java.util.List)ではなく、 Scalaのcollection型 (scala.List) を返す。土台の JPA の実装は、Scalaのcollectionの概念をもっていないが。Scalaのcollectionは、Javaのcollectionよりかなり強力なので、DAOメソッドを呼ぶ方は、Scalaのcollectionを戻すようにするのが望ましい。なのでJPAから戻るJavaのcollectionをScalaのcollectionに変換するうまい方法を見つける必要がある。
  • コールバックを考える: JPA, JMSなどの他のフレームワークに対するほとんどの Springのグルーコードは、 JpaTemplate, JmsTemplateなどのようなテンプレート パターンに基づいている。これらのテンプレートは、convenience メソッドによってある程度は、基礎となるフレームワークの複雑性を隠しているが、多くの場合、 EntityManager, JmsSessionなどの下層の実装クラスに直接アクセスせざるを得ない。そのような場合、 Springは、 JpaCallbackのような Callbackクラスを提供している。コールバックdoIn…(..) メソッドの唯一のパラメータは、 EntityManagerのような望みの実装クラスへの参照を持っている。以下の例がこのプログラミングモデルを説明している:
    jpaTemplate.execute(new JpaCallback() {
    	public Object doInJpa(EntityManager em) throws PersistenceException {
    	//… do something with the EntityManager
    	return null;
    }
    });

    このコードで2つのことを指摘する:第一に、匿名インナー コールバック クラスをインスタンス化するには、大量の決まりきったコードが必要なこと。二つ目は、匿名の JpaCallbackクラスのスコープ外のすべてのパラメータは、 finalである必要がある、という制約をがまんしなければならない。Scalaの視点からコールバックのパターンを見ると、我々が本当に見ているものは、「関数」の厄介な実装である。我々が本当に欲しいのは、そのEntityManagerへの直接のアクセスで、1行のdoInJpa(…)メソッドの実装を持つ匿名のインナーコールバック クラスの手間は省きたい。言い換えると、我々がやりたいのは、1行で済ませることである:
    jpaTemplate.execute((em:EntityManager) => em.createQuery(…)// etc. ); 

    問題は、いかにこのことがエレガントなやり方で成し遂げられるか、ということである。
  • getterとsetterを考える: Spring beanを使うクラスは、少なくとも1つの 特定のbeanの名前に対応したsetterメソッドを提供する必要がある。再び、これらのsetterは、フレームワークのための決まりきったコードである。至る所で、 Constructor注入を使わずに、これらのコードを避けることができたらすばらしいのだが。

実装

上述した全てのあればすばらしいフィーチャに対する答えを提供するジェネリックとPerson DAOのScalaによる実装は、以下のようになる:

object GenericJpaDaoSupport {
 
   implicit def jpaCallbackWrapper[T](func:(EntityManager) => T) = {
    new JpaCallback {
      def doInJpa(session:EntityManager ) = func(session).asInstanceOf[Object]}
    } 
}

import Scala.collection.jcl.Conversions._
class GenericJpaDaoSupport[T](val entityClass:Class[T]) extends JpaDaoSupport with GenericDao[T] {

      def findAll():List[T] = { 
            getJpaTemplate().find("from " + entityClass.getName).toList.asInstanceOf[List[T]]
      }

      def save(entity:T) :T = {
        getJpaTemplate().persist(entity)
        entity
      }

      def remove(entity:T) = {
        getJpaTemplate().remove(entity);        
      }

      def findById(id:Serializable):T = {
        getJpaTemplate().find(entityClass, id).asInstanceOf[T];
      }
}

class JpaPersonDao extends GenericJpaDaoSupport(classOf[Person]) with PersonDao {
     
        def findByName(name:String) = { getJpaTemplate().executeFind( (em:EntityManager) => {
            val query = em.createQuery("SELECT p FROM Person p WHERE p.name like :name");
            query.setParameter("name", "%" + name + "%");
            query.getResultList();
      }).asInstanceOf[List[Person]].toList
      }
}

使用法:

class PersonDaoTestCase extends AbstractTransactionalDataSourceSpringContextTests {
    @BeanProperty var personDao:PersonDao = null
    
    override def getConfigLocations() = Array("ctx-jpa.xml", "ctx-datasource.xml")
 
    def testSavePerson {
        expect(0)(personDao.findAll().size)
        personDao.save(new Person("Rod Johnson"))
        val persons = personDao.findAll()
        expect(1)( persons size)
        assert(persons.exists(_.name ==”Rod Johnson”))
    }
}

以下に、所望のフィーチャがいかに実装されたかを説明する:

collectionを考える

Scala 2.7.xは、便利な Java collectionから Scala collectionに変換するクラスを提供しており、暗黙的な変換を基にしている。上の例では、Java list から Scala list に変えるのは:

  1. Scala.collection.jcl.Conversionsクラスの全メソッドをimportする:
    import Scala.collection.jcl.Conversions._ 
    このクラスは、暗黙の変換メソッドを提供し、 Java collectionを対応するScala collectionの 「ラッパー」に変換する。 java.util.Listに関しては、 Scalaは、 Scala.collection.jcl.BufferWrapperを生成する。
  2. BufferWrapperのtoList()メソッドを呼ぶと、 Scala.Listの collectionのインスタンスが戻ってくる。

上記例のこの使用シナリオは、以下のコードで見ることができる:

def findAll() : List[T]  = { 
    getJpaTemplate().find("from " + entityClass.getName).toList.asInstanceOf[List[T]]
}

いつも collection変換のために ‘toList’ をマニュアルで呼ばなければならないのは、ちょっと面倒だろう。幸いにも、この記事を書いている時には、最終版ではないが Scala 2.8では、 scala.collection.JavaConversionsクラスにより、この不完全さは、解決され、透過的にJava から Scala へ変換されるだろう。

コールバックを考える

あらゆるSpring callback のScala 関数への変換は、GenericJpaDaoSupport オブジェクトに定義されている暗黙の変換を使って簡単に実現できる:

implicit def jpaCallbackWrapper[T](func:(EntityManager) => T) = {
    new JpaCallback {
def doInJpa(session:EntityManager ) = func(session).asInstanceOf[Object]}
}

この変換で、匿名インナー JPACallbackクラスを呼ばずに、関数を持ったJpaTemplate の executeメソッドを呼ぶことができる。こうすると本当に興味のあるオブジェクトを直接操作できる:

jpaTemplate.execute((em:EntityManager) => em.createQuery(…)// etc. );

こうすると、決まりきったコードを書かなければならない元がなくなることになる。

getterと setterを考える

デフォルトでは、 Scalaコンパイラは、 JavaBeansコンベンションに従うgetterと setterを生成しない。しかしScalaは、 JavaBeansのようなgetterと setterを生成するようにコンパイラに伝えるアノテーションを提供している。このアノテーションを宣言しているインスタンス変数に対しては、getterと setterを生成する:

import reflect._
@BeanProperty var personDao:PersonDao = _

@BeanPropertyアノテーションは、Scalaコンパイラに依存性注入のためにSpringが必要なsetPersonDao(…) と getPersonDao()を生成するように告げる。この単純な構成で、getter と/あるいは setterが必要な各インスタンス変数用の余分な3~6行のコードが節約される。

Step 2:オンデマンドな依存性注入を持つ Rich Domain Object(リッチなドメイン オブジェクト)

ここまでやったのは、「状態のみ」のエンティティの永続化ができるDAOパターンの経費節約型の実装である。エンティティ自身は、かしこいオブジェクトではない。その責任は、状態を保持するだけである。 Domain-Driven Design (DDD) の支持者にとっては、単純なエンティティは、複雑なドメインで直面する課題に立ち向かうには不十分である。リッチ ドメインオブジェクトと考えられるエンティティは、状態を保持するだけでなく、ビジネスサービスを呼ぶことができるべきである。これを実現するには、たとえコード上のどこでインスタンス化されても透過的にドメインオブジェクトにサービスを注入できる仕組みがあるべきである。

Springと結びついたScalaによって、サービスを実行時にインスタンスかされたあらゆる種類のオブジェクトに透過的にサービスを注入するのは非常に簡単である。更にこれから説明するように、その仕組は、DDDの技術的な基礎を構築し、エンティティが洗練された方法でリッチなドメイン オブジェクトに昇格するようにできる。

要件

オンデマンドな依存性注入を例示するために、サンプル アプリケーションに新しい要件を加える。 Personエンティティのlinkメソッドが呼ばれた時に、リンク先にリンクするだけでなく、NotificationService を呼んで、リンク元とリンク先に通知すべきである。この追加要件は、以下のコードに示される:

class Person {
   @BeanProperty var notificationService:NotificationService = _
   def link(relation:Person) = {
     relations.add(relation)
     notificationService.nofity(PersonLinkageNotification(this, relation))
   }
   //読みやすさのために他のコードは省く
 }

当然、 NotificationServiceは、マニュアルでの設定無しで、Person エンティティがインスタンス化された直後あるいは、データベースから呼び出された直後に入手可能であるべきである。

Springのautowire機能へのハンドル

このことが実現するために、我々は、 Springの autowire機能を使う、これは、 RichDomainObjectFactoryと呼ばれる自己設計されたJavaのシングルトン クラスの中でアクセスできる:

public class RichDomainObjectFactory implements BeanFactoryAware {

    private AutowireCapableBeanFactory factory = null;
        
    private static RichDomainObjectFactory singleton = new RichDomainObjectFactory();
    
    public static RichDomainObjectFactory autoWireFactory() {
        return singleton;
    }
    
    public void autowire(Object instance) {
        factory.autowireBeanProperties(instance) 
    }

    public void setBeanFactory(BeanFactory factory) throws BeansException {
        this.factory = (AutowireCapableBeanFactory) factory;
    }
    
}

この RichDomainObjectFactoryを Spring beanとして宣言することにより、 Spring Containerは、コンテナが初期化されたときに AutowireCapableBeanFactoryがセットされているのを確認する:

<bean class="org.jsi.di.spring.RichDomainObjectFactory" factory-method="autoWireFactory"/>

Spring Containerに RichDomainObjectFactoryのインスタンスを作成させる代わりに、 beanの定義の中で使われる factory-methodアトリビュートが使われて、SpringにautoWireFactory()メソッドによって返された参照、これはシングルトン自身である、を使うように強制する。こうすることによって、RichDomainObjectFactory のシングルトン インスタンスは、AutowireCapableBeanFactory を注入される。シングルトンは、同じクラスローダのスコープで、どこからでもアクセスできるので、そのスコープのあらゆるクラスは、RichDomainObjectFactoryを使用でき、控えめな緩く結合した方法で、Springのautowireフィーチャにアクセスできるようにする。もちろん、Scalaコードは、 RichDomainObjectFactoryにアクセスでき、そのautowire 機能を使うことができる:

  • エンティティがデータベースからロードされるORM層において;
  • 新しいエンティティが「マニュアルで」作成されるカスタムコードにおいて、すなわち、アプリケーション コードでインスタンス化される。

ORM層におけるドメイン オブジェクトの autowiring

このアーティクルのコード例では、 JPA/Hibernateを使っているので、これらのフレームワークが提供する機能は、エンティティがロードされる時に、 RichDomainObjectFactory中でフックするのに使われる必要がある。 JPA/Hibernateは、interceptor (横取り)APIを提供しているので、特に、このAPIによって、エンティティのローディングを横取りして、カスタマイズできる。ロードされているエンティティを新たにautowireするには、以下のようなinterceptor の実装を提供する必要がある:

class DependencyInjectionInterceptor extends EmptyInterceptor {
    override def onLoad(instance:Object, id:Serializable, propertieValues:Array[Object],propertyNames:Array[String], propertyTypes:Array[Type]) = {
      RichDomainObjectFactory.autoWireFactory.autowire(instance)
      false
   }
}

この interceptorが実行する必要のある唯一のタスクは、 RichDomainObjectFactoryの autowireメソッドにロードされているエンティティを渡すことである。サンプル アプリケーションを考えると、 onLoadメソッドの実装によって、Personエンティティがデータベースから読み出される度に、NotificationService が Personエンティティに、注入されるのが保証される。

更に、この interceptorは、hibernate.ejb.interceptor プロパティによって、JPAの永続化コンテキストに登録される必要がある。:

<persistence-unit name="ScalaSpringIntegration" transaction-type="RESOURCE_LOCAL">
     <provider>org.hibernate.ejb.HibernatePersistence</provider>
     <property name="hibernate.ejb.interceptor"      value="org.jsi.domain.jpa.DependencyInjectionInterceptor" /> 
     </properties>
     <!-- more properties here-->
</persistence-unit>

この DependencyInjectionInterceptorは、強力な仕組みで、データベースからロードされる度に、 Springに設定されたサービスといっしょにリッチなエンティティを用意できる。しかし、JPAのようなフレームワークではなく、アプリケーションのコードで、インスタンス化されるエンティティについては、どうなのか?

Autowiring「マニュアルで」インスタン化されたドメインオブジェクト

アプリケーション コードでインスタンス化されたエンティティをautowireする最も簡単で、しかしかなりカッコ悪い方法は、 RichDomainObjectFactoryによって明示的に、エンティティを autowireすることであろう。この方法は、エンティティが作成されたコードの全域にRichDomainObjectFactory クラスをしっかり繋いでしまうので、勧められるやり方ではない。幸いにして、Scalaは、autowireのようなコンストラクタの論理がうまく合うような場所で、 factoryの責務を引き継ぐように指名される「companionオブジェクト」という構成要素を提供する。

サンプル アプリケーションでは、Person companionオブジェクトは、以下のように実装され、autowire機能は「自動かつマジック的に」提供される:

import org.jsi.di.spring.RichDomainObjectFactory._
object Person {
    def apply(name:String) = {
       autoWireFactory.autowire(new Person(name))
    }
}

importステートメントは、 RichDomainObjectFactoryの全静的メソッドすなわち、 RichDomainObjectFactoryのシングルトン インスタンスへのハンドルを提供するautoWireFactory()メソッドをインポートする。

Scalaオブジェクトに対する別の重宝な構成要素がapply() メソッドである。ルールでは、apply() メソッドを持つ全てのオブジェクトで、.apply()を省略してapply() メソッドを呼ぶことができる。したがって、Scalaは自動的に、Person()への全呼び出しをPerson.apply()に転送するので、apply() メソッドが autowireコードにとって理想的な場所となる。

この構成要素があるので、キーワードの ‘new’無しで、単にPerson()を呼ぶだけで、「リッチな」DDDジョブを実行できるように、必要な全サービスを注入した新しいエンティティが返されてくる。

この節に書いた追加項目によって、我々は今や、永続化可能なリッチなドメイン オブジェクトを使うことができ、必要なときはいつでもサービスを使うことができる:

val p1 = Person(“Martin Odersky”)
val p2 = personDao.findUniqueByName(“Rod Johnsen”)
p1.link(p2) 
personDao.save(p1)

もっと高度な使用方法に移る前に、なぜ RichDomainObjectFactoryは、Scalaではなく、Javaで実装されているのかを説明する必要がある。その理由は、Scalaの staticの扱い方に関係している。 Scalaには、意図的に、 staticキーワードが無い。staticは、オブジェクト指向/関数の混合パラダイムと衝突するからである。Scala言語が提供する唯一の staticなフィーチャは、(companion) オブジェクトであり、Javaにおけるそれに対応するものは、シングルトンである。 Scalaにstaticメソッドがないので、上述したような factory-methodプロパティによって、RichDomainObjectFactoryのような factoryオブジェクトへのハンドルを取得することは、Springにはできないのである。結果として、 SpringのAutowireCapableBeanFactory を直接、 Person companionオブジェクトに注入することはできない。よって、 staticの溝を完全に埋めるために、Scalaのではなく、 Springのautowire機能にアクセスできるようにJavaによる実装が使われるのである。

Step 3: Scala traitsを持った本格的な ドメイン オブジェクト

ここまでのところは、順調である。Scalaには、OO純粋主義者に、提供するものがもっとある。DAOを使って、エンティティを永続化するのは、ちょっと純粋のOO原則に反する。広く使われている伝統的な DAO/Repositoryパターンでは、DAOは、永続化操作を実行する責務を持っている。これは、純粋に振る舞い的であり、エンティティは、状態を保持することになっている。それどころ、純粋OOオブジェクトは、振る舞いと状態を一体化できるべきである。

前節で示したように、サービスを持つエンティティを使って、エンティティに永続化の部分以外の振る舞い上の責務を持たせる。では、なぜエンティティに全ての振る舞い上の責務と状態の責務を与えないのか?OO純粋主義者に支持されるように、この中には、永続化の操作を実行する責務を含むのである。実際のところ、これは好みの問題である。しかしJavaで、簡潔に、エンティティが自身を永続化させるのは、かなり難しい。そのような設計は、継承にひどく依存しなければならない。その場合、永続化メソッドがスーパークラスに実装されるだろう。そのようなアプローチは、かなり不細工で、柔軟性に欠けている。Javaは、単に、そのようなロジックをうまい設計方法で実装するための概念的な基盤を欠いているのである。Scalaは、そうではない、Scalaには、 traits があるから。

簡単に言えば、 traitsは、実装を持つことができるinterfaceである。C++の多重継承に相当するが、ダイヤモンド シンドロームとして知られる扱いにくい 副作用がない。trait にDAOコードをカプセル化することで、このDAO trait よって提供される全ての永続化メソッドは、自動的に全ての実装クラスで使用可能である。このアプローチは、 DRY (Don’t Repeat Yourself 繰り返しを避けよ ) 原則を完全に守っている。永続化のロジックは、一度だけ実装され、必要のたびにドメインクラスにミックスインされるからである。

サンプル アプリケーションのための DAO trait は、以下のようになる:

trait JpaPersistable[T] extends JpaDaoSupport  {
   def getEntity:T;

   def findAll():List[T] = { 
        getJpaTemplate().find("from " + getEntityClass.getName).toList.asInstanceOf[List[T]]   
   }

   def save():T = {
       getJpaTemplate().persist(getEntity)
       getEntity
   }

   def remove() = {
       getJpaTemplate().remove(getEntity);        
   }
      
   def findById(id:Serializable):T = {
        getJpaTemplate().find(getEntityClass, id).asInstanceOf[T];
   }
   //…読みやすくするために他のコードは省略   
}

伝統的なDAOのように、 このtraitはSpringのJpaDaoSupportをextendする。エンティティを引数としてとる save, update や deleteメソッドを提供する代わりに、このtraitは、抽象メソッド getEntityを定義する。このメソッドは、永続化機能が欲しいドメイン オブジェクトによって実装される。 JpaPersistable traitは、下のコード片で示すように、特定のエンティティをsave, update あるいは deleteするために内部的な実装において getEntityメソッドを使用する。

trait JpaPersistable[T] extends JpaDaoSupport  {
def getEntity:T
      
def remove() = {
   getJpaTemplate().remove(getEntity);        
}
//…読みやすくするために他のコードは省略   
)

この trait だけを実装するドメイン オブジェクトは、 getEntityの実装を提供しなければならない。それは単に自身への参照を返すだけである:

class Person extends JpaPersistable[Person] with java.io.Serializable {

  def getEntity = this
  //…読みやすくするために他のコードは省略   
}

これだけである。よって、永続化可能な振る舞いが欲しい全てのドメイン オブジェクトは、 JpaPersistable traitを実装する必要があるだけ、それだけである。その結果、OOプログラミングの最も純粋な意味で、状態と振る舞いが一体化した本格的なドメイン オブジェクトができあがる:

Person(“Martin Odersky”).save 

あなたが純粋のOO原則の支持者であろうがなかろうが、この例は、Scala特にtraitの概念によって、純粋のOO設計を実装するのが著しく簡単であることを示している。

結論

このアーティクルの例は、 Scala と Springがお互いをちゃんと褒めあっていることを示している。関数とtraitのようなScalaの簡潔さと強力なパラダイムが、 Springの依存性注入、AOPそして最先端のJavaAPIの統合とが一体化して、Javaと比べてずっと表現力を持ち、少ないコード量で、多様かつ面白い可能性を提供している。

Spring とJavaのバックグラウンドがあるとすれば、たくさんの新しいAPIを持った新しい言語を学ぶのではなく、新しい言語だけを学べばいいので、学習曲線は、中程度である。

Scala と Spring がもたらす優れた可能性により、この組み合わせが、エンタプライズにおいてScalaを採用するための面白い候補になる。結局のところ、著しく強力なプログラミングパラダイムに乗り換えるのは、低コストだと言える。

著者について

Urs Peter氏は、Xebia のSenior Consultant で、エンタプライズJavaとAgileソフトウェア開発を専門にしている。IT業界で9年の経験がある。ITのキャリアで、たくさん役割を演じてきており、開発者、ソフトウェア アーキテクト、チームリーダそして Scrum Masterを経験している。現在は、 Dutch Railwaysにおける、次世代の情報システム(部分的にScalaで実装されている)開発で、 Scrum Masterをやっている。彼は、 XebiaのScalaエバンジェリストとオランダScalaユーザグループの活発なメンバである。

ソースコード

完全なソースコードがgitを使ってhttp://github.com/upeter/Scala-Spring-Integration からダウンロードできる:git clone git://github.com/upeter/Scala-Spring-Integration.git そしてmavenでビルドする。

この記事に星をつける

おすすめ度
スタイル

BT