{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# scalaxbとSkinny Frameworkで\n", "# SOAPに立ち向かう\n", "\n", "\n", "[第十四回 #渋谷java](http://shibuya-java.connpass.com/event/25382/)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## おまえ、誰よ\n", "\n", "\n", "* twitter: [@grimrose](https://twitter.com/grimrose) /とーます \n", "* Community: [Yokohama.groovy](http://connpass.com/series/253/)\n", "* 好きな言語: [Groovy](http://www.groovy-lang.org)\n", "* 好きなIDE: [IntelliJ IDEA](https://www.jetbrains.com/idea/)![IntelliJ IDEA](img/icon_IntelliJIDEA.png)\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## お仕事\n", "\n", "* 所属: とある人材紹介会社のマーケティング部門\n", "* 役割: データ分析チームのデータエンジニア\n", " * 社内BIツールの開発、設計、運用\n", " * Skinny-ORM(ScalikeJDBC), Scalaz, Hazelcast, scalaxb\n", " * Laravel 5, FuelPHP, Angular + TypeScript\n", " * Electron, Mithril.js\n", " * etc...\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## 課題\n", "\n", "外部のSOAPサービスにアクセスして前日のデータを取得する。" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## [SOAP](https://ja.wikipedia.org/wiki/SOAP_(%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB)\n", "\n", "> 元はSimple Object Access Protocolの頭字語とされていたが、現在は「何かの頭字語ではない」とされている" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## SOAPといえばXML" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## ScalaとXML\n", "\n", "* Scalaは、XMLリテラルを持っている。\n", "\n", "* 2.11から標準ライブラリから分離。\n", "\n", "* 依存関係を追加するには、以下のようにする。\n", "\n", " ```scala\n", " libraryDependencies += \"org.scala-lang.modules\" %% \"scala-xml\" % \"1.0.5\"\n", " ```\n", "\n", "* [Scala逆引きレシピ](http://www.shoeisha.co.jp/book/detail/9784798125411)にも「第7章 XML」の項目がある。\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## ScalaとXML\n", "\n", "* サポートされているとはいえ、XMLは人間が書くにはツライ。\n", "* 誰もが必要としているレガシー技術対応ライブラリは、誰かが作っているはず。\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## [scalaxb](http://scalaxb.org/ja)\n", "\n", "> scalaxbはScalaのためのXMLデータバインディングツールで,W3C XML Schema (xsd) や Web Services Description Language (wsdl) から case class を生成します。" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## scalaxbとSOAP\n", "\n", "準備\n", "\n", "1. [sbt-scalaxb](http://scalaxb.org/ja/sbt-scalaxb)を参考に、sbtのpluginとbuild.sbtの設定を行う。\n", "2. [soap を使う](http://scalaxb.org/ja/wsdl-support)を参考に、src/main/wsdlに外部サービスのwsdlファイルを置く。\n", "3. compileタスクに関連付けてあるので、タスクを実行する。\n", "4. 設定した出力先にscalaファイルと併せて、compile先にclassファイルが出力されているのを確認する。\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## scalaxbとSOAP\n", "\n", "リファレンスは[Dispatch](http://dispatch.databinder.net/Dispatch.html)をHTTPクライアントとして利用している。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "/* @see http://scalaxb.org/ja/wsdl-support\n", " * scalaxb/httpclients_dispatch_async.scala\n", " * Copyright (c) 2010 e.e d3si9n\n", " * Released under the MIT license\n", " * https://github.com/eed3si9n/scalaxb/blob/release/1.3.0/LICENSE\n", " */\n", "package scalaxb\n", " \n", "import concurrent.Future\n", " \n", "trait DispatchHttpClientsAsync extends HttpClientsAsync {\n", " lazy val httpClient = new DispatchHttpClient {}\n", " \n", " trait DispatchHttpClient extends HttpClient {\n", " import dispatch._, Defaults._\n", " \n", " val http = new Http()\n", " def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {\n", " val req = url(address.toString).setBodyEncoding(\"UTF-8\") <:< headers << in\n", " http(req > as.String)\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Dispatchの問題点\n", "\n", "[App never shutdowns when using Dispatch #99](https://github.com/dispatch/reboot/issues/99)\n", "\n", "-> `sys.exit`でないと終了しない。\n", "\n", "[setUseProxyProperties does nothing #122](https://github.com/dispatch/reboot/issues/122)\n", "\n", "-> プロキシサーバの設定を`AsyncHttpClient`に伝えられない。\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Dispatchの問題点\n", "\n", "解決してそうなPRはmergeされる気配は無さそう。" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## scalaxbとCakeパターン\n", "\n", "[soap を使う](http://scalaxb.org/ja/wsdl-support)\n", "> 何らかの理由で独自の http 処理がしたい?独自の HttpClientsAsync モジュールを書けばいい。\n", "\n", "つまり、DispatchHttpClientsAsyncの代わりとなるものを作ればいい。" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## プランB\n", "\n", "[Skinny Framework](http://skinny-framework.org/)のHTTPクライアントモジュールを採用。\n", "\n", "理由\n", "\n", "* Skinny FrameworkからWebに関するモジュールを外して、バッチ処理の基盤として利用中。\n", "* 素直なScalaのコードで見やすく、分かりやすい。\n", "* テストも書かれている。\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## [Skinny HTTP Client](http://skinny-framework.org/documentation/http-client.html)\n", "\n", "> Skinny Framework has a quite simple and handy HTTP client library. Of course, you can use it with non-Skinny apps.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## SkinnyHttpClientsAsyncを作ってみた\n", "\n", "ポイント\n", "\n", "* HTTPヘッダーにSOAP用のヘッダーを追加する。\n", "* Content-Typeに**text/xml; charset=utf-8**を指定する。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "trait SkinnyHttpClientsAsync extends scalaxb.HttpClientsAsync {\n", " lazy val httpClient = new SkinnyHttpClient {}\n", "\n", " trait SkinnyHttpClient extends HttpClient {\n", " // 外部からExecutionContextを渡すなら必要に応じて変える必要あり\n", " import scala.concurrent.ExecutionContext.Implicits.global\n", " import skinny.http._\n", "\n", " def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {\n", " val req = Request(address.toString)\n", " headers.foreach { case (k, v) => req.header(k, v) }\n", " val contentType = \"text/xml; charset=utf-8\"\n", " req.body(in.getBytes(), contentType)\n", " HTTP.asyncPost(req).map(_.asString)\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## まとめ\n", "\n", "* モジュールの組み合わせで実装を切り替えられるようにしておくと、最小限の変更でその他のコードやテストを壊すこと無く対応ができる。\n", "* Skinny Frameworkはモジュールの組み合わせたフルスタックフレームワークなので、モジュール単体でも利用可能。\n", "* OSSのissueやPRは解決済みも含めて見ておく。\n" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Scala 2.11", "language": "scala211", "name": "scala211" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": "scala", "mimetype": "text/x-scala", "name": "scala211", "pygments_lexer": "scala", "version": "2.11.6" } }, "nbformat": 4, "nbformat_minor": 0 }