なぜ僕たちはサーバレスでJavaを諦めTypescriptを採用したか

この記事はエストニアのタリンから書いています。
期間に大小あれど、すでに日本・ベトナム・中国・台湾・シンガポール(・オフショアでインドとも)の現地で仕事し、すでにアジアでの労働は満喫した感があるので、ヨーロッパにそろそろ足を伸ばそうかなと。

そこで、第一候補として、大学生の頃から憧れだったIT先進国エストニアに下見に来ています。

まぁ、現地の開発者と何人か話して、もうほぼ心は決まりましたね。半年くらいを目処にこちらに移住しようかと考えています!
幸運にも日本人は比較的簡単に労働許可が得られるようなので、夏くらいを目処に今の会社を退職し、こちらに来ようと考えています。

これについては今後別に記事を書きます。いく前の期待といった後の感想とか、結構需要がある気がするので。

ところで、現地の開発者と話しているうちに、技術モチベが高まりに高まってしまったので、久々に何か記事を書いてみようかなとか考えて、この記事を書くことに決めました。
記事書いてるなら読みたいと言われたので、PR目的もかねて先に英語版を公開したのですが、日本語でも描きたいなぁと思ってしこしこ日本語訳しました。
[元記事]: Why we replaced Java with Typescript for Serverless in dev.to

テーマはなぜ僕たちがサーバレスプロジェクトでJavaを諦めてTypescriptを採用したかについてです。

はじめに

サーバレス(serverless)は昨今もっとも注目を集める設計手法の一つで、おそらく多くの開発者が大なり小なり自分のプロダクトに応用し始めているのではないでしょうか?

僕自身、完全にサーバレスに魅せられてしまい、昔ながらの自分でサーバやミドルウェアを管理しながら運用するみたいな世界には戻れる気がしません。

そもそもスケーリングや分散可能性をきちんと考えて設計されたアプリケーションであれば、旧来のサーバーアプリケーションの機能から受けられる恩恵も比較的少なくなりますし、サーバレスに切り替えるデメリットはそこまでありません。

最近は設計に関して相談された時は、必ずサーバレスの話題を出してみることにしています。

さて、サーバレスは既存の開発手法とは大きく異なるため、今持っている知識を刷新し、既存の手法や技術スタックを見直しながら開発していく必要があります。

見直しというからには、開発基盤として何の言語を使うかも、当然ながら見直さなくてはいけない技術スタックの対象になります。

タイトルにある通り、最終的に僕たちはTypescriptを採用し、およそ一年半開発・メンテナンスを行ってきました。
そして一年半経った今、あくまで個人的な感想ではありますが、Typescriptは僕たちが期待した以上に成果を出してくれました。

そこでこの記事では、以前使用していた言語にどんな問題があったのか、そしてなぜTypescriptに切り替えたことでどんな恩恵があったのかをこの記事では解説していきたいと思います。

なぜJavaを諦めなくてはならなかったのか

さて、なぜTypescriptを採用したかについて話す前に、まずなぜ以前使用していた非常に強力な言語であるJavaを諦めなくてはいけなかったかについてお話ししたいと思います。


NOTE

先に述べておきますが、僕は結構なJava好きです。なんなら初めて触った言語もJavaでした。
JVMに関してもそれなりに勉強して、その神がかったランタイムの仕組みにかなり感銘を受けています。(てか多分作ったやつは神)
なので、どこかの大学生のようにJavaがクソだとかレガシーだとか使い物にならんとか、この記事でそういうことを言うつもりは一切ありません。
また、そういったコメントもあまり嬉しくないです。あくまでサーバレスという仕組みにJavaがあまり合わなかっただけなので。
その点だけはご了承いただければ幸いです。


さて、本題に戻りましょう。

僕たちのサービスでは、サーバサイドはサービス設立当時から基本的にJavaだけで書かれていました。
当然ながらすでにJavaには多くの利点があり、特に

  • プラットフォームフリー
  • よくできたJITコンパイル
  • やばいGC
  • よく構成された文法
  • 静的型付け
  • 関数型サポート(最近は特に)
  • 多様なライブラリ
  • 信頼できるコミュニティ(Oracleではなく、開発者の方)

などなど挙げればきりがありません。

しかし、AWS Lambda上でコードを試していて気づいたのですが、Javaはあまりサーバレスに向かないことがわかりました。

理由としては以下のことが挙げられます。

  • JVMの起動オーバーヘッドが大きい
  • Springフレームワークを使用してるとさらにエグくなる
  • 最終的なパッケージアーカイブがでかすぎる(でかいのは100MB以上)
  • 関数が増えてくるとWebフレームワークなしでリクエストを捌くのがきつくなる
  • コンテナは30分程度しか走らないため、G1GCやJITなどのJavaの利点が生かせない
  • Lambdaは基本的にEC2上に建てられたAmazon Linuxのコンテナで動くため、プラットフォームフリーは関係ない。 (欠点ではないけど)

上述の点は全てなかなかに厄介ですが、今回は特に厄介だった問題についてもう少し書いてみたいと思います。

Cold Startがまじで厄介

一番厄介だったのは、圧倒的にCold Startのオーバーヘッドです。おそらく多くの開発者の方々もこいつに悩まされているのではないかと思います。。。

僕たちはコンピューティング基盤としてAWS Lambdaを使っていたのですが、AWS Lambdaはユーザからのリクエストが来るたびに新しいコンテナを立ち上げます。

一度立ち上がってしまえば、しばらくは同じコンテナインスタンスを再利用してくれるのですが、初回起動時にはJavaのランタイムに加え、フレームワークで利用されるDIコンテナやWebコンテナなども全て初期化する必要があります。

さらに言えば、一つのコンテナで処理できるのはあくまで一つのリクエストのみで、複数のリクエストを処理することはできません。(内部で数百のリクエストスレッドをプーリングしてたとしても同じです。)

つまりどういうことかというと、もし複数のユーザがリクエストを同時に送ってきた場合、Lambdaは起動中のコンテナの他に、別のコンテナを起動しなくてはいけなくなるということです。
通常、僕たちはどの時間に具体的に何軒のリクエストが同時に来るかを事前に予測することはできません。
つまり、何らかの仕組みを作ったとしても、事前に全てのLambdaをhot standbyさせることはできないのです。

これは必然的にユーザに数秒から10秒以上の待機時間を強制し、ユーザビリティを著しく下げることにつながります。

こんな感じでCold Startがえげつない事を理解した僕らは、これまでの数年かけて書かれた技術スタックを捨てて、 他の言語を選択することを決めました。

なぜTypescriptを選んだのか

めちゃくちゃ恥ずかしい話なのですが、正直Lambdaでサポートされている全ての言語をきちんと精査・比較して決めたわけではないのです。 ただ、正直な話、状況的にTypescript以外の選択肢はなかったのです。

まず第一に、動的型付け言語は外しました。長期に渡ってスキルのバラバラな開発者によって保守・メンテ。拡張されるコードなので、動的型付けはあまり使いたくありません。

したがって、PythonRubyに関してはかなり序盤で選択肢から外れました。

C#Goに関しても、現在ほとんどのチームがJavaをメインに開発しているサービスなので、既存言語とあまりかけ離れた言語を使うと新規開発者のジョインが難しくなると判断し、今回は見送られました。

もちろん、昨今この二大言語は非常に注目度が高く、特にGolangに関しては徐々にシェアを伸ばしつつあるのは知っています。

しかし、急いでサーバレスに開発を移す必要があったため、僕たち自身のキャッチアップの時間も考慮し、見送らざるを得なかった感じでした。

Typescriptの利点

という事で、僕たちはTypescriptを使い始めたわけです。
Typescriptの利点を挙げるとしたらこんな感じでしょうか?

  • 静的型付け
  • 小さいパッケージアーカイブ
  • ほぼ0秒の起動オーバーヘッド
  • Javaとjavascriptの知識が再利用できる
  • NodeJSのライブラリやコミュニティが使える
  • javascriptと比べても関数型プログラミングがしやすい
  • ClassとInterfaceにより構造化されたコードが描きやすい

長期に渡って運用・開発が行われるプロジェクトにおいて静的型付け言語がどれだけ大きな恩恵を与えるかは今更語るまでもありませんので、ここには書きません。
ここでは主に、Typescriptのどういった点がサーバレス開発によく馴染んだかについて書いていきたいと思います。 静的型付け以外にもTypescriptを使う利点は非常に大きく、

小さいパッケージと小さい起動オーバーヘッド

おそらくサーバレスでTypescriptを使う利点という観点からいうとこれが一番大事だった気がします。(なにせ他のメリットはほぼTypescript自体のメリットなので・・・)

先ほど触れた通り、JavaはJVM本体やフレームワークが利用するDI/Webコンテナなどの起動にかかるオーバヘッドが非常に大きいです。 加えて、Javaの性質上、AWS Lambdaで流すには以下の Additionally, as the nature of Java, it has the following weak point to be used in the AWS Lambda.

マルチスレッドとそれを取り巻くエコシステム

マルチスレッドは非常に強力な機能であり、事実として僕たちはこの機能のおかげで多くのパフォーマンス問題を解決してきました。
JVM自体もガーベージコレクションやJITコンパイルにおいて、デフォルトでにマルチスレッドを活用してあの素晴らしいランタイムを実現してます。
(詳しくはG1GCJIT Compileを参照)

しかし、起動時間単体で見ると、アプリケーションに使用する全てのスレッドを立て終わるまでに、100ミリ秒から数秒かかっていることがわかります。
この機能自体は旧来のいわゆるクラサバモデルでEC2上で動くアプリケーションならほぼ無視できるオーバーヘッドですが、LambdaなどのFaaS上で動くサーバレスアプリケーションでは決して無視できません。

Typescriptはnodejsベースであり、基本的にシングルスレッドです。非同期は別スレッドや別プロセスではなくあくまでジョブキュー、イベントループなどで管理されます。

したがって、ほとんどのライブラリやフレームワークは起動時にスレッド展開をする必要はありませんし、ランタイムを起動するためのオーバーヘッドもほとんどかかりません。

巨大なパッケージアーカイブ

サーバレスにおいてソースコードのパッケージアーカイブは、基本的に小さいに越したことはありません

Lambdaのコンテナは起動時、AWSにより管理されたソースコード用のS3バケットからソースをダウンロードし、コンテナに展開します。

S3からのダウンロード時間は通常非常に短時間ですが、100MBや200MBとなると無視はできません。

NodeJsのパッケージは基本的にJavaに比べて小さくなります。

正直なんでそうなるかに関しては不勉強でわかっていないのですが、以下の理由が関係してるんじゃないかなと思ったりします。(もしこれやでっていうのをご存知の方はコメントで教えてください)

  • Javaのフレームワークやライブラリは包括的なものも多く、本来使いたい機能に必要ない依存性を引き込んで来るが、javascriptは目的特化のライブラリが多く、必要最低限に依存を抑えられることが多い。
  • Javascript(nodejs)は1ファイルに複数のmoduleを書くことができ、それでいてメンテもしやすいが、Javaにおけるメンテナンス性の重要なポイントはファイル分割とパッケージ管理のためソースが肥大化しやすい。

実際Javaで書いていた時は最大で200MB以上のパッケージができることもあったのですが、nodejsに変えてからは35MB程度で済んでいます。

この巨大なパッケージアーカイブは、僕たちがSpringで書かれた旧来のコードを再利用しようとしたのが大きな原因なのですが、実際これらのいらないフレームワークを除いて最適化したコードでも、どうしても50MBは必要になってしまいました。

Javascriptの知識やエコシステムを利用できる

僕たちもWeb開発者のため、基本的にフロントエンドも書きます。したがって、ある程度のjavascriptやnodejsに関する知識は蓄えていました。

Jquery全盛時代からReact/Vueのようなモダンフレームワークでの開発までを通じて、言語的な特徴はある程度抑えていましたし、どうやって書けばいいコードになるかもある程度理解してるつもりです。

Typescriptはjavascriptの拡張言語であり、最終的にはjavascriptにトランスパイルされます。

多くの文法やイディオムはjavascriptから受け継がれているので、実際それほど準備期間を要さずにサービス開発を始められました。

加えて、ほとんどのメジャなNodeJSのライブラリはTypescriptに必要な型定義を提供しているので、NodeJSのエコシステムのメリットをそのまま享受できたのも非常に嬉しいポイントでした。

関数型の実装が非常にしやすい

昨今の技術トレンドを語る上で、関数型の台頭はなくして語ることはできません。
関数型の実装はその性質上、シンプルでテスト可能で危険性の低い安定したコードを書くのに大きく寄与します。

特にAWS Lambdaの場合、常に状態を外部化するコードが求められるため、状態や副作用を隔離する関数型の実装は非常に相性が良く、メンテもしやすくなります。

そもそも、jqueryの生みの親であるJohn ResigがJavaScriptニンジャの極意で語ったように、javascriptはそもそも関数型のプログラミングをある程度サポートしています。
javascriptにおいて関数は関数は第1級オブジェクトであり、jqueryも実は関数型で書かれることを期待して作られています。

しかし一方で、動的型付け言語で関数型のコードを書こうとすると、時折非常にめんどくさい事になることがあります。
例えば、プリミティブ型だけで表現できる関数は非常に限られますし、返り値や引数にObjectを取るのは普通に結構危険です。

しかしtypescriptでは引数や返り値に型を指定することができます。

加えて、以下のTypescriptの機能は、僕たちの達の書く関数の表現の幅を広げ、より安全でシンプルなコードを書くのに寄与してくれます。

  • Type: 共通に使用される型をコンテクストに合わせて型付けできる。(stringUserIdPromiseResponseなど)
  • Interface/Class: Objectで表現されるの引数や返り値をコンテクストにあった型で表現できる。
  • Enum: よもや語る必要もあるまい
  • Readonly: 自分で作成した型をImmutableに出来る
  • Generics: 関数のインターフェイスの表現の幅が広がる

Typescriptは他にも関数型で書こうとした時に非常に便利な機能をいろいろ備えていますが、全てをここであげることはしません。(っていうか、結構javascript由来のものが多い)

関数型とTypescriptに関する記事は今後どこかで書いていきたいなと思っています。

Javaで学んだBest Practiceを再利用できる

typescriptの文法を学ぶと、かなりJavaやScalaに似通った記述ができることに驚きます。

僕たちはそもそも、それなりの期間をJavaで開発してくる中で、Javaにおけるいいコードのお作法をある程度蓄積してきました。
ClassやInterfaceをどう設計すべきか、enumはどう使うと効率的か、Stream APIはどう書くと保守性が上がるかなど、蓄積してきたノウハウはそれなりに捨てがたいものがありました。

Typescriptはインターフェイスやクラスに加えて、アクセスモディファイアやreadonly(Javaでいうfinalのプロパティ)をサポートしており、僕たちは割とさらっとJavaで育んだノウハウをそのまま導入することができました。

これにはオブジェクト指向的なベストプラクティスやデザインパターンなども含まれます。
(関数指向とオブジェクト指向は二律背反ではないので、プロジェクト内で同時に使用されても問題ないと考えています。個人的には。)

もし僕たちがやや文法が独特なPythonやRubyを採用していたとしたら、より品質の高いコードを書くためのプラクティスをどうこの言語に応用すべきかに多くの時間を費やすこになったことかと思います。(それも楽しいんですよ、知ってます、ただ時間がね。。。。)

当然ながら全ての設計やロジックをコピペしたわけではないですし、むしろ大半のコードを書き直ししました。
ただ、おおよその部分をスクラッチで書き直した割に、それなりの品質でそれなりの短期間で書き直しが終わったんだよということは特筆しておくべきかと思います。

結論

僕たちもまだまだTypescriptに関しては初心者といっていいレベルでまだまだ勉強が必要ですが、すでにそのメリットは全力で享受しておりいます。

今聞かれれば、Golangもいいなあとか、MicronautとGraalVMとかも面白そうだなあとか、もっと他の選択肢もあったかもなあとか考えたりもするのですが、現状Typescriptには非常に満足しており、最善の選択肢の一つではないかと信じています。

もちろん、処理遅いけどバッチ処理どうすんねんとか、並行処理とか分散処理同すんねんとか、、ワークフロウどう設計すんねんとか、API Gatewayのタイムアウトどうハンドルするねんとか、データの一貫性どう担保すんねんとか、サーバレスやTypescriptに起因する問題にはたくさんぶち当たりました。

ただ、それはそれでギークとして非常に楽しく取り組んできて、すでにいくつかのこれが今の所best practiceじゃね?っていう方法もいくつか見つけました。(これはのちのち記事にしていきたい。)

もし今Javaでサーバレスに取り組んでいて、サーバレスくそやん、きついやん、やっぱ普通にサーバ欲しいわってなっている方がいたら、ぜひTypescriptも試してみてください。想像する以上に生産性出るんじゃないかなぁって期待してます。

長文おつきあいいただきありがとうございました。何かコメントや訂正があればぜひお願いします。

Why we replaced Java with Typescript for Serverless

I’m writing this article in Tallinn, Estonia.

Since I've already worked a lot in Asian countries such as Japan, China, Vietnam, Singapore, and Taiwan, I feel like moving to other regions such as Europe.

As the first candidate, now I am coming to Estonia, where the IT industry takes place in the center of the whole country, to see how the working environment is.
After talking with several developers in local, my mind has been almost determined. With a 90% possibility, I will move here within this year.
Fortunately, the Japanese can relatively easily obtain working permission in this country. Probably gonna be here again by this Summer.

Btw, after talking with the developers here, I feel a kinda motivation for IT more than before and decided to write this article.
The theme is the reason why we started to use the Typescript for our serverless application.

NOTE
Since this is related to our business application, I do not write all the things happened to our project in detail and some backgrounds are manipulated.
However, I believe that the tech-related parts are all the fact and I tried to write as precisely as possible.
I hope this article will help you gain some knowledge and solve your problem on serverless shift.
If there is some misunderstanding in this article, please feel free to give me a comment. Will fix it immediately after self-verifications.

Introduction

Serverless is one of the most modern and highlighted software architecture and recently more and more developers are starting to using it in their own application or services.

I also am loving it a lot now and I cannot think of getting back to the self-managed server model anymore.
Basically, if your application is well designed for scaling and distributing, most of the feature we rely on the server application has lost its benefit, I believe.
So these days, I always encourage serverless if soebody asks me about the architecture or designs of web service.

Btw, since it is a totally different approach from the traditional development method, Serverless requires us to refresh our knowledge and review the tech stacks we've been using.

What language we should use also is one of the things we need to review.
Finally, we started to use *Typescript and have been working with it for more than 1 and a half years.
And, as just a personal opinion impression though, it was much nicer than we expected it to be.

So I would like to write what was a problem with the old tech stack and what was good after switching it to Typescript.

Why we needed to give up Java

Before talking about the reason for choosing the Typescript. I would like to explain the reason why we gave up the previous tech stacks with one of the most excellent languages, Java.

**NOTE**

In the first place, I'm an enthusiastic Java lover and my mother tongue also in Java. (Java 4 or 5, when there was no generics feature.)  
I have studied about the JVM and was inspired a lot from it as well. I guess it was made by god.  
So here I do not mean to despise or insult Java at all.  
Any comments or complaints about Java are not welcomed, just it didn't work well with serverless at the moment.  

Ok, sorry, let's go ahead.

We’ve been using Java as the primary language for our service for a long time and we actually know that Java has a lot of advantages like

  • Platform-Free
  • Well designed JIT compile
  • Excellent GC
  • Well-structured grammar
  • Type strong
  • Supports functional programming recently
  • Having a lot of libraries
  • Trustable communities.(Not Oracle, but developers community)

and etc..
We really appreciated it and rely on them a lot.
However, when we tested our code with serverless, we found that Java is not too good to be running on the FaaS service such as AWS Lambda.

The reasons are the following.

  • The overhead to launch the JVM is not ignorable.
  • Moreover, our primary framework Spring took more time for launching containers.
  • The final package of source code is relatively large. (Sometimes more than 100MB)
  • Hard to proxy the requests without using web framework when the number of functions increased
  • G1GC or JIT compile not works well since the container stops very shortly
  • Can not enjoy the benefit of the platform free since it always running on EC2 with Amazon Linux image. (Not cons, but just reduced the reason to use Java)

All the problems listed above were so annoying, but here I wanna explain the most troublesome one of the above.

Cold Start of Lambda is too troublesome

The most troublesome thing we faced at first was the overhead of the cold start. Yeah, I guess most of the serverless developers may have faced the same issue.

We used AWS Lambda for computing and AWS Lambda launches the container every time a request comes from users.
Once it is launched, it reuses the same container instance for a while, but in the initial launch, it needs to launch the Java Runtime environment and all the necessary web container or environments of frameworks.

Additionally, one container can be used to handle just a single request and cannot be used for multiple requests concurrently even though your application is ready with hundreds of request threads in your thread pool. It means that when several users send the request to the endpoint at the same time, AWS Lambda needs to launch another Lambda container to handle the other requests.

It was so troublesome actually since normally we cannot estimate the number of concurrent requests and hot standby mechanism doesn't work. (even if we make it somehow.) Eventually, it will force users to wait for several seconds to open the page or process the request and we were sure that it will surely degrade the user experience.

After seeing how the cold start is annoying, though we’ve already had a lot of codes written in the past several years, finally, we gave them up all and switched to use another language.

Why we chose Typescript

Actually, it is a bit shameful though, we’ve decided to use the Typescript from a really early phase without deep thought or comparison with other languages.
However, honestly, we have no choice of using other languages supported by Lambda from the beginning other than Typescript under that circumstance.

At first, we have no choice to use dynamic typing languages. The service and code are supposed to be running, supported, maintained and extended for a long time by variously skilled developers. So we would not like to use the dynamic typing languages for serverside.

Thus, Python and Ruby were out of options.

C# and Go have a totally different character from the language we (and other teams) were working on and it may take some time for other newbies to catch up.
Of course, we all were aware that these days those 2 languages, especially Golang is winning the share gradually thanks to its nature.
However, the arch change was a too immediate mission and we didn’t have much time to catch it up for ourselves as well. Thus, though those 2 languages were fascinating for us, we gave up using those langs.

Benefits of using the Typescript

So finally, we have decided to use Typescript.
The benefits of Typescript are as the following.

  • Type Strong
  • Much small size package
  • Super fast launch overhead
  • Able to reuse the knowledge of javascript and Java
  • Node libraries and communities are awesome
  • Suitable for functional programming even compared with the javascript
  • Able to write well-structured codes with Class and interface

As everybody knows, static typing is quite an important factor for the long-running project like B2B so I do not write much about it here. Here I wanna explain how the Typescript worked well with. With other features of the typescript, the type really works well more than we expected.

Less overhead to launch with small packages

Probably this is the most important factor to switch from java to Typescript in serverless. (Other benefits are almost about the benefit of using the Typescript itself)

As mentioned in the previous part, Java has overhead to launch the JVM and DI/Web container for the framework.
Additionally, as the nature of Java, it has the following weak point to be used in the AWS Lambda.
Typescript doesn't have those weak points and it resolved our concerns.

Multithreading and its eco-system

Multithreading is a powerful functionality of Java and it really helps us implement the high-performance codes.
Even the JVM itself is using it for the garbage collections to provide great performing runtime.
(See G1GC or JIT Compile)

However, you will find it takes from 100s milliseconds to several seconds to prepare for all the thread used in the container.
It is small enough and ignorable for the ordinal architecture like client-server running on EC2, but totally not ignorable for serverless applications which is running on the FaaS like Lambda.

Typescript is based on the nodejs and it only supports single thread by default. (Async or Sync is just controlled by call stack, not by thread)
Thus, the time to launch it is much short than Java with modern frameworks.

Big Package Archive

In serverless, normally, a small-sized package is preferred.

When the lambda container is launched, the container downloads the source code from the AWS managed source bucket in S3.
Time to download the S3 is normally small, but not ignorable if it is 100MB or 200MB.

With nodejs, the code size of a package could be relatively small compared with Java.

Honestly, I am not too sure why it is even now, but probably for the following reasons. (Please teach me in a comment if you know more.)

  • Java frameworks are usually comprehensive and can contain a lot of dependent libraries to cover everything, but javascript framework or libraries are more like on-the-spot and doesn't contain unnecessary files so much.
  • Javascript can write multiple modules or functions in one file and can maintain it with less effort, but Java requires to design the classes and interfaces with multiple files to write maintainable and well-structured code.

Actually, when using Java, the packaged jar was nearly 200MB at the biggest.
However, with using the nodejs, it could be reduced to 35MB+ at last.

It was partly because we tried to reuse the Spring Tech stack in the previous arch.
However, even after removing the unnecessary dependency and optimization, a package for one function still required 50MB.

Able to use the knowledge and eco-system of javascript

As we have been working on the web service, we have some kinda stacks of knowledge about javascript and nodejs.

Through the era of Jquery to the modern javascript like React or Vue, we’ve already learned the pros and cons of it and have obtained some know-how to write good code in javascript.

Typescript is a kinda extensive language of javascript and will be transpiled into javascript at last.
Therefore, many of the idiom or grammar is extended from the javascript and we could easily start to write the code without many preparations.

Additionally, most of the useful libraries are providing its type definition for the typescript so that we were able to enjoy the benefit of nodejs eco-system as well.

Works well with the functional programming paradigm

Functional programming is quite an important paradigm when we are talking about the tech trend these days.
It will let you write simple, testable, less-dangerous and stable codes with its nature.

AWS Lambda always requires us to get rid of the state from our code. Functional programming is requiring us to isolate the side effect or state from the functions and this idea surely is making our codes for Lambda more maintainable.

Basically, as John Resig told in Secrets of the JavaScript Ninja, javascript is supporting functional programming from the beginning.
It treats the Function as the first-class object and jquery also were supposed to be written in a functional way as well.

However, plain javascript is a dynamic typing and it sometimes introduces some difficulties to write good functions.
The variety of functions we can express with a single primitive type is quite limited and using the Object type for the arguments/return value is sometimes troublesome.

With typescript, we can specify the type of arguments or return value.

Additionally, following functionalities lets you write the code more safe, simple and expressive.

  • Type: Lets you distinguish the common type and its aspects such as string and UserId or Promise and Either.
  • Interface/Class: Lets you organize the sets of the arguments/return type as suitable for the context in the service.
  • Enum: No explanation necessary I guess.
  • Readonly: Lets you make your objects immutable.
  • Generics: Lets your functional interfaces be more expressive.

Typescript has more advantages for the functional programming, but do not mention them here all. (Partly because it's the advantage of javascript rather than Typescript..)
Please try it and enjoy your discoveries.

Able to reuse the best practice we used in Java

Once you see the tutorial of the typescript, you would find it is quite similar to the Java or Scala.

We’ve been trained how to write good code in Java through our long journey with them to some extent.
We were aware of how we should design the classes and interfaces, how to use enum efficiently, how to make the stream API maintainable in Java and it was not the thing we can throw away instantly.

Thanks to the similarity of Typescript and Java, we could easily take the previous practices over to the new codebase.
Typescript supports the interfaces, classes, access modifier, and readonly properties(equivalent to the final of property in Java) and It actually helped us a lot to reuse the best practices we learned in Java including Object-oriented programming practices and the Design Patterns. (FP and OOP are not antinomy and can be used in the same project, I believe. )

If we would have chosen Python or Ruby, probably we needed to struggle again to find how to apply the practices into the new language for a long time,
(Actually, of course, I know it’s a lot of fun, but not for the time in hurry arch change)

Of course, we didn’t do the copy-paste of the logics in the existing java classes.
However, even though we re-wrote them with 80% from scratch, it didn’t take much time to write it again with acceptable quality.

Conclusion

We are still new in the journey of Typescript and needing a lot to learn, but already found a lot of benefits of it and we really are enjoying it.

If asked now, probably using the Golang can be an option, using the Micronauts with GraalVM also can be an option or maybe there can be more options we can choose. However, I am really satisfied with the typescript so far and believe it is one of the best options we can choose in serverless.

Of course, already have faced some difficulties with Typescript and serverless like how to do the batch processing with relatively slow language, how to do the concurrent computing or distributed processing, how to make the workflow, how to overcome the timeout of API Gateway or how to ensure the data consistencies.

However, all those things are the most interesting things for us, geeks, to resolve.
Actually, we already have found some practices and have overcome them. I will write it in the near future.

If you are struggling with Java on serverless and losing hope for serverless, I strongly suggest you consider Typescript. I can promise that it will work better than you expect it to be.

Thanks for reading through this long article. I am happy to receive your comment or contact if any.

Exceptionをもみ消すなってどうせえちゅうねんって話

QiitaのJavaアドベントカレンダー14日目になります。

若干時間オーバーです、ごめんなさい、時差的にこちらではセーフなので許してください。 うそやんけ、僕の担当14日でした。 今日15日・・・圧倒的遅刻・・・・ごめんなさい!!!!

そういえば、最近開発者コミュニティに入りました。 初学者が多めのコミュニティのようですが、それでも勉強になることが多く、参加してよかったなと感じています。

主にSlackで活動しているようです。

海外からでも参加できるため、非常にありがたいです。 僕は現地語がまだ喋れないので、現地の開発者コミュニティにはまだ参加できていないんですよね。。。。

さて、というわけで、今回は初学者向けのエラーハンドリングの記事を書いていきたいと思います。

基本的にはJavaで書かれていますが、他の言語でも参考になることはあるかと思いますので、初学者でもみ消すなに手をコマネいている方はぜひ読んでみてください。

そもそも揉み消しとは

仕事でコードを書き始めた人は、先輩やマネージャから「Exceptionをもみ消すな」という言葉を聞いたことがあるかと思います。
具体的に言うと、こんな感じのコード。

static void momikeshiTheException(SomeObject input) {  
  try {  
    someIoOperation(input);  
  } catch (IOException e) {  
    e.printStackTrace();  
  }  
}  

ひどい場合だとこう。

static void momikeshiTheException(SomeObject input) {  
  try {  
    someIoOperation(input);  
  } catch (IOException e) {}  
}  

初学者のうちはExceptionの扱い方がわからずこういったコードを書いてしまいがちです。
なんなら、僕も学生の頃はよく書いてました。

このコードには以下のような問題があります。

  • エラーが発生した事実が呼び出し元に通知されないため、呼び出し元は成功したものとして処理を続けてしまう。
  • エラーの詳細が記載されていないため、デバッグが困難になる

初学者や自習で学んでる人たちには、現場でどんな不都合が起きるかイメージしづらい部分があるかもしれないので、具体的なケースを挙げてみる。

だらだら長いので、読みたくない方は飛ばしてください笑

簡単に説明すると、レストランの発注システムでExceptionがもみ消されたせいで、季節限定メニューのオーダーが厨房に届かずキャンペーンが失敗して、そのデバッグに受注したフリーランスの私が苦しむ話です。

コスタリカブルーの悲劇

喫茶店コスタリカブルーでは、2015年の開店以来、ずっと手書きの伝票を使っていた。

しかし、スマホ決済が徐々にメジャとなってきていること、また来年には新たに支店を開設することを鑑み、2019年の夏から注文・決済・記帳フローの自動化に踏み切った。

友人のつてで紹介されたフリーランサーによって作成したアプリでは、スマホで注文を受けを受けることができ、注文内容はキッチンとレジスターに送られる。

会計は現金もしくは任意のスマホ決済で行え、それぞれの会計内容は帳簿に自動で記帳される。

夏に導入してから半年、初期のバイトの子たちへの講習に手間取った以外に大きな問題はなく、システムは概ね好調に稼働しているように思われた。

しかし、10月に入り事態は一変した。

コスタリカブルーでは、10月に入り、ハロウィンにあやかり、ハロウィンランチセットの提供を始めた。

ハロウィンランチを注文した顧客は、パスタ・サンドウィッチ・ロコモコのうちからメインを1品を選ぶことができる。

事前の試食会での反応は上々で、料理長をはじめとするスタッフ一同は、それなりの手応えを感じていた。

しかし、彼らの期待に反し、ハロウィンランチセットの提供初日、店内は大混乱した。

ハロウィンランチセットのオーダーがキッチンに届かないのである。

正確には、パスタセットは届くのだが、サンドウィッチとロコモコが届かない。

厨房スタッフは何も知らないまま来たものを順番に作り、フロアのスタッフは何か遅いなと思いつつも提供を続けた。

20分後。あるテーブルからクレームが入る。

四人のグループ席で注文したハロウィンセットのうち、ロコモコとサンドウィッチを頼んだ三人の分がまだ来ていないと言う。

残ったパスタを頼んだ一人も、食べずに残りの三人の分を待っていたようで、すでにパスタの表面は乾き始めている。

フロアスタッフは厨房に駆け込み、いつ出来上がるかと厨房スタッフに尋ねるが、厨房スタッフはそんなオーダーは入っていないという。

何かがおかしい。

フロアスタッフはシェフに伝票を見せ、急ぎでロコモコとサンドウィッチを作り始めてもらうよう頼み、お客様の待つテーブルに戻る。

しかし、四人組は今から作るならもういらないと、怒りながら店を後にしてしまった。

それもそのはずである。
コスタリカブルーのランチタイムのメインターゲットはオフィスレディたちであり、少ない昼休憩を使ってコスタリカブルーでランチを取ってくれている。

時間になればオフィスに帰り仕事に戻らなくてはいけない彼女たちに取って、今から作り直すなど、10分とて待つのは難しいのである。

また、このやりとりを見ていた周りの客たちも口々にハロウィンセットのオーダーをキャンセルし、店を後にした。

システムが故障しているらしいと察したフロアのスタッフは、持っていたペンとメモ帳でオーダーを続けたが、すでにランチタイムは終了間近。
結局初日のランチセットは売り上げがほぼないまま終了してしまった。

その夜、連絡を受けたオーナーはフリーランサーに連絡し、翌日までにバグの修正をするよう依頼した。

フリーランサーは深夜の連絡に気を滅入らせながらlogを確認した。
エラーログにはいくつかスタックトレースが出力されていたが、どれも時間が記載されておらず、また問題が起きたクラスの行と名前以外何もわからない。

会計未終了通知サービスクラス。これは違うだろう。
帳簿の勘定項目のDaoクラス。これは毎月月末にバックグラウンドで深夜に走るバッチで使うクラスだ、違うだろう。

……(2時間後)

オーダーの備考マスターのDaoクラス。
もしかしてこれか?

オーナーに連絡し、DBの閲覧許可をもらい、オーダーの備考マスタを確認する。

無い。

ハロウィンランチセットの厨房への注文票に追加で出力される備考マスターに何もデータがないのだ。

備考マスタはハロウィンセットのような1つのメニューに複数の選択肢がある場合に使われるマスターだ。

幸か不幸か、これまでコスタリカブルーはそのようなセットを設けてこなかった。
したがって、このテーブルを利用される機会がなく、半年間このバグに遭遇することがなかったのだ。

このマスタが存在しないと言うことは、備考登録時に何らかのバグが発生し、登録が完了しなかったに違いない。

さらに過去のログを見る。
時刻はすでに二時を回っている。

……2時間後
見つけた。備考マスタを登録するときに、ClassCastExceptionが吐かれている。

しかし、何がcastされているんだ…

例外はわかった。しかし、Inputがわからない以上、ソースを追うしかない。
時刻は午前4時。明日の仕事には確実に響くだろう。

結局、1時間にわたるデバッグの結果、テーブルに保存するEntityクラスのプロパティのenumが、きちんとvalueに変換せずそのままDBにinsertされていたため、enumのordinalで保存しようとしていたのが原因だった。

修正は非常に簡単なため、30分で修正を終え、テスト後デプロイし直した。

時刻はすでに6時。彼は睡眠を諦め、レッドブルを買いにコンビニへ向かうことにした。

ではどのように例外を取り扱えばいいか

さて、上記のような悲劇を避けるために我々はどのようにExceptionを扱えばいいのでしょうか。

そもそも論でいうと、実はこのエラーハンドリングが商用プログラムを書く上で最も重要になってくる箇所であり、このエラーハンドリングをいかに上手くやるかが腕の見せ所だったりします。

この記事では、僕が経験的に例外はこう言う風に扱うといいよと言う例をいくつか提示していこうと思います。

鉄則と言うか絶対やらなきゃいけないこと

例外が発生した場合、開発者が絶対にやらなくてはいけないことがあります。

具体的にいうと次の二つ。

  • エラーの内容を、いつ、どんな状況で、何の入力に対して例外が発生したかをlogに出力する
  • エラーが発生したことを呼び出し元のクラスやユーザーに対して通知する。

この2つのうち、2番目に関してはこの後、順番に例示していきますが、1つ目に関しては非常に明快です。

すぐできます。

ほとんどの言語にはloggerを提供するライブラリが存在するため、そういったライブラリを用いてlogを出力していきます。

Loggerライブラリは様々ありますが、基本的にはプロジェクトの標準で使っているloggerライブラリを使えば問題ないかと思います。

オレオレで自分の好きなライブラリを勝手に使い出すと別の意味で怒られます。

他の人のコードなどを参考に何が使われているか調べましょう。

System.out.printlnやconsole.logが標準ならそのプロエジェクとはもうダメだ。転職を考えよう。

logに何を出力するか

さて、冗談はさておき。。

そういったライブラリは、基本的に発生時間と発生クラスをプリントし、また発生した例外を引数に加えることでスタックトレースを出力してくれます。
したがって、あとはメッセージにinput内容と、何をしようとした時に起きたかを書けば最低限何かが起こっても対応出来ます。

例えばこんな感じ。

static void momikesanaiTheException(SomeObject input) {  
  try {  
    someIoOperation(input);  
  } catch (IOException e) {  
    log.error("Exception occurred while calling the someIoOperation with the input {}", input, e);  
  }  
}  

inputに関しては必要に応じてマスクしたり、プロパティを絞ったりする必要があるが、何の入力に対してエラーが発生したかを記述しておくことは、圧倒的にdebugコストを下げます。
したがって、パスワードや個人情報などには留意した上で、inputも出来るだけ詳細に出力するように心がけるべきです。

もっと言うなら、影響範囲や起こりうる障害、対応方法なども書いておくと、将来別の開発者の手にメンテが渡った時に役立ちます。
これに関してQiitaで自作のライブラリを作ったって言う超良記事があったのだけど、いつの間にかストックから消えていたので、知ってる人がいたらコメント欄に書いてもらえるととっても嬉しい、です。

Loggingに関していえば、それだけで僕でも2、3記事書けるくらい実は奥深いコンテンツなので、今回はいったんこの程度でまとめさせていただきます。

logの出力に関する注意

初学者向けの記事につき、一応注意喚起しておきます。
商用含め、自分以外に公開されるのプログラムでは、ユーザの個人情報やパスワード、クレジットカード情報など、logに出力してはいけない情報が山ほどあります。

ちなみに、Facebookですらやらかしてます。
参考: Facebookが数億人のパスワードを平文で保存していたと認める

こういった情報は必ずマスクするか、出力から外すようにしましょう。

Javaでは、Objectをloggerや標準出力に渡すと、instanceをtoStringした値を出力します。

したがって、toStringを適切に実装してやることが非常に重要です。
lombokであれば@ToString.Excludeをプロパティにつけてやることで、toStringの対象から外すことができます。

気づけば当たり前のことなので、logを出力する際には、注意して実装してください。

また、出力していいかわからないときは、上司やクライアントに必ず確認をとるようにしてください。

あ、パスワードとかクレジットカード情報とかは、上司やクライアントがいいっていってもダメです。

呼び出し元に通知する方法

さて、ここからは、呼び出し元のクラスやユーザに対して失敗した事実を通知する方法に関して書いていきます。

僕が一番使っている期間が長いことや、Javaアドベントカレンダーの記事であることを鑑みて、今回はJavaでかかせていただいております。

他の言語の方には申し訳ありません。。(´・ω・`)

後半にいくにつれて実装難易度(って言っても大したことないけど)が上がっていくように書こうと思っています。現時点では。

また、それぞれの実装方法に僕の独断と偏見で以下のようなランクづけをしました。

この基準が役に立つかは知らないし、状況や実装ロジックによって安全性や実用性は変わるので何とも言えません。

ただ、なんとなく実装の安全さや実用性に関してイメージを掴んでもらえたらいいなと言う意味でつけています。

  • 実装難易度:
    実装の難易度や工数など。高いほど言語に対する理解が必要になり、書かなくてはいけない行数も増える。
  • 実用性:
    実際の現場でどの程度使われるか。難しい実装でも、そこまでする必要はないとか、逆に簡単な実装でもそれでは不十分と判断されることも多いため。
  • 安全性:
    呼び出し元に、プログラム的な意味でどれだけ正確な情報を伝えられるか。正確な情報を呼び出し元に伝えることで呼び出し元はより柔軟に呼び出しの失敗に対応できる。

1: booleanで結果を返す

実装難易度: ★
実用性: ★★
安全性: ★

説明

1つ目は、成功した場合trueを、失敗した場合falseを返すと言う実装です。
非常に簡単で、今この瞬間からもできるので、複雑な実装をしている時間がないと言う場合は、最低限このくらいはするように実装して欲しい。

static boolean momikesanaiTheException(SomeObject input) {  
  try {  
    someIoOperation(input);  
    return true;  
  } catch (IOException e) {  
    log.error("Exception occurred while calling the someIoOperation with the input {}", input, e);  
    return false;  
  }  
}  

このような実装にすることで、最低限、失敗した場合falseが帰ってくることで、呼び出し元のクラスは失敗した場合の処理を流すことができます。

この実装は呼び出し元に失敗の原因が通達されていないため、若干心もとないですが、失敗する条件が明確な時はこれでも十分かと思います。
例えば、JavaのSetのaddなどは、すでに同じ要素がCollectionに存在する時falseを返しますね。

いつ使うべきか

基本的にはあまりおすすめはしませんが、以下のようなケースでは使えるかと思います。

  • 他の開発者もしくはかつての自分が実装したもみ消しを発見してしまい、急ぎ挙動を修正する必要がある場合
  • シンプルかつアトミックなことが自明な場合。

2: Optionalで包んで結果を返す。

実装難易度: ★★
実用性: ★★
安全性: ★

解説

これもほぼbooleanと同じですが、booleanより使える箇所がやや限られます。
Optionalは、詳しくは他の記事を漁って欲しいのだけど、ある操作に対して要素が存在するかしないか不定の時に使われます。
したがって、Optionalで返していいのは、例えば指定したIDやKeyに対して要素が存在しない時に例外が投げられるケースが基本です。
例えば、FileNotFoundExceptionやNoSuchElementExceptionなどはoptionalで包んで返してもいいと思います。

static Optional<FileReader> momikesanaiTheException(SomeObject input) {  
  try {  
    return Optional.of(new FileReader(new File(input.getTargetPath())));  
  } catch (FileNotFoundException e) {  
    log.error("File not found to the path {}", input.getTargetPath(), e);  
    return Optional.empty();  
  }  
}  

実装の手間がbooleanとそう変わらないのに実装難易度を高めにつけたのは、 このOptionalを使っていいかと言う判断が多少経験を要するからです。

いつ使うべきか

正直、上の解説を書いててエラーハンドリングでOptional使うのは微妙かなと思い始めたのですが、以下のSOの記事で条件付きでですが、そこそこ支持されていたので、一応書いてみます。
参考: Can I use std::optional for error handling?

  • IOを伴う操作で指定したリソースが存在しない、もしくは取得できない可能性がある場合
  • 例外を投げたくない場合

なお、後述のEither型の方がより正確に呼び出し元に何が起きたかを伝えられるため、個人的にはEither型をオススメしています。

3: 別の例外を実装し、投げられた例外を包んで呼び出し元に投げる

実装難易度: ★★★
実用性: ★★
安全性: ★★★

解説

ロジックの中で呼び出したメソッドが非チェック例外(※1)を投げる場合、チェック例外(※2)などに包んで返すのも1つの手段です。
呼び出し元にExceptionのハンドリングを委託し、自分のロジック内でのハンドリングを諦めるパターンです。

この場合、呼び出し元のloggerにメッセージが出力されるため、鉄則と言うか絶対やらなきゃいけないことで書いたlogは省略することができる場合もあります。

static void momikesanaiTheException(SomeObject input) throws InvalidInputException{  
  try {  
    someOperation(input);  
  } catch (InvalidSyntaxException e) {  
    throw new InvalidInputException(input, "Input contains some invalid value and some Operation failed.", e);  
  }  
}  
public class InvalidException extends Exception{  
  private final SomeObject input;  
  public InvalidInputException(SomeObject input, String message, Throwable e) {  
    super(message, e);  
    this.input = input;  
  }  
}  
  

チェック例外などと書いたのは、非チェック例外を投げるケースも少なからずあるためです。

非チェック例外を投げる場合は、必ずJavadocのthrows欄に明記するようにしましょう。

※1:RuntimeExceptionもしくはそれを継承した、呼び出し元でtry-catchしなくてもコンパイルエラーが起きない例外。呼び出し元に瑕疵がある場合に投げられることが多い。NullPointerExceptionやIllegalArgumentExceptionなど。

※2:Exceptionもしくはそれを継承した、呼び出し元でのtry-catchを義務付ける例外。I/Oでのエラーやプログラムの実行ユーザーが権限を持っていない場合など、ロジックやinput由来ではない時に投げられることが多い。IOExceptionやExecutionExceptionなど。

いつ使うべきか

実際の開発現場ではこのように独自の例外を実装して呼び出し元に返す方法はよく使われています。
あえて使用場面をあげるなら以下のようになります。

  • 発生した例外に対し、自分の実装したメソッド内での消化が難しい場合
  • 呼び出し先クラスがチェック例外を投げるべきところで非チェック例外を投げている時
  • 例外のハンドリングに自信がない場合(早く抜けてください。許されるのは最初だけです。)

余談

僕は過去ベトナムの開発会社にいた時、JavaDocが全く書かれていないにも関わらず、何でもかんでも例外を非チェック例外に包んで投げまくるのがデフォルトと言う、地獄のようなプロジェクトに参加したことがあります。

その時は、ありとあらゆる箇所でハンドルされない例外が投げられまくり、何かあるとすぐシステムが落ちると言うえげつない状況に陥りました。

4: 返り値のクラスで結果を表現する

実装難易度: ★★★★
実用性: ★★★★
安全性: ★★★

解説

処理の結果をクラスで表現するのもありです。
具体的に書くと、成功した場合の返り値となる値と失敗した場合の情報を1つのクラスにして返す方法です。

例えば、以下のようなResultクラスを実装し、それを呼び出し元に返すようにします。
(@Builderや@Getterと言うのはlombokアノテーションです。詳しくはlombok公式ページへ)

@Builder(access = AccessLevel.PRIVATE)
@Getter
public class MomikesanaiResult {
    private final ResultType result;
  private final List<SomeResult> successResults;
  private final Map<FailedCauseType, List<SomeInput>> failedInputs;

  private MomikesanaiResult(ResultType result, List<SomeResult> successList, Map<FailedCauseType, List<SomeInput>> failedInputs) {
    this.result = result;
    this.successResults = Optional.ofNullable(successList).map(Collections::unmodifiableList).orElse(Collections.emptyList());
    this.failedInputs = Optional.ofNullable(failedInputs).map(Collections::unmodifiableMap).orElse(Collections.emptyMap());
  }

  public static class MomikesanaiResultBuilder {
    public MomikesanaiResultBuilder addSuccess(SomeResult result) {
      if (this.successResults == null) {
        this.successResults = new ArrayList<>();
      }
      this.successResults.add(result);
      this.result = this.failedInputs == null ? ResultType.SUCCESS : ResultType.PARTIALLY;
      return this;
    }

    public MomikesanaiResultBuilder addFailed(FailedCauseType failedCause, SomeInput input) {
      if (this.failedInputs == null) {
        this.failedInputs = new HashMap<>();
      }
      if (!this.failedInputs.containsKey(failedCause)) {
        this.failedInputs.put(failedCause, new ArrayList<>());
      }
      this.failedInputs.get(failedCause).add(input);
      this.result = this.successResults == null ? ResultType.FAILED : ResultType.PARTIALLY;
      return this;
    }
  }
    
  public enum ResultType {
    SUCCESS, FAILED, PARTIALLY
  }

  public enum FailedCauseType {
    NONE,AUTH_INVALID, INVALID_INPUT, TIMEOUT, UNKNOWN
  }
}

このようなクラスを実装することで、以下のように具体的にエラーと結果を受け手に返すことができます。

static void momikesanaiTheException(List<SomeInput> inputs) throws InvalidInputException{  
  val resultBuilder = MomikesanaiResult.builder();  
  for (SomeInput input : inputs) {  
    try {  
      resultBuilder.addSuccess(someOperation(input));  
    } catch (IllegalArgumentException e) {  
      log.error("Input contains some invalid value and some Operation failed. input is {}", input, e);  
      resultBuilder.addFailed(FailedCauseType.INVALID_INPUT, input);  
    } catch (AuthFailedException e) {  
      log.error("Authentication is failed in some Operation. input is {}", input, e);   
      resultBuilder.addFailed(FailedCauseType.AUTH_INVALID, input);  
    } catch (TimeoutException e) {  
      log.error("Request seems to be timeout in some operation, input is {}", input, e);  
      resultBuilder.addFailed(FailedCauseType.TIMEOUT, input);  
    } catch (Exception e) {  
      log.error("Unknown error occured in some operation, input is {}", input, e);  
      resultBuilder.addFailed(FailedCauseType.UNKNOWN, input);  
    }  
  }  
  return resultBuilder.build();  
}  

なお、ResultTypeとFailureCauseTypeをenumにする理由は、受け手側がswitchでより簡易にerrorハンドリングを実装できるようにするためです。
また、Map<FailureCause, List<SomeInput>> のようなコレクションやマップのネストは気持ち悪い場合は別の構造体を定義しても大丈夫です。

いつ使うべきか

かなり実装コストが高いですが、それなりにメリットの大きい実装になります。
使用場面をあげると、

  • 他のサービスや外部の開発チームに提供されるライブラリ・SDKなど
  • パースしてWebApiやXHRの返り値に使う場合(ユーザへの通知はAPIの呼び出し元が行う。webならjavascriptでトースターを表示するなど。)
  • 多くの開発者に使われる基盤クラス
  • 例外を投げたくない場合

などなど、個人的には、後述のEitherが使えない現場では個人的にこの実装を勧めています。

5: Either型で返す

実装難易度: ★
実用性: ★★
安全性: ★★★

解説

Either型は関数型でよく使われる型で、成功した結果もしくは失敗した例外を返すと言う型です。
関数型では、呼び出したメソッドを返り値に置換しても同じ挙動ができることを担保すべしと言う考え方があり、例外を投げること自体があまり好まれません。

したがって、メソッドの返り値として、成功した値もしくは発生しうる例外をEither型で指定することで、例外をthrowするのを回避すると言うやり方が取られることが非常に多いです。

Either型では伝統的にright(右, 正しいと言う意味もある)に成功した時の値を、left(左)に例外を入れて呼び出し元に返します。

なお、Either型はJava標準ではサポートされていないため、Functional Javaなどの依存性を追加するか、自前で実装する必要があります。(サンプルコードはFunctionalJavaEitherを使用)

static Either<FileNotFoundexception, FileReader> momikesanaiTheException(SomeObject input) {  
  try {  
    return Either.right(new FileReader(new File(input.getTargetPath())));  
  } catch (FileNotFoundException e) {  
    log.error("File not found to the path {}", input.getTargetPath(), e);  
    return Either.left(e);  
  }  
}  
  

割と僕の周りではこの型を好む人が多いですが、周囲の開発者が関数型に慣れていない場合、呼び出し元の開発者がうまく使えない可能性が高く、日本の開発者環境においてうまく機能する現場はあまりないかもしれません。

下手に導入するとExceptionよりもみ消されるという結果になることも無きにしも非ずです。

ただし、Eitherは非常に強力な型で、シンプルな実装で安全かつ正確に何が発生したかを呼び出し元に伝えることができます。
したがって、一通り使い方を覚えるだけでかなり表現の幅が上がるため、マスターしておいて損はありません。

詳しく知りたい人は以下の記事を参考にしてみてください。
参考: Lazy Error Handling in Java, Part 3: Throwing Away Throws

また、こちらは僕の愛読書になります。
下のiframeはアフィリエイトなので、アフィなんて死んでも踏みたくないという方はこちらから

僕はScalaは申し訳程度にしか書けませんが、関数型の概念を言葉とコードでかなり詳細に説明してあり、仕事では常に携帯しています。 まじオススメです。

いつ使うべきか

前述の通り、Eitherは非常に強力ですが、受け取り側にもそれなりの知識が必要になるので、周囲の開発者の知識量などに合わせて使うべきです。
使用場面としては、

  • Exceptionを投げたくない場合
  • 失敗した場合と成功した場合の返り値を一つの型で表現したい時
  • 関数型を使う風土が社内もしくは最低限チーム内に共有されている時

となります。

まとめ

以上、初学者向けに例外が発生した場合のハンドリングの仕方を書いてみました。

ほんとはfrontendに絡めたり、呼び出し元がどういう風にこのエラーハンドリングに対して対応するかなども、コードとして書きたかったのですが、時間切れでした。

プログラミングを自習したり、自分専用のアプリを作ったりしてるうちは、例外の処理は甘くなりがちです。
しかし、実際にお客さんに使われるシステムやサービスでは、例外処理とロギングこそ、最も時間と経験と知識を費やす箇所になります。

上記で紹介したエラーハンドリングの方法も、どれか一つだけを使うわけではなく、状況に合わせて組み合わせて使うことも多くあります。
そこらへんのことも、そのうち記事に書きたいなとか、ぼんやり考えています。

あ、あと今回書かなかったけど、呼び出し元がエラーハンドリングしやすいように実装してやるのも大事だよっていうのを書き忘れていたので、補足でそれも意識するといいと思います。(雑ですみません。。。)

以上、Exceptionをもみ消すなってどうせえちゅうねんっていう話でした。
この記事が、上司や先輩から例外をもみ消すなと言われて途方に暮れている初学者の方々の助けになれば幸いです。

REST API開発者はPOSTも冪等になるように設計して欲しいよねって言う話

こんにちは。
もう冬なのにこの国にはまだ蚊が出るんですよね…やってらんねえ〜〜。

さて、そろそろこの国に来て二年になるので、後一年くらいしたら北欧の方に移住しようかと考えています。
2月に長期休暇が取れそうなので、二週間くらい書けてエストニアを中心にヨーロッパの下見旅行に行こうかなと考えています。
エストニアの現地コミュニティに連絡をとったところ、何人か合ってくれそうな開発者がいたので、今からとても楽しみです。

あと、AWSのSolution Architect Professionalの勉強を始めました。
流石にそろそろとっておかないと色々厳しいので、この動画を使って勉強してます。

AWS Certified Solutions Architect (CSA) Professional: Exam | Udemy

超わかりやすい、焦る。
たぶんネイティブじゃないけど、英語もはっきりしてて、1.25 ~ 1.5倍速くらいで聞いてても全然入ってきます。 今年中に取れるかなぁ…来年までかかるかなぁ…とりあえずがんばります。

さて、今回は短めの記事です。

本題

分散型アプリの開発をしていて、別の開発者とPOSTの仕様で少し討論になったので、メモがわりに残しておきます。
このDiscussionのおかげで私はidempotentと言う単語を完全に記憶しました。

冪等性に関する簡単な説明

初学者向けにまず 冪等性(Idempotency) に関する説明です。
冪等性という言葉に関しては、Wikipediaの言葉を引用すると

ある操作を1回行っても複数回行っても結果が同じであることをいう概念である

とあります。

簡単に身近な例で例えると、

  • 汚れたお皿Aに洗うと言う行為を1回やる → 綺麗なお皿Aが残る
  • 汚れたお皿Aに洗うと言う行為を100回やる → 綺麗なお皿Aが残る

という感じで、洗うと言う行為はお皿に対して冪等な操作であると言えます。

一方、ポケットのビスケットを叩いて増やす歌を考えると

  • ポケットを叩く → ビスケットが2つ
  • もひとつ叩く → ビスケットが3つ

となり、叩くごとにポケットの中のビスケットは増えていくため、叩くと言う行為はポケットに対して冪等でない操作と言えます。

システム開発で例えると、

  • データベースのQUERY -> 何回叩いてもDBのレコード群の状態を変えない -> 冪等である

ですが、

  • INSERTをIncrement IDで流す -> 叩いた数だけレコードが作成される -> 冪等ではない

と言う感じになります。

この説明は全く厳密でないため、もっと詳しく知りたい方は @KyojiOsada さんの記事がとてもくわしく書かれており、とても参考になったので、ぜひそちらを読んでみてください。

参考記事: 冪等と安全に関する誤解

REST APIのメソッドごとの割り当て

詳しくは以前書いたこちらの記事に詳しく書きましたが、REST APIに置いて、リソースに対する操作は基本的に、HTTPのメソッドごとに処理を割り当てます。

そのうち、GET, PUT, DELETEなどのメソッドは、冪等性が保証されなくてはいけません。
(参考:REST API Tutorial)

一方で、POSTに関しては明確に

POST is NOT idempotent. (POSTは冪等ではない)

と言う風に記述されてます。

もうすこし上述のサイトのPostに関する部分を引用すると、

Generally – not necessarily – POST APIs are used to create a new resource on server. So when you invoke the same POST request N times, you will have N new resources on the server. So, POST is not idempotent.

要約すると、必須ではないが、一般的にPOSTは新しいリソースを作成するのに使用されるため、N回呼ばれればN個のリソースを作成されることが多いため、POSTは冪等ではない、と言う風に書かれています。

分散型アーキテクチャとWebAPIのエラーハンドリング

MSAなどサービス同士の疎結合を維持しながらWeb API経由でコミュニケーションをとるアーキテクチャを取る場合、クライアント側は以下のエラーの可能性を気にしながら実装を行う必要があります。

エラータイプ 代表的なステータス 対応例
即時の復旧が見込まれる一時的なServerエラー 503, 504, 509など 一定時間sleepさせたあとリトライする。
MQなどに流して復旧後にリトライ
復旧に長期間かかる(可能性のある)Serverエラー 500, 501, 502, 507など 処理を中断してロールバック
MQなどに流して復旧後にリトライ
ユーザにwarningを提示し復旧後に再度処理を流してもらう
リトライ可能なクライアントエラー 401, 407, 408, 429など 再認証したのちリトライ
一定時間sleepさせたあとリトライ
タイムアウトを伸ばしてリトライ
リトライ不可能なクライアントエラー 400, 403,405, 406など 入力をマスクしたのちlogに出力し開発者に通知
ユーザにwarningを提示し復旧後に再度処理を流してもらう
状況によっては無視できるクライアントエラー 404, 409など 既存オブジェクトを確認して同一ならスキップ(409)

※対応例やレスポンスのステータスコードはあくまで例示であり、通信先のサービスの仕様やビジネス要件に依存します。期待されるエラーやリトライ方法は鵜呑みにせず、自分のサービスや通信先の仕様や要件に合わせて柔軟に設計・実装してください。

また、WebApi経由の処理群はトランザクション管理が難しいため、処理群の中のAPIコールが1つでも失敗した場合、一連の処理を流し直したり、作成・編集された可能性のあるレコードを全てロールバックしたりと言う処理を行う必要があります。
(ここに関してはpub-subなどを利用した回避方法もあるのですが、それはアドベントカレンダーの記事で紹介します。多分。)

POSTが冪等性じゃないと困る理由

以上のように、Web APIコールで処理を行う設計の場合、多くのケースでAPIコールで失敗した場合ロールバックやリトライの処理を挟む必要があります。

単純にロールバックして処理を終える場合はまだいいのですが、リトライを行う際にPOSTが冪等性でない場合、APIコールごとにリソースが作成されるため、一旦すでに作成された可能性がある要素を削除して再度作り直すという処理が必要になり、リトライのコストが高くなります。

Web APIというのは使用者にとって使いやすく設計することが最も重要であり、リトライにたいするコストが高いAPIはいいAPIとは言えません。

POSTを冪等にするための実装

POSTを冪等にするための実装方法は、GOOGLEで検索するといくつか出てきますが、僕はPOSTを設計する際、よくResourceのIDを事前に指定して作成するようにしています。

例えば、

  
/api/docs/${docType}/${docId}

と言うRestfullなpathを設計した場合、一般的にはPOSTは一つ上の階層の/api/docs/${docType}/にたいして処理を割り当てることが多いですが、ここで/api/docs/${docType}/${docId}に対してもPOSTを割り当てられる用にします。

このような設計にした場合、

  • IDが重複しないこと
  • 重複した場合適切なHTTP Statusを返す事 -重複した場合、できるだけClient側でGetし直さずにエラーハンドリングができるようにすること

などを設計の段階で織り込んで置く必要があります。

IDのコンフリクト回避には、UUIDのversion4や、作成したいリソースのhashから作成したUUIDなどを使い、作成方法をAPI Docsなどに明示します。 ←ここ大事

作成したいリソースのHashから作成する場合、一意性を強固にするため、作成時間のTimestampと作成者のIDを必ず含めるようにしてhashを作成し、Pathに割り当てた上でPOSTしてもらいます。

また、エラーハンドリングを用意にするため、作成が成功した場合200を、已に作成されていた場合は409を返すようにします。
IDがConflictする可能性はほぼゼロと言っていいレベルで低いですが、409が返す時、一緒に作成者のIDのSHA256HashおよびTimestampを返す用に設計すれば、409が返ってきた際にもAPIの利用者は安心して作成をスキップすることができます。

また、僕がメンバーと議論してる時に大いに参考にさせていただいた、Saurav SinghさんのHow to achieve idempotency in POST method?では、headerにidempotentKeyを設定し、そのidempotentKeyをなんらかのStorageに保存するという方法が紹介されています。

個人的にはやや冗長に感じるためあまりモチベーションはわかないのですが、こういう方法もあるんだなといい勉強になりました。

まとめ

POSTは一般的に冪等性が担保しにくいメソッド、もしくは担保しなくていいメソッドという認識が一般的かもしれません。
しかし、POSTの冪等性をAPI開発者側が担保してやることで、リトライやロールバックが用意になり、利用者からはより使いやすいAPIになると僕は考えています。

通信先のサービスの一部のNodeが落ちていることや、リクエスト過多によるスケーリング中でタイムアウトしてしまうことなど、分散型では日常茶飯事です。

フォールトトレラントなAPIを提供するためにも、明確にPOSTの冪等性を担保することは有意義だと考えています。

もしこの記事がこれからAPIを設計しようとしている誰かの役に立てば幸いです。

JavaのFutureの取り回しがダルすぎるので色々工夫してみる

まえがき

街の名はホーチミン1区。
一夜にして崩落・再構成されパフォーマンス向上の租界となったこの都市は、マルチスレッドを臨む境界点で、極度の緊張地帯となる。  
ここで世界の均衡を守るため暗躍するJava標準クラスFuture。この物語はこのFutureに挑む開発者の戦いと日常の記録である。  
(血界戦線風)  

ということで、久々の記事はjavaの非同期処理の返り値ラッパーである、Futureの取扱いについて、僕がよくやる実装Tipsについて書いていきたいと思います。

今回のテーマとしては、

  • マルチスレッド、非同期処理でも上手いことエラーハンドリングしたい
  • Futureの返り値展開をスッキリさせたい

の2つです。

1. 標準的な非同期処理とFutureの取り回し

Javaでマルチスレッド処理を書く際、多くの人は以下のような処理を書いていると思います。

  1. Executorsクラスでスレッドプールを作成する
  2. Callable/Runnableを実装したサブクラスを作成する (もしくはLambda式を使う)
  3. ExecutorServiceのsubmitで別スレッドに処理を任せる
  4. 返り値のFutureをListなどに格納する
  5. 全部を別スレッドに投げ終えたところでlistの中のFutureクラスのgetメソッドで同期を取る
  6. チェック例外のInterruptedExceptionExecutionExceptionを処理する

コードにするとこんな感じ。(Exceptionでウケるなとか、parallelStream使うなとかは見逃してください。)

@Slf4j
public class Main {
  public static void main(String[] args) {
    // If wanna make it parallel with 100 threads.

    val executor = Executors.newFixedThreadPool(100);

    val futureList = IntStream.range(0,100)
        .mapToObj(num -> executor.submit(() -> waitAndSaySomething(num)))
        .collect(Collectors.toList());
   
    futureList.parallelStream()
        .map(future -> {
          try {
            return future.get();
          } catch (Exception e) {
            log.error("Error happened while extracting the thread result.", e);
            // Do something for recover. Here, just return null.
            return null;
          }
        }).forEach(System.out::println);
    executor.shutdown();
  }

  static String waitAndSaySomething(int num) {
    try {
      Thread.sleep( num%10 * 1000);
    } catch (Exception e){
      // do nothing since just sample
    }
    if (num%2 ==0)
      throw new RuntimeException("Error if odd");
   return num + ": Something!";
  }
}

わりとよくあるコードだけど、Futureのget部分が冗長で書くのがダルい。
しかも、どのinputがエラー起こしたのかがわからないのでエラー処理がとても難しい。

2. まずエラー処理がちゃんとできるようにしてみる

Javaでマルチスレッドを実装する時、処理にinputを渡す方法は大体次の2つのうちのどちらかになる。

  1. Callable/Runnableを実装したクラスにプロパティとして持たせて、newする際にコンストラクタの引数で渡す。
  2. Lambdaの外部に宣言したfinalの変数をLambda式の中から参照する。

ただし、このいずれの場合も、エラーハンドリングは難しい。

1の場合、スレッドインスタンスを保存しておくのはそもそも微妙だし、FutureとThreadインスタンスのマッピングを何処かで管理しなきゃいけない。

2の場合、Futureからは投げた元のthreadの引数を取得できないためどのinputに対して起きたエラーなのか判別がつかない。

そこで、この問題を解決するために、以下の方法を取ることにしました。

Tupleを使ってプロパティとFutureをまとめて管理する

Tupleは簡単に言うと、複数の値の組のことで、プログラミングでよく使われる概念です。

Javaには標準でTupleが提供されて無いのですが、同様の方法は色々あって、Common LangPairTripleクラス、reactorTupleクラスやJavaTuplePairクラスなどを使って実現できます。

(複雑なクラスじゃないので、自分で実装してもいいです。)

Tupleを使って、inputをLeftに、FutureをRightに保存するようにすることで、Error処理で入力元の値を利用したエラー処理ができるようになります。

え?propertyがたくさんある?inputが重すぎてOOM起こしそう??設計を見直さんかい。

さて、今回はほぼすべてのプロジェクトで使われてると思われる、Commons LangのPairを使ってみます。
Tupleを使うと上のmainクラスはこんな感じに書き直せます。

@Slf4j
public class Main {
  public static void main(String[] args) {
    // If wanna make it parallel with 100 threads.
    val executor = Executors.newFixedThreadPool(100);
 
   val futureList = IntStream.range(0,100)
        .mapToObj(num -> Pair.of(num, executor.submit(() -> waitAndSaySomething(num))))
        .collect(Collectors.toList());

    futureList.parallelStream()
        .map(future -> {
          try {
            return future.getRight().get();
          } catch (Exception e) {
            log.error("Input {} was not processed correctly.", future.getLeft(), e);
            // Do something for recover. Here, just return null.
            return String.format("Input %s Failed in process, damn shit!! ", future.getLeft());
          }
        }).forEach(System.out::println);

    executor.shutdown();
  }

これで、入力値を利用したエラー処理とLoggingができるようになりました。

ただ、Futureを展開する箇所があいかわらず冗長で、なんだかイライラします。

そこで、次はこの部分を共通化してみます。

3. Futureの展開部分を共通化する

やりたいことはfutureの展開だけなので、そこだけ切り取れば、共通化は非常に簡単です。

一方で、エラー処理はinputに対して相対的に行いたいので、その部分は柔軟にできるような設計にしたいです。

そこで、エラーハンドリングの部分をExceptionとinputを利用して、任意のFunctionで処理できるように以下のようなFutureの展開クラスを用意します。

@RequiredArgsConstructor
public class FutureFlattener<L, R> implements Function<Pair<L, Future<R>>, R> {
  /**
   * Callback function to recover when exception such as {@link InterruptedException} or {@link
   * java.util.concurrent.ExecutionException}.
   */
  private final BiFunction<L, Exception, R> recoveryCallback;

  @Override
  public R apply(Pair<L, Future<R>> futurePair) {
    try {
      return futurePair.getRight().get();
    } catch (Exception e) {
      return recoveryCallback.apply(futurePair.getLeft(), e);
    }
  }
}

これを先程のMainクラスに組み込むと以下のようになります。

@Slf4j
public class Main {
  public static void main(String[] args) {
    // If wanna make it parallel with 100 threads.
    val executor = Executors.newFixedThreadPool(100);

    BiFunction<Integer,Exception,String> errorHandler =
        (in, e) -> {
          log.error("Input {} was not processed correctly.", in, e);
          return String.format("Input %s Failed in process, damn shit!! ", in);
        };
    val flattener = new FutureFlattener<Integer, String>(errorHandler);

    val futureList =
        IntStream.range(0, 100)
            .mapToObj(num -> Pair.of(num, executor.submit(() -> waitAndSaySomething(num))))
            .collect(Collectors.toList());

    futureList
        .parallelStream()
        .map(flattener)
        .forEach(System.out::println);
    executor.shutdown();
  }

しかし、せっかくFunctionインターフェイスを使ってるのに、内部で関数をプロパティに持つのは、正直めちゃくちゃダサいです。

ダサいのはいけません、クールに決めたい。

ということで、もう少しだけ拡張してみます。

4. JavaのFunctionインターフェイスを継承してエラーハンドリングを追加する

多くの他の言語がモナド型のクラスに、onCatchやthenCatchなど、Exceptionが投げられた時のためのメソッドを用意しています。

しかし、残念なことに、JavaのFunction Interfaceはcompose, apply, andThenの成功を前提としたメソッドチェーンしかできません。

そこで、JavaのFunctionインターフェイスを継承して、onCatchを実装してみます。

public interface CatchableFunction<T, R> extends Function<T, R> {

  /**
   * by calling this method in advance of calling {@link Function#apply}, any exception thrown in
   * the apply method will be handled as defined in the argument onCatch.
   *
   * @param onCatch callback method to handle the exception. First Type T is the input of the base
   *     function.
   * @return fail-safe function with a callback. This method will generate a new Function instance
   *     instead of modifying the existing function instance.
   */
  default Function<T, R> thenCatch(BiFunction<T, Exception, R> onCatch) {
    return t -> {
      try {
        return apply(t);
      } catch (Exception e) {
        return onCatch.apply(t, e);
      }
    };
  }
}

Javaの使用上、Type parameterをcatchすることはできないため、Exceptionで受けなくてはいけないのがもどかしいですが、これでかなりfunctionalに書けるようになりました。

このクラスを先程のFutureFlattenerクラスに実装すると以下のようになります。


@RequiredArgsConstructor
public class FutureFlattener<L, R> implements CatchableFunction<Pair<L, Future<R>>, R> {

  @Override
  public R apply(Pair<L, Future<R>> futurePair) {
    try {
      return futurePair.getRight().get();
    } catch (InterruptedException | ExecutionException e) {
      throw new FutureExpandException(e);
    }
  }

  // To be caught in the then catch method.
  private static class FutureExtractException extends RuntimeException {
    FutureExpandException(Throwable cause) {
      super(cause);
    }
  }

チェック例外はLamdba式の中で処理しなくては行けないため、FutureExtractExceptionでラップしてあります。

これでMainクラスもスッキリします。

@Slf4j
public class Main {
  public static void main(String[] args) {
    // If wanna make it parallel with 10 threads.
    val executor = Executors.newFixedThreadPool(100);

    val flattener = new FutureFlattener<Integer, String>()
            .thenCatch(
                (in, e) -> {
                  log.error("Input {} was not processed correctly.", in, e);
                  return String.format("Input %s Failed in process, damn shit!! ", in);
                });

    val futureList = IntStream.range(0, 100)
            .mapToObj(num -> Pair.of(num, executor.submit(() -> waitAndSaySomething(num))))
            .collect(Collectors.toList());
    futureList.parallelStream().map(flattener).forEach(System.out::println);
    executor.shutdown();
  }

  static String waitAndSaySomething(int num) {
    try {
      Thread.sleep(num % 10 * 1000);
    } catch (Exception e) {
      // do nothing since just sample
    }
    if (num % 2 == 0) {
      throw new RuntimeException("Error if odd");
    }
    return num + ": Something!";
  }
}

ネストが減って、関数の宣言もスッキリして、Futureの展開周りのソースもスッキリしました。

終わりに

さて、いかがだったでしょうか?

Functional Javaを使えばもっと楽に実装できたりする箇所はあるのですが、急いでいたため自前で実装してしまいました。

並列処理に関して言えば、最近はkafkaなどのメッセージキューを使って非同期かつ粗結合に作るのが基本ですが、だからといってマルチスレッドを使わないわけではありません。

一方で冗長なFuture展開はネストを増やし可読性を下げるだけでなく、最も気を使うべきエラーハンドリングに気が回らなくなります。

今回僕は上記のような解決方法を取りましたが、いかがだったでしょうか?

もっとええ方法あるで、という方がいらっしゃいましたら、コメント欄にお願いします。

それでは!

S3に完全に同じファイルが上がってるか確認するロジックでちょっとハマったのでメモ。

S3上のファイルのチェックサムを取得する方法でハマりやすいとこと、下の方におまけでJavaでfileのチェックサムを計算する方法を書いておきました。

誰かの役に立てば幸いです。

S3のチェックサムを比較したい

数GBのファイルをS3に挙げるような処理を書いていて、後続処理の失敗などでリトライしたいというのがあった。

この場合、ファイルをアップロードし直すのはムダだが、移行元ストレージ内の対象ファイルが変更にされている可能性もある。 したがって、すでにS3上に完全に同じファイルがあるときだけアップロードをリトライしたい。

この場合、チェックサムを比較するのが楽なので、S3のgetObjectMetaData APIを利用して以下のようにコードを書いた。

 private boolean shouldSkip(String bucketName, String key, String md5CheckSum) {
      try {
        ObjectMetadata meta = s3Client.getObjectMetadata(bucketName, key);
        if (meta == null || meta.getContentMD5() == null) {
          log.info("meta data not exist for the file {} in bucket {}", key, bucketName);
          return false;
        }
        log.info(
            "Checksum of existing file is {} and present file checksum is {}",
            meta.getContentMD5(),
            md5CheckSum);
        return meta.getContentMD5().equals(md5CheckSum);
      } catch (SdkClientException e) {
        log.error("Exception thrown while validating the checksum of the file {}", key, e);
        return false;
      }
    }

しかし、どうもうまく行かない。 ObjectMetaData#contentMD5がどうしてもNullになってしまうのだ。

調べたところ、S3における既存オブジェクトのチェックサムはcontentMD5ではなく、Etagに付与されるらしい。

じゃあcontentMD5は何に使うかと言うと、更新時にS3内で元のファイルから改ざん確認に使うため、getで取得する時は返されないのだとか。

したがって、S3から落として来たファイルのチェックサムを知りたい時はEtagと比較する必要がある。

こんな感じ。

    private boolean shouldSkip(String bucketName, String key, String md5CheckSum) {
      try {
        ObjectMetadata meta = this.s3Client.getObjectMetadata(bucketName, key);
        if (meta == null || meta.getETag() == null) {
          log.info("meta data not exist for the file {} in bucket {}", key, bucketName);
          return false;
        }
        log.info(
            "Checksum of existing file is {} and present file checksum is {}",
            meta.getETag(),
            md5CheckSum);
        return meta.getETag().equals(md5CheckSum);
      } catch (SdkClientException e) {
        log.error("Exception thrown while validating the checksum of the file {}", key, e);
        return false;
      }
    }

誰かの役に立てば幸いです。

おまけ Javaでのチェックサム確認方法

チェックサムでググって飛んで来た人のために一応書いておくと、Javaでチェックサムを計算する方法はこんな感じ。

  public static String checkMd5Checksum(File file) {
    try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file))) {
      return DigestUtils.md5Hex(is);
    } catch (Exception e) {
      // Not likely to occur.
      log.error(
          "ERROR Happened while calculating the check sum for file {}", file.getAbsolutePath(), e);
      return "NOT FOUND";
    }
  }

sha256なら DigestUtils#md5HexDigestUtils#sha256Hexに変えるだけ。

チェックサムに関してもっと知りたい人は世界でもっとも強力な9のアルゴリズムを読もう。

簡単に鍵交換やチェックサムの理論を紹介していて、読み物としてはとても面白いよ。

以上、メモでした。

これからの新卒は自己責任で即戦力として社会に出る準備しとかないと人生詰むぞって話

やや煽り気味のタイトルになった感は否めないけど、TwitterやFacebookで吹き荒れる即戦力議論を眺めていて、違和感を感じる瞬間が多いので久々に筆、もといキーボードをとってみた。
ちなみに後半はもっと扇情的な言葉遣いをすることになるという予感はしている。

社会自体が新卒に投資しなくなるよっていう話

トヨタの社長が終身雇用に対して諦観を示し、来年からは経団連が通年採用を開始、また、第二新卒第三新卒を含む転職市場は年々活発化している。
新卒に対して即戦力を求める企業も増加し、大学は企業側のニーズに沿ったカリキュラムを増やす。それに対してアカデミックな人々は異論を唱える。

これらの出来事や議論そのものは、個別に見るとどれも筋が通っていて、正論に聞こえなくもない。
正論と正論のぶつかり合いなので、おそらく決着は、より大きな権力を持っている方に都合よく流れていくのではないかと思う。

さて、この議論に一番振り回されるのは、これまで終身雇用を前提に生きて来た、社外で通用するスキルを持たない中年世代と、これから社会に投げ出されようという新卒世代なのは間違いない。

正直前者は御愁傷様としか言いようがないし、どのように対応するかは、それぞれが歩んできたキャリアに依存してくると思うので、いったん置いておく。

問題は後者だ。
まず彼らにとってこれから厳しくなるのは、彼らの競争相手にはもはや同世代の新卒だけではなく、海千山千の中途入社たちも含まれるということになるということだ。

なぜかつて新卒が特別扱いされ、中途入社よりも優遇されていたかといえば、新卒は今後数十年に渡って会社を支えてくれる屋台骨となる可能性が高かったからだ。

生え抜きと外様(とざま)という言葉があるように、新卒からずっといた社員は中途入社よりもある程度優遇され、能力的にはまだ劣っているにも関わらず社内で出世コースに入ることも十分ありえた。
企業は彼らに十分な投資と教育を行うし、実際そのリターンは投資に十分に見合うものだっただろう。

しかし、上記のように終身雇用が崩壊し、転職活動が活発化するということは、もはや新卒社員は別に将来的な屋台骨になりうる人材ではなくなる可能性が非常にに高くなる。

企業にとっても、彼らに対する教育や投資は費用対効果が薄く、おそらく新卒に対する教育投資はどんどん抑えられていくものだと思われる。
現に、多くの企業ではコストのかかる専門知識に関する座学研修がどんどん減り、入社前に資格取得を要求したり、OJTという名のなんちゃって研修で済ませようとする動きが増えている。

生え抜きプレミアはどんどん価値を失い、新卒と中途を同じ軸で比べるようになって来ている、というのが僕なりの所感だ。

マネージャにとって新卒は足手まとい

非常に残酷な事実を述べようと思う。

多くのマネージャーにとって、ほとんどの新卒は自分の生産性を下げる足手まといでしかない。
特に、終身雇用・年功序列が崩れた今、成果主義と生産性の名の下にマネージャも自分の組織するチームの成果を問われ続ける。
そんな中間管理職にとっては、なおさら新卒は足手まといだ。

工数上は一人としてカウントされるが、仕事もできない、日本語もいまいち通じない、特別なスキルを持っているわけでもない、指示しないと動けない、おまけに何かにつけて権利を主張してくる。

おそらく中には、心のそこから、新卒など採らなければいいのにと思っているマネージャーも少なくないのではないかと思う。

新卒側からすればお前だってかつてはそうだっただろうと文句を言いたくなるだろうが、マネージャ側からすると、その事実を理性として理解した上でも、感情として受け入れられない部分はある。

ゆえに、彼らは新卒に厳しくあたるし、逆にある程度のスキルを持って入社してくる中途入社を歓迎する。
これは個人や企業単位の問題ではなく、社会としてそういう方向に動いて来ているというだけで、個人や個別の企業を責めるのは間違っていると思う。

つまり、何が言いたいかというと、社会の流れとして、大学や企業は教育コストを自分以外の場所に負担させたがるようになり、その社会にすでに存在している個人たちは、そのコストが十分に投資されていない新人に冷たくあたるようになるということだ。

一旦自己保身

ちなみに、これだけ散々言っていると炎上しそうなので、僕個人の話をすると、僕は個人的に人材育成が好きなので、かなり手厚く後輩を教育している、と思う。

僕はシステムエンジニアを生業としているが、後輩の大半は大学でコンピュータサイエンスの基礎を学んできたほぼ素人たちだ。
エンジニアの人たちにしか通じないかもしれないが、カーネルの仕組みは知っていても、ログをSystem#out#printlnで出力したり、assertを返り値でif分岐したりしてしまうようなレベルだ。

だからこそ、就業時間に彼らに対してコーディングの基礎はみっちり教え込むし、必要であれば業務時間後にスカイプやFacebookメッセンジャで愚痴を聞いたり、個別に講座を開いたりする。

エンジニアという文化上、また終身雇用など初めからない国にいることもあって、当然二、三年もすれば転職していく。
それでも、彼らが転職して、有名な外資企業に入れたという知らせを聞くと心から嬉しく思う。

ただし、これはあくまで個人の趣味でやっていることであり、仕事ではない。
僕にとって、多くの人間にとっても同じように、仕事より趣味の方が真剣になれるからやっているだけである。
仕事としてやれと言われればおそらく断るだろうし、また、同じ対応を他のマネージャ仲間に要求するのはお門違いだと個人的には思っている。

流れ自体は問題ではない

個人的には、この流れ自体は問題ではないと思っている。
というのも、僕が今いる国、かつていた国、また欧米諸国でもこれは非常に一般的な話で、そのせいで社会が崩壊したという話は聞いたことがない。

ただし、日本はつい最近その流れに追いついて来ただけで、企業や大学を含めた社会の仕組み的にも、学生の意識的にも、これを受け入れる準備はできていない。 冒頭のトヨタ含め、多くの企業では問題そのものは認識しつつも、手をこまねいているのではないかと思う。

なにせ、問題は社会レベルの問題だ。
個別企業が動いたところでどうこうなる問題ではない。

僕個人としては、この流れに対して社会が適応できるようには10年単位の時間がかかるのではないかと思っている。
なにせこの流れ自体はもう10年以上前から予見できていたことであり、いまだに社会は負荷にばかり目を向け、具体的な対応策に向けて動けているようには見えない。

ゆえに、今から10年くらいの間、新卒たちはかなり厳しい状況に置かれていくのではないかと考えている。
大学からも企業からも十分な教育は与えられず、海千山千の中途たちとしのぎを削ることになり、上司には厳しく当たられ、精神的にも体力的にも追い詰められていく。

若さ、専門性、自頭、コミュニケーション能力を最大限に活用して平凡な中途たちとも渡り合っていけるような、ごく一部の優秀層はむしろそちらの方が都合がいいかもしれない。

しかし、9割以上の平凡な学生たちにとって、おそらくバブル時代以上に根性を試される時代が当分は続くことになるのではないかと思う。
そして、そこからドロップアウトしたものは精神を病んだり、ほぼ何も身についていない状態で転職市場をさまよったり、非正規雇用の闇に飲まれて不安定な人生を送ったりすることになる。

新卒採用は売り手市場が続いているが、個人的な所感としては、今後10年は、失われた10年以上の厳しい時代になるのではないかと思う。
そして、その10年以内の新人は、失われた10年当時の新卒同様、人生単位でそれなりに厳しい生活を送ることになる可能性もある。

自己責任で即戦力

さて、ここでタイトルに出した、自己責任と即戦力という言葉が出てくる。

まず、前者の自己責任という部分は多少なりとも同情はするが、後者の即戦力という点に関しては、個人的にはごく普通のことだと思っている。
というのも、日本以外の多くの国では、全く未経験の新人などほぼ雇うことはないからだ。

彼らは、大学生の時点でワーキングホリデーやインターンなどを通じて一定の技能を習得してから、企業に対して正式に入社してくる。
また、専門知識に関しては、企業が資格を要求するまでもなく、大学での勉学を通じて身につけているのが前提だ。

ゆえに、前述の後輩たちに対してメモリやCPUの説明をする必要はないし、自社独自の箇所だけ説明すれば開発環境の設定も自分たちで行えるし、いきなり専門用語をガンガン使ったディスカッションを要求しても、内容の質には程度の差があれど、ほぼ問題が生じることはない。

これは日本のトップ校の学生たちも相違ないが、彼らは大学のレベルに関係なく、平均的にそれを行うことができる。

ちなみに加えて言えば、僕は当然ほとんどの国で現地語を喋れないのだが、多くの国のインターン生たちは多少不恰好ではあるが、それなりに英語でコミュニケーションを取れたということだ。

彼らは、自分のキャリアパスを学生のうちにある程度固めた上で自身の能力開発を行い、知識を蓄え、経験をしてきている。
故に、企業に入社する段階ではある程度即戦力としての期待ができるし、力不足であっても基礎が出来ているため覚えるのが早い。

また、大学や企業、そしてそれを包含する社会もそれに十分な機会を与えている。

例えば、大学は半年程度のインターンの参加を卒業要件に加え、単位として認める。

企業はインターンを積極的に受け入れるし、小間使いではなく実際の業務に参加させる。また、場合によってはインターン生が入社しようとしてる企業に対して推薦状を書いたりもする。

そして社会はそれらの専門知識と業務経験をきちんと備えた学生にだけ職を与える。
希望の職種に対して要求される専門知識を持たないものには、学士入学などを認め奨学金をだしたりもする。

こういった仕組みは、おそらく当分日本には根付かない。
だからこそ、根付くまでの間は、非常に残念ながら「自己責任」になってしまうのだ。

大学も企業も社会も、おそらく新卒に対してこういった仕組みを提供するには時間がかかる。
国として登り調子だからできる政策や制度もあるだろう。

しかし、彼らがその重い腰をあげるのを待っていたら、あっという間に時間は過ぎ去っていく。
そして、その重い腰が上がった時に救われるのは、この期間に割りを食わされた世代ではない。その時代の新卒たちだ。

だからこそ、社会が重い腰をあげるのを待つのではなく、自分自身で即戦力と言わしめるだけの能力を培っておくことを、個人的にはオススメする。

悪いことばかりでもない

さて、散々不安を煽ってきたが、しかし、かといって悪いことばかりでもない。

我々おっさん(三十路前半だけど)から言わせてもらえば、学生時代にある程度知識と経験を積んできた頭のいい学生たちというのは、なかなかに得難い存在だ。

自分の持っていない能力や価値観を持っている上に、一から教えなくてもどんどん業務をこなす。
当然飲み込みも早いし、若い分だけ中途よりも教えがいもある。
また、コミュニケーション能力の基礎もできているので会話に苦労しない。(学生というのは自分たちが思う以上に会話が下手なものだ)

これだけいうとおっさん側にばかりメリットがあるように感じるかもしれないが、新卒側にも大いにメリットがある。
上司に信頼されれば、小間使いからはすぐに脱却し、それなりに重要な仕事をもらえるようになる。
それなりに重要な仕事というのは、自分自身の転職市場での価値を大いに向上するものだし、また当然小間使いよりも楽しい。

また、足手まといと認識されていればネグレクトされていたであろう教育もして貰えることが多い。
僕も何も知らない新人よりは、ある程度自分で勉強している新人に対しての方が教育にかける熱は上がる。

そうしてどんどん知識と経験を獲得していけば、転職するにしても自分自身の価値は上がるし、残るとしてもそれなりに仕事はしやすくなるだろう。
期待されすぎて業務過多になるのなら辞めればいい。それでも、年齢に対してたくさんの経験をしてきている人材を、転職市場はいつでも欲しがってくれるだろう。

仕事ばかりが人生ではないが、それでも大半の人間は、生きていくために、何かしらの仕事をして社会に貢献しなくてはいけない。
であれば、自分自身の人生を守るために、そのくらいの準備はしてもいいのではないだろうかと、僕は考えている。

ちなみに、ずっと小間使いでいいという人たちに関しては、今回は考慮していない。
おそらく、そういう人たちも今後の社会では必要とされていくと思う。

しかし、その理由は、人が足りないから、仕方なく、だ。
故に、上述したような風当たりは強くなるだろうし、競争相手は移民や外国人労働者になり、それはそれでかなり根性を試されるのではないかと思う。

僕個人に関していえば、小間使いでいいとは思ってはいないが、仕事に生活を蝕まれて毎日終電で帰るような生活がしたくないからこそ、日本を出た。

毎日9時18時半で勤務し、昼休みは家に帰って昼寝したり、Game of thronesを見たりして、仕事終わりはバーに飲みにいったりバドミントンしたりコードの勉強したりこそが、人間の生活だと思っている。

何れにせよ、楽しくおかしく暮らしたいなら、自分の人生に関しては、それなりの労力と時間をかけて作っていった方がいいと僕は思う。
特に、これから社会に出る新卒は、社会の流れがそういう風になってきているという点を踏まえた上で、十分に準備したほうがいいのは確かだろう。

もし仮にどこかでこの流れの狭間に追いやられてしまったとしても、誰のせいにもできない。
いや、誰かのせいにすることはできるかもしれないけど、その話を聞いてくれるのはスナックのママと精神科医だけなのだから。