2023年
もう半分以上過ぎているが何をやっていたかをメモ
仕事
- 2023年から新規サービスの立ち上げ
転職してた
- 上の新規プロジェクト立ち上げで半年くらいやってたが、なんとなく流れで5月に転職してた
- 引継ぎ先の人材がおらず、引継ぎ期間1週間かつ、有休を10日くらい残して辞めることになった
- 特に大きな不満があったわけではないが、楽しそうだったので。
- なんだかんだ転職は2回目
- 30歳直前で一回目、そして今回40歳手前で二回目
- まったく意図してなかったが10年スパンくらいで環境を変えている
- とても楽しいフェーズ
リモートワーク
- 本社が東京なので引き続きリモート
- リモートやればやるほどハードな働き方であることを実感する
- 万人にお勧めできる働き方ではない
40になった
- 40になって開発経験を生かしてうまく立ち回れている
- 20代、30代とは違う動きを期待されている
- どこかでリモートワークと40代の働き方とかについて自分の考えまとめておく
問題解決力を高める推論の技術という本を読んだ
- 作者:羽田康祐k_bird
- 発売日: 2020/01/08
- メディア: Kindle版
問題解決力を高める推論の技術という本を読んだ。
読書時間
大体2日ほど
内容について
帰納法や演繹法、仮説的推論等々を紹介している。具体例を交えながら、日常に意識的に取り入れて習慣化できるよう、どういうシーンでどのような思考でどのような法則や仮説が得られるかといった話題が多かった。
帰納法、演繹法いずれも、読んでみると意外と日常的に行なっていることであり、明文化して改めてそれに名前がつけられると、「ああ、あれはそういう思考法だったのか」くらいの感じで特に違和感なく読めた。
プログラムを書いたことがあるならば、複数の事項から一般化した共通の事項を見つけるといったプロセスは、何かと自然にやっている気がする。
日々なんとなくみているものに疑問を持ち、その現象が「なぜそうなっているかを考える」というのは確かに意識しないとなかなかできない、という箇所は心に刺さるものがあった。
微妙だった部分
とにかく具体例がしっくりこないものが多かった。
身近な話題からそれぞれの思考法で法則や予測を導き出すという試みは、一見すると難解な思考法も気軽に適用して鍛えることができるよ、というのをアピールしたい意図は伝わるのだが、
池上彰の番組が人気なのは世間が知的スッキリ感を求めているからだ
という具体例あたりで、思い込みの域から出ないんじゃないかなぁという気持ちがあり、それ以外の具体例もいくつかしっくりこないものがあった。
ただ、前半の物理的なものごとからその物体がもつ特徴を抽象化し、それを一般化するといった思考の例にあがっている「水」に関しての例に関しては、なるほど、といったこともあるので、須く全ての具体例が悪いわけではないと思う。
気になる部分は多いものの読んで時間を無駄にしたという気はしなかったのでこれはこれでよし。久しぶりに技術書(これも一種技術書かもしれないけど)を読んで楽しかった。
ホームベーカリーにハマった
コストコでホームベーカリーがやすかったので思わず買ってしまった。以下が同じ種類
元々朝食はひたすらにパン食なため、割と食パンの消費量は半端ない。
関東にいた頃は祖父母宅で毎朝出ていたアンデルセンのイギリスパンが大好きだったのだが、残念ながらアンデルセンが北海道になさそう。ただ、北海道は日本随一の小麦の生産地なため当然ながらパン屋が豊富だった。こちらに移り住んで最初の家の近くにボストンベイクという、焼きたてパン屋なのだがアホなのかというくらい安いパン屋があったため、ほぼ毎朝そこでパンを買ってから出社していた。それ以外にも地元密着のパン屋がいくつもあり、パン党の自分としてはとても嬉しい限り。
最近ではペンギンベーカリーカフェの食パンが非常にお気に入りなのだが、一斤で300円ほど。少々値が張る。
そこでせっかく小麦粉の生産地が近くにあるのだからということで自作をしてみることにした。いろんな食パンを食べてるうちに「春よ来い」とか「ハルユタカ」とか北海道産の小麦粉を色々知り、これらで作られた食パンが非常に好みだということもわかってきた。
そこで1から自作することにした。最初はホームベーカリーのミックス粉を使って食べてみるがなかなかうまい
レシピ
Siroca の食パンミックス
siroca×日本製粉 毎日おいしいパンミックス お手軽食パンミックス(1斤×10袋) スウィートパン SHB-MIX1290[ドライイースト付]
- 発売日: 2017/02/11
- メディア: ホーム&キッチン
ちょっとサイズが小ぶりなので、自分の機種でやると少し背が低い食パンになる。甘みがあるけどくどくなくて軽く食べれて良い。1歳ちょいの娘も何もつけずにパクパク食べていた。
ぼーのの食パンミックス
個人的にはこれが一番美味かった
ただし3袋で980円くらいするので、少々お高い部類ではある。しかし、小麦の味がよく感じられるのと粉の量が多目なのかそこそこ背の高い食パンらしいいで立ちで出来上がる。原材料を見ると粉末はちみつが入っているので、0歳児等は注意が必要。
うちの娘は知らずに食べてしまったが、こちらもパクパク食べていた。こころなしかSirocaの食パンよりもおかわり要求が強かった気がする。
自作レシピ
パンミックスは水を入れるだけで、とても便利だが、まあ少々お高い。そこで粉も自分で調合するように変えた。参考にしたレシピは以下。
分量を見ると他のレシピより、砂糖とバターが若干多め。そのため少々甘めの仕上がりになる。これは非常に美味い。
無塩バターがない場合は、エキストラヴァージンオリーブオイルで代用できるようだ(10gほど?)今度やってみようかと思う。
音
同じ部屋で寝ている場合はきつい。混ぜる際の音がそれなりに騒音である。ウオンウオンという音にネコも当初は非常にビビっていたが、今や慣れたものである(いたずらされなければ何でも良い)
調理時間
大体、食パンの通常時間は4時間ちょい。そのため、朝焼き立てが食べたいと思った場合は、3時くらいから動くようにタイマーをセットすれば良い。ただ、焼き立て直後は柔らかくてなかなか切るのが難しいので、焼き上げてから1時間ほどのベンチ時間を見ておいたほうが良さそうだ。従って、実際に窯から出してから1時間後に食べられると思ったほうが良さそう(1時間後なら普通の包丁でも切れた。焼き上げ直後は柔らかすぎて潰れる)
コスト
パンミックスを使うと買うより少々割高である。最低でも1斤につき200円弱と考えると、高い部類ではある。自作の場合は100円以下まで下げられそうだが、作る時間やらの手間暇を換算するともしかすると割高になるかもしれない。そのため、以下のような奇特の人以外はやめたほうがよいかもしれない
- 材料を全部自分で把握した上で作ってみたい
- 別に多少の手間なら無料と換算するわ
- 焼き立てパンにまさるものはない。プライスレス
- 朝パンがないと絶望だ
とりあえず
購入して一ヶ月ほど楽しくパンを焼いている。粉の調合さえ面倒でなければ自分でやったほうが遥かに安くあがるようだ。
「とてつもない失敗の世界史」を読んだ
とてつもない失敗の世界史
- 作者:トム・フィリップス
- 発売日: 2019/06/18
- メディア: 単行本
気になっていたので購入していた本の一つ。 有給を使って休んだものの札幌はあいにく天気が悪く、どこへでかける気力もなかったので積ん読消化にあてた。
結論から言うと、少々話題がとっちらかっている感じはあるがとても面白かった。特に今のコロナ禍で様々な正義が錯綜している中、みんなこの本読んで落ち着いた方が良いんではないかな。人間が間違った方向に進むときは、たいてい情報を収集するものの自分に都合の良い情報だけを選択し、都合の悪い情報や反論などは取るに足らないものとして無視するか、過剰に攻撃して封殺するかなど、ああ、歴史上何度も繰り返してきてるんだなというのがよく分かる。
この本は、徹頭徹尾、人間はしょうもない種族であるということを一貫して主張していてとても良い。ここまで歴史上の小さな失敗から大きな失敗までを寄せ集めたエピソード集はなかなかない。内容としては、本当にしょうもない失敗やら妄想によって些細な結末から、国が傾くもしくは消滅してしまうレベルの結末まで様々なバリエーションが紹介されている。
名前を聞いたことがある身近な人から、そんな人が歴史上いたのかと発見することもあり、単純に知識が増えるのは面白い。
歴史書ではないので事実を淡々と書くというよりかは、エピソード一つ一つに対して都度茶化した記述になっているので(植民地政策の奴隷制度のところはそんな茶化した記述にはなっていないが)割とサクサク読めてしまう。日本史、世界史が大の苦手だった自分がスラスラ読むことができたのは、面白おかしく(ときにはエピソードを想像で補いながら)記述されているのが大きかった気がする。全面的にこれが正しい世界史と思うわけではないが、単純に出来事と登場人物、そこにまつわる失敗談を知ることができたので、興味の取っ掛かりとしては良かったかもしれない。
どうもこのシリーズはいくつか出ているのようなので他のも買って読んでみようかと思う。
- 作者:トム・フィリップス
- 発売日: 2020/06/26
- メディア: 単行本
ClientHttpRequestInterceptor で RestTemplate のリクエスト処理に割り込む
やりたいこと
RestTemplate をつかう際に共通処理をリクエスト前に潜り込ませたい。例
- ローカルキャッシュに持ってる認証情報を Authorizationヘッダ にセットする
- 認証情報がなければ取りに行ってローカルキャッシュに保存しつつ Authorizationヘッダにセットする
ClientHttprequestInterceptor インタフェース
RestTemplate には Interceptor を複数設定できるようになってるので、このインタフェースを実装すれば良い。
Mock でちゃんと Intercept されるか確認してみる
動作環境は以下
- openjdk 11.0.6 2020-01-14 LTS
- macOS Mojave 10.14.6
@ExtendWith(MockitoExtension.class) public class DummyClientTest { @Mock TestClientInterceptor interceptor; @DisplayName("Interceptor内で処理が行われることを確認") @Test public void inject_interceptor_success() throws IOException { String success = "success"; var mockResponse = new MockClientHttpResponse(success.getBytes(Charset.defaultCharset()), HttpStatus.OK); when(interceptor.intercept(any(), any(), any())).thenReturn(mockResponse); var restTemplate = new RestTemplateBuilder().additionalInterceptors(interceptor).build(); var value = new LinkedMultiValueMap<String, String>(); value.add("main", "request"); var requestEntity = RequestEntity .post(URI.create("https://example.com/dummy")) .body(value); var actual = restTemplate.exchange(requestEntity, String.class); assertNotNull(actual); assertEquals("success", actual.getBody()); } }
RestTemplate に何も設定しなければ https://example.com
に謎の情報をPostしてしまうので当然エラーで弾かれる。今回は Interceptor 実装を RestTemplate に設定してるのでそちらに処理が委譲されてるはず。
このUnitTestは通る。
23:38:31.434 [Test worker] DEBUG org.springframework.web.client.RestTemplate - HTTP POST https://example.com/dummy 23:38:31.448 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/cbor, application/*+json, */*] 23:38:31.453 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Writing [{main=[request]}] with org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter 23:38:31.472 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK 23:38:31.474 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/octet-stream"
ちなみにInterceptor を設定しない場合
23:26:16.094 [Test worker] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {s}->https://example.com:443] can be kept alive indefinitely 23:26:16.094 [Test worker] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0 23:26:16.095 [Test worker] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://example.com:443][total kept alive: 1; route allocated: 1 of 5; total allocated: 1 of 10] 404 Not Found: [<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w... (445 bytes)] org.springframework.web.client.HttpClientErrorException$NotFound: 404 Not Found: [<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w... (445 bytes)] ....
めっちゃエラーで怒られる。ごめんなさい。一応ちゃんとテストで404ナノを確認
@DisplayName("存在しないURIにアクセスし404が返却される") @Test public void not_inject_interceptor_failed_notfound() { var restTemplate = new RestTemplateBuilder().build(); var requestEntity = RequestEntity .get(URI.create("http://dummy.restapiexample.com/api/v1/dummy")).build(); var actual = assertThrows(HttpClientErrorException.class, () -> restTemplate.exchange(requestEntity, String.class)); assertNotNull(actual); assertEquals(HttpStatus.NOT_FOUND, actual.getStatusCode()); }
実装
雑な実装とイメージ。
@Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // どこか保存したところからATを復元する var accessToken = repository.findToken().orElseGet(() -> { var token = getNewAccessToken(); if (token != null) { repository.saveToken(token); } return token; ); if (accessToken == null) { log.warn("can not get AccessToken."); throw new TestClientException("can not get AccessToken"); } // Bearerトークンとして付与 request.getHeaders().add(HttpHeaders.AUTHORIZATION, "bearer " + accessToken.getAccessToken()); var response = execution.execute(request, body); var statusCode = response.getStatusCode(); if (statusCode.equals(HttpStatus.UNAUTHORIZED)) { // ATが無効. リトライは一回でいいか log.info("Expired AccessToken. Try call TokenEndpoint."); var token = getNewAccessToken(); if (token == null) { log.warn("can not get AccessToken."); throw new TestClientException("can not get AccessToken"); } // どこか共有するとこにATを保存する repository.saveToken(token); var renewToken = token.getAccessToken(); request.getHeaders().add(HttpHeaders.AUTHORIZATION, "bearer " + renewToken); response = execution.execute(request, body); } return response; }
トークン取得、キャッシュ保存、リクエストが何箇所か重複してるのできれいではない気がするんだけど、 とりあえずやりたいことはできた。
3分間ネットワーク基礎講座を読んだ
IPAの基本情報技術者試験や応用情報技術者試験を取る際に一通り学んだはずだし、大学でも一通り入門はしたはずなのだがやはり何年も使ってないと次々と忘れてしまうネットワーク周りの話。
今や AWS や GCP 等によってアプリケーションエンジニアでも気軽にインフラ環境を手に入れることができるのだが、ネットワーク周りの設計等は正直公式リファレンスから毛の生えたような程度の設定でなんとなく動かせてしまっている。なんでこういう設計にしたのかがちゃんと説明できないなどなど、なんだかんだ基盤周りの知識はないと困るので、まずはネットワークから復習のために買った
読んだもの
- 作者:網野 衛二
- 発売日: 2010/09/11
- メディア: 単行本(ソフトカバー)
読了までかかった時間
3時間くらい
雑な感想
物理層からアプリケーションレイヤーまで一通りエッセンスをギュッとまとめて紹介してくれていて、復習にはとても良い。読んでいてわかったのだが、どうやら自分はレイヤー3以下が苦手らしい。ルーティングプロトコルやデフォルトゲートウェイなどの話は、単語は聞いたことあるしどういう設定すれば動くのかわかるけど、実際中で何をやっているのか説明ができない、という状態だったのでそのあたりざざっと復習ができたのでとても良かった。
正直普段アプリケーションを開発している側からすると、TCP/IPあたり以降の話しかあまり意識したことがなく、それこそ自前でネットワークを組んでみるとかしない限りはうすらぼんやりとした知識しかないのが正直なところだ。そういう意味ではネットワークのレイヤー構造は下位レイヤーの隠蔽がうまくいってるんだなぁと感心する。
スイッチ、ハブ、ルーターはその昔はそれなりに研究室に転がっていたりして、なんとなく研究室内のネットワークへつなげるために利用していたが、それこそあまり使い分けは意識していたわけではない(さすがにルーターとハブの違いくらいはわかっていたが)
ただ、やはり当然ながらそれぞれの機器にはそれぞれの利点と不利な点があるなど再度色々と勉強できたのは良かった。以下途中からだけど雑なメモ
- RIP
- メトリック
- 最適な経路を決定する際の判断基準
- ホップ数、回線スピード、込み具合、エラー率とか
- ICMP
- エラー報告プロトコル
- ICMPメッセージ
- QUERY, ERROR
- IPデータグラムのTTL
- タイプ3
- 宛先到達不能
- ping
- タイプ0
- エコー応答
- タイプ8
- エコー要求
- タイプ0
- タイプ11
- レイヤー4
- TCP
- TCPヘッダ=20オクテット
- データ転送の許可要求
- 3wayハンドシェイク
- MSS
- Max Segment Size
- シーケンス番号
- シーケンス番号は送るデータの先頭オクテット番号
- 確認応答番号は次に送ってほしいデータの先頭オクテット番号
- RTT=Round Trip Time
- ウィンドウ制御
- 複数のセグメント転送 → 確認応答
- バッファで一時的に保管
- ウィンドウサイズ=バッファ量=確実に受け取れる最大のデータ量
- Wellknown Port
- 送信元は49152~の重複してないポートを利用する
- UDP
- 何も制御しないのでめちゃくちゃ早い
- ブロードキャストはUDP
- クラスレスアドレッシング
- NAT
- ネットワークアドレス変換
- 同時接続数の限界=割り振られたGlobal IPの最大値
- NATの発展したもの=NAPT
- NAPT
- ネットワークアドレスポート変換
- ポート番号も変換対象なので同じGlobal IPから複数のアプリケーションが外に出れる
- IP以外にポートも正しくないとデータが流れないので、より安全
- 内側から外へアクセスする分にはIP、ポート番号が記録されるので問題ない
- 外側から内側へアクセスするときには、変換テーブルがないので無理
- LAN内部に80ポートの公開サーバーがある場合等
- 静的NAPT
- あらかじめIPとポート番号の変換を書いておく
Go Lambda + aws cdk でLambda実行エラー
最近コードを書く機会が極端に減ってしまっている(読む機会はたくさんある)ので、業務外で会社の人たちがたくさん公開しているLambdaの資料を見ながら、お酒飲みながら雑なコードを書いてなるべく筋トレしています(圧倒的に記述量が足りないので色々忘れる速度の方が早いのが悩み)
しょうもない話です
少し考えればわかることでしたが。以下しょうもない経緯の話なのでここに結論を書いておきます。
aws-cdk はほぼ関係ありません。
macで単純に go build
したバイナリをLambdaとして登録すると「当然アーキテクチャやらOSが違うからまともに動かないので、ちゃんとアーキテクチャやOSを指定してターゲットに合わせてビルドしたものを登録しようね」という話でした。
$ GOARCH=amd64 GOOS=linux go build -o bin/main
これを指定しないと以下のエラーが出力され、何をやってもLambdaが起動しません。
{ "errorMessage": "fork/exec /var/task/main: exec format error", "errorType": "PathError" }
stackoverflow - exec format error when running AWS Golang Lambda
"exec format error" from SAM Local with Golang on Mac
Qiita - alpineのイメージでAWS Lambda用のgoバイナリをビルドしたらPathErrorになった話
長らく環境に合わせたバイナリを作って云々というのをやっていなかったのもあって、このあたりの考慮がさっぱり抜けてました。
経緯
Lambda を書く際には Typescript が圧倒的に多いようですが、自分の場合なかなか慣れることができておらず、別の言語(大体 Go )を選択することが多いのです。
aws-cdk は TypeScript を書きつつ、Go で Lambda を Deploy した際に変なところでハマったので記録しておきます(多分頭が回っていなかった)
go lambda + cdk の構成
こちらの記事を参考にさせていただきました。
aws cdk でGolangのLambdaをデプロイしてみる
ふむふむ。わかりやすいですね。これなら悩むことなくササッとできそう。
バージョン情報は以下のとおりです。
$ cdk --version 1.51.0 (build 8c2d53c)
基本的に cdk の作り方はいつもどおり。
$ mkdir lambda-go $ cd lambda-go $ cdk init --language=typescript
雛形ができました。
go lambda
Lambda は handers
というディレクトリを作成し、こちらの配下に配置することにしました(このあたり、ちゃんと読まずに適当にやっていることがバレる)
package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" ) // MyEvent lambda用カスタムEvent type MyEvent struct { Name string `json:"name"` Age int `json:"How old are you?"` } // MyResponse lambda用カスタムレスポンス type MyResponse struct { Message string `json:"Answer:"` } // HandleRequest Lambdaリクエストハンドラー func HandleRequest(ctx context.Context, event MyEvent) (MyResponse, error) { return MyResponse{Message: fmt.Sprintf("%s is %d years old!", event.Name, event.Age)}, nil } func main() { lambda.Start(HandleRequest) }
go の Lambda パッケージは導入済みとしています。必要であれば go get github.com/aws/aws-lambda-go/lambda
して導入しておきます。
参考にさせていただいた記事の通り記載しました。記述した go のコードはビルドしてバイナリに変換します。はい。もう一度いいます。変換します。
$ go build -o bin/main
生成したバイナリは handlers/bin/main
に配置されました。Lambda の準備が完了したので Lambda を配置する Stack のコードを修正します。
lambda-go-stack.ts
@aws-cdk/aws-lambda
を導入しておきます。
import * as cdk from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; export class LambdaGoStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here new lambda.Function(this, 'GoFunction', { runtime: lambda.Runtime.GO_1_X, handler: 'main', code: lambda.Code.asset('./handlers/bin') }); } }
runtime
に GO を指定し、 handler
には main
, code
には変換したバイナリが配置されている相対パスを指定します。
stack を編集した内容を変換する必要があるので、npm run build
を実行すると、jsコードに変換されます。参照した記事にもあるように、tsc;
する際に同時に go build
をしてくれると楽なので package.json
を以下に変更。
"scripts": { "build": "tsc; cd handlers; go build -o bin/main", "watch": "tsc -w", "test": "jest", "cdk": "cdk" },
これで npm run build
した際に同時に go build
も実行してくれるようになりました。ではcdk deployします。
$ cdk deploy --profile hoge LambdaGoStack: deploying... .... arn:aws:cloudfromation:us-east-1:xxxxxxxx
Lambdaをテストするとエラー
簡単なコードだから普通に動くだろうと高を括っていたので、以下のエラーを見て鳩が豆鉄砲を食ったよう。全く想定外。
{ "errorMessage": "fork/exec /var/task/main: exec format error", "errorType": "PathError" }
exec format error なんだかそもそも根本が間違ってそうなエラーメッセージ。
そういえば Go の Lambda ってソースコード上げてるわけじゃないしあたりで気づいた結果、そもそも起動する対象がmacと違うというのに今更気づき、調べてみた結果が冒頭の結論です。
まとめるほどでもないもの
冒頭の結論が全てです。
バイナリを生成するタイプのものは、どこで動かすのかをきちんと意識してやりましょうという話でした。
2020/07/19 追記
GoのLambdaの公式リファレンスにちゃんと書いてあった。
GOOS を linux に設定すると、非 Linux 環境でコンパイルする場合でも、コンパイルされた実行可能ファイルと Go ランタイムとの互換性を確保できます。
ドキュメントを読め。本当にすいませんでした。