日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

scalikejdbcを使ってMySQLにクエリを投げるだけのメモ

久しぶりにScalaを触ってMySQLにクエリを投げたいだけなんだけど、
それすらもぱっとは思い出せなかったので備忘録として書いとこうと思います。

scalikedjbc

github.com

検証用レポジトリはこちら。
make run で多分動きます。

github.com

build.sbt

とりあえずおもむろにbuild.sbtにscalikejdbcを追加します。
3系出てたんですね。
長いこと触って無くて知りませんでした。

scalaVersion := "2.12.5"

libraryDependencies ++= Seq(
    "org.scalikejdbc" %% "scalikejdbc" % "3.2.3",
    "mysql" % "mysql-connector-java" % "6.0.6"
)

書いたコードは結局このくらいでした。
localTxの実装を見たらなにかあったらThrowableが投げられるみたいだったのでcatchingで囲むことにしました。
Throwableが投げられたとしても特に何もしなかったらrollbackされなかったので、
投げられたら(Leftが返ってきたら)rollbackするような実装にしました。

package com.ru.waka

import java.time.LocalDateTime

import scalikejdbc.{ConnectionPool, DBSession, NamedDB, SQL}

import scala.util.control.Exception._

object Hello {
  private val connectionSymbol = 'testDB

  Class.forName("com.mysql.cj.jdbc.Driver")

  ConnectionPool.add(connectionSymbol, "jdbc:mysql://localhost/test?characterEncoding=UTF-8", "root", "")

  val repository = new HelloRepository(connectionSymbol)

  def main(args: Array[String]): Unit = {
    val time = LocalDateTime.now().toString
    NamedDB(connectionSymbol) localTx {implicit  session =>
      (
        for {
          _ <- createTable()
          _ <- repository.put(time)
          rs <- repository.fetch()
        } yield rs) match {
        case Right(rs) =>
          session.connection.commit()
          println(rs)
        case Left(th) =>
          session.connection.rollback()
          println(th)
      }
    }
  }

  def createTable()(implicit session: DBSession): Either[Throwable, Boolean] = catching(classOf[Throwable]) either {
    SQL(
      """
        |CREATE TABLE IF NOT EXISTS foo (hello varchar(100))
      """.stripMargin
    ).execute().apply()
  }
}

class HelloRepository(connectionName: Symbol) {
  def put(hello: String)(implicit session: DBSession): Either[Throwable, Int] =
    catching(classOf[Throwable]) either
      SQL(
        """
          |INSERT INTO foo (hello) VALUES (?);
        """.stripMargin
      ).bind(hello).executeUpdate().apply()

  def fetch() (implicit session: DBSession): Either[Throwable, Seq[Map[String, Any]]] =
    catching(classOf[Throwable]) either
      SQL(
        """
          |SELECT hello FROM foo;
        """.stripMargin
      ).map(_.toMap()).list().apply()
}

scalikejdbc.SQLの引数にクエリを渡して、
executeしてapplyすればクエリが走りました。
めでたしめでたし。
implicit parameterってどう使われるのかの理解が浅かったのでこのあたりも参考に読みました。

参考

いちいちsessionを渡さなくてもいい感じに渡してくれるので便利ですね。
(暗黙なのちょっと不安だけど)

PHPのjson_encodeで空オブジェクトを出力する

phpで空オブジェクトを出力したくなって、
おもむろに json_encode([]); とやったら空配列になってしまいました。

$php -a
Interactive shell

php > echo json_encode([]);
[]

さてどうしたものかと思ってふと浮かんだstdClassを突っ込んでみました。
そしたら空オブジェクトがおもむろに表示されました。

php > echo json_encode(new stdClass());
{}

もしかしてオプションある?と思って調べてみたらありました。

http://php.net/manual/ja/json.constants.php

php > echo json_encode([], JSON_FORCE_OBJECT);
{}

php最高!めでたしめでたし。

API BlueprintとdrakovとdreddでAPIドキュメントを書きつつモックサーバを立ててさらにテストを走らせる

タイトルの通りのことをやってみました。
どんな時にテストがコケるか等の精査が出来てないのでまた続きを書くことになりそうですが、
とりあえずやってみたメモです。

API Blueprint?

API Blueprintはmarkdown形式でドキュメントを書けるweb APIドキュメンテーションを書く用の言語ですかね。

API Blueprint | API Blueprint

この形式で書いて別のツールで、モックサーバを起動したりテストを走らせたり出来るみたいです。

API Blueprint Tools | API Blueprint

drakov?

drakovはblueprintの形式で書かれたファイルを利用してモックサーバを起動できるツールです。

github.com

API Blueprintとでググるapi-mockというものがよく出てきたんですが、
最近メンテされてないような雰囲気があり使うのはやめました。

MacOSでビルドが出来ずこのissueにたどり着き、

github.com

このissueにたどり着いて、よし、別のを探そうとなりました。

github.com

dredd?

dreddはblueprint形式で書かれたファイルを利用して、
特定のエンドポイントに対してその仕様になっているかをテストしてくれるツールです。

github.com

ドキュメント

Dredd — HTTP API Testing Framework — Dredd latest documentation

やったこと

API Blueprintの適当なAPIドキュメントを書いて、
drakovでモックサーバを起動して、 dreddでphpで実装したサーバに対してテストを投げてみました。

ソースコードはこのあたりに置きました。
make server/mockでモックサーバが起動し、
make testでdreddのテストが走ります。
どちらも利用前にmake installを走らせる必要があります。
(makeで依存書けるのに忘れてた。)

github.com

上では触れてないのですが、
make documentで aglioというツールを利用したいい感じのドキュメントを表示することができます。

github.com

モックサーバの起動

api.apibというファイルでとても雑なドキュメントファイルを作りました。
/api/user?id=1 のようなエンドポイントを叩くとjsonでResponseを返してくれます。

FORMAT: 1A

# テストAPIドキュメント

## ユーザ一覧を出力する [/api/user?id={id}]

### user_detail [GET]

+ Parameters
    + id: 1 (number)


+ Response 200 (application/json)

        [
          {
            "id": "1",
            "first_name": "田中",
            "last_name": "太郎"
          }
        ]

make server/mockでdrakovを実行するとこんな感じになります。
別のシェルからcurlを叩いたらちゃんと定義したレスポンスが返ってきました。

$make server/mock
npm run mock-server

> hello-api-blueprint@1.0.0 mock-server /Users/a-tanaka/Documents/Ghq/github.com/ara-ta3/hello-api-blueprint
> drakov -f ./api.apib --watch

[INFO] No configuration files found
[INFO] Loading configuration from CLI
   DRAKOV STARTED
[LOG] Setup Route: GET /api/user user_detail
   Drakov 1.0.4      Listening on port 3000
 FILE SPY   ACTIVE
[LOG] GET /api/user?id=1
[MATCHING] by url pattern: /api/user MATCHED
[DRAKOV] GET /api/user?id={id} user_detail

# 別のシェルから
$curl 'localhost:3000/api/user?id=1'
[
  {
    "id": "1",
    "first_name": "田中",
    "last_name": "太郎"
  }
]

便利そう!(小並感

テストを特定のエンドポイントに対して走らせる

上に書いたapi.apibをそのまま利用します。
サーバの実装にはこんなファイルを用意しました。
php便利ですね。

main.php

<?php

header("Content-Type: application/json");

echo json_encode([
    [
        "id"=> 2,
        "first_name"=> "田中",
        "last_name"=> "太郎"
    ]
]);

dredd用の設定ファイルはこんな感じになってます。
serverの項目にサーバを起動するコマンドを書いてあげました。
make serverは php -S 127.0.0.1:8080 main.php のコマンドをwrapしていて、
どのリクエストが飛んでもmain.phpの出力を表示してくれます。

dry-run: null
hookfiles: null
language: nodejs
sandbox: false
server: make server
server-wait: 3
init: false
custom: {}
names: false
only: []
reporter: null
output: []
header: []
sorted: false
user: null
inline-errors: false
details: false
method: []
color: true
level: info
timestamp: false
silent: false
path: []
hooks-worker-timeout: 5000
hooks-worker-connect-timeout: 1500
hooks-worker-connect-retry: 500
hooks-worker-after-connect-wait: 100
hooks-worker-term-timeout: 5000
hooks-worker-term-retry: 500
hooks-worker-handler-host: 127.0.0.1
hooks-worker-handler-port: 61321
config: ./dredd.yml
blueprint: api.apib
endpoint: 'http://127.0.0.1:8080'

実際にテストコマンドを実行してみました。
成功パターンだとつまらないので、
main.phpからfirst_name, last_nameの項目を削除して、テストをこけさせてみました。

<?php

header("Content-Type: application/json");

echo json_encode([
    [
        "id"=> 2,
    ]
]);
# 成功パターン
$make test
npm run dredd

> hello-api-blueprint@1.0.0 dredd /Users/a-tanaka/Documents/Ghq/github.com/ara-ta3/hello-api-blueprint
> dredd

info: Configuration './dredd.yml' found, ignoring other arguments.
info: Starting backend server process with command: make server
info: Waiting 3 seconds for backend server process to start
php -S 127.0.0.1:8080 main.php
info: Beginning Dredd testing...
pass: GET (200) /api/user?id=1 duration: 53ms
complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 56ms
make[1]: *** [server] Terminated: 15
info: Backend server process exited

# main.phpから削ったあとのパターン
$ make test
npm run dredd

> hello-api-blueprint@1.0.0 dredd /Users/a-tanaka/Documents/Ghq/github.com/ara-ta3/hello-api-blueprint
> dredd

info: Configuration './dredd.yml' found, ignoring other arguments.
info: Starting backend server process with command: make server
info: Waiting 3 seconds for backend server process to start
php -S 127.0.0.1:8080 main.php
info: Beginning Dredd testing...
fail: GET (200) /api/user?id=1 duration: 39ms
info: Displaying failed tests...
fail: GET (200) /api/user?id=1 duration: 39ms
fail: body: At '/0/first_name' Missing required property: first_name
body: At '/0/last_name' Missing required property: last_name

request:
method: GET
uri: /api/user?id=1
headers:
    User-Agent: Dredd/5.1.4 (Darwin 16.7.0; x64)
    Content-Length: 0

body:



expected:
headers:
    Content-Type: application/json

body:
[
  {
    "id": "1",
    "first_name": "田中",
    "last_name": "太郎"
  }
]
statusCode: 200


actual:
statusCode: 200
headers:
    host: 127.0.0.1:8080
    date: Thu, 22 Mar 2018 20:39:44 +0900
    connection: close
    x-powered-by: PHP/7.1.4
    content-type: application/json

body:
[
  {
    "id": 2
  }
]



complete: 0 passing, 1 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 42ms
make[1]: *** [server] Terminated: 15
info: Backend server process exited
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! hello-api-blueprint@1.0.0 dredd: `dredd`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the hello-api-blueprint@1.0.0 dredd script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/a-tanaka/.npm/_logs/2018-03-22T11_39_44_687Z-debug.log
make: *** [test] Error 1

エラー内容がわりと豊富で便利そうです。

まとめと感想

  • API Blueprint、色んなツールが存在して便利そう
  • API Blueprint形式のドキュメントを書いて、フロントエンドの開発時にモックサーバを立てて、サーバサイドにはテストを流すことができそうで便利そう
  • どこまでテストが信頼できるかの調査が必要そう
  • markdownとはいえ、独自言語なのでチームメンバー全員が覚えたりするの大変かも

もう少し使ってみて色々試していけたらなと思います。

monologで例外のstack traceをいい感じに表示する

普段phpでログを出力するのにmonologを使っていて、
開発時にはdocker-composeでサーバを起動してログを標準出力に出したりしています。
開発時に例外が発生したタイミングでmonolog経由でエラーログを出しているのですが、
stack traceが1行に表示されてしまって見づらかったのでなにかいい方法はないかと調べてみました。
検証に使ったmonologのversionは 1.23.0 です。

monolog

github.com

そしたらこのstack overflowにたどり着いて、なにかのPRにたどり着きました。

stackoverflow.com

github.com

PRのコメントをみたらincludeStacktraces()を呼ぶといいらしいとのことでした。
LineFormatterクラスのメソッドらしいですね。

monolog/LineFormatter.php at 1.23.0 · Seldaek/monolog · GitHub

雑に検証してみました。
コードはこちらです。綺麗さのかけらもないです。
もちろんですが composer install の実行が必要です。
3段階くらい例外がネストしているケースを表示してみました。

monologで例外のstack traceをいい感じに表示する · GitHub

<?php

require_once 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;

$log1 = new Logger('いい感じにみえないやつ');
$log1->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));

$log2 = new Logger('いい感じにみえるやつ');
$handler = new StreamHandler('php://stdout', Logger::DEBUG);
$formatter = new LineFormatter();
$formatter->includeStacktraces(true);
$log2->pushHandler($handler->setFormatter($formatter));

function a() {
    throw new \Exception('aaa');
}

function b() {
    try {
        a();
    } catch (\Exception $e) {
        throw new \Exception('bbb', 0, $e);
    }
}

function c() {
    try {
        b();
    } catch (\Exception $e) {
        throw new \Exception('ccc', 0, $e);
    }
}

try {
    c();
} catch (\Exception $e) {
    $log1->error($e);
    $log2->error($e);
}

この状態で実行すると下記のような感じになります。
includeStacktracesを設定していないとstack traceが全て1行に出てしまいますが、
設定していると各行にいい感じに出ているかと思います。
便利。

$php main.php
[2018-03-16 21:23:20] いい感じにみえないやつ.ERROR: Exception: aaa in /Users/a-tanaka/monologphp/main.php:19 Stack trace: #0 /Users/a-tanaka/monologphp/main.php(24): a() #1 /Users/a-tanaka/monologphp/main.php(32): b() #2 /Users/a-tanaka/monologphp/main.php(39): c() #3 {main}  Next Exception: bbb in /Users/a-tanaka/monologphp/main.php:26 Stack trace: #0 /Users/a-tanaka/monologphp/main.php(32): b() #1 /Users/a-tanaka/monologphp/main.php(39): c() #2 {main}  Next Exception: ccc in /Users/a-tanaka/monologphp/main.php:34 Stack trace: #0 /Users/a-tanaka/monologphp/main.php(39): c() #1 {main} [] []
[2018-03-16 21:23:20] いい感じにみえるやつ.ERROR: Exception: aaa in /Users/a-tanaka/monologphp/main.php:19
Stack trace:
#0 /Users/a-tanaka/monologphp/main.php(24): a()
#1 /Users/a-tanaka/monologphp/main.php(32): b()
#2 /Users/a-tanaka/monologphp/main.php(39): c()
#3 {main}

Next Exception: bbb in /Users/a-tanaka/monologphp/main.php:26
Stack trace:
#0 /Users/a-tanaka/monologphp/main.php(32): b()
#1 /Users/a-tanaka/monologphp/main.php(39): c()
#2 {main}

Next Exception: ccc in /Users/a-tanaka/monologphp/main.php:34
Stack trace:
#0 /Users/a-tanaka/monologphp/main.php(39): c()
#1 {main} [] []

まとめ

  • monologのLineFormatterで例外のstack trace表示したいときはincludeStacktracesを設定するといいっぽい

travis上でdockerのmysqlを使ったらCan't connect to MySQL serverと言われてハマった

travisでdocker container上のmysqlに接続しようとしたらなぜか接続できずハマりました。
最終的な原因は $HOME/.my.cnf に設定が上書きされてたのという点でした。
検証用にちょろっと書いたレポジトリはこちら

github.com

travis上でdocker containerを立ち上げて、--defaults-extra-fileのオプションで設定ファイルを読み込み接続しようとしました。
ローカルでやるとこんな感じです。

$docker run --detach --env MYSQL_ROOT_PASSWORD=root --publish 3307:3306 mysql:5.6
bcd7e3220a8efdf52198ab7f03cf2990ce4e00da2e25e417fe5a2f4e24c8f11c

$cat mysql.conf
[client]
user=root
password=root
host=127.0.0.1
port=3307

$mysql --defaults-extra-file=mysql.conf -e 'SELECT 1'
+---+
| 1 |
+---+
| 1 |
+---+

接続余裕ですね。
しかし、同じようなことをtravis上でやると失敗しました。

https://travis-ci.org/ara-ta3/travis-docker-mysql-sample/jobs/350786275

$ docker run --detach --env MYSQL_ROOT_PASSWORD=root --publish 3307:3306 mysql:5.6
Unable to find image 'mysql:5.6' locally
5.6: Pulling from library/mysql
Status: Downloaded newer image for mysql:5.6
6cbf40ed94765b27eaafe2fb6f00be11e0a00265bd30ac125eeab2cbbb071c22

$ sleep 10

$ sudo service mysql stop
mysql stop/waiting
$ mysql --defaults-extra-file=mysql.conf -e 'SELECT 1;'
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111)

原因はmy.cnfの読み込みの順序でした。
--helpやら色々やっていくと読み込まれている設定ファイルの順序が出ていました。

$ mysql --defaults-extra-file=mysql.conf --help
mysql  Ver 14.14 Distrib 5.6.33, for debian-linux-gnu (x86_64) using  EditLine wrapper
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

...

Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf /home/travis/build/ara-ta3/travis-docker-mysql-sample/mysql.conf ~/.my.cnf 
The following groups are read: mysql client

なるほど、--defaults-extra-fileで指定されたファイルの後に ~/.my.cnfが読まれるんですね!!! 😇
~/.my.cnfにしっかり[client]の設定が書かれていました。
--print-defaultsというオプションを指定してみたら先になんかport指定されてるぞ・・・?というところから気が付きました。
--print-defaults便利だったので、なんか設定が変だなとなったときは使っていきたいですね。

$ cat ~/.my.cnf
# Managed by Chef for packer-5a26f3f3-4b65-3c91-579b-7cc9c3fb2e7f.c.eco-emissary-99515.internal :heart_eyes_cat:
[client]
default-character-set = utf8
port = 3306
user = root
password =
socket = /var/run/mysqld/mysqld.sock
[mysql]
default-character-set = utf8

$ mysql --defaults-extra-file=mysql.conf --print-defaults
mysql would have been started with the following arguments:
--port=3306 --socket=/var/run/mysqld/mysqld.sock --user=root --password=root --host=127.0.0.1 --port=3307 --default-character-set=utf8 --port=3306 --user=root --password= --socket=/var/run/mysqld/mysqld.sock --default-character-set=utf8 

これがベストかは置いといてrm ~/.my.cnfしたらとりあえず動きました。
動いたサンプルのビルドはこちらです。

https://travis-ci.org/ara-ta3/travis-docker-mysql-sample/builds/350787431

2017年振り返りと2018年どうしようかなみたいな雑記

なんとなく振り返ります
今年は
1月に突発性難聴になり、
2月は知り合いの結婚式で仙台に行き、
3月は一緒にコミュニティ運営してて海外に行ってる親友の結婚式に行き、
5月最後に会社のチームで合宿に行き、

techlog.voyagegroup.com

6月に1dayのインターン手伝いで京都に行き、
Treasureの講義用にレゴスクラム初めて出てみたり、

レゴ(R)で始めるスクラム入門(レゴスクラム) - ワイクル株式会社 | Doorkeeper

7月は死ぬほど暑い中DarkでBBQして、

Dark BBQ 2017 · Issue #41 · ngineerxiv/dark · GitHub

Splatoon2にハマり始め、
8月はTreasureの手伝いをして、
9月はPHPカンファレンスの資料作りで死にかけ、
10月にPHPカンファレンスで登壇して、

techlog.voyagegroup.com

ISUCONで敗北して人権を失い、イカになり、

arata.hatenadiary.com

11月、12月は無限にゆるいかの人とSplatoonをやった1年でした。

arata.hatenadiary.com

去年書いた抱負とか全く覚えてないw

arata.hatenadiary.com

■去年書いてた今年の抱負

  • 本を3冊は完走したい
  • ピアノで10曲暗譜
  • Darkのpublicなもくもく会の回数を増やす
    • 今年は3回だったので5回くらいは最低限やりたいなぁ

本を3冊は完走したい

ほとんど読めんかったです\(^o^)/
とりあえず3冊は読めたのでよかったです

読んだ本

ゼロから作るDeep Learningは結局よくわかってないので、もう一度読み直したい

bookmeter.com

完全独習 統計学入門は簡単な統計がわかってすごいよかった
統計学ももうちょっと踏み込みたいなぁ

bookmeter.com

スティーブ・ジョブズ 驚異のプレゼンはPHPカンファレンスの前に読んだけど、
すごく活かせたかで言うと怪しいのでまた読まないといけない

bookmeter.com

読みたい本

来年はDDDとか設計の本とか統計の本あたりを読んでいきたい

bookmeter.com

bookmeter.com

bookmeter.com

ピアノで10曲暗譜

くらいしか弾けるようにならんかったw
無理ぽよw
来年はもう少し公開したりとかできたらいいなぁ

Darkのpublicなもくもく会の回数を増やす

3回でした・・・
明らかにSplatoon2の影響なので、来年は頑張りたい・・・

(だいたい)新卒エンジニア もくもく会 #4 - connpass

(だいたい)新卒エンジニア もくもく会 #5 - connpass

(だいたい)新卒エンジニア もくもく会 #6 - connpass

感想と来年の抱負

ピアノよく弾くようになって新しい曲でも楽譜見ながら弾けるようになってきたので継続して弾いていきたい。
Splatoonのコミュニティの人ともっともっと遊んでいきたい。
勉強する機会が明らかに減ってしまったので、頑張ってやるようにしていきたい。
今年関わった方々みなさんありがとうございました!
また来年も仲良くしてください\\\ ٩( 'ω' )و ////

■目標

  • 本は最低3冊読む
  • ピアノ10曲暗譜目標
  • Darkのpublicなもくもく会5回
  • めっちゃ遊んで人生を楽しんでいく

MacのFinderとかIntellijのOpen Projectでdot directoryを表示させる

GolandでGoのProjectを開こうとしたんですが、
$GOPATHが .go になっていて、
Openしようとしてもどうやって選択すればいいかわかりませんでした。

Golandでプロジェクトを開こうとしている時の状態

f:id:arata3da4:20171219183118p:plain

困って inttelij dot directory open とかで頑張ってググッた結果
こんなところにたどり着きました。

intellij-support.jetbrains.com

Command + Shift + '.' (Dot) で.から始まるディレクトリを表示できるらしい。
押したらこうなりました。

f:id:arata3da4:20171219183431p:plain

Finderでも同じショートカットなんですね。
とりあえずよかった。めでたしめでたし。