tezu memo blog

日々行った作業をメモしていきます

AWSで実現するモダンアプリケーション入門 を読んだ

自身のモダンアプリケーション?の経験

何となく経験出来ているような・・

  • Unit Testのカバレッジ100%を実現
  • CircleCIで以下を実行
    • Unit Test
    • E2E Test
    • TestがAll Greenの場合はデプロイを実行
  • Twelve-Factor AppなAppを作成したので、イミュータブルなデプロイを実現
  • (Unit Testのカバレッジ100%なので)GitHubのDependabotを有効にして、Pull Requestをmerge

感想

  • 構成が良い。最初に言葉の定義をして、次にモダンでは無いアプリケーションとこのアプリケーションが抱える具体的な課題も明確にしている。ピラミッドストラクチャーですね!
  • モダンでは無いアプリケーションをモダン化する際に既存のアーキテクチャを無理矢理変更しない点が実際の現場に寄り添っている感じで良い
    • MVP達成後、更にビジネスを加速させたいが技術的負債が溜まってキツイ人達って結構多いですよね
    • MVCアーキテクチャのModelがAPIファーストによってクラスのメソッドからAPIになっていく姿が脳内に浮かびました
  • コンテナやサーバレスの前にオブザーバビリティを取り上げてるのも素敵。過去に、NewRelicやDatadogを導入してAPMの画面を見せたら、「え?こんな情報が見れるんですか?」的な反応をする人が多かったです
  • オブザーバビリティとモニタリングの違いについて簡潔に説明してるのも良い
  • コンテナとサーバレスを比較して、サーバレスの方が開発に集中出来るとあるが、APIのエンドポイントが多いサービスだとコンテナ上でWebアプリケーションフレームワークを動かして、エンドポイント量産する方が集中出来るような。もう少し具体例が欲しい・・
  • アプリケーション側が発火したEventはSNSAWSのリソースが発火したEventはEventBridgeと勝手に理解していたのだが、少し混乱してきた
  • 開発のベストプラクティスやAPIの冪等性など、AWSのサービス以外の知識が増える
    ECサイトで決済代行のAPIを利用した際に注文番号をRequestに付与していた事を思い出しました
  • 自分自身が大量データの扱いについて悩んでいたので、要件にあったデータベースの選択は助かりました
  • (ADOTなど)全く知らないサービスを知ることが出来た。NewRelicやDatadogがサポートしていない言語はADOTを使用すれば良いのだろうか?

まとめ

モダンなアプリケーション開発したいが、周りの理解が得られず悩んでいる方はこの本で布教活動を開始できるくらいの良書です

Software Design 2023年2月号 ドメイン駆動設計入門 を読んだ

gihyo.jp

自身のドメイン駆動設計の理解や状況

感想

  • DDDの目的(事業の変化とともに成長と進化を続ける)を意識していなかった p19
    実務では、アジャイルな開発・テスト自動化・CICDパイプラインで成長と変化に対応している
    DDDはソフトウェアアーキテクチャとして使用しているが、この視点は欠けていた
  • 業務とプログラムのトレーサビリティを確保 p23
    業務知識・モデル・プログラムのトレーサビリティを確保しないと、事業の変化にシステムが追いつかないですよね・・
    普段から後続のフェーズを意識した成果物を作成しているので、これは大丈夫
  • 「複雑な業務ロジックに焦点を合わせる」のドメイン駆動設計の開発のやり方 p27
    • 複雑な業務ロジックが少ないシステムで全ての処理にDDDを適用!となると、フルスタックフレームワークの長所を消してしまう短所の方が上回るので注意が必要
    • 複数のレイヤーで構成。かつ、各レイヤー間の値を受け渡しにDTOを使っていた設計を見直しした記憶が蘇ってきました
  • 業務ルールからエンティティ・値・コレクション・区分オブジェクトを抽出する方法が纏まっていてとても参考になる p35-40
    • 特に業務の概要からエンティティを見つける点に関しては、業務知識とモデルのトレーサビリティ確保の点でとても重要
    • 実務ではECサイトの注文番号を値オブジェクトにして、Eloquent:ミューテタ/キャスト 8.x Laravel で取り出せるようにしていました
  • SIer時代、システムのリニューアル時は現地で現行システムの具体的な運用(画面操作して頂くなど)を見せて頂きたいと依頼してました。見学は良いですね p53
  • WBSで予めリファクタリングのタスクを入れるのは自分も難しいと感じた。機能開発時に要リファクタリングみたいなTODOコメントを残して、これらが溜まったらリファクタリングしているので、予めスケジューリングするのは難しい p59

まとめ

「複雑な業務ロジック」を対象していることが理解出来たのが良かった

業務知識・モデル・プログラムのトレーサビリティが最も重要だと感じたので、これらのサンプルがあれば嬉しかった

37signals Dev — Vanilla Rails is plenty を読んだ ServiceじゃなくてMix-inで良いのかも知れない

dev.37signals.com

自身のフレームワーク経験

感想

色々な問題が解決するので、Mix-inを試してみようと思いました

命名問題

recording.incinerate
Recording::Incineration.new(recording).run
Recording::IncinerationService.execute(recording)

it feels more natural, like plain English. It feels more Ruby.

{DomainName}Service.execute だと、使う側は仕様が分かり辛い、作る側はServiceの単位や名称を考えるのが辛い問題があるので、Mix-inの圧勝

正直、Service層を追加してから毎回悩んでます

Anemic Domain Model問題

Don’t lean too heavily toward modeling a domain concept as a Service. Do so only if the circumstances fit. If we aren’t careful, we might start to treat Services as our modeling “silver bullet.” Using Services overzealously will usually result in the negative consequences of creating an Anemic Domain Model, where all the domain logic resides in Services rather than mostly spread across Entities and Value Objects

Modelの属性しか操作していないServiceのfunctionがあったら、適宜Modelに移動している。Serviceが無ければModelに実装するので、この作業は不要になる

移譲問題

Tons of boilerplate code because many of these application-level elements simply delegate the operation to some domain entity. Remember that the application layer should not contain business rules. It just coordinates and delegates work to domain objects in the layer below.

マスタメンテ系の単純な画面だと、ServiceがModelに移譲するだけになるので、Service層を使わない判断をしている。これも、Serviceが無ければModelに実装するので、この作業は不要になる

LaravelのRoute Model Bindingを使用すると、LaravelがControllerとModelを紐付けするのでServiceを強制すると冗長的なコードになってしまう

laravel.com

  • Service無し
<?php
use App\Models\User;
 
// Route definition...
Route::get('/users/{user}', [UserController::class, 'show']);
 
// Controller method definition...
public function show(User $user)
{
    return view('user.profile', ['user' => $user]);
}
  • Service有り
<?php
public function show(int $userId)
{
    $service = app(UserService::class);
    $user = $service->findBy($userId);
    return view('user.profile', ['user' => $user]);
}

図解 Amazon Web Servicesの仕組みとサービスがたった1日でよくわかる を読んだ

購入した理由

50%OFF and AWS全然知らない人とのコミュニケーションを円滑にする

自身のAWS経験

  1. 3年程度。それまではオンブレミス

  2. 主なロールはBackendのSWE。オンプレミス時代はインフラ未経験

  3. 触ったことのあるサービス

    • Elastic Beanstalk / EC2 / ECS / App Runner / Lambda
    • CloudFront / RDS / Aurora / ElastiCache / S3 / CloudWatch Logs / Athena / SES
    • CodeDeploy / SAM / CDK

良かった点

  1. プロビジョンド方式 という単語。「サーバレスじゃなくて通常の奴って何て言うんだっけ」とよく忘れるので

  2. 物理サーバ・仮想サーバ・コンテナの違い。一軒家・マンション・シェアハウスの例は分かりやすい

  3. 割引(RIとSP)に触れている。長期間の利用が確実な場合は使った方が良い
    高価なRDSは劇的に安くなりますよ!

  4. コンテナのメリット コンテナイメージはどの環境でも起動出来る点
    EC2を直接使用の場合はAMIを生成するツール(Packerなど)、Elastic Beanstalkの場合は設定ファイル(.ebextensions)にDockerfileと同じような記述をすることになるので2重メンテナンスが面倒だった

  5. ネットワーク。VPCやサブネットを単体で理解するのは難しいですが、Webアプリケーションの一般的な構成例が図解されてるので理解が比較的容易になっている

  6. データベース RDSとAuroraの違い。Auroraは書き込みと読み込みのエンドポイントが異なる点など
    LaravelはWrite/Read毎にエンドポイント指定出来ますね

  7. IAM ユーザとロールの違い。ユーザは人、ロールはAWSのサービスが使う
    Backendのプログラムで環境変数にIDとシークレットを登録して、AWS SDKを使用しているのをたまに見かけます

  8. 複数AWSアカウントの利用
    後からマルチアカウントにするのは結構大変なので、最初から本番とテストは別のアカウントの方が良いです

  9. CloudWatch Logs アプリケーションログを転送することでAWSの管理コンソールでログを確認出来る
    オンプレミスだとサーバにSSHして、tailgrep してログ見てますけど、これは不要

追加するとすれば

  1. JSONを説明するなら、jq も一緒に
    AWSの利用頻度が少ない人はJSONに不慣れな可能性があるので jq の存在を知らないかも

  2. (DynamoDBの利用シーンの記載はあったので)DocumentDBの利用シーン
    JSON形式で保存出来るので、Google Formsみたいなフォームビルダーで活躍するのかも(使用した事が無いので想像です・・)

まとめ

主要なサービスの概要が纏まっているので、薄く広く理解出来る良書でした
深堀したい場合は公式を見る。内容が難しいと感じたら、AWSの管理コンソールを操作すれば理解が深まるでしょう

VPC関連が完全な初心者だと結構難しいはず
一旦スキップして、ELB・Route53・CloudFrontだけの理解でも良いかも

SES メールを連続送信後にAthenaで結果を確認すると1件しか表示されない Kinesis 動的パーティショニングキーで解決

最近、SESと戯れています

事象

SESを使用してメールを連続送信後にAthenaでメール送信の結果を確認すると1件しか表示されない

CREATE
EXTERNAL TABLE SES_RESULTS (
  notificationType string,
  mail struct <messageId:string, commonHeaders:struct < `from`:array<string>,`to`:array<string>, subject:string >>,
  delivery struct < timestamp:string >
 )
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES ('ignore.malformed.json' = 'true')
LOCATION 's3://bucket_name/[folder]/'

メールボックスシミュレーターを使用しています docs.aws.amazon.com

WITH dataset AS (
SELECT
    notificationType,
    mail.commonHeaders.to as to_addresses,
    mail.commonHeaders.subject as subject,
    mail.messageId AS messageId
FROM SES_RESULTS )
SELECT 
  notificationType,
  TIMESTAMP,
  to_addresses[1] as to_address,
  subject,
  messageId
FROM dataset
WHERE to_addresses[1] = 'success@simulator.amazonses.com'
ORDER BY TIMESTAMP DESC
LIMIT 10

原因

aws.typepad.com

Q8. Amazon Kinesis Firehose を利用してCloudWatch LogsをS3に転送してそれをAthena で分析したいのですが、Kinesis Firehoseを通すと{json}{json}のように1行に複数のJSONオブジェクトが保存されるようです。このデータを効率的にAthenaで分析するにはどういった方法がありますか?? A8. Amazon Kinesis FirehoseにはData TransformationをAWS Lambdaで行う機能がございますので,こちらを使って所望の形式に変換すると良いです. http://docs.aws.amazon.com/firehose/latest/dev/data-transformation.html

確かに1行に複数のJSONオブジェクトが保存されている。送信したメールの件数も20件で一致している

% cat ses-kinesis-8-2022-10-07-00-19-59-f5cec068-edf9-414b-a03d-xxxxxxx | jq | grep notificationType | head -n 5
  "notificationType": "Delivery",
  "notificationType": "Delivery",
  "notificationType": "Delivery",
  "notificationType": "Delivery",
  "notificationType": "Delivery",

% cat ses-kinesis-8-2022-10-07-00-19-59-f5cec068-edf9-414b-a03d-xxxxxxx | jq | grep notificationType | wc -l
     20

対応

動的パーティショニングを使用する aws.amazon.com

SES ログ

こんな感じのフォーマットなので

{
  "notificationType": "Delivery",
  "mail": {
    "timestamp": "2022-10-07T00:19:58.503Z",
    "messageId": "01010183afd029a7-914ed9cd-641a-4eaf-87de-xxxxxxx-xxxxxxx",
  }
}
{
  "notificationType": "Delivery",
  "mail": {
    "timestamp": "2022-10-07T00:19:58.498Z",
    "messageId": "01010183afd029a2-a2d69f7f-4b3f-4a07-9935-xxxxxxx-xxxxxxx",
  }
}

Kinesis

ses-log-partitioning-!{partitionKeyFromQuery:year}/!{partitionKeyFromQuery:month}/!{partitionKeyFromQuery:day}/!{partitionKeyFromQuery:messageId}/

S3

  • 年月日 + MessageIdのフォルダに分かれている
  • 送信したメール数とオブジェクト数が一致している

Athena

送信したメールとレコード数が一致する

おまけ

制限とクォータ

aws.amazon.com

Kinesis Data Firehose 動的パーティショニング では、データをアクティブにバッファリングしている間、配信ストリームあたり 500 のアクティブパーティションの制限があります。これは、設定されたバッファリングヒント中に配信ストリームに存在するアクティブなパーティションの数です。この制限は調整可能で、引き上げたい場合は、制限引き上げのためのサポートチケットを送信する必要があります。

一度に500件以上のメールを送信すると発生しました。なかなか面倒ですね

{
   "attemptsMade":1,
   "arrivalTimestamp":1665109794722,
   "errorCode":"DynamicPartitioning.ActivePartitionsLimitExceeded",
   "errorMessage":"active-partition-count-exceeded",
   "attemptEndingTimestamp":1665109885901,
   "rawData":"="
}