tokuhirom's blog

なぜ Teng は良いものなのか

なぜ Teng は良いものなのか、を YAPC で再考させられたのでここにメモしておく。

Teng は自社開発のウェブアプリケーションを作ってる人たちが作っていて、それがうちのニーズにあってるのでいいっていう話であって、どこでもすごい最高!! と主張したいわけではないです。まあ、個人の感想ですね。

ソースが読みやすい

ソースがよくモジュール化されていて、読みやすい。自身で書いている部分が多いという贔屓目を抜きにしても読みやすいんじゃないかなーと。

僕らのような自社開発のウェブ屋では、なにか無茶な要望を受けた時にささっと対応するということが求められるシーンが多いので、ソースの読みやすさというのはかなり重要なファクターとなっています。

複雑な SQL を発行できないように機能が制限されている

SQL ビルダーを使って JOIN やサブクエリを駆使したウェブアプリケーションを開発してしまうと、運用担当や後世の開発者が、以下のような問題を抱えることになります。

  • mysql のスロークエリログを元にクエリを修正しようとしても、どこで実装されているのかを調べることが困難
  • クエリビルダの文法に運用担当が慣れていないとチューニングが困難

実装箇所がわかりづらいという点については、SQL Comment で生成位置を挿入する方法もあるので、それでも解決できます。 後者の問題は深刻で、大体の場合では社内でSQL に詳しい人は運用を担当しているので、そういう人に診てもらう時にすごく嫌なかおをされたりします。

まあ、そんな感じでこのへんははある程度以上トラフィックがあるサイトでは致命的な問題となってきます。

Teng では、SQL ビルダーライブラリの機能はある程度制限したうえで、複雑なクエリは直接SQLをゴーリごりと書けばいいという感じになっています。

どこでクエリが発行されているかコードを見ればすぐわかる

過去の O/R Mapperでは、コードを見てもどこで DB にアクセスしているのかわからないものもありました。

そのような O/R Mapper を使っていると、まあ常識的に考えてチューニングが困難になります。

Relationship サポートをコアに断固としていれていない

has_ahas_many などのサポートを標準ではいれていない。

これを入れると、開発が楽になるが一方で何も考えずにクエリを発行してしまいがちになる。

まあ、has_a なり has_many にあたるメソッドは各自で生やせばいいんで、いいですね。 一息おいて、自分で考えて実装する分には問題はない。

Teng が作成された本当の理由

DBIx::Skinny っていう Teng の元になった O/R Mapper で上記のうちの大体の機能は実現されていたんですが、DBIx::Skinny ではクラスとしてもインスタンスとしても動くという謎の機構が入っていて、これのせいで非常にコードが読みづらくなっている。この問題を解消するために Teng として作りなおしたというのが大きい気がしている(これは、僕が持ってる印象なので、他のこみったの人は違う印象かもしれない)。

まとめ

Teng は初期開発の楽さよりも安定した運用を重視したハイパフォーマンスウェブアプリケーション開発者養成ギプスである。

Created: 2014-09-01 12:47:46
Updated: 2014-09-01 12:47:46

「Javaとベンチマーク」というタイトルでJJUG ナイト・セミナー 「ビール片手にLT&納涼会」で話してきたよ~

nanobench という java 用の簡単に使えるベンチマークツールについてお話させていただきました。 ご査収ください。

Created: 2014-08-25 15:34:42
Updated: 2014-08-25 15:34:42

Apache HttpClient で Content-Body を String として得る

public String getContentBody(CloseableHttpResponse res) {
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    res.getEntity().writeTo(stream);
    byte[] bytes = stream.toByteArray();

    ContentType contentType = ContentType.getOrDefault(res.getEntity());
    new String(bytes, contentType.getCharset());
}

としてもいいけど、

EntityUtils.toString(res.getEntity());

とすればいい。

ただこの方法でやる場合、Entity 一回読むと二回は読めないのが不便ではある。 使い勝手のいいゆるふわライブラリを作りたい場合は、Entity 読みだした後でどっかに保存しておいたほうがいいのかも。

Created: 2014-08-25 13:23:20
Updated: 2014-08-25 13:23:20

[Security Notice] Amon2, HTTP::Session2 security updates

Last week, I shipped Amon2 and HTTP::Session2 includes security fix.

[Must] Update "secret" if you are using HTTP::Session2::ClientStore

Amon2::Flavor::* generates the 'secret'. If your are using generated value, You MUST update it.

[Recommended] Update each libraries.

I recommend to update Amon2, HTTP::Session, HTTP::Session2 to the latest version.

[Recommended] Switch HTTP::Session2::ClientStore2 from HTTP::Session2::ClientStore

For reducinng security risk.

[Must] Update HTTP::Session2 if you are using ClientStore

It includes security fix.

[Recommended] Update HTTP::Session2 if you are using ServerStore

It includes security fix.

Created: 2014-08-11 14:55:06
Updated: 2014-08-11 14:55:06

jackson で JDK8 の Optional をうまく扱ってほしい

jackson では JDK8 の Optional にまだ対応していません。 Jackson 自体が JDK6 をサポートしているので、コアのデータバインディングに入ることはないのですが、それにしても JDK8 の Optional に対応していないのは不便ですね。まあ JDK8 はまだあれなので、しょうがないですが。。

しかしまあ、JDK8 使ってると Optional を返したいときはままあるので、はやく jackson-databind-jdk8 が出ることを願ってやみません。

取り急ぎ、https://github.com/FasterXML/jackson-databind/issues/494 この issue に書いてある方法で、対応は可能。これをコピペッペして、使えば動きます。

jersey でカスタマイズした ObjectMapper を使う方法についてはこちらを御覧ください。 https://jersey.java.net/documentation/latest/media.html#json.jackson

package me.geso.mobirc4j;

import java.io.IOException;
import java.util.Optional;

import javax.ws.rs.ext.ContextResolver;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
 * Module for JDK 1.8 types.
 * 
 * <p>
 * https://github.com/FasterXML/jackson-databind/issues/494
 * 
 * @author Gili Tzabari
 */
final class Jdk18Module extends SimpleModule
{
    private static final long serialVersionUID = 1L;

    private static class OptionalSerializer extends StdSerializer<Optional<?>>
    {
        /**
         * Create a new OptionalSerializer.
         */
        OptionalSerializer()
        {
            super(Optional.class, true);
        }

        @Override
        public void serialize(Optional<?> value, JsonGenerator generator,
                SerializerProvider provider)
                throws IOException
        {
            if (value.isPresent())
                generator.writeObject(value.get());
            else
                generator.writeNull();
        }
    }

    private static class OptionalDeserializer extends
            StdDeserializer<Optional<?>>
            implements ContextualDeserializer
    {
        private static final long serialVersionUID = 1L;
        private Class<?> targetClass;

        /**
         * Creates a new OptionalDeserializer.
         */
        OptionalDeserializer()
        {
            super(Optional.class);
        }

        @Override
        public JsonDeserializer<?> createContextual(
                DeserializationContext context,
                BeanProperty property) throws JsonMappingException
        {
            if (property != null)
            {
                // See
                // http://jackson-users.ning.com/forum/topics/deserialize-with-generic-type
                JavaType type = property.getType();
                JavaType ofType = type.containedType(0);
                this.targetClass = ofType.getRawClass();
            }
            return this;
        }

        @Override
        public Optional<?> deserialize(JsonParser parser,
                DeserializationContext context)
                throws IOException, JsonProcessingException
        {
            return Optional.of(parser.readValueAs(targetClass));
        }

        @Override
        public Optional<?> getNullValue()
        {
            return Optional.empty();
        }
    }

    /**
     * Creates a new Jdk18Module.
     */
    public Jdk18Module()
    {
        super("Jdk18Module", new Version(1, 0, 0, null,
                "com.fasterxml.jackson.databind", "jdk18"));
        addSerializer(new OptionalSerializer());
        addDeserializer(Optional.class, new OptionalDeserializer());
    }
}

public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
    final ObjectMapper objectMapper;

    public MyObjectMapperProvider() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk18Module());
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return objectMapper;
    }
}

としておいて、以下のようにリソースコンフィグに登録すればよいです。

ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
resourceConfig.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
resourceConfig.register(MyObjectMapperProvider.class);
resourceConfig.packages(ServerController.class.getPackage().getName());
resourceConfig.register(JacksonFeature.class);

ちなみに、こういう方法いれないと、単に OpionalLong#getLong() とかをガッツリ呼んじゃって、例外があがって悲しみにくれることになります。

Created: 2014-08-02 07:44:13
Updated: 2014-08-02 07:44:13

cpan-outdated 的なことを maven でやりたい

mvn versions:display-dependency-updates

でできる。コアなやつなので、pom.xml をいじらなくても使える。便利。

Created: 2014-08-02 06:58:23
Updated: 2014-08-02 06:58:23

Karabiner の private.xml を設定した

Realforce 買ったので private.xml を書いた。

private.xml を書くと、MBP 本体のキーボードと外付けキーボードとの間で別の設定ができるようになるし、設定を複数の環境で共有するのが楽になるから、書いたほうがよさそう。

今回かいたのは以下の様な感じである。

<?xml version="1.0"?>
<root>
    <appdef>
        <appname>TERMINAL</appname>
        <equal>com.googlecode.iterm2</equal>
    </appdef>

  <devicevendordef>
    <vendorname>RealForce_VendorID</vendorname>
    <vendorid>0x0853</vendorid>
  </devicevendordef>
  <deviceproductdef>
    <productname>RealForce_ProductID</productname>
    <productid>0x0111</productid>
  </deviceproductdef>
  <devicevendordef>
    <vendorname>MBP_VendorID</vendorname>
    <vendorid>0x05ac</vendorid>
  </devicevendordef>
  <deviceproductdef>
    <productname>MBP_ProductID</productname>
    <productid>0x0262</productid>
  </deviceproductdef>

  <item>
    <name>RealForce用セッティング</name>
    <appendix>Swap COMMAND and OPTION</appendix>
    <identifier>private.for_RealForce</identifier>
    <block>
      <device_only>
        DeviceVendor::RealForce_VendorID,
        DeviceProduct::RealForce_ProductID
      </device_only>
      <!-- 左コマンドを左OPTIONに -->
      <autogen>__KeyToKey__ KeyCode::COMMAND_L, KeyCode::OPTION_L</autogen>
      <!-- 右コマンドを右ALTに -->
      <autogen>__KeyToKey__ KeyCode::COMMAND_R, KeyCode::OPTION_R</autogen>
      <!-- 左ALTを左コマンドに、空打ちでEISUU -->
      <autogen>__KeyOverlaidModifier__ KeyCode::OPTION_L, KeyCode::COMMAND_L, KeyCode::JIS_EISUU</autogen>
      <!-- 右ALTを右コマンドに、空打ちでKANA -->
      <autogen>__KeyOverlaidModifier__ KeyCode::OPTION_R, KeyCode::COMMAND_R, KeyCode::JIS_KANA</autogen>
    </block>
  </item>
  <item>
    <name>MBP用セッティング</name>
    <appendix>Swap COMMAND and OPTION</appendix>
    <identifier>private.for_mbp</identifier>
    <block>
      <device_only>
        DeviceVendor::MBP_VendorID,
        DeviceProduct::MBP_ProductID
      </device_only>
      <!-- 左Command空打ちで英数 -->
      <autogen>__KeyOverlaidModifier__ KeyCode::COMMAND_L, KeyCode::COMMAND_L, KeyCode::JIS_EISUU</autogen>
      <!-- 右コマンドを空打ちでかな -->
      <autogen>__KeyOverlaidModifier__ KeyCode::COMMAND_R, KeyCode::COMMAND_R, KeyCode::JIS_KANA</autogen>
    </block>
  </item>

    <item>
        <name>Control+J to Kana</name>
        <identifier>option.kana.control_j</identifier>
        <autogen>__KeyToKey__ KeyCode::J, ModifierFlag::CONTROL_L, KeyCode::JIS_KANA</autogen>
    </item>

</root>
Created: 2014-08-01 17:19:42
Updated: 2014-08-01 17:19:42

Split Amon2::Plugin::Web::CSRFDefender from core distribution.

1. Amon2::Plugin::Web::CSRFDefender was removed from Amon2 core distribution.

Amon2::Plugin::Web::CSRFDefender is no longer default CSRF defender module in Amon2. I suggest to use HTTP::Session2.

If you still use Amon2::Plugin::Web::CSRFDefender, you need to write dependency explicitly in your cpanfile.

2. Latest Amon2::Plugin::Web::CSRFDefender is bit secure.

@mala says Amon2::Util::random_string is bit unsecure for token generation. I change the default token generation algorithm in Amon2::Plugin::Web::CSRFDefender.

I think it's not a critical security issue. But you can update it.

Created: 2014-07-30 17:03:20
Updated: 2014-07-30 17:03:20

Regexp::Trie を java に移植した

https://github.com/tokuhirom/regexp-trie

Regexp::Assemble っぽいことをするには graph-expression に入ってるやつを使えばいい、という話は昨日書いた のですが、生成コードがあまり綺麗ではないし、まあ今どき google-collections に依存しているところからも分かる通り、メンテナンスはされていないようです。

というわけで、@dankogai の Regexp::Trie を java に移植してみました。

使い方は以下の様になります。Perl5 とほぼ同じ感じで使えていますね(例は例によって groovy)。

@GrabResolver(name='tokuhirom', root='https://tokuhirom.github.io/maven/releases/')
@Grab('me.geso:regexp-trie:0.0.1')

import me.geso.regexp_trie.RegexpTrie;

def trie = new RegexpTrie();
["foobar", "fooxar", "foozap", "fooza"].forEach {
    trie.add(it);
}
println(trie.regexp()) // → (?:foo(?:bar|xar|zap?))

依存もないし、シンプルなコードなので使いやすいと思います。

必要な JDK バージョンは 7 です。


コードは Perl 版に比べて長くなっているが、読みやすくなっていると思います。 長くなってる感じがするのは、元のコードが dan さんのコードなので、めっちゃ詰めて書いてあるからというのもでかい。

今回、唯一詰まったのは、

TreeMap<String, TreeMap<String, TreeMap<String, ...>

みたいな再帰的な型をどうやって定義しようかな、というところだったのですが、これは

class CharTrie extends TreeMap<String, CharTrie> { }

とすればよいのでした。

あと、当初は Character 型を Trie のキーにしていたのだが、null は TreeMap の key に使えないということに気づいたので、String 型に変更し、terminator は "" で表現することに変更しました。 (本当は Terminator オブジェクトとツリーオブジェクトを定義する方が Java らしいのかもしれないけど、めんどくさいし今動いてるのでいいということにします。もっと Java らしくてかっこいい設計が思いつく人がいたら教えてほしいです。)

Created: 2014-07-24 07:02:03
Updated: 2014-07-24 07:02:03

groovy の grapes がうまくいかないときのデバッグ方法

groovy -Divy.message.logger.level=4 -Dgroovy.grape.report.downloads=true examples/synopsis.groovy

みたいにすると、ivy のログがめっちゃ出るようになるので、なにが原因だか探しやすくなる。


なお、ivy は dependency resolver で、cpanm の依存解決部分だけをやってくれる機能みたいなライブラリです。

Apache ivy は、単体のライブラリとしても使えるし、ivy.jar を使って ivy.xml からダウンロードさせる、みたいなこともできます。ant と組み合わせる場合には ivy を ant の中に埋め込んだりできる。

Maven が、依存関係の解決とビルドシステムを密結合させているのに対し、Ivy は単体のコンポーネントとして再利用可能なところが Cool。

あと、Apache Ivy は、Gradle の依存解決にも使われています。

Created: 2014-07-24 06:31:08
Updated: 2014-07-24 06:31:08