ryhmrt’s blog

意識低い系プログラマの雑記

DB定義ドキュメント生成のワンライナー

稼働中のDBスキーマをリバースエンジニアリングしてドキュメント生成するようなツールはいくつかあるけれど、フリーで使えて便利なものの一つが SchemaSpy

オフィシャルのDockerイメージがあるので、これで実行するとお手軽

$ docker run --network host -v "$PWD/doc:/output" schemaspy/schemaspy -t pgsql11 -host localhost -db mydatabase -u postgres -p password

最近システムの引き継ぎをしていて思い出したのでメモ

ソフトウェア/プロダクト開発、あるいは企業の文化

このゴールデンウィークで「Hit Refresh」と「ユニコーン企業のひみつ」という2冊の本を読んだ。

ソフトウェア開発には組織や文化が重要ということは数十年前から言われているけれど、ソフトウェアの重要性が高まる中で、開発の部署やプロジェクトといった局所的な取り組みではなく、会社全体/経営としての取り組みが強く求められているという風潮を感じる。

一年位前に読んだ「ジョイ・インク」も、この2冊と同じようなことを主張しているように思える。

若者が下手に読むと現状との格差に絶望してしまうかもしれないのでお勧めしない。上司や社長の机にそっと置いておく本という位置付けにしたい。

AWS事始め

仕事でAWSを使っていくことになったので、自宅で勉強するための個人アカウントを作りました。

http://aws.amazon.com からAWSアカウント作成のボタンを踏んで必要な情報を入力。 登録を進めていると、途中で電話番号を入れて確認の電話がかかってくる手順がある。 アメリカからかかってくるので多分国番号から入力するべき。 080-xxxx-xxxx だったら +81-80-xxxx-xxxx みたいな感じ。

これでルートアカウントが生成されるが、ルートアカウントは普段使うべきではないらしい。IAMで普段使いの管理用アカウントを作成する。

サービス一覧からIAMを選んで、左のメニューからUser、次に Add user ボタンをクリック。

User name は好きなように入力。
Access type は両方チェック。
Console password は面倒なので自動生成で。
Require password reset は自分使いなのでチェックを外した。

次の権限画面では、Attach existing policies directly で AdministratorAccess と Billing を付与。 多分これで何でもできる。

Tagは何もいらない。

最後に出来上がったユーザ情報のCSVを落としておく。 次からはこのCSVにあるURLとパスワードでログインする。

AWSのコマンドラインツールもついでに入れておく。

MacでHomebrewが入っている環境なら以下のようにインストールできる。

$ brew install python3
$ pip3 install awscli --upgrade --user

これでawsコマンドが ~/Library/Python/3.7/bin に入るのだけれど、ここにはパスが通っていないと思うので。

export PATH=~/Library/Python/3.7/bin:$PATH

を、zsh なら ~/.zshrc、bash なら ~/.bash_profile あたりに追記。

次に以下のコマンドで資格情報を設定する。

$ aws configure
AWS Access Key ID [None]: CSVに書いてある情報
AWS Secret Access Key [None]: CSVに書いてある情報
Default region name [None]: us-east-1
Default output format [None]: json

これで aws ec2 describe-instances とかが成功すればとりあえず設定できているんじゃないかと思う。

参考

フィリピン滞在に有益そうな情報

最近なぜかフィリピンに行くという知り合いが多いので、フィリピン滞在に有益そうな情報を少し考えてみました。

私がフィリピンにいたのは2016年6月までで、基本的にはマカティから外には出ずに活動しておりました。その範囲で見聞きしたり実体験した情報です。

持っていくもの

とりあえず最低限これだけあればなんとかなります。

  • クレジットカード(キャッシングできるやつ)
    • 海外キャッシングの枠は国内キャッシング枠より低かったりするので注意。
    • 詳しくは後述。
  • 携帯電話
    • ネット接続は必須です。迷ったときでもネットで地図をみれば安心。
    • 詳しくは後述。

あると良いもの

  • 英語で書いてある写真付きの身分証(例えば国際免許)
    • ビルに入ったりするときに身分証を求められたりするのですが、英語で書いてないといけません。
    • パスポートはおいそれとは出したくないので、別の身分証があると安心感が増します。

ホテル

ホテルは多少高くついてもなるべく治安の良いところを選ぶべきだと思います。田舎の方では場所による治安の差はそんなに無いと思いますが、都市部では気をつけたいところです。新宿の表通りと裏通りの治安感を思い浮かべていただければ何となく雰囲気は浮かぶかと思います。

最近はフィリピンも都市部はGoogleMapsのStreetViewでカバーされてますので。通りがどんな感じか、開けた通りでお店があって夕方から夜にかけてもそれなりの人通りがあるところなのかどうかなど、調べておくと安心です。

ビザ

日本人は事前の手続きをしなくとも、ビザなしで30日間の滞在許可がおります。滞在許可期間内に出国するチケットさえあれば、よほどのことが無い限り入国拒否をされることはないはずです。

帰りの日程がはっきりと決まっていない場合も、仮の滞在スケジュールを滞在許可期間内で立てて、それに合わせて帰りのチケットを持っておく必要があります。JetStartはオプション料金を少し払うと安いチケットでもスケジュール変更可能になるのでよく使ってました。

30日を超えて60日以内の滞在をする場合は、事前に日本のフィリピン大使館で短期滞在ビザを取っておくと、フィリピンで滞在延長手続きをせずに済んで便利です。

滞在延長手続きは、自分でイミグレのオフィスに行くか、旅行代理店に頼むかになります。何度か自分でもやりましたが、イミグレまで行くのもかったるいですし、手続きにも時間がかかるので、オススメは代理店です。

現金の扱い

フィリピンの通貨はフィリピンペソです。さっき見たら1円=0.47ペソでした。

以下の種類のお札と硬貨があります。1ペソ=100センタボ

  • 1000ペソ札
  • 500ペソ札
  • 200ペソ札
  • 100ペソ札
  • 50ペソ札
  • 20ペソ札
  • 10ペソ硬貨
  • 5ペソ硬貨
  • 1ペソ硬貨
  • 25センタボ硬貨
  • 10センタボ硬貨
  • 5センタボ硬貨

割とどこでも円からペソに換金できますし、ほぼマーケット情報に出ているレートと同じ条件で換金できるので、なるべく円を現金で持って行くのがお得です。

足りなくなったらPLUSやCirrusのマークがある銀行のATMでクレジットカードのキャッシングを利用できます。一回の取引上限は10,000ペソとかだと思いました。

日本で換金して持っていくのは非常に換金効率が悪いのでやめましょう。

余ったペソは10,000ペソを上限に国外に持ち出せます。空港スタッフがX線スキャンでチェックしてイチャモンをつけてくるので、もしペソが余って持って帰りたい人は気をつけるようにしてください。

通信手段

ごく短い滞在であればローミングで済ませてしまうのもありです。料金は各通信会社のページに載っていると思います。

ネットの使用頻度や滞在日数によっては、SIMフリーの携帯を持っていて(向こうで買っても良い)、ローカルの回線を取得すると安く済むかもしれません。

空港では各回線業者がプリペイドのSIMを売ってますので、携帯を渡してSIM入れてネットの設定までやってもらうこともできます。かわいいお姉ちゃんだったらついでに番号ゲットしてしまえばいいと思います。

空港からの移動手段

5つ星ホテルに滞在するなら迎えを頼めると思いますが、迎えがないならば空港からの移動はタクシーを使うことになると思います。空港で見かけるタクシーにもいくつかありまして、

  • ボックスタイプの車を使って行き先のエリアごとに料金が決まっている固定料金(Fixed Rate)タクシー
  • 黄色い車体の空港メータータクシー
  • 白い車体の市中メータータクシー
  • レンタカー

料金が安い順に言えば、基本的に白、黄色、固定の順です。ただし、ぼったくり遭遇率もその順に高いので、メーターのタクシーに乗るときは、乗る前に「Meter?」とか言ってメーター使うかどうか確認して、走り出したらメーター動き始めたかどうか確認すると良いです。固定料金のタクシーはそうそう問題は起きないと思います。

尚、運ちゃんは英語を解しますが、日本人の発音が通じない可能性は大いにありますので、行き先の名前と住所は紙に書いておいて渡すと良いと思います。

日常生活の心得

  • スキミングの話をたまに耳にします。カードの利用履歴を監視して、何かあればすぐにカード会社に連絡しましょう。
  • 都市部では治安の悪いエリアがどこなのか把握して、特に日が落ちてからは近寄らないようにしましょう。
  • チップ文化は基本的にありません。請求された分だけ払いましょう。
  • お腹に自信の無い方は飲み物や食べ物に警戒しましょう。
  • 悪いお姉ちゃんやオカマに捕まらないようにしましょう。
  • フィリピンはゴキブリ天国です。慣れましょう。

長期滞在に向けて

長期滞在ではコンドミニアム(マンション)を借りることになると思います。自分で探すのも良い経験になりますが、知り合いの一人は日系の不動産から仲介してもらってました。英語がそこまで得意じゃ無い方はオーナーと直接やりとりするのはかったるいと思いますので、仲介屋を通すとそこら辺も安心できるかと思います。

携帯は後払いの契約型のものの方が安くなりますし、いちいちロードする必要もなくなるので便利です。個人的な経験からは、外人で国内に住居と職があればすんなりと回線契約できるのではないかと思います。

銀行口座も持っていると便利です。フィリピンでは他行の口座とはやりとりできませんが、自行のATMからはいつでも手数料なしで引き出せます。会社の口座がある銀行支店で店長とかとフレンドリーに話をしていればすんなり開設できるのではないかと思います。

最後に

慣れたと思った頃にヘマをする人が多いらしいので、海外で気を引き締め続けるのを忘れずに、と諸先輩が仰ってました。

フィリピンでの移動手段について

先月末から日本に戻りましたが、日本の交通網の発達は素晴らしいなあと改めて思ったりしています。

参考までにフィリピンの交通事情がどんなものなのか私の主観的な情報をここに記しておきます。

タクシー

フィリピンの短期滞在者が一番お世話になるのがタクシーではないかと思います。料金は日本と比べるとずっと安いです。初乗りが日本円で100円とかそんなもの。

空港からの移動も、ホテルやら知り合いやらの出迎えがなければタクシーを使うことになると思います。空港からの移動で気をつけるのは、声高に客引きをしているのは基本的にボッタクリだということです。タクシーと言って客を引きつつ、実はレンタカーだったりもします。空港から出ている正規のタクシーは、黄色い車体のメーター制(市中のメータータクシーよりちょっと割高)のものと、行き先によって値段が固定になっているボックスタイプのクーポンタクシー(基本的にメーターより高くつく)の2つです。参考までにマニラ空港からマカティ市内までの料金ですと、利用するターミナルにもよりますが、200ペソから600ペソほどの間に収まると思います。これを大きく上回るようだとボッタクリかと。

空港からの移動は以下の順に優先して利用しております。

  1. メータータクシー
  2. クーポンタクシー
  3. ボッタクリ

メータータクシーが一番安いので、できればそれを使いたいのですが、到着時間によってはに長蛇の列ができている場合があります。その場合でもクーポンタクシーは割と空いていることが多いので、そっちに行きます。急ぎの用事がある場合は仕方がないので、ボッタクリの中からどうにか我慢出来る値段のところを探すことになります。

裏技として、空港の到着エリアから出発エリアに移動して、客を空港まで送り届けた後の市中タクシーを拾うという手もあります。しかし、市中のタクシーは空港では客を拾ってはいけないことになっていると思いましたので、やるなら荷物が少ないときなどで、目立たないようにやるべきかと。

ちなみに、空港からマカティ市内に抜ける際、Skywayという高速道路を使うと、20ペソを別途払うことになります。

フィリピンのタクシーは、市中でつかまえた正規のライセンスのものであっても油断はなりません。走り出した時にきちんとメーターが回っていることを確認しないと、後から法外な料金を請求してくる悪質なドライバーもおります。乗車拒否も日常茶飯事で、道中に渋滞があったり、雨天時で稼ぎどきともなると、乗車時に通常のメーター料金よりも高額で交渉をしないといけない場合もあります。メーター+50ペソというのが割とよく出る条件ですが、ケースバイケースです。

稀なケースだとは思いますが、タクシー運転手が強盗に様変わりするなどとの話もあるようです。

尚、陸路全般に言えることですが、渋滞が酷いため、それを計算に入れて移動する必要があります。

レンタカー

田舎方面に行く場合はタクシーに頼れなくなってきますので、レンタカーを使うという選択肢も出てくるかと思います。

フィリピンではレンタカーを借りると基本的に運転手がついてきます。小ぶりなセダンタイプならば、運転手込みで1日1万円とか、そんなものではないかと思います。

自分で運転するという選択肢もないわけではありませんが、日本とは車線が逆ですし、運転マナーも悪いため、短期滞在で運転に挑戦するのは自殺行為かと思います。

バス

現地で一番メジャーな長距離移動手段がバスだと思います。だいたいの町にはバスで行けるのですが、ぱっとバスを見ても行き先はよくわからないと思いますし、車内のアナウンスは現地の言葉ですので、かなりの慣れが必要です。

時間や路線によってはかなり混み合いますので、大きな荷物を持っていると辛い場面もあるかと思います。コストパフォーマンスは良いです。

バン

ボックスタイプの車が一定の区間を往復していて、バスと同じように利用することができます。バスよりも行き先がわかりにくく、難易度が高いです。きちんと営業許可を取っているものと、白タクのような無法営業のものがあるようです。料金的にはバスとそんなに変わらないのではないかと思います。

Uber / Grab Taxi

私は両方ともほとんど使いませんでしたが、高評価をしている人もいました。

個人的にはいい思い出がありません。Uberの運転手は素人ばかりで、スマホのナビに失敗するとすんなりと目的地に着けないことがままある。Grab Taxiは基本的にメータータクシーをつかまえるためのサービスですが、繁忙時には結局これを使っても捕まえられないですし、暇な時間帯はこんなのがなくとも流しのタクシーを簡単に捕まえられるのであまりメリットが感じられません。

両方共オンラインで車両情報が追跡されるので、セキュリティ上安心できるということはあるかと思います。

電車

フィリピンの電車網は、首都マニラ圏に、山手線のような環状の路線と、それを東西に突っ切る短い中央線があるような感じです。

激しく混みますし、上記以外に細かい路線網がないので使い勝手は悪いです。ただし料金は安い。

最近ではIC乗車券なんかも導入されてたりするらしい。

空路

日本のように陸路で素早く移動する手段がありませんので、長距離移動は空路に頼るのが賢明です。群島国家なので、そもそも陸路で行けないということもあるかと思います。

Philippine Airlines と Cebu Pacific がメジャーです。クオリティは Philippine Airlines の方が安定しているように感じます。料金は Cebu Pacific が格安フェアをよくやっていて、そのチケットが取れればかなりお得です。通常の料金はどちらもそんなに変わらないと思います。

海路

飛行機を使うまでもないような距離での海峡越えなど、海路を使う場面もあるかと思います。

短距離ではバンカと呼ばれているボートを使いますが、日本で使われているような高速艇と比べると遅くて乗り心地も悪いです。

長距離ではフェリーになるかと思いますが、どうしても自分の車を運びたいといったことがなければ空路を選ぶのが無難です。

Jacksonのデフォルトタイムゾーン

今週に入ってからSpringMVCでWebAPIをごりごり書いているのですが、いざ日付型を扱うというところになってちょっとはまったのでメモ書き。

謀ったなJackson

SpringMVCのJSON処理は標準でJacksonを使うようになっています。

Jacksonで日付型をフォーマットするためには

    @JsonFormat(pattern = "yyyy/MM/dd")
    private Date date;

とかやるわけなんですが、これだとタイムゾーンがGMTで計算されて変になったりします。

    @JsonFormat(pattern = "yyyy/MM/dd", timezone = "Asia/Tokyo")
    private Date date;

とか書くこともできるんですが、さすがにこれを一々指定するのは気が重い。

@JsonFormat のJavaDocのtimezone部分を見ると、

TimeZone to use for serialization (if needed). Special value of DEFAULT_TIMEZONE can be used to mean "just use the default", where default is specified by the serialization context, which in turn defaults to system defaults (TimeZone.getDefault()) unless explicitly set to another locale.

とか書いてあるので、

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));

と、アプリの初期時にやっておけば良いと思ったそこの貴方は残念賞。

Jacksonで明示的にデフォルト指定をしない場合のタイムゾーンは "GMT" 固定です。

com.fasterxml.jackson.databind.cfg.BaseSettings を見ると、昔は TimeZone.getDefault() を使っていたのかな、と思うようなコメントアウト(!!)があって目を疑います。

    private static final TimeZone DEFAULT_TIMEZONE = 
            //  TimeZone.getDefault()
            /* [databind#915] 26-Sep-2015, tatu: Should be UTC, plan to change
             * it so for 2.7
             */
            TimeZone.getTimeZone("GMT");

エスパーするとしたら、タイムゾーンの環境依存で発見しにくいバグに悩まされて、デフォルトをGMT固定に変更したとかでしょうか...

尚、Jacksonの公式WikiにあるFAQの日付処理のトピックでは、一番最初に「タイムゾーン何も指定しなければGMTだから、よろしく」と書いてありました。 http://wiki.fasterxml.com/JacksonFAQDateHandling

これで勝てねば貴様は無能だ

以下のようなクラスをプロジェクトの適当な場所に作ればOKです。

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .timeZone("Asia/Tokyo")
                .dateFormat(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"));
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }
}

上のコードではついでにデフォルトの日付フォーマットも変更してます。

参考: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-config-message-converters

ページ遷移時にAPIを叩いてデータを読み込む with react-router-redux 4.0.0

react-router-redux を 4.0.0 にしたらメソッド名とか中の処理とかけっこう変わってました。

前に書いたURLのフック処理がいろいろ駄目になっていたので、4.0.0でどんな処理になっているのか、ざっと追って、新しくコードを書き直してみました。

react-router-redux が何をやっているのか

最新の 4.0.0 では react-router-redux がラッピングした history オブジェクトを React Router に渡すようになっています。

このラッピングされた history は listen イベント登録が書き換えられており、store に格納されているURLが変更されると、その情報をパラメータに listener がトリガーされるようになっています。

流れを3ステップで説明すると以下のようになります。

  1. オリジナルの history のURL変更イベントで LOCATION_CHANGE アクションを dispatch する
  2. routerReducer が LOCATION_CHANGE を受けとって、新しい URL 情報を store に格納する
  3. react-router のURL監視用 listener が呼び出され、ルーティングが書き換わる

3.0.0からの違い

以下のコードは react-router-redux の README.md にあるもの。

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore, routerReducer } from 'react-router-redux'

import reducers from '<project-path>/reducers'

// Add the reducer to your store on the `routing` key
const store = createStore(
  combineReducers({
    ...reducers,
    routing: routerReducer
  })
)

// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store)

ReactDOM.render(
  <Provider store={store}>
    { /* Tell the Router to use our enhanced history */ }
    <Router history={history}>
      <Route path="/" component={App}>
        <Route path="foo" component={Foo}/>
        <Route path="bar" component={Bar}/>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('mount')
)

以前は syncHistory で middleware が返ってきましたが、4.0.0 の syncHistoryWithStore は react-router-redux がラッピングした history を返してくるので注意が必要。

これを使わずにオリジナルの history を react-router に渡してしまうと、細かいところで変なことが起こります。要注意。

前のバージョンで syncHistory が返していた middleware は、routerMiddleware として別物になってオプション扱い。

というのも、この middleware は URL 変更のアクションを受けとって history に対して操作を行うためにあるのですが、そんな回りくどいことをせずに history を直接呼べば middleware 登録をする必要はないでしょうという話だと思います。

以下のコードは README.md に載っているもので、Redux のアクションを使ってURL遷移をするサンプルコードです。

import { routerMiddleware, push } from 'react-router-redux'

// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
  reducers,
  applyMiddleware(middleware)
)

// Dispatch from anywhere like normal.
store.dispatch(push('/foo'))

しかし、history を直接使うならば以下のようにシンプルにできます。

import { browserHistory } from 'react-router'

browserHistory.push('/foo')

あと、前のバージョンでは index.js に全てがずらずらっと(とは言っても短いですが)書かれていたのが、4.0.0 ではファイルが細切れになっています。

ページ遷移のフック処理

現状、ページ遷移をフックしてAPIを呼び出す処理は以下のようになっています。

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import { match } from 'react-router';
import { UPDATE_LOCATION } from 'react-router-redux';
import { loading, updateData } from 'actions/common';
import reducer from 'reducers';

const dataFetchMiddleware = routes => store => next => {
    // Utility method for API call
    function updateDataFromAPI(location) {
        match({ routes, location }, (error, redirectLocation, renderProps) => {
            if (error || redirectLocation) {
                return;
            }
            // Find API
            const route = renderProps.routes[renderProps.routes.length - 1];
            if (route.fetchAPI) {
                // Call API if it's available
                api(renderProps.params, renderProps.location.query, data => {
                    store.dispatch(updateData(data));
                    store.dispatch(loading(false));
                });
            } else {
                Promise.resolve(loading(false)).then(store.dispatch);
            }
        });
    }
    // Return core middleware function
    return action => {
        // Fetch data on update location
        if (action.type === LOCATION_CHANGE) {
            next(loading(true));
            updateDataFromAPI(action.payload);
        }
        return next(action);
    };
}

export default function configureStore(initialState, routes) {
    return createStore(reducer, initialState, applyMiddleware(thunkMiddleware, loggerMiddleware, dataFetchMiddleware(routes)));
}

URLとAPIのマッピングは routes の定義に書くことにしました。

match というユーティリティが react-router にあり、routes 定義と location を渡すと、マッチしたルーティング情報を返してくれます。

末端の route 定義に fetchAPI というキーでデータ取得のための関数を定義するようにして、それをここで取得するようにしました。

注意点は、match が onEnter を呼び出しますので、onEnter で副作用のあることをしないように。そもそも、onEnter はリダイレクト処理くらいしかやってはいけないのだと思います。