BullよりElk

洋服と食をこよなく愛するWebプログラマ。

コイニーとFCPと -副業はじめました-

コイニー(本業)で開発をしながら、FASHION CHARITY PROJECT(FCP)というサービスの開発を副業で始めました。

FCPとは

FCPとは、「ファッションアイテムの寄付とお買い物でチャリティ活動できる」サービスです。
コイニーに入る前に勤めていたwajaという会社が運営しているサービスです。
詳しくは、ココをみてください。
バックエンドは1人で1ヶ月半でフルリプレースを行いました。これは、エンジニア生活で一番の大仕事でした。
技術的な変化としては次の通りです。

(旧)

(新)

  • Spring Boot 2.0.x
  • Spring Data JPA
  • Spring Security
  • Thymeleaf

ただリプレースするだけでなくて、リファクタリングをやったり、パフォーマンス改善も行いました。

なぜ副業を始めようと思ったのか

副業を始めた理由として、もちろん「お金が欲しい!」という気持ちはあります。が、1番の理由は、「転職をしないため」です。

自分は、1つの環境だけに長くいられない性格をしているようです。これまで転職を何度かしてきました。自分でいうのもアレなんですけど、その期間でちゃんと貢献はしてきたつもりです。いろんな会社を見てきた経験はできました。しかし、それとは引き換えに長い時間いることでしか味わえない本当に辛い時期や信頼関係というものを経験したことがありません。
これについては、そろそろ経験しておかないとまずいなとは思っています。

コイニーの所属するheyグループは、まだまだ発展途中ですが本当にいい会社だと思います。
けっこうなペースで人が増えていて、これから組織的な強化が行われていくのだと思います。

それに伴って懸念していることがあります。いい環境というのは心地よくて、長くいたい(出たくない)と思うようになり、外を知るということを自然としなくなるのではないだろうかと思います。そうなるのが非常に怖くて、会社とは違うどこかに所属しておきたいと感じていました。そうすることで転職への熱はだいぶ抑えらます。

なぜFCPを選んだのか

3つ理由があります。

スーパーできるディレクターともう1度仕事をしたかったから

この人と入社時から絡むことはあったけど、仕事で絡んだのはやめる3ヶ月前からです。仕事の熱とか視点とか知識はかなり持っていて、純粋に「この人すごいな」ってなりました。仕事だけでなく、その他の引き出しも多いし何者なのだろうかと思ってました。
ある程度コード書けるので、1人で回せなくはないかなと思います。しかし、リプレースしたばっかで数字もよくて、「今がチャンスだからディレクター業に専念したいから可能なら開発はやって欲しい」というのは退職してからも話は持ちかけられてました。
本業の試用期間も終わって少し落ち着いたころに直で、CEOにFCPの開発やらせてよって連絡しました。思ったよりすんなりOKでました。
そんな感じで始まって、また一緒に仕事ができるのでとてもワクワクしています。

自分が作ったサービスは愛着がわいていたから

だいたいの人はそうなんじゃないかな?って思うんですけど、DB設計からリリースまでほぼ1人でやりましたのでとても愛着が湧いてるサービスです。この経験ってなかなか積めないし、社会に貢献できるサービスなので、これからも成長させていきたいと考えてます。

小さいサービスだしいろいろと試せて、会社では学べないことを学べそうだから

会員数はけっこういますけど、サービスの規模は小さめなので技術的にも施策的にもいろいろと試せるのではないかと考えています。
上記のディレクターとほぼ2人で進めて行くので、ビジネスのこともかなり理解しないといけないです。降りてきた仕様をそのまま実装するのはよくないですし。そしてスピード感も必要。ビジネスの話はあまり得意でないので、そこについても学んでいけるのはメリットが大きいです。
ここで学んだことを本業で活かせるタイミングはあるはず。

最後に

開業freeeを使えばすぐに開業届作れます。税務署での手続きも5分で終わります。

スピーカーとして参加してきたSpring Fest 2018

10/31に開催されたSpring Fest 2018で「Amazon Cognito使って認証したい?それならSpring Security使いましょう!」というタイトルで話してきました。
資料は、これです。

www.slideshare.net

なぜ話すことになったのか

Spring Festを運営しているJSUGから「何か技術的な話ある?」と連絡が来たからです。声がかかったのは純粋に嬉しかったし、人前話す機会はなかなかないので迷うことなくYESと答えました。(人前で話すのかなり苦手ですけど)

〜当日まで

私は日常的に人前で話してないし、ネタになるような勉強はそこまでしていないので1ヶ月半前くらいから準備をはじめました。
サンプル作って、リファレンス読んでソースコード読んでをひたすら行ってネタ集めをしました。ネタ集めを進めていくと、わかっていたこと、わかっていなかったこと、勘違いして認識していたこと、全く知らなかったことがすごくはっきりわかります。
認可周りのは知識ゼロでした。登壇の機会がなかったらたぶん勉強しなおさない部分なのでよかったです。AccessDecisionVoterを使って認可処理を行っている何て想像もしてませんでした。
メソッドセキュリティは好きですが、@PreAuthorize以外がほぼ使っていなくて改めて他のアノテーションについても学べました。

ある程度調査が済んでからは、Keynoteと戯れる毎日。見た目、フォント、図すごく迷いました。(スライドの色は2日前まで全く違う色でした)
社内リハもしました。人に見てもらうと全然目をつけていない部分にもちゃんとアドバイスが来るのでほんとによい。そんなこんなで前日。3回くらい個人リハやりました。

資料作りながら感じたことです。

当日

スピーカー控え室に行きました。スピーカーの方は、みんなギリギリまで資料を見直したり、個人リハをしたりしています。登壇慣れしてる方でも。

自分のセッション

14:15、セッションの時間になりました。立ち見が出るレベルに満席でした。足を運んでくださって本当にありがとうございました。
セッションの頭でコイニー知ってるかのアンケートとってみましたけど、10/90と想像以上に少なくて凹みました...
満席だし、Springの講師やってる多田さんの前だし緊張MAXでした。本当は話すつもりなかったメソッドセキュリティの話もするくらいに時間早く終わってしまった...結果1分しか余らなかったのでよしとしました。
次の多田さんのセッションにパス出せたのかもしれない


懇親会

美味しいご飯ありがとうございました。なぜか司会をやりましたw
懇親会でお話した2人が自分のセッション聞いてくださっていました。
1人は、Spring Security特に使ってないけど今後使うことになりそうだからという理由でした。Springのソースコードの解説はよかったとのことでした。
もう1人は、普段からSpring Security使ってるけど自分の理解に自身がなかったし、認証認可周りで知らないことあるからという理由でした。
「自分の理解はあっていたようだ、知らないとこについても知れた。フレームワークソースコードについても話してくれてありがたかった。メソッドセキュリティは全く知らなかったけど便利そうだから使いたい。」という嬉しい言葉をたくさんいただきました。
こんな風にいろんな人話す機会がある懇親会っていい時間だなと強く感じました。

反省等

アンケートの結果には悪かったという評価もありました。とてもありがたいですが、欲を言えばどこが悪かったかまで書いてもらえるとありがたいです。

自分なりに考えた悪かったところ

  • タイトルにCognitoと入れてたけど実際に話したのは、概要くらいで実装は特に紹介しなかった。Spring Security

の話が割合として多すぎた。

  • なぜとか考察が少なかった。淡々と解説をしている感じになっていた気がする。今後にどう活かせばいいのかというイメージを描きにくかったと思います。
  • 話の流れがうまく繋がっていなかった部分があった。

よかったこともいくつかありますが、恥ずかしいので省略します。
自分で満足するセッションができなかったのは悔しかったので、社で開催する勉強会で反省点を生かしつつ、場数踏んでリベンジします。

その他

アンケートを書くまでがカンファレンスです。フィードバックをもらうことで次の機会に生かすことができます。スピーカーの皆さん単に話したいことを話しているわけではないと思います。何かしら持って帰って仕事に活かしてほしいと思いながら準備・セッションをしています。聴く人が求めていることはなんだろうといったことも考えていると思います。そういうわけで聴いてる人の声がすごく役立つのです。

会全体のアンケートも同様です。3分もあれば終わるので、お礼の意味を込めてアンケートに回答するくらいのことはやってもいいのではないのかな?と思います。

Hibernate Enversで履歴管理

エンティティが登録、更新、削除された履歴を管理します。

Hibernate Enversは、履歴管理テーブル全体を管理するエンティティ(デフォルトでRevinfo)をいて、自動採番されるid(プロパティ名はrev)と履歴を永続化した日時(プロパティ名はrevtstmp)の2プロパティを持っています。
エンティティ(後述のClient)の登録、更新、削除された履歴を管理するエンティティ(後述のClientHistory。デフォルトのクラス名はClientAud)が履歴管理するプロパティに加えて、Revinfoのid(デフォルトのプロパティ名はrev)とどのようなイベントが行われたかのフィールド(デフォルトのプロパティ名はrevtype)を持っています。ちなみにrevは、Revisionの略です。

環境

やること

hibernate-enverを依存関係に追加する
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
</dependency>
@Auditedをエンティティクラス(またはエンティティの特定のフィールド)に付与する

今回は、Clientエンティティを使って説明します。

Client.java

@Entity
@Audited
public class Client implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Getter
	private Integer id;

	@Getter
	@Setter
	private String name;

	@Embedded
	@Getter
	@Setter
	private Address address;
}

Address.java

@Embeddable
@Getter
@Setter
@Audited
public class Address implements Serializable {

	private int postalCode;

	private String address1;

	private String address2;
}

ここでClientエンティティの履歴を管理したいのでクラスに対して@Auditedを付与します。

Clientエンティティの履歴を管理するエンティティは、デフォルトでClientAudとなります。Audは、Auditの略ですがわかりずらいのでClientHistoryにします。また、ClientHistoryのプロパティ名も、それぞれrevをauditId、revtypeをauditTypeに変更します。application.propertiesに次のように書けば変更できます。

spring.jpa.properties.org.hibernate.envers.audit_table_suffix=_history
spring.jpa.properties.org.hibernate.envers.revision_field_name=audit_id
spring.jpa.properties.org.hibernate.envers.revision_type_field_name=audit_type
テーブルを準備する

Flywayを使うので、sqlファイルに次のように書きます。
これで、Clientと対になるclientテーブル、ClientHistoryと対になるclient_historyテーブル、履歴管理テーブル全体の管理をするrevinfoが作られます。

create table client (
 id integer not null auto_increment,
 name varchar(255),
 postal_code integer not null,
 address1 varchar(255),
 address2 varchar(255),
 primary key (id)
)
 engine=InnoDB;

create table client_history (
 id integer not null,
 name varchar(255),
 postal_code integer,
 address1 varchar(255),
 address2 varchar(255),
 audit_id integer not null,
 audit_type tinyint,
 primary key (id, audit_id)
) engine=InnoDB;

create table revinfo (
 rev integer not null auto_increment,
 revtstmp bigint,
 primary key (rev)
)
engine=InnoDB;

alter table client_history add constraint FK_client_history_revinfo foreign key (audit_id) references revinfo (rev);

できたテーブルがこれです。

https://d2l930y2yx77uc.cloudfront.net/production/uploads/images/6744813/picture_pc_e03a99bea17d7c41c2c71ac4ad27b504.jpg

登録、更新、削除やってみる

それぞれ、Hibernateが生成したSQLです。

登録
Hibernate: insert into client (address1, address2, postal_code, name) values (?, ?, ?, ?)
Hibernate: insert into revinfo (revtstmp) values (?)
Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)

作られたデータ

"client" : {
    "id": 1,
    "name": "hey,inc",
    "postalCode": 1500011,
    "address1": "東京都",
    "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階"
}

"clientHistory": {
    "id": 1,
    "name": "hey,inc",
    "postalCode": 1500011,
    "address1": "東京都",
    "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階",
    "audit_id": 1,
    "audit_type": 0
}

"revinfo": {
    "rev": 1,
    "revtstmp": 1528214457258
}
更新
Hibernate: update client set address1=?, address2=?, postal_code=?, name=? where id=?
Hibernate: insert into revinfo (revtstmp) values (?)
Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)

作られたデータ

"client" : {
   "id": 1,
   "name": "Coiney,inc",
   "postalCode": 1500011,
   "address1": "東京都",
   "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階"
}

"clientHistory": {
   "id": 1,
   "name": "Coiney,inc",
   "postalCode": 1500011,
   "address1": "東京都",
   "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階",
   "audit_id": 2,
   "audit_type": 1
}

"revinfo": {
   "rev": 2,
   "revtstmp": 1528214465266
}
削除
Hibernate: delete from client where id=?
Hibernate: insert into revinfo (revtstmp) values (?)
Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)

作られたデータ

"clientHistory": {
   "id": 1,
   "name": null,
   "postalCode": null,
   "address1": null,
   "address2": null,
   "audit_id": 3,
   "audit_type": 2
}
"revinfo": {
   "rev": 3,
   "revtstmp": 1528214465681
}

ここでauditTypeの0、1、2は何ですか?という疑問が残ってるかと思いますが、それぞれ次の通りです。
0 → 登録
1 → 更新
2 → 削除
イベントにあったタイプが与えられます。

Hibernate Enversで履歴管理が楽になりました。現場からは以上でした。

私のPC事情

会社変わる時とかに、前の会社のPCでのブックマークとかアプリ移行できないときのためにメモ。

Chrome拡張機能

その他

Portailleとmitakeと

いろいろと落ち着いて最近はよいことだらけで、楽しい毎日を送ってます♪今日は、久々にテックじゃないこと書こうと思います。

今日は、Portailleとmitakeの展示会&ポップアップショップに遊びに行ってきました!私の靴と帽子をお世話になってるブランドさんたちです。

Portailleは、代官山で展示会でした。

今回は、バッグや小物なども作られていて、とても楽しみにして向かいました。
着いてまず飛び込んで来たのが今回からの新しいレザーで新しい型の靴。このレザー伸ばすと色が少し変わるんですよ?
いつも、大渕夫妻がレザーとか靴の説明を丁寧にしてくださって毎回感動しています。そして、作り手の声を直できけるのはすごくよい経験です。

次にバッグ。案の定すごくかわいい。特に小さい方のバッグがとても好き!しかし、スペイン行くかもで金銭的にきついし、行きつけのお店で3つほど入れてるとのことで今回は断念。


原宿ラフォーレに移動して、mitakeのポップアップショップ行きました。
デザイナーの三岳さんがいらっしゃいました。ETHOSENSにいらっしったので、ユニセックスっぽい方かと思っていたら、ワーク系の方でちょっとびっくりしました。
そして、以前書いたブログの記事を読んでくださってて、2度びっくりでした。
色々と三岳さんとお話できて、どんな方かふんわりわかって、ますますmitakeの帽子が好きになりました!そして、また買っちゃいましたねw
大好きなビッグベレー。
f:id:b1a9id:20180310223700j:plain

直接被り方教えてもらえたけど、自分ので問題なかったw
オールシーズンいける生地なので、ヘビロテしようと思います。

これからもPortailleとmitakeには、お世話になるだろうな。

JUnit5試したみた #3(ParameterizedTest)

前回に引き続き、JUnit5について書きます。2018年1月15日に5.0.3がリリースされましたね。今回は、ParameterizedTestについてです。
元のソースコードGitHubにおいてあります。
github.com

ParameterizedTest

ParameterizedTestは、指定した引数を用いて、複数回テストを実行することができます。同じテストだけど、引数だけが違うというときに使えます!
引数のタイプを指定できるアノテーションは、以下の6つです。
@ValueSource、@EnumSource、@MethodSource、@CsvSource、@CsvFileSource、@ArgumentSource
詳しくは、後ほど説明します。

@ValueSource

@ValueSourceは、最もシンプルに引数のタイプを指定できます。
@ValueSourceで指定できる型は、String、int、long、doubleの配列です。配列の前から順番にテストします。
以下がStringの配列を指定したときの例です。int、long、doubleのときも同様です。

テスト対象クラス

テストクラス


実行結果

f:id:b1a9id:20180118101714p:plain

@EnumSource

@EnumSourceで指定できる属性は、value、names、modeがあります。配列の前から順番にテストします。

属性 説明
value Enumのタイプを指定する。
names Valueで指定したEnumのタイプの特定の値をStringの配列で指定する(正規表現も使用可能)。これらの値をテストでどういう扱いにするかをmodeで指定する。
mode EnumSource.Modeのどれか(EXCLUDE, INCLUDE, MATCH_ALL, MATCH_ANY)を指定する。

EnumSource.Modeの説明

説明
EXCLUDE namesで指定した値を除外して抽出する。
INCLUDE namesで指定した値のみ抽出する。namesのデフォルトはこれです。
MATCH_ALL namesで正規表現を指定した場合にすべてに当てはまる値のみ抽出する。
MATCH_ANY namesで正規表現を指定した場合にどれかに当てはまる値のみ抽出する。

なお、namesを未指定でMATCH_ALLまたはMATCH_ANYを指定した場合は、すべての値を抽出します。

テスト対象のEnum

テストクラス


@CsvSource

@CsvSourceで指定できる型は、カンマ区切り文字列の配列です。配列の前から順番にテストします。
説明があまりうまくできていない気がするのでサンプルみてみましょうw
ちなみに指定の仕方は、JUnit 5 User Guideに詳しく書いてあるのでこちらを参照していただければと思います。

テストクラス


実行結果

f:id:b1a9id:20180118145219p:plain

@CsvFileSource

@CsvSourceとほぼ同様ですが、こちらは、ファイルから読み込むことができます。

テスト対象CSV

テストクラス


実行結果

f:id:b1a9id:20180118150155p:plain

解説

ちなみに、@CsvFileSourceは、ファイルの文字コード、改行文字、区切り文字を指定できるようです。(区切り文字は、@CsvSourceでも指定できます。)
区切り文字に「・」を指定して実行してみましたが、表示するメッセージはcsv用になっていました。まあ、アノテーションにもCsvとついていますし、区切り文字は指定しない方がいいかもしれませんね。

@MethodSource

@MethodSourceは、自分で実装した指定したい型のSteramまたはIterableまたはIteratorまたは配列を返すメソッドをStringの配列で指定します。

メソッドの引数が1つのときの、テストクラス

methodSourceIntStreamメソッドの実行結果

f:id:b1a9id:20180118152736p:plain

methodSourceStreamメソッドの実行結果

f:id:b1a9id:20180118152802p:plain

メソッドの引数が2つのときの、テストクラス

実行結果

f:id:b1a9id:20180118153322p:plain

解説

メソッドで複数の引数を指定したい場合は、ArgumentインターフェースのCollectionかStreamを返す必要があります。

@ArgumentsSource

上で紹介したものは全てArgumentsProviderインターフェースを実装したクラスを呼んでいます。
f:id:b1a9id:20180118155745p:plain
つまり、再利用可能なArgumentsProviderを実装して、値を自由に指定しようということです。

テストクラス

実行結果

f:id:b1a9id:20180118162125p:plain

おまけ(表示するメッセージをカスタマイズする)

@ParameterizedTestのname属性を指定することで、表示するメッセージをカスタマイズできます。

サンプルコード

実行結果

f:id:b1a9id:20180118163135p:plain

解説

次のプレースホルダが使えます。

プレースホルダ 説明
{index} 現在の呼び出し回数
{arguments} メソッドの引数のリスト(カンマ区切りで表示される)
{0}, {1},... 個々の引数

以上です。次はDynamicTestとか紹介できたらいいかなと思います。