Open6

cqrs/esをAWSサービスを使って実装してみる

ほりしょーほりしょー

現時点で見えている懸念点

  • キャプチャしているLambdaの実行が何らかの理由で失敗してしまった場合、変更の再送はできるのか?(データは24時間しか保存されない)

    関数がエラーを返した場合、処理が正常に完了するか、データの有効期限が切れるまで Lambda はバッチを再試行します。Lambda では、より小さなバッチで再試行したり、再試行回数を制限したり、古すぎるレコードを破棄したりするよう設定できます。
    https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.Lambda.html

    • 代替案(いずれも重複・順序の入れ替えが発生しうるので冪等・順序を保証すべき)
      • Kinesis Data Streamsを使って変更データキャプチャする。この場合データは最大1年保存可。
      • キャプチャするLambdaはシンプルにSNS/SQSに流すだけにして、デッドレターキューを活用
  • イベントA、イベントBの順でStreamsに流れてきた時、イベントAの処理が長く、イベントBの保存が先に終了・イベントAの処理が失敗するケースにどう対応するか?
    • 同期的に処理されるので問題なさそう

      Lambda 関数が同期的に呼び出されます。同じ DynamoDB ストリームに最大 2 つの Lambda 関数をサブスクライブできます。
      https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.Lambda.html

    • そもそも長時間実行されるLambdaはよろしくない

      パフォーマンスのベストプラクティスとして、Lambda 関数は存続期間を短くする必要があります。また、不必要な処理の遅延が発生するのを防ぐため、複雑なロジックは実行しないでください。特に高速ストリームの場合は、同期的に長時間実行する Lambda よりも、非同期的な後処理ステップ関数ワークフローをトリガーすることをお勧めします。
      https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.Lambda.html

ほりしょーほりしょー

Honoの環境構築

https://hono.dev/docs/

create hono

npm create hono@latest


> npx
> create-hono

create-hono version 0.14.3
? Target directory command-app
? Which template do you want to use? nodejs
? Do you want to install project dependencies? yes
? Which package manager do you want to use? npm
✔ Cloning the template
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd command-app

初期状態

npm run dev
初期状態(http://localhost:3000)

ほりしょーほりしょー

関数型*TSで実装

https://github.com/H0R15H0/cqrs-es-aws-services/pull/2

biome入れた

https://biomejs.dev/ja/guides/getting-started/
https://biomejs.dev/ja/reference/vscode/

問題点1:構造的部分型による不正値の混入問題

type name = {
    readonly value: string
}

type id = {
    readonly value: string
}

const id = {value: "id"}
const name = id // これさせたくない
  • classであればprivateフィールドを定義
  • ブランド型*as typeNameによるアサーション

で回避できるがしっくり来ない

解決策
newtype-tsを使ってnominalな型を定義。
newtypeを自作する。

参考
https://qiita.com/suin/items/35c3953254255d24a193
https://zenn.dev/okunokentaro/articles/01gmpkp9gzfyr1za5wvrxt0vy6
https://speakerdeck.com/kakehashi/strike-a-balance-between-correctness-and-efficiency-with-fp-ts?slide=27
https://speakerdeck.com/naoya/typescript-guan-shu-xing-sutairudebatukuendokai-fa-noriaru?slide=9

Result型の利用

候補

neverthrowが軽量サクッと試すにはよさそう。
https://github.com/supermacro/neverthrow

つまりポイント: Resultの合成

やりたいこと
https://speakerdeck.com/kakehashi/strike-a-balance-between-correctness-and-efficiency-with-fp-ts?slide=19

できたもの

const validateRegisterItem = (
	command: RegisterItemCommand,
): Result<ValidatedRegisterItem, RegisterItemValidationError> => {
	const itemName = createItemName(command.name);
	const itemPrice = createItemPrice(command.price);

	const values = Result.combineWithAllErrors([itemName, itemPrice]);

	return values.map(([name, price]) => ({ name, price }));
};

https://github.com/H0R15H0/cqrs-es-aws-services/pull/2/commits/60c3d8c5e2fbaf76206f3a22b53e8c445f11126f

参考
https://speakerdeck.com/naoya/typescript-niyoru-graphql-batukuendokai-fa?slide=50
ただのcombineだとエラーが最初の一つのみしか返されないので注意

イベントペイロードのデータ型

理想:protobuf
一旦JSONで次に進む

ほりしょーほりしょー

Railway Oriented Programing

https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/

Result is a tool for domain modeling, so if the domain model doesn’t need it, don’t use it.
A similar example can be found when implementing event sourcing, in the command processing function which has the standard signature

'state -> 'command -> 'event list
If something goes wrong in executing the command, how does that affect the return value (the list of events created by the command) in practice? Of course you need to handle errors and log them, but do you actually need to return a Result from the function itself? It will make the code more complicated for not much benefit.
https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/#5----dont-use-result-if-no-one-cares-about-the-error-cases