ソフトウェアアーキテクチャ・ハードパーツを読んだ

この記事は FOLIO Advent Calendar 2022の一日目です。

ソフトウェアアーキテクチャ・ハードパーツ ―分散アーキテクチャのためのトレードオフ分析 を読んだので感想です。

※ この本は翻訳を担当された@snoozer05 さんから頂きました。ありがとうございます!

感想

マイクロサービス・サーガ・オーケストレーションといった個別の技術についてはある程度理解している人でも、いざ実システムに応用しようとすると「結局、マイクロサービスってどの単位で切るのがいいの?」みたいな疑問が次々出てきてしまう・・・といったことはよくある悩みだと思います。(本書では例として郵送通知とプッシュ通知とメール通知は通知サービスにまとめたほうが良いのか、個別にしたほうが良いのかなんて悩みが紹介されていました。)

(この疑問も含めた)様々なアーキテクチャに関する疑問の回答は「何が良いかは場合による」であり、実際に意思決定を行うためには様々なトレードオフについて考える必要があります。本書ではそういった「何が良いかは場合による」問題に対して、技術的選択肢の特徴を整理し、対立するトレードオフ関係を見出し、ビジネス上の価値・優先度・目的に照らし合わせ、後から変更しにくい事柄(=ハードパーツ)について意思決定を行うためにどのような考え方をしていけばいいか、どのような議論を行えばいいかが記載されています。

議論パートは物語形式になっているのですが、いろいろな人(モノリスを分解したい人、DBを分散させたくない人、...)が自分の立場でメリット・デメリットを語り、挙句の果てに「マイクロサービスだからこうあるべき」、「こういう技術を使うのがモダンらしい」みたいな絵に書いたようなフワッとした会話が行われているところから話を着地させ、意思決定し、ADR(アーキテクチャデシジョンレコード)にまとめていく、という過程が読んでいて面白かったですし、実際の仕事でも役に立ちそうな現実味がありました。

マイクロサービスについてもそれありきではなく、「マイクロサービスにするとあなた達の問題は解決するんですか?」といった問いから初めて、マイクロサービスに伴い得るもの・失うものを技術的な視点から出発しビジネス的な価値につなげるところまで解説しています。

マイクロサービスではDBを共有しない(ので、共有は避けるべきだ!)という話もよくあると思いますが、本書ではDBを共有する形であるサービスベースアーキテクチャとの比較も踏まえ、「○○だからこうあるべき」ではなく「自分たちが求めているのが○○だから△△を採用する」といった正しい意思決定へ導きます。(注: マイクロサービスでDB共有を推奨しているわけではなく、サービスは分けるけどDBは共有する別のパターンもあるよねといった話です。)

また最終的には、(本書で登場するトレードオフを暗記するのではなく)それらのトレードオフを自分で整理し意思決定できるようにするための方法論も紹介されています。アーキテクトという役職は「どちらが良いかは場合による」問題に対して対立軸を見出し、ビジネス上の優先度と照らして意思決定を行っていく役職であるという筆者の思いを感じました。ともするとエンタープライズサービスバスとかコレオグラフィとかそういった概念・技術に詳しい人がアーキテクトだ!みたいな考えにもなりがちですが、後から変えにくいもの(=ハードパーツ)の意思決定に携わるためには手段に精通した上でビジネス上の目的・優先度と照らして意思決定が出来るというのがより重要だというのは個人的にも頷けます。技に溺れたアーキテクトではなく、技を使いこなすアーキテクトになるための一歩としてこの本は非常に有用なのではないかと思いました。

逆に言うとマイクロサービスってなんだろうとか、サーガってそもそもなんだろうみたいな解説については(記載はあるものの)詳しくはないので別の本を読んでからの方が良いかもしれません。

手法面では、どのようにDBを分割していくか・分析データをどのように連携していくか(データレイク・データメッシュetc)などについても記載があるのが嬉しいところでした。

ということで、これからアーキテクトになりたい人・よりアーキテクトとして成長したい人にとってはかなり良い本だと思ったので気になったらぜひ!

証券Fintechに入ったエンジニアが証券ドメインの業界知識を身に着けたいと思って読んだ本

これは何

  • 証券Fintechに入るぞ・入ったぞ!という人がドメインについて学べるような本・ページを集めてみました。
  • 会社特有の業務知識ではなく業界で広く使える知識を学べる物を集めようとしていますが、筆者の守備範囲依存による分野の偏りは否めません。(追記: たとえばデリバティブ関連などは筆者の経験がないのでほぼ記載がありません。)
  • こういう分野があってこういうことが議論されてるんだなーといった形で興味を持つきっかけとしての本を挙げています。(自分で金融商品を考えるぞ!という人向けではなく、ETF扱うサービスの開発するならETFって何かくらいはうっすら知っておきたいよねーくらいのモチベーションの人向き)

最初に何読めばいい?

せっかくだからなにか勉強したいなーという人は資格系の欄にある証券外務員についての本を買っておくと広めに勉強できます。

ただし、個別の金融商品ETF, 投資信託,…)の解説が詳しく載っているわけではないので、そういったことを知りたい場合はETF投資信託の欄からチョイスしてみてもよいかもしれません。

資格系

現物株

投資一任, ロボアド

  • ラップ口座入門
    • ラップ口座の拡大・現在の状況・今後の方向性がファクトベースで記載されている。
    • 投資の基本的なところから説明してくれているので事前知識があまりなくても読める。

投資信託

ETF

資産運用の考え方について

決済系

金商法

  • 金商法入門
    • 解説対象を現物株に限ることで難しさを軽減している。
    • 法の背景にあるやりたいこともきちんと解説してくれる。

リスク管理

aws s3でオブジェクトkeyの先頭にスラッシュ(/)をつけるとどうなるか?

これはなに

  • s3はbucketとkeyの組み合わせでオブジェクトを保存している
  • このbucketとkeyを s3://$bucket/$key のように組み合わせると S3URIとなる
  • keyの先頭のslashがついているとき、aws cliを含む一部のツールでは 先頭のslashを削除する場合がある。
  • この仕様についての記載はaws cli側のドキュメントでは見つからなかったが、本記事では go-sdk での命名に習ってURI cleaningと呼ぶことにする。
  • この場合、ファイルは存在するように見えるが取得しようとすると NoSuchKey が返却されるといった事象が起きる可能性があるので、それについて解説する

HeadObjectの仕様

まずファイルの存在確認ではHeadObjectを利用することが多いので、そこの仕様を確認する。

HeadObject - Amazon Simple Storage Service を読むと

Permissions

You need the relevant read object (or version) permission for this operation. For more information, see Specifying Permissions in a Policy. If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.

If you have the s3:ListBucket permission on the bucket, Amazon S3 returns an HTTP status code 404 ("no such key") error.

If you don’t have the s3:ListBucket permission, Amazon S3 returns an HTTP status code 403 ("access denied") error. 

とある。つまり

  • ListBucket権限がありファイルがないなら404
  • ListBucket権限がないなら403 (access denied)

が返ってくる。

aws java-sdk v2 などでは 404の場合 NoSuchKeyExceptionthrowされ、それ以外の場合は SdkClientException などがthrowされる。

そのため NoSuchKeyException がthrowされている場合は、権限不足ではなくファイルが実際に存在していないことを疑ってもよいだろう。

HeadObjectをcliで叩く

HeadObjectをaws-cliで叩くためには通常の aws s3 <command> ではなく aws s3api <command> という低レベルAPIを直接たたくコマンドを利用する必要がある。(詳細は AWS CLI での API レベル (s3api) コマンドの使用 - AWS Command Line Interface)

head-objectコマンドを利用する場合はkeyとbucketを以下のように分けて指定することが可能なので、先頭にスラッシュを含まないファイルと含むファイルの存在を以下のようにチェックすることができる。

# keyの先頭にスラッシュなし
aws s3api --profile ... head-object --bucket some-bucket --key some/key/foo.json

# keyの先頭にスラッシュあり
aws s3api --profile ... head-object --bucket some-bucket --key /some/key/foo.json

もし後者の先頭スラッシュつきでファイルが存在するレスポンスが返ってきたらURI cleaningの影響でファイルが見つからない状態になっている可能性が高い。

余談

  • 基本的にHeadObjectはGetのbodyを取得しない版と同等なので性能を気にしなくていい場合はaws s3 cp $file . などでファイルを取得してもよい。
  • 今回はURI cleaningを避けるためにs3URI形式ではなくbucket, keyを分割して渡すようにする目的で低レベルAPIを利用している。

各ツールの仕様

java-sdk

java-sdkでは、bucket, keyを分けて put, get する仕様になっている。

このとき、key側の先頭slashが消されると言った挙動は(少なくともHeadObjectでは)確認できなかったのでURI cleaningの罠を踏むことはなさそう。

val request = HeadObjectRequest.builder().bucket("some-bucket").key("some-key").build()

go-sdk

go-sdkでもbucket, keyを分けてput, getする仕様になっている。

しかし Automatic URI cleaning という仕様があり、key先頭のslashは削除される。

この挙動は DisableRestProtocolURICleaning をtrueに設定すると回避できる。

svc := s3.New(sess, &aws.Config{
      DisableRestProtocolURICleaning: aws.Bool(true),
})
out, err := svc.GetObject(&s3.GetObjectInput {
      Bucket: aws.String("bucketname"),
      Key: aws.String("//foo//bar//moo"),
})

aws-cli

aws-cliでは s3://$bucket/$key という形式でget, putすることになる。

このとき key先頭のslashが削除されるために先頭にslashが入っているオブジェクトは取得ができないことがある。

なお、先頭slashは1個だけ削除される仕様ではなく、複数個削除されるようになっているため、以下のように $bucket//$key のような文字列を指定しても、ファイルは存在しない扱いになる。

$ aws s3 --profile ... ls s3://some-bucket//some/key/foo.json

web-console

試してないので誰か教えて ( ◜◡‾)

結論

keyの先頭にスラッシュをつけるとファイルを手動操作したい場合に余計な混乱を呼ぶのでやめたほうが無難そう。

※ s3から静的ファイルを配信する際のURLも難しくなってしまうので、そういう意味でも避けたほうが良さそう

sbt-native-packagerでパッケージングしたアプリケーションのJVMオプションに-Jをつけるべきかどうか

普通に自分が分かってなかったのでメモシリーズ

これはなに

  • -Xmx オプションを指定する際に -J をつけている。 -Dfoo.barなどのプロパティ指定にはつけたりつけなかったりしている
  • いつ -J をつけて、いつつけなくてよいのか、 -J の意味などをまとめる。

-J の意味

-J の意味1 javaコマンドのオプションである -J

java -J” あたりでググるとコレがでてくる。 例: Javaアプリケーション メモ(Hishidama's Java-Application Memo)

これはjavacが使用するjavaコマンド(javacと同バージョンのjava)へのオプションを指定するものなので、Xmxの指定などの実行時のオプション設定には関係しない。

-J の意味2 jarコマンドのオプションである -J

こちらもググってみると、

たとえば、 -J-Xms48m と指定すると、スタートアップ・メモリーは48Mバイトに設定されます。

jar のように記載されている。

しかし「jarコマンド」はjarを作ったりjarの中身を一覧表示するためのコマンドであり、jarの実行(java -jar …)とは異なる物なのがポイントで、こちらもアプリケーション実行時に影響するオプションではない。

つまり jar -J-Xms48m はjarを作る作業中にjvmが使うメモリを変更するオプションであるという意味になる。

-J の意味3 scalaコマンド的な-J

scala -J-Xmx 2GB -Dfoo=bar baz.scala みたいにjavaコマンドではなくscalaコマンドでアプリケーションを実行するときのオプション

scalaコマンドは結局モロモロの処理をした後javaコマンドを実行するものになっている。

ここでのポイントは-Jオプションをscalaコマンドに渡すと以下の2つを行ってくれるということ

  • 渡された -J-Xmx1500m などの文字列をそのままjavaコマンドに渡す
  • -J を消した-Xmx1500mをscala引数として渡す

つまり -J の意味1 で述べたjavacうんぬんとは関係なく、scalaコマンドに -J を渡すとjava側に渡してくれる機能がある( -Jという名前が被っているので混乱するが、全然違う機能) scala/tool-unix.tmpl at c804289144c006e91a206ff12e94e5a39ac73a9f · scala/scala · GitHub

-J の意味4 sbt-native-packager的な -J

こちらはscalaコマンドに似ている。

sbt-native-packagerが生成するスクリプトはモロモロの処理をしてjavaコマンドを実行する。

このとき -J の意味2で述べたscalaコマンドの挙動を模した実装が行われており、scalaコマンドと同じように -J をstripして、 -Xmx 1500m にしてからjavaコマンドにわたすという形式になっている。

(scalaコマンドとは違いscala_argsには値が渡らない気がしているが未調査。気になる人は実装読んだりしてください)

sbt-native-packager/bash-template at master · sbt/sbt-native-packager · GitHub

-J をつけないとどうなるかというと sbt-native-packager/bash-template at 1ee84811c3ce2048e2ea857aece3fbe563b8089e · sbt/sbt-native-packager · GitHub によりresidual_argsに入り sbt-native-packager/bash-template at 1ee84811c3ce2048e2ea857aece3fbe563b8089e · sbt/sbt-native-packager · GitHub でmainClassやappCommandの後に引数として渡されるようです。

まとめ

sbt-native-packagerを使う場合、jvmオプションは -J-Xmx 1500m のようにする必要がある

これはscalaコマンドやsbt-native-packagerが独自実装しているshellスクリプトの機能でしかなく、jvmには-Jが削除された-Xmx 1500mだけが渡るようになっている。(-J-Xmxってなんか気持ち悪いなと思っていたけど-Jという文字列を機械的に削除すると考えれば割と理解しやすい)

この際使われる-J prefixはjavaコマンドについてググった時にでてくるjavacコマンドのオプションとは全く別物

おまけ

sbt-native-packagerを使う場合 -Dfoo=bar はそのままjvmに渡される (sbt-native-packager/bash-template at 1ee84811c3ce2048e2ea857aece3fbe563b8089e · sbt/sbt-native-packager · GitHub) ので、

例えばエンコーディング指定であれば -Dfile.encoding=UTF-8 とすればよい。

が、 -J-Dfile.encoding=UTF-8 としても -J機械的に削除されてjvmに渡るので結局 -Dfile.encoding=UTF-8となり、挙動はかわらない(はず)。

Error downloading org.scalameta:semanticdb-scalac_2.13.7:4.4.28 や scalafix-test-kit_2.12.13:0.9.29 と言われたら

この記事について

  • scalafixに依存しているプロジェクトでscalaのバージョンを上げるとタイトルのエラーが出る場合があるので原因などを整理したもの
  • ちょっと前のメモ書きをブログ化してるので各種バージョンは古いです

各エラーについて

エラー1: Error downloading org.scalameta:semanticdb-scalac_2.13.7:4.4.28 と言われたら

原因

scalafixが依存しているsemantic DBのライブラリが存在していないことが原因

semanticdbのバージョンは Installation · Scalafix に従っていれば以下のようになっているはず。

semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision

semanticdbVersion は実際には下記の様に指定されている。

https://github.com/scalacenter/sbt-scalafix/blob/b7b890d00598a574b9d4ea88e4f42168cc83765c/src/main/scala/scalafix/sbt/ScalafixPlugin.scala#L91-L94

    val scalafixSemanticdb: ModuleID =
      scalafixSemanticdb(BuildInfo.scalametaVersion)

    def scalafixSemanticdb(scalametaVersion: String): ModuleID =
      "org.scalameta" % "semanticdb-scalac" % scalametaVersion cross CrossVersion.full

この例では実際には org.scalameta:semanticdb-scalac_2.13.7:4.4.28 と解決される(細かいバージョンはscalaとsbt-scalafixのバージョンで変化する)

今回の細かい値は以下のようになっていた。

  • scalaVersion=2.13.7
  • sbt-scalafix version=0.9.31

このライブラリがmaven上にpublishされていないことが依存が解決できないのが表題のエラーの原因。

publishされているかの確認は https://repo1.maven.org/maven2/org/scalameta/semanticdb-scalac_2.13.7/ に存在しているかで行える。

解決方法

単純にsemanticdbが存在しないことが原因なので、すでにpublish済みのsemanticdbバージョンを使えば良い。

sbt-scalafixでは scalafixSemanticdb(scalametaVersion: String) で指定できるので、以下のように好きなように指定すれば良い。

import scalafix.sbt.ScalafixPlugin.autoImport.scalafixSemanticdb

...
semanticdbVersion := scalafixSemanticdb("4.4.30").revision,

指定するバージョンは Central Repository: org/scalameta から semanticdb-scalac_${使いたいscalaバージョン} 配下にある最新のversionのものにすればよい。

例: Central Repository: org/scalameta/semanticdb-scalac_2.13.7/4.4.30 なら 4.4.30 を指定する。

余談

余談1: ワンポイントsbt

scalafixのコードのなかで CrossVersion.full という指定が出てきていたので意味を整理しておく。

ライブラリのバージョン指定時に CrossVersion.full が指定されていると "org.scalameta" % "semanticdb-scalac" % バージョン番号"org.scalameta" % "semanticdb-scalac_<scalaバージョン>:バージョン番号 として解決され、最終的に org.scalameta:semanticdb-scalac_2.13.7:4.4.28 という指定になる。

普段依存ライブラリを指定する場合は "org.scalameta" % "semanticdb-scalac" %% バージョン番号のように %% を使った指定を行うことが多いが、この場合は "semanticdb-scalac_<scalaバイナリバージョン>:バージョン番号 のように解決され、最終的に org.scalameta:semanticdb-scalac_2.13:4.4.28 のようにバイナリ互換性のあるバージョンまで(2.13.7ではなく2.13まで)を指定するようになっている。これは CrossVersion.binary を指定するのと等しい挙動になる。

CrossVersionには他にCrossVersion.disableも存在するが、これは単にscalaバージョン番号を埋め込んでいないもの(javaのライブラリなど)の指定に用いる。

※ 通常はjava製ライブラリは%、scala製ライブラリは%%を使うと覚えておくと良い。

参考

余談2: scalametaのバージョンはどう指定されているか?

sbt-scalafix version=0.9.31 の場合、scalametaのバージョンは4.4.28と解決される。

これは

をみると scalafix-interfaces.properties というリソースの scalametaVersion に記されていることが分かる。

このファイルの場所はsbt-scalafixに依存している適当なプロジェクトで以下のようにすれば特定できる。

$ sbt
> reload plugins
...
> console
scala> getClass.getClassLoader.getResource("scalafix-interfaces.properties")
res0: java.net.URL = jar:file:/略/https/repo1.maven.org/maven2/ch/epfl/scala/scalafix-interfaces/0.9.31/scalafix-interfaces-0.9.31.jar!/scalafix-interfaces.properties

が、 GitHub - scalacenter/scalafix: Refactoring and linting tool for Scalaリポジトリを見てみるとそういったファイルは存在しない。

こういった場合は自動生成されている可能性があるので build.sbt を探すと以下のように自動生成を行うタスクの記述がある

scalafix/build.sbt at v0.9.31 · scalacenter/scalafix · GitHub

これを見ると scalametaV という変数を参照していることが分かるので

scalafix/Dependencies.scala at v0.9.31 · scalacenter/scalafix · GitHub

にある

  val scalametaV = "4.4.28"

が該当する。

エラー2: not found scalafix-test-kit_2.12.13/0.9.29と言われたら

現象

  • scalafix(scalafix-testkit)はscalaのメジャーバージョン(scala 2.12 or 2.13)だけでなくマイナーバージョン (2.12.13 or 2.12.14)のレベルまで依存している。
  • scalafixとscalaのバージョンの組み合わせが悪いのでnot foundになっている

解決方法

以下のような要領で使えるバージョンを探しましょう