エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

JavaのHashMapは無限ループを引き起こす

知らんかった。理由がぼんやりはイメージ湧くけどしっくり来てないので後でちゃんと調べ…たい。(だいたいやらないパターン…)

d.hatena.ne.jp
www.atmarkit.co.jp
wadahiro.hatenablog.com


回避方法はいろいろあるみたいだけど、ConcurrentHashMapをつかってスレッドセーフにするのが楽なのかな?
web.plus-idea.net

価値観ババ抜き大体験会!に参加してきました #devlove

悩めるオジサンの私が、自分を見つけるためにぴったりそうな価値観ババ抜き体験会に参加してきました。
https://devlove.doorkeeper.jp/events/85438

価値観ババ抜きって何?? というのを書こうと思ったのですが 冗長になる&講師じゃない自分が変に説明して誤解が広がっても嫌だな
ということで、上記のイベント告知 及び、コチラの公式サイトのリンクをご参照ください。
http://myvaluecard.com/

いきなり考えさせられる名札書き

f:id:taichiw:20190121225328p:plain
何この「どこから来た」って!
ストレートに社名を書こうとも思ったんですが、あぁ、なんかあるんだろうなこれ と 邪推し、こんな感じに。
f:id:taichiw:20190121225438p:plain
「『シングルプレーヤーとマネジメントの間』から来ました」って… 今見返すとすごく恥ずかしい。
でもこの手の、自分と対話するゲームするときはこのくらい、のっけから自分を晒していかないとですよね!

ちなみに、この名札は、開始前の雑談以降は全く使いませんでした(笑)

ババ抜きの結果

さて、最終的に5枚のカードが手元に残るのですが… 私の結果はこちら!!
f:id:taichiw:20190121225716p:plain

いやいやいやいや 「美」ってなんだよ、「美」って。よりによって俺が「美」って。
って思うんですが。

このカード、偶然にも最初の5枚に入ってまして、

「なんだこれ? すぐ捨てよう」

と思ったものの、

「でも、バグのないシステム、きちんと『切られ』た、読みやすいコードって、まさに『美』だよなぁ」

と思ったら 

「これはこれで 大切かぁ…」

と思い

気づけば最後まで残ってしまいました。

また、ゲーム中は各カードはあまり繋がりがなく、バラバラに5枚残った… と感じていたのですが、
よくよく見ていると…

なんとなーく 隣のカード同士の関連のようなものが見えてきました。
f:id:taichiw:20190121230148p:plain

まさかの、最後の1枚、「メイン」になったのは…

更にここから追加のワーク。残った5枚の中から1枚のメインと2枚のサブを選ぶことに。

「そら、この中からメインを選ぶんだったら『楽しさ』よ」

と思ったんですが、どうしてもしっくりこない。

どうしてもどうしても気持ちがそっちに行かず…

最終的に私が選んだのは… これ。

f:id:taichiw:20190121230842p:plain

「『美』しいシステムを作るには『知識』が必要であり、それを学ぶモチベーションとして支えるのが『楽しさ』」

という、「美」を頂点にしたピラミッドができてしまいました。


えええ…

これだと俺の中で一番大事なことが「美しい」ことになってしまうんですけど……

とはいえ、恥ずかしさとか、どう思われるかとか、そういうのを捨てて、自分に問いかけたときに、

上の3枚の三角が最も最終結果として「美しい」 と思うくらい「美」を意識してしまったんだからしゃーない。

もう一回やったらどうなるんだろう

今回は偶然にもはじめの手札に「美」があったのでこのような結果になってしまったのですが、もし「美」がずっと他の人に持たれていたり、あるいはずっと場にあったとしたら*1、全く違う結果になったのでしょうか。
その場合自分は何をメインに置いたのか…

時期をおいてまたやってみたいものです。

実際、平日の夜にやるのか休日にやるのかでも結果が変わるとのこと。
脳内がお仕事モードなのかプライベートモードなのか、あるいは、仕事モードの中でも何に時間や脳みそを取られているのか といった状況によってかなり結果が変わりそうですね。

最後にお手紙タイム

最後は、一緒にババ抜きをした他の3名に向けて、お手紙を書いて渡す というちょっと恥ずかしいけど嬉しい時間でした。
f:id:taichiw:20190121231527p:plain

*1:カードを取られるたびに自分で場から一枚好きなものを選ぶことができるのですが… 自分から「美」は取らないような気がします。それとも今回みたいに、たまたま目があってしまった後に同じ思考になって結局取るんだろうか…

あらゆる「システム」の挙動は入力と出力だけで説明できる

どんな粒度、どんな用途の「システム」も、挙動は入力と出力だけで説明できます。
f:id:taichiw:20190118235359p:plain

プログラムのサブルーチン(function, method, ...)

「引数」と「返り値」で挙動が説明できます。

public String greet (String name){
    return "Hello " + name + "!";
}

Microserviceアーキテクチャの、各Service

APIのリクエストとレスポンスで挙動が説明できます。
f:id:taichiw:20190119000332p:plain

Microserviceアーキテクチャ全体

内部すべてをブラックボックスと見て、一番外側のAPIのリクエストとレスポンスで挙動が説明できます。
f:id:taichiw:20190119000823p:plain
f:id:taichiw:20190119000848p:plain

Webページ

リクエストであるURL(+POSTで送られるテキストや画像などのリクエストボディ)と、返却されるHTMLやその他コンテンツで挙動が説明できます。
f:id:taichiw:20190119001413p:plain

でも、データ更新したりするでしょ? 入力と出力だけじゃ説明できないじゃない!

このようにシステムを見ると、「状態が更新される」ことも挙動の説明としてする必要があります。
f:id:taichiw:20190119002052p:plain

ですが、ここだけをシステムとして見ると… DBへの更新は「出力」の一種と見ることができます。
f:id:taichiw:20190119002118p:plain
このシステムは、APIのクライアントと、DB という2種類の外部システムに、出力をするシステムです。

プログラムのサブルーチンの場合も同様です。

class Person {
  String name;
  public setName(String name) {
    this.name = name;
  }
}

このコードは、このようにも捉えられますが
f:id:taichiw:20190119002532p:plain

nameはPersonから依存があるだけの別インスタンスなので、このようにも捉えられます。
f:id:taichiw:20190119002715p:plain

逆にDBからデータを読むようなシステムは、「入力が2つある」と捉えることができます。
f:id:taichiw:20190119002824p:plain

バグチケットに各APIのリクエストとレスポンスが貼ってあったらバグの調査は早く終わるのか?

モヤモヤと考えたことを書き出しています。結論ありません。実践もまだしてません。

背景

私は今、こんな感じでInternalなWeb APIが相互作用するサービス*1に関わっています。
f:id:taichiw:20190107184003p:plain

また、こんな感じで組織が別れています*2
f:id:taichiw:20190107184619p:plain

さて、このサービスについて不具合が起票されることがあります。
リリース前のQAだったり、残念ながらリリース後、本番可動しているサービスに対してだったりします。
いずれにしても大事なこととして、不具合はここの視点、最も浅いレイヤである、UIの「挙動が期待通りでない」事象として発見されます。
f:id:taichiw:20190107185147p:plain

一方システム的には、不具合の原因は以下のいずれかになります(複数の組み合わせの場合もある)

  • UI componentにバグが有る
  • API 1にバグが有る
  • API 2にバグが有る
  • API 3にバグが有る
  • API 4にバグが有る
  • DB1のデータが正しくない
  • DB2のデータが正しくない

不具合を修正するためには、この中のどこに問題があるかを見つける必要があります。

どうすれば早く原因が見つかるのか

シンプルに、いずれか一つのプログラム(=UI component または API 1~4 のいずれか)にバグが有るケースを考えます。
バグが有る、ということは外から見たプログラムの挙動として、以下の2つのいずれかに分類することができます。

  • 正しいリクエストを受けているのに、レスポンスが正しくない
  • 正しいリクエストを受けているのに、次のAPIの呼び出しが正しくない あるいは データの更新が正しくない

たとえばAPI 3がバグっていて、「正しいリクエストを受けているのに、レスポンスが正しくない」場合
f:id:taichiw:20190107190453p:plain
API 3が正しくないレスポンスを返すので、それがAPI 1→UI と伝搬することになります。
この場合、API 3を修正する必要があります。

また、API 2がバグっていて、「正しいリクエストを受けているのに、次のAPIの呼び出しが正しくない」場合
f:id:taichiw:20190107191303p:plain
API 4そのものは正しく動作していますが、リクエストが誤っているため期待通りでないレスポンスを返し、最終的にそのレスポンスが原因となってUIの挙動が期待通りになりません。
この場合、API 2を修正する必要があります。

「赤い矢印」が見つかればどのプログラムがバグっているのかわかる

前項で見たとおり、期待通りでないリクエスト、またはレスポンスである、「赤い矢印」
f:id:taichiw:20190107191317p:plain
がどこから始まっているかがわかれば、バグのあるプログラムである、「赤い四角」
f:id:taichiw:20190107191417p:plain
の箇所が特定できそうです。

ということで、バグが起票された際に、全APIのリクエストとレスポンスがチケットに貼ってあれば、即座にどのAPIがバグっているのかわかるのでは? と考えました。
こんな表を自動生成することはできないでしょうか。

API リクエス レスポンス APIへのリクエス
API 1 f:id:taichiw:20190107191851p:plain f:id:taichiw:20190107191902p:plain to API 2 f:id:taichiw:20190107191929p:plain, to API 3 f:id:taichiw:20190107191851p:plain
API 2 f:id:taichiw:20190107191929p:plain f:id:taichiw:20190107192044p:plain to API 4 f:id:taichiw:20190107191317p:plain
API 3 f:id:taichiw:20190107191851p:plain f:id:taichiw:20190107192118p:plain
API 4 f:id:taichiw:20190107191317p:plain f:id:taichiw:20190107191902p:plain

※矢印はイメージです。実際はこの表の各セルに具体的なJsonが書かれます

Spring Cloud Sleuth でTraceIDを発行しているため、一つのUIアクセスから始まる各APIのリクエスト/レスポンスを集めてくるのは技術的には可能そうです。

「赤か青か」を判断するのが実は難しい?

上の表では、API 2は正しいリクエストを受けているにもかかわらず、API 2からAPI 4へのリクエストが間違っています。ここから、API 2にバグが有る、と推測することが可能です。
f:id:taichiw:20190107193039p:plain
では、API 2→API 4のリクエストが間違っている と気づくことができるのは誰でしょうか…。API 2はTeam C、API 4はTeam Dが担当しているので、それぞれのチームのエンジニアに気づくチャンスがありそうです。

API 2の担当エンジニアは気づけるか?

まず、API 2の担当エンジニアは、上の表から以下のことに比較的簡単に気づけそうです。

  • API 2のレスポンスが期待通りでない
  • API 4からのレスポンスが期待通りでない

ここから、API 4へのリクエストが間違っているか、API 4そのものにバグが有る という推測は立てられそうです。
しかし、API 4へのリクエストの検証についてはどうでしょうか。
シンプルな間違え*3ならばすぐ気がつけそうですが、もう少し複雑な間違えですとすぐには気づけないかもしれません。
また、API 2の担当エンジニアが、API 4のリクエスト使用を誤って理解しているかもしれません。

API 4の担当エンジニアは気づけるか?

API 2→API 4のリクエストが間違っている」ということにAPI 4の担当エンジニアが気づくためには、以下のことを理解している必要があります。

なぜならば、バグの起票時点では、UIレベルの情報しかないからです。
f:id:taichiw:20190107193948p:plain
これも、即座に正しいか誤っているか判断できる場合もあると思います。しかし、API 4の使われ方によっては、中間のAPIの仕様を知らないとリクエストの正誤の判断が難しいかもしれません。

リクエストの正しさの確認は狭間に落ちる?

…ということを想像していくと、API 2→API 4のリクエストの正しさの確認というのは、どちらのエンジニアにとっても難しいのでは…? という気がしてきました。

レスポンスの正しさの確認のほうが簡単かも

では一旦、「すべてのAPIのリクエストは正しい」と仮定して、レスポンスの正しさだけに注目したらどうでしょうか。

バグの起票時に、UIレベルで「本来Aという振る舞いをすべきところ、Bという振る舞いをしている」ということは記載されています。
この場合、「『B』の原因となるレスポンスを自分のAPIが返している」かどうかの確認は、リクエストの確認の正しさに比べると比較的難易度が低い …気がします。 自分の経験的には。

APIのレスポンスだけとりあえず並べてみる

レスポンスの正誤の判別は比較的簡単… という前提で、「バグ起票時に各APIのレスポンスが自動的に貼り付けられるツール」のようなものを想像してみます。

API レスポンス
API 1 f:id:taichiw:20190107191902p:plain
API 2 f:id:taichiw:20190107192044p:plain
API 3 f:id:taichiw:20190107192118p:plain
API 4 f:id:taichiw:20190107191902p:plain

※繰り返しになりますが、矢印のところには具体的なJSONが入るイメージです

システム構成が頭に入っていれば、以下のような図が見えるわけです。
f:id:taichiw:20190107195535p:plain
※データがおかしい可能性もありますが、今は除外して考えます

API 3には絶対バグがないことがわかった!バンザイ! といいたいところなのですが、
UI, API 1, API 2, API 4 のどれもバグ持ちの可能性が消えていません。
これだけでは、あまり何かが解決しているようには思えません。

いろいろ考えてみたけどこれだったら結局…

先程問題にあげた、「リクエストの正誤の方法」について掘り下げたほうが何か効果があるかもしれません。

今日はここまでです。

続きます…(多分)

*1:図は簡略化しています。現物はもっと大きいです。また、いわゆるマイクロサービスアーキテクチャに近いとは思いますが微妙に違う気もします。組織とか。

*2:この組織構成が良いのかどうかの話は、ここではしません。

*3:例えば、API 1 -> API 2 では Item A がリクエストされているのに、API 2 -> API 4 ではItem Bがリクエストされている というケースです。

『複雑なドメインに泥臭く立ち向かう』JJUG CCC 2018 Fall #jjug_ccc

JJUG CCC 2018 Fallに参加し、@su_kun_1899 さんの 『複雑なドメインに泥臭く立ち向かう』というセッションに参加してまいりました。

speakerdeck.com

twitter.com


法律という完全に外部要因で決まって変えることもできないものが要件に深く絡む世界で、
どのように立ち向かっているのか、というお話でした。

今一番読みたかった本が現れたかのように感じたセッション

どうやって複雑なドメインを理解し適切にコードに落とし込むか
というのは今現在の私の最大の関心事の一つでありながら、
もう一段自分のレベルを上げたいのだけれど何を勉強したら良いかわからないのが課題でした。

そんな中、今日のセッションは

  • 自分が聞きたかった話を
  • 理解できる言葉で
  • 体系立てて

話してもらえたセッションでした。

更にいうと、一部の内容は、普段から自分がぼんやりと「思っていた*1」ことを、明確に言語化してもらった内容でした。

以下、私が感じたことが中心です。
順序も元の発表と異なります。ご了承ください。

共感したポイント

一本道を見つける

「変化は受け入れ、一本道に対して複雑さを足していくアプローチをとる」

そうなんですよね。いかに幹を見極め、枝葉と分離できるか。
いまから作ろうとしているプロダクトの背景にある真の目的はなんなのか。
それを見つけ出せることが重要なんだと思います。

モデルを抽出するするために ひたすら write & talk

とにかくなんでも良いから書き始めてみること。
脳内でウンウン考えているだけだと、自分自身が認識するのもなかなか難しい。

サービスクラスがユースケースを表す

すごくわかります。
個人的に、サービスクラスが数行でかけたら価値だと思っています。(というよりはサービスクラスがごちゃごちゃしたら負け)
taichiw.hatenablog.com

コードに書いて、動かして、初めて理解したと言える

今年の私の実体験。
単なるAPIだろうがんなんだろうが、実際に動くものを見て、触れて、初めて理解できることや気づける疑問が人間どうしてもあります。
更にこの時、クソコードだろうがなんだろうが、
とりあえずでも「動いて」、そして一応でも「読める」プログラムがあることによって、それまでに比べて格段に理解がはかどるようになります。
皆さんエンジニアですから。
(クソコードでもよいのですが、「適切な枠で切られている」ことはとっても大事。その中がクソな分には最悪差し替えれば良いのです)

「枝葉」は課題管理表にきちんと残す

「一本道の骨格を洗い出す過程で、とりあえず置いておくことに決めたものはきちんとバックログに、すぐに残すこと。」

これは悪い方向での今年の私の実体験。
途中から管理が雑になって、結局漏れた要件や、
他のメンバーから「なにそれ聞いてない!追加要件だ!」って言われてしまった*2ものがいくらか。

気づかせてもらったこと

最低限のドメインナレッジの勉強は必要

「はじめての『介護保険』」のような、初心者向けの書籍は大抵あるので、それだけでも読んでおく

これだけでもドメインエキスパートと話すときに単語の理解が捗る。

実際の業務のロールプレイをしてみる

手書きで帳票を書く、という言うようなことをしているそうです

「ユーザーストーリーマッピング

まさかの、ここでこの本。
もともと本書がターゲットにしている話題とは少し異なるが、ドメインを知ることにも使える手法がたくさんだと。

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

ユーザーストーリーマッピング [ ジェフ・パットン ]
価格:3240円(税込、送料無料) (2018/12/15時点)


Talkの重要性

モデルを作り上げていく際、付箋とホワイトボードを使って「Write & Talk」を行うという話。

書くだけでなく、他のメンバーと話すことによって理解が深まっていくわけですね。
個人的にはここがうまくできていないことがあって、常に試行錯誤中…。
それぞれの箇所について1対1ではできているんだけど他のメンバーが巻き込めていなかったりとか。

*1:思うことと実践することは別です

*2:とはいえ、「枝葉」なので、後で足しても根底から設計を覆すものではなかった… はずなのですが

3歳の息子が私のサービスを使った日

このエントリは、子育てエンジニア Advent Calendar 2018の14日目のエントリです。
このブログではプライベートなことをあまり書いたことがないのですが、乗っからせていただきます。

About 私 & 息子 (と奥さん)

私には、現在3歳半の息子がおります。一日の生活リズムはこんな感じです。
f:id:taichiw:20181215011212p:plain
f:id:taichiw:20181215011256p:plain

私の「夢」

私には、ずっと以前、社会人になるよりももっと前から思い描いていた「夢」が一つありました。

「家のリビングにテレビが置いてあって、そのテレビを指差して、『このテレビ、お父さんが作ったんだよ』って自分の子供に向かって話すんです」

この話を唐突に、(今も働いている)インターネットサービスの会社の採用面接で話したので、当時面接官の方にポカーンとされたのを今でも覚えています。
「テレビ」というのは喩えでして、
自分の作った「モノ」を自らの子供に見せたい、使ってもらいたい、という思いを、子供もいなければ結婚もしていない当時から抱いていたんです。

ようやく念願のリリース…!

私の「お仕事」に話を移します。
最近、実に一年半もかけた新規開発案件がようやくリリースされました。*1
Webサービスをやっていて一年半もの間じっくりと未稼働のサービスに取り組む、というのは初の経験で、勉強になる部分も多かったのですが、

やはり、「本番で動いている」というのは良いものです。

家に帰ってからも思わず、自分のスマートフォンを取り出しては、
こんな感じの画面を見ながら、
動いてる姿にニヤニヤしておりました。
f:id:taichiw:20181215014255p:plain

すると覗き込んでいた息子が、脇から「ANAだー」「JALだー」と言い出したんです。
この、羽のアイコンだけ見て飛行機を表していると気づいたことにまず驚いたのですが、
f:id:taichiw:20181215014703p:plain
どうやらこの検索画面が大変お気に召されたようで、息子氏は私から携帯電話を奪い取り、グリグリと画面をスクロールして、
「この水色のは何?」「これは?」
と、航空会社を覚えることを数日間お楽しみあそばされたのです。

いちいち携帯電話を奪われるので大変面倒くさかったのですが、この日は「 息子が私のサービスを使った記念日」になりました。

残念ながら息子は予約までしてくれたわけではないので、真に「使ってくれた」わけではありません。

また、「お父さんがこれを作った」と話してみたのですが、ピンときていないようでした。

しかしながら、
自分が作ったモノを、誰でも利用できる
というコンシューマ向けサービスのエンジニアならではのやりがいを、息子を通して改めて感じた日でした。

*1:…が、この後、事情により一旦サービ停止中となっております。ご迷惑をおかけしています

ParallelStreamで外部接続しちゃいかんの? → 独自スレッドプールでParallelStreamを使ってみた

ParallelStreamでI/O待ち状態を作るとプール内のスレッドを食いつぶす?

ParallelStreamはアプリ全体で共通で持っているスレッドプールを使う上、CPUのコア数分しかスレッドが無いのでI/O待ちが発生するようなものには使ってはいけない、という話を聞きました。

そうなの?ということで検証してみました。

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
	list.add(Integer.valueOf(i));
}

list.stream()
  .parallel()
  .forEach(i -> {
    //1秒かかる外部サービス
    restTemplate.getForObject("http://localhost:8081/sleep/", String.class);
    System.out.println(i);
});

f:id:taichiw:20180930225024p:plain
手元のPC上で動かした結果を、Visual VMでみてみたところ。

ForkJoinPool.commonPool-worker- ...
という、アプリ内での共通スレッドプールが、ParallelStreamで使われています。
4コアCPUで実験しているため、プール内のスレッドは3つ。(それに加えて呼び出し元のスレッドがあるので、JVM全体で可動しているスレッドは4つ)。

I/O待ちの間はスレッドは専有状態になるため、
4個通信 → (1秒経ってレスポンス) → また4個通信

というように、外部通信が4件ずつしか行われません。

また、この間に同じアプリ内の他の処理でもParallelStreamを使おうとすると、そちらとの、Common Pool内のスレッド取得合戦が起こってしまうことも確認できました。

オレオレスレッドを使ってみる …でもこれ大丈夫?

Common Poolのスレッド数を設定で増やすことも可能なようですが、「スレッド取得合戦」を避けるため、独自のスレッドを作ってParallelStreamを回すことを検討してみます。

ForkJoinPool pool = new ForkJoinPool(10);
pool.submit(() ->
  list.stream()
  .parallel()
  .forEach(i -> {
    //1秒かかる外部サービス
    restTemplate.getForObject("http://localhost:8081/sleep/", String.class);
    System.out.println(i);
  })
).get();
ForkJoinPool pool = new ForkJoinPool(10);

で、スレッドプールを作成し、このプールを使ってparallel streamを回します。

すると

  1. スレッド数の上限が10になったので、10並列で通信を実行。一気に10回分のサービス呼び出しが終了。
  2. 同時に、アプリ内の他の箇所でParallelStream(デフォルトのCommon Poolを使用)を実行してもスレッド取得が競合しない

という結果が得られました。

ただ… メソッド実行のたびにスレッドが増えていきます。大丈夫なんでしょうか。
f:id:taichiw:20180930222344p:plain

これ、本番可動させてたら無尽蔵にスレッドが増え、一定期間経つとアラートが飛んできて、再起動を余儀なくさせられるんじゃないでしょうか。
過去の悪夢が蘇ってきます。


…ということで軽くロングランテストを実施。
3時間位流し続けてみた限りでは、
GCのタイミングで古いスレッドは消えていっており、特にスレッドが溢れたりメモリリークしたりしている様子はありませんでした。
f:id:taichiw:20180930225134p:plain
49652番目のスレッドプール。これより以前に作成されたものは消えている。

ただ、都度スレッドを作成して破棄するのは、CPUとメモリの無駄遣いっぽい雰囲気がします。

専用の「スレッドプール」を作る

ということで、以下のように(シングルトンの)インスタンス変数としてスレッドプールを定義してみました。

@RestController
public class TestController {

  final ForkJoinPool pool = new ForkJoinPool(100);

  @GetMapping(value = "test")
  public String test() throws ExecutionException, InterruptedException {

    RestTemplate restTemplate = new RestTemplate();

    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      list.add(Integer.valueOf(i));
    }

    pool.submit(() ->
        list.stream().parallel()
            .forEach(i -> {
              //1秒かかる外部サービス
              restTemplate.getForObject("http://localhost:8081/sleep/", String.class);
              System.out.println(i);
            })
        ).get();

    System.out.println("OK");
    return "OK";
  }
}

f:id:taichiw:20180930224809p:plain

いい感じでスレッドの使い回しができているようです。