scalaxbとSkinny Frameworkで

SOAPに立ち向かう

第十四回 #渋谷java

おまえ、誰よ

お仕事

  • 所属: とある人材紹介会社のマーケティング部門
  • 役割: データ分析チームのデータエンジニア
    • 社内BIツールの開発、設計、運用
      • Skinny-ORM(ScalikeJDBC), Scalaz, Hazelcast, scalaxb
      • Laravel 5, FuelPHP, Angular + TypeScript
      • Electron, Mithril.js
      • etc...

課題

外部のSOAPサービスにアクセスして前日のデータを取得する。

SOAP

元はSimple Object Access Protocolの頭字語とされていたが、現在は「何かの頭字語ではない」とされている

SOAPといえばXML

ScalaとXML

  • Scalaは、XMLリテラルを持っている。

  • 2.11から標準ライブラリから分離。

  • 依存関係を追加するには、以下のようにする。

    libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.0.5"
    
  • Scala逆引きレシピにも「第7章 XML」の項目がある。

ScalaとXML

  • サポートされているとはいえ、XMLは人間が書くにはツライ。
  • 誰もが必要としているレガシー技術対応ライブラリは、誰かが作っているはず。

scalaxb

scalaxbはScalaのためのXMLデータバインディングツールで,W3C XML Schema (xsd) や Web Services Description Language (wsdl) から case class を生成します。

scalaxbとSOAP

準備

  1. sbt-scalaxbを参考に、sbtのpluginとbuild.sbtの設定を行う。
  2. soap を使うを参考に、src/main/wsdlに外部サービスのwsdlファイルを置く。
  3. compileタスクに関連付けてあるので、タスクを実行する。
  4. 設定した出力先にscalaファイルと併せて、compile先にclassファイルが出力されているのを確認する。

scalaxbとSOAP

リファレンスはDispatchをHTTPクライアントとして利用している。


In [ ]:
/* @see http://scalaxb.org/ja/wsdl-support
 * scalaxb/httpclients_dispatch_async.scala
 * Copyright (c) 2010 e.e d3si9n
 * Released under the MIT license
 * https://github.com/eed3si9n/scalaxb/blob/release/1.3.0/LICENSE
 */
package scalaxb
 
import concurrent.Future
 
trait DispatchHttpClientsAsync extends HttpClientsAsync {
  lazy val httpClient = new DispatchHttpClient {}
 
  trait DispatchHttpClient extends HttpClient {
    import dispatch._, Defaults._
 
    val http = new Http()
    def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {
      val req = url(address.toString).setBodyEncoding("UTF-8") <:< headers << in
      http(req > as.String)
    }
  }
}

Dispatchの問題点

App never shutdowns when using Dispatch #99

-> sys.exitでないと終了しない。

setUseProxyProperties does nothing #122

-> プロキシサーバの設定をAsyncHttpClientに伝えられない。

Dispatchの問題点

解決してそうなPRはmergeされる気配は無さそう。

scalaxbとCakeパターン

soap を使う

何らかの理由で独自の http 処理がしたい?独自の HttpClientsAsync モジュールを書けばいい。

つまり、DispatchHttpClientsAsyncの代わりとなるものを作ればいい。

プランB

Skinny FrameworkのHTTPクライアントモジュールを採用。

理由

  • Skinny FrameworkからWebに関するモジュールを外して、バッチ処理の基盤として利用中。
  • 素直なScalaのコードで見やすく、分かりやすい。
  • テストも書かれている。

Skinny HTTP Client

Skinny Framework has a quite simple and handy HTTP client library. Of course, you can use it with non-Skinny apps.

SkinnyHttpClientsAsyncを作ってみた

ポイント

  • HTTPヘッダーにSOAP用のヘッダーを追加する。
  • Content-Typeにtext/xml; charset=utf-8を指定する。

In [ ]:
trait SkinnyHttpClientsAsync extends scalaxb.HttpClientsAsync {
  lazy val httpClient = new SkinnyHttpClient {}

  trait SkinnyHttpClient extends HttpClient {
    // 外部からExecutionContextを渡すなら必要に応じて変える必要あり
    import scala.concurrent.ExecutionContext.Implicits.global
    import skinny.http._

    def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {
      val req = Request(address.toString)
      headers.foreach { case (k, v) => req.header(k, v) }
      val contentType = "text/xml; charset=utf-8"
      req.body(in.getBytes(), contentType)
      HTTP.asyncPost(req).map(_.asString)
    }
  }
}

まとめ

  • モジュールの組み合わせで実装を切り替えられるようにしておくと、最小限の変更でその他のコードやテストを壊すこと無く対応ができる。
  • Skinny Frameworkはモジュールの組み合わせたフルスタックフレームワークなので、モジュール単体でも利用可能。
  • OSSのissueやPRは解決済みも含めて見ておく。