正解が見えない課題を圧倒的に解決する超仮説思考 #書籍

正解が見えない課題を圧倒的に解決する 超仮説思考

正解が見えない課題を圧倒的に解決する 超仮説思考

印象に残ったところを自分なりにまとめるとこんな感じでした。

  • 5F, 3C, SWOT等のフレームワークは有用ではあるものの、枠にあてはめてしまうが故に、多様的・立体的である現実の事象に対して見落としや乖離があり、的外れな解を導き得る。
  • すぐに思いつく最初の10個のアイディアは捨て、ひねり出した11個目以降のアイディアを採用する。
  • 自分の見えないところに答えがある。視点を移し、想像力を磨いていく。
  • 前提条件はいつか変わる、変えられる。
  • 実践(仮説検証)を繰り返していく。うまくいかなかった時は、仮説が間違っていたことよりも、仮説はあっていたがどこかにボトルネックがあって見落としているという可能性を疑う。

Spring BootでJettyのログを少しカスタマイズする

Spring Bootでリクエストログを出力するための方法については、以下の記事で紹介されています。

qiita.com

以下の内容はSpring Boot 1.4.3で確認してます。また、Jetty以外のアプリケーションサーバの場合は当然ながら全く違った設定方法になります。

X-Forwardedヘッダを解釈させる

ELB(AWS)やHerokuなど、前段でSSLが終端されてから(SSL Termination)後続のSpring Bootアプリがリクエストを処理するようなケースでは、本来の送信元IPをアプリが知るためにX-Forwarded-For他、X-Forwarded系のヘッダを解釈させるようにする必要があります。

JettyではForwardedRequestCustomizerというクラスが提供されているので、それを追加してあげればよいです。

    // 途中略

    for (Connector connector : server.getConnectors()) {
        if (connector instanceof ServerConnector) {
            HttpConnectionFactory connectionFactory = connector.getConnectionFactory(HttpConnectionFactory.class);
            connectionFactory.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
        }
    }
}

出力されるログに独自項目を追加する

元々用意されている NCSARequestLog を継承し、 logExtended メソッドを上書きした拡張ログクラスを作ることで、元の出力内容に加えて独自の項目を出力することができます。
例えばAuthorizationヘッダの内容を最後の項目に出力させたい場合は、以下のような感じです。

public class NCSARequestLogExt extends NCSARequestLog {

    @Override
    protected void logExtended(StringBuilder b, Request request, Response response) throws IOException {
        super.logExtended(b, request, response);

        String authToken = request.getHeader("Authorization");

        if (authToken == null) {
            b.append(" \"-\" ");
        } else {
            b.append(" \"");
            b.append(authToken);
            b.append("\" ");
        }
    }
}

Cloud First Architecture 設計ガイド #書籍

気が付いたら2017年も1週間が過ぎてしまいました。まさか7月のイベントが2016年の最後のエントリになるとは思いませんでした。

年々顕著にエントリが減ってきているので、今年は週1くらいのペースで書く習慣をつけていきないなと思います。
ネタが少ない時なんかは、読んだ本の簡単な感想で、エントリ数だけは積んでいきたいと思います(読書ログでいいじゃん、というツッコミはあるんですが…)

すぐに読めちゃうボリュームの割には広く浅く書かれていて、復習になりました。
AWSにおける具体的なアーキテクチャ例、みたいなものは書かれておらず、アーキテクチャ設計の考え方を知るための本、という印象です。 Salesforceの名も所々出てきます。 第5章「アーキテクチャー設計ガイド」はまた読み返したいと思います。

Doma勉強会 in 東京に行ってきた #doma_tokyo

Doma勉強会に行ってきました。

eventdots.jp

最近はJPAを使っていてDomaを使えていないのですが、以前そこそこ長い間(3年以上)Doma(1, 2両方)を使ったシステム開発をしてたので、そのあたりからの進化とか色々知ることができました。

スライドは公開されていますので、個人的に知らなかっこと、気付きなどについて超簡単なメモ。

知らなかったこと

  • SQL中のIN句で利用されるバインド変数のリストが空でも、 IN (NULL) となり、SQLエラーにならない
    • これは地味ながら嬉しいアップデート。
  • 複数カラムをグループ化するための仕組みとしてエンベッダブルクラスが利用できる。
    • 色々使い道がありそうな気がしました。結合した結果を受け取るエンティティクラスを簡潔に定義するのに使えたりしないかなー、とか。Domaだと結合したクエリの結果ごとにエンティティクラスが必要なので、プロジェクトで使ってた時はエンティティクラスのフィールド定義が重複しがちだった記憶があります(2テーブルなら継承でもよいのですが)
  • IntelliJ IDEAでも使える → JetBrains Plugin Repository :: Doma Support
    • だいぶ前の情報ですが、Eclipseでしか使えないという古い情報のままだったので…
  • Domaと直接関係のないJavaの話ですが、 Stream#map(...).collect(toList())Stream#collect(mapping(..., toList())) とも書ける。

実践的な活用方法として得たこと

  • ドメインクラスをうまく活用すると、かなりのことを型検査でき、余計なミスを早期につぶせる
  • Streamを返す検索は、クローズを呼び出し側の責任で行わないとならないので、避けた方が無難そう
  • collect検索を利用し、1件検索、複数検索で同じSQLファイルを使うとよい
  • エンティティのイミュータブル性を確保する
  • 複数データソースを使う場合のレイアウト。やっぱりパッケージレベルで分けた方がメンテナンスしやすいっぽい

感想

  • 中村さんの発表 Domaの開発で大切にしている10のこと - Qiita に共感できるところが多くて、やっぱりDomaは使いやすく、開発者に優しいツールだなと改めて思いました。
  • 当初作る予定だった機能が2010年に完成していながらも、今に至るまで開発を継続していけるのは純粋にすごいと思いました。個人的にはJava8対応したバージョンがかなり早い時期にリリースされたのが印象的だった記憶があります。
  • doma-spring-bootもあるし、JPAにあれこれハマるよりは、Domaに移行するのもちょっと検討したい。
  • DOMAのロゴがN◯SAに見えたのは私だけじゃなかったようでした。

色々と勉強になりました。ありがとうございました。

Spring Bootでprototypeスコープを使う方法あれこれ&ベンチマーク

今更感はありますが…。

Spring Boot(というよりSpring)でインジェクションされるコンポーネントのデフォルトのスコープはSingletonになります。
そのため、Singletonスコープのコンポーネントに対して、Singletonより短いスコープであるPrototypeコンポーネントをインジェクションしようと思った場合、@Scope("prototype") をつけてコンポーネントを定義してインジェクションさせるだけではうまくいきません。

prototypeスコープを使うには、いくつか方法があります。今回は4つの方法を試してみました。

なお、以下の本エントリでは、動作の説明のため、カウンタコンポーネントを利用します。

方法1: proxyMode = ScopedProxyMode.TARGET_CLASS を指定する

カウンタの定義。proxyMode を指定しているところがポイント。

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Counter1 {

    private int count;

    public int increment() {
        return count++;
    }
}

ロジックそのものは説明するまでもないですが、同じインスタンスに対して increment メソッドを呼ぶと、そのたびに、戻り値が0, 1, 2, …と1ずつ増えていくものです。

利用例(spring-boot-starter-webを使ったWebアプリを想定しています)

@RestController
public class Counter1Controller {

    @Autowired
    Counter1 counter1;

    @RequestMapping("counter1")
    public int counter1() {
        return counter1.increment();
    }
}

8080ポートでアプリを起動して、 http://localhost:8080/counter1 にアクセスすると、何度ブラウザをリロードしても、常に0が表示されます。 このことから、毎回新しいCounter1のインスタンスが参照されていることが分かります。

方法2: ApplicationContextからgetBeanで取得する

カウンタ。

@Component
@Scope(value = "prototype")
public class Counter2 {

    private int count;

    public int increment() {
        return count++;
    }
}

利用例。Counter2を直接@Autowiredでインジェクションせずに、 ApplicationContext#getBean で取得します。

@RestController
public class Counter2Controller {

    @Autowired
    ApplicationContext context;

    @RequestMapping("counter2")
    public int counter2() {
        Counter2 counter = context.getBean(Counter2.class);
        return counter.increment();
    }
}

アプリの動きは方法1と同じです。

方法3: @Lookupでnullを返す

カウンタ(方法2とまったく同じ)

@Component
@Scope(value = "prototype")
public class Counter3 {

    private int count;

    public int increment() {
        return count++;
    }
}

この方法の場合、新しくLookup用のサービスクラスを作ります。

@Component
public class Counter3Lookup {

    @Lookup
    public Counter3 lookup() {
        return null;
    }
}

Lookupメソッド経由で取得します。

@RestController
public class Counter3Controller {

    @Autowired
    Counter3Lookup lookup;

    @RequestMapping("counter3")
    public int counter3() {
        Counter3 counter = lookup.lookup();
        return counter.increment();
    }
}

方法4: JSR 330のjavax.inject.Providerを使う

ちょっと蛇足的ですが、一部Java EEAPIが利用できるので、それを利用します。 カウンタは方法2、方法3と同様。

@Component
@Scope(value = "prototype")
public class Counter4 {

    private int count;

    public int increment() {
        return count++;
    }
}

使う側は、 javax.inject.Provider#get メソッド経由で取得します。

@RestController
public class Counter4Controller {

    @Autowired
    Provider<Counter4> counterProvider;

    @RequestMapping("counter4")
    public int counter4() {
        Counter4 counter = counterProvider.get();
        return counter.increment();
    }
}

性能を比較

今回の記事のメイン。それぞれの方法について、実行時間的にどれくらいの差が現れるのかを簡単に計測してみました。

計測の条件をなるべく揃えるために、以下のように、方法1〜方法4までのカウンタを取得するためのコンポーネントを1枚かませてみます。

@Component
public class CounterProvider {

    @Autowired
    Counter1 counter1;

    @Autowired
    ApplicationContext context;

    @Autowired
    Counter3Lookup lookup;

    @Autowired
    Provider<Counter4> counterProvider;

    public Counter1 getCounter1() {
        return counter1;
    }

    public Counter2 getCounter2() {
        return context.getBean(Counter2.class);
    }

    public Counter3 getCounter3() {
        return lookup.counter3();
    }

    public Counter4 getCounter4() {
        return counterProvider.get();
    }
}

その上で、コンポーネントの取得&incrementメソッドをの呼び出しのセットを1,000,000呼び出すのに、どれくらいの時間がかかるのかを測ってみました。 以下のメソッドは方法1の場合ですが、他の方法についても同様に実施します。

    public int benchmarkCounter1() {
        long start = System.currentTimeMillis();

        int sum = 0;
        for (int i = 0; i < LIMIT; i++) {
            Counter1 c = counterProvider.getCounter1();
            sum += c.increment();
        }

        log.info("sum = {}, total = {} ms", sum, (System.currentTimeMillis() - start)); // sum はどれも 0

        return sum;
    }

結果

方法 実行時間
1: @ScopeproxyMode = ScopedProxyMode.TARGET_CLASS をつける 4,430ms
2: ApplicationContext#getBean 4,105ms
3: @Lookup 4,284ms
4: Provider#get 7,908ms

方法4だけが突出して遅いですね。
何度か試しても、この傾向は同じでした。

それ以外の方法はどれも大差なく proxyMode を使っても、顕著に遅くなる、ということはないようです。

まとめ

今回実行時間を比較してみることにした動機は、 方法1の場合に極端に処理が遅くなることがない、という裏付けをとることだったので、確認できてよかったです。

Spring Bootのアプリを作る上で、極力プロトタイプスコープのコンポーネントが必要ないようにはしているものの、どうしても必要な時は、1を使う、という方針でいこうかなーと思いました。

モダンJavaScript(ES6)で書いたコードがIEで動かなかったので対応ポイントをメモ

ES6で書いていたJavaScriptのコードがIE(※)で動かなかったので、その時に対応した内容のメモです。

IEのバージョンは9以上を想定してます。IE8、あるいはそれ以前となるとさらに面倒さが増すので。

各ブラウザの対応状況を知る

各ブラウザの対応状況を調べるには、以下のサイトが便利でした。

ECMAScript 6 compatibility table

「Show obsolete platforms」にチェックを入れると、サポート切れの古いブラウザについても状況を確認することができます。
なお、IE9以下は、ECMAScriptとして「6」ではなく「5」を選んでおかないと表示されません。ES6はIE10でも絶望的なのに、それより古いバージョンが対応してるわけないですもんね。。

Chromeが良くも悪くも手厚くサポートしているので、普段の動作確認をChromeでのみ行うのはリスクだなと実感。

前提条件

ES6のコードはBabelでトランスパイルして使っていました。 詳しく言うと、Babelはwebpackのloaderを使っており、各バージョンは以下の通りです。

  • babel-core (^6.5.2)
  • babel-loader (^6.2.3)
  • babel-preset-es2015 (^6.5.0)

とはいえ、今回の対応箇所は、webpack関係なく通用するものだと思います。

実際にやったこと

そもそもES6の新しい仕様をフルに使って書いていたわけではないため、実はそこまで手間はかかりませんでした。

Promiseを置き換え

原因)IEではネイティブでPromiseがサポートされていません。

対策)代替ライブラリとしてbluebirdを利用しました。

今回、webpackでビルドしていたため、webpack.ProvidePlugin を使うことで、既存のコードに手を加えることなく Promise をすべて bluebird を使うようにひねることができます。

webpack.config.js に以下を追加してあげればOKです。

  plugins: [
    new webpack.ProvidePlugin({  
      "Promise": "bluebird"  
    })
  ] 

Object.assign を置き換え

原因)Object.assign はオブジェクト同士をマージするメソッドですが、これもIEでは使えません。

対策)代替ライブラリとしてobject-assignを利用しました。

Object.assign を使ったコードをすべて置き換えて対応しました。

Object.assign(foo, bar);objectAssign(foo, bar);

console.log を置き換え

原因)IE9以前では、開発者ツールを起動していない場合、consoleがundefinedになります。

対策)代替ライブラリとしてConsole.log wrapperを使いました。

こちらも前述の Promise のように、単なる log メソッドとして呼び出せるようにしました。

webpack.config.js

  plugins: [
    new webpack.ProvidePlugin({  
      "Promise": "bluebird",
      "log": "consolelog"
    })
  ] 

を追加し、 console.log を使っている箇所を log に置き換えて対応しました。

ちなみに、consoleが undefined だったら自作のオレオレconsoleに差し替える、みたいな方法でももちろん可能なのですが、雑に作ってしまうと、開発者コンソールで見た時のログ出力行が、console.log を呼び出した行ではなく、全てオレオレconsoleの行になってしまう、みたいな残念なことになってしまうので気をつける必要があるかと思います。

forcedotcom/wscにプルリクエストを送ってみた

先日Qiitaにこんな記事を書きました。その続きです。

qiita.com

さて、変換仕様をwscを使ってアップロードするメソッドは2つあります。

  • void BulkConnection#createTransformationSpecFromStream(JobInfo, InputStream)
  • TransformationSpecRequest BulkConnection#createTransformationSpec(JobInfo)

Qiitaでは前者の方だけ紹介しました。
使い分けですが、それぞれのメソッドシグネチャを見ると想像がつく通り、CSVファイル(やその他何かしらのストリーム)がそのまま渡せる場合は前者を、プログラム的に変換仕様を渡したい場合は後者を使う、みたいな感じだと思います*1

後者の場合の利用例は以下のような感じ。

    TransformationSpecRequest specRequest = connection.createTransformationSpec(job);
    specRequest.addSpecRow("Name", "Full Name", "", "");
    specRequest.addSpecRow("Title", "Job Title", "", "");
    specRequest.addSpecRow("LeadSource", "Lead Source", "Import", "");
    specRequest.addSpecRow("Description", "", "Imported from XYZ.csv", "");
    specRequest.addSpecRow("Birthdate", "Date of Birth", "", "dd MMM yy");
    specRequest.completeRequest();

変換仕様は、データ項目の対応付け | Bulk API 開発者ガイド | Salesforce Developersでサンプルとして載っているspec.csvと同等のものです。

しかし、これがどうしてもエラーで動かず、最終的にwscのバグという結論に落ち着きました。
そこでコードを修正し、正常に期待した動きになるところまで確認したので、プルリクを送ってみました。

github.com

正直なところ、業務で使っているわけでもないので、別にマージされなくてもいいやと思ってはいるのですが、プルリクを送るまでの手順だけ、メモとして残しておこうと思います。
以下の流れです。
(追記:無事、マージされました。)

  1. forcedotcom/wsc を clone する
  2. clone してきたソースコードを修正する
  3. ビルド
  4. 動作確認用のコードにおいて、依存関係を修正し、Mavenリポジトリや(Salesforceからダウンロードしてきたものでなく)3. で自前ビルドしたjarを参照するようにする
  5. 期待する動作になることを確認
  6. プルリクを送る

このうち、 3. と 4. のところだけメモ。

3. wscのビルド

READMEの通りです(要Maven)。きちんと既存のテストがパスすることは確認します。

mvn clean package -Dgpg.skip

ビルドが成功すると、 target フォルダの下に force-wsc-35.2.6.jar のようなファイルが作られるので、これを使います。

4. 依存関係の修正

私はビルドにGradleを使っているので、元々、

dependencies {
    compile "com.force.api:force-wsc:35.2.7"
    compile "com.force.api:force-partner-api:35.0.1"
}

のようにしていたところを、

dependencies {
    compile files('/path/to/wsc/target/force-wsc-35.2.6.jar')
    compile ("com.force.api:force-partner-api:35.0.1") {
        transitive = false
    }
    compile 'org.antlr:ST4:4.0.7'
    compile 'org.codehaus.jackson:jackson-core-asl:1.9.13'
    compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.13'
}

のように修正します。force-partner-apiがforce-wscに依存関係を持っているので、推移的な依存関係を無効にしつつ(transitive = true)、ST4やらjacksonやらの必要なものだけ別途追加してます。

修正内容

ちなみに修正内容ですが、10行程度の修正の中で3〜4つのバグを直した(つもり)です。

  • 書き込んでいるCSVの内容が正しくない(先頭の項目の頭に余計なカンマが入ったり、改行されていなかったり)
  • completeRequestで処理の成否を判定する時に、常にリクエスト失敗と判定される
  • CSVしか扱えないのに、 Transport#connect でZIPフラグがtrueで処理されている

*1: 正直、プログラム中で変換仕様を組み立てたい場合でもByteArrayInputStreamあたりを渡せば事足りますが…。