Clipboard API

備忘録。

ある要素の click イベントで、Clipboard API を利用して要素内のテキストをクリップボードにコピーする。

document.querySelectorAll('.copy-to-clipboard').forEach((el)=> {
  el.addEventListener('click', (e) => {
    const text = e.currentTarget.textContent
    navigator.clipboard.writeText(text).then(() => {
      console.log('copied!!');
    });
  }, false)
})

モデリングの学び方:座談会 に行ってきた #modeling_zadankai

モデリングの学び方:座談会 に参加しました。簡単に所感をまとめます。

modeling-how-to-learn.connpass.com

所感

現場でどうモデリングをやっているか、モデリングをどう学んできたか、などいろいろな話が聴けて面白かったです。最後に言っていた「本をきちんと読むのはモデリングの基本スキル」というのが刺さりました。読解力。つまり Input を深く理解する力が大事。

あと、語彙のモデリング。用語集もモデリングのひとつと言えるのか、なるほど。

RDRA 2.0 ハンドブックはさらっとしか読んでないのでしっかり読み直したい。

今回の座談会にいた方の YouTube 動画。自分が若手のときにこういうコンテンツがあったらなぁ...。

youtu.be

youtu.be

以下、メモから抜粋。

モデリングの学び方:座談会

  • モデリング
  • 重要な要素, 重要な関係性, 適切な名前
  • 要点の発見 (言語化/可視化)
  • 事業活動のモデリング
  • ソフトウェアのモデリング
  • 語彙のモデリング
  • アプローチ
    • プロセス中心
    • データ中心
    • ルール中心
  • ドメインモデル中心
  • RDRA 2.0
  • モデルの改善 > データの変更 > マイグレーションどうする?
  • どういう事業をやっているか
    • ビジネスの理解
    • ビジネスを俯瞰する
  • コンテキストマップ
  • ビジネスの目的を達成するためのモデル
  • どういうビジネスをやっていて何をシステム化するのか
  • 用語, 関連, 多重度
  • command/query
    • command を処理するのに必要なデータしか持たせない
  • 問題があるモデルを改善する経験
  • 使えるモデル, 役に立つモデル
  • Tell, Don't Ask
    • やることを依頼する
    • message passing
    • Tell, Don't Ask しないならデータモデリングでいい
  • リファクタリングは設計
  • 本をきちんと読むのはモデリングの基本スキル

あとで読む。

JSUG勉強会2021年その2 Spring GraphQL をとことん語る夕べ に行ってきた #jsug

JSUG勉強会2021年その2 Spring GraphQL をとことん語る夕べ に参加しました。今回はオンライン開催。簡単に所感をまとめます。

jsug.doorkeeper.jp

所感

Spring GraphQL は内部的には graphql-java を使っているようです。昔、graphql-java と Spring Boot を試してみたことがあったっけ。

DataLoader, ページングのデモが分かりやすかったです。このあたりは実際に書いてみた方がより理解できそう。

M2 でマルチパート (ファイルアップロード) が対応されたら確かにおもしろそうだけど、これ本当に GraphQL で対応する必要あるんだろうか...。これこそ GraphQL に合わない API な気もしたりしなかったり。

以下、メモから抜粋。

Spring GraphQL introduction

https://backpaper0.github.io/spring-graphql-introduction/

  • Spring GraphQL 1.0.0-M1
  • Query, Mutation, Subscription
    • schema キーワードで型をカスタマイズできる
  • 1回のリクエストで複数のクエリを送信できる
  • バージョンなしでAPIを進化できる
    • 型にフィールドが追加されても影響ない
    • 削除には @deprecated ディレクティブを付ける
  • Spring GraphQL
    • M2 でクライアント側の対応が入るかも
    • Web MVC, WebFlux
  • subscription
    • spring-boot-starter-websocket
  • 型定義ファイルは分割可
  • GraphiQL
  • GraphQlTester
  • N+1 問題
  • DataLoader
  • BatchLoader
    • maxBatchSize (バッチサイズ設定可)
  • MappedBatchLoader
    • 戻り型が Map になる
  • ページング
  • DataFetcherExceptionResolver
    • 例外ハンドリング
  • GraphQL に合わない API が欲しいとき
    • RestController 書けばいい
  • 認証/認可
    • エンドポイントが1つなのでエンドポイントごとの認証/認可は不十分
    • @PreAuthorize, @Secured
  • メトリクス (actuator)
  • M2
    • Spring Data 統合 (Querydsl)
    • DataFetcher, DataLoader 登録の改善
    • マルチパート対応
  • https://github.com/backpaper0/spring-graphql-introduction

その他

ik.am

  • MyBatis Thymeleaf
    • Thymeleaf テンプレートで SQL を動的に生成できる
    • MyBatis 以外でも使える
  • JdbcTemplate 使うひとにはよさそう

AWS SDK for Java + KMS で S3 暗号化

備忘録。前回の続き。

AWS SDK for Java で署名付き URL 生成 - kntmr-blog

Key Management Service (KMS) と AWS SDK for Java で、クライアントサイドで暗号化して S3 にアップロードする。

キー作成 (CMS)

事前に Key Management Service > カスタマー管理型のキー でキーを作成する。今回はダウンロードして復号したいので、キーのタイプには 対称 を選択する。

AWS Encryption SDK

AWS Encryption SDK for Java - AWS Encryption SDK

暗号化に必要なライブラリを追加。これがないと AmazonS3EncryptionV2 を初期化する際に実行時エラーが発生する。

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-encryption-sdk-java</artifactId>
    <version>2.0.0</version>
</dependency>

暗号化&アップロード

パターン1

AmazonS3EncryptionV2 s3Encryption = AmazonS3EncryptionClientV2Builder.standard()
        .withCredentials(new ProfileCredentialsProvider())
        .withCryptoConfiguration(new CryptoConfigurationV2()
                .withCryptoMode(CryptoMode.StrictAuthenticatedEncryption))
        .withEncryptionMaterialsProvider(new KMSEncryptionMaterialsProvider(keyId))
        .build();

try {
    s3Encryption.putObject(bucketName, objectKey, file);
} finally {
    s3Encryption.shutdown();
}

パターン2

SDK から KMS のキーを作成してアップロードするパターン。ついでにキーの削除スケジュールをリクエストする。ScheduleKeyDeletionRequest#withPendingWindowInDays にはキーが削除可能になるまでの待機日数を指定する。(7〜30)

AWSKMS kmsClient = AWSKMSClientBuilder.standard()
        .withCredentials(new ProfileCredentialsProvider())
        .build();

CreateKeyRequest createKeyRequest = new CreateKeyRequest();
CreateKeyResult createKeyResult = kmsClient.createKey(createKeyRequest);
String keyId = createKeyResult.getKeyMetadata().getKeyId();

AmazonS3EncryptionV2 s3Encryption = AmazonS3EncryptionClientV2Builder.standard()
        .withCredentials(new ProfileCredentialsProvider())
        .withCryptoConfiguration(new CryptoConfigurationV2()
                .withCryptoMode(CryptoMode.StrictAuthenticatedEncryption))
        .withEncryptionMaterialsProvider(new KMSEncryptionMaterialsProvider(keyId))
        .build();

try {
    s3Encryption.putObject(bucketName, objectKey, file);

    ScheduleKeyDeletionRequest scheduleKeyDeletionRequest = new ScheduleKeyDeletionRequest()
            .withKeyId(keyId)
            .withPendingWindowInDays(7);
    kmsClient.scheduleKeyDeletion(scheduleKeyDeletionRequest);
} finally {
    s3Encryption.shutdown();
    kmsClient.shutdown();
}

ダウンロード&復号

AmazonS3EncryptionV2 s3Encryption = AmazonS3EncryptionClientV2Builder.standard()
        .withCredentials(new ProfileCredentialsProvider())
        .withCryptoConfiguration(new CryptoConfigurationV2()
                .withCryptoMode(CryptoMode.StrictAuthenticatedEncryption))
        .withEncryptionMaterialsProvider(new KMSEncryptionMaterialsProvider(keyId))
        .build();

try {
    GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectKey);
    s3Encryption.getObject(getObjectRequest, file);
} finally {
    s3Encryption.shutdown();
}

その他

暗号化したときの keyId 以外を指定した場合、AmazonS3EncryptionV2#getObject でエラーが発生する。(status code 400)

com.amazonaws.services.kms.model.IncorrectKeyException: The key ID in the request does not identify a CMK that can perform this operation.

暗号化してアップロードしたファイルは AmazonS3#getObject でもダウンロードできるが、暗号化されているため開けない。マネジメントコンソールからダウンロードした場合も同様に開けない。

暗号化したファイルのコピーや削除では keyId の指定は不要で、AmazonS3#copyObject or AmazonS3.deleteObject が使える。もちろん、暗号化したままコピーされる。

AWS SDK for Java で S3 署名付き URL 生成

備忘録。S3 の 署名付き URL を AWS SDK for Java で生成する。AWS の設定周りについては正しいかどうかは自信がない...。

<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 -->
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.11.1034</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-sts -->
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-sts</artifactId>
    <version>1.11.1034</version>
</dependency>

パターン1

IAM ユーザーの認証情報 (accessKey, secretKey) を利用して生成するパターン。

AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
        .withCredentials(new ProfileCredentialsProvider())
        .build();

Date expiration = new Date();
expiration.setTime(Instant.now().toEpochMilli() + 1000 * 60 * 5); // 有効期限5分
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey)
        .withMethod(HttpMethod.GET) // GET のみ許可する
        .withExpiration(expiration);

URL presignedUrl = amazonS3.generatePresignedUrl(request);

パターン2

AWS STS で一時的なセキュリティ認証情報を取得して生成するパターン。事前に AmazonS3ReadOnlyAccess をアタッチしたロールを作成する。また、「信頼関係の編集」で sts:RoleSessionName を追加する。

... (略)
"Condition": {
  "StringLike": {
    "sts:RoleSessionName": "${aws:username}"
  }
}
AWSSecurityTokenService stsClient = AWSSecurityTokenServiceClientBuilder.standard()
        .withCredentials(new ProfileCredentialsProvider())
        .build();

AssumeRoleRequest roleRequest = new AssumeRoleRequest()
        .withRoleArn(roleArn)
        .withRoleSessionName(roleSessionName);
AssumeRoleResult roleResult = stsClient.assumeRole(roleRequest);
Credentials sessionCredentials = roleResult.getCredentials();

BasicSessionCredentials awsCredentials = new BasicSessionCredentials(
        sessionCredentials.getAccessKeyId(),
        sessionCredentials.getSecretAccessKey(),
        sessionCredentials.getSessionToken());

AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
        .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
        .build();

Date expiration = new Date();
expiration.setTime(Instant.now().toEpochMilli() + 1000 * 60 * 5); // 有効期限5分
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey)
        .withMethod(HttpMethod.GET) // GET のみ許可する
        .withExpiration(expiration);

URL presignedUrl = amazonS3.generatePresignedUrl(request);

その他

パターン2では URL に X-Amz-Security-Token=... パラメータが付く。このパラメータで一時的なセキュリティ認証情報を渡す。

ちなみに、マネジメントコンソールの「開く」でも署名付き URL は生成できる。有効期限は5分。(X-Amz-Expires=300)

JJUGナイトセミナー「オブジェクト指向プログラミング入門」に行ってきた #jjug

JJUGナイトセミナー「オブジェクト指向プログラミング入門」に参加しました。オンライン開催。簡単に所感をまとめます。

jjug.doorkeeper.jp

所感

Software Design 2021年3月号 の特集を執筆された3名によるセッション。設計やオブジェクト指向についてどう考えているかを聴けて面白かったです。

増田さんの話は自身の経験をベースにしてるんだろうけど、こういう話を体系的に整理して説明できるところがすごい。

あと、建築の設計はぜんぜん詳しくないけど、システム開発の設計は建築の設計よりもっと上流のフェーズにあるようなものな気がして、対比して語るのはちょっと違和感あるかなって。

小クラス主義については、全体的な設計の見通しを立てるために (分割する前提で) 最初は大きく作ることはあるかもしれないけど、難しいところ。

個人的には、設計は全体を俯瞰するプロセスで、プログラミングはもっとスコープが小さい話かなと思っています。で、犬猫とか車とエンジンみたいな例え話はプログラミング言語の文法を学ぶときに有用かなと思っていて、設計を身に付けるならシステムを図とか文章で書き起こして全体を理解するところから始めるとよさそうかなと思いました。たぶん。

以下、メモから抜粋。

なぜ設計を学ぶ必要があるのか

  • 設計はシステム具象化の準備
  • システムを抽象的に捉える
  • 抽象, 捨象
  • 見積もりや設計は過去の経験の抽象化
  • プログラミング=具象 と 設計=抽象 を往復する
  • 抽象化能力を経験以外から学べるか
  • 設計を学問として学ぶ

オブジェクト指向と関数型を組み合わせる

  • 関数型とオブジェクト指向は直交する
  • 関数型の利点
    • オブジェクトの不変性
    • スレッドセーフ
  • オブジェクト指向の利点
    • メソッドがクラスに所属する
    • 関係する演算はクラスの public API を見ればわかる
  • 不変クラスの設計
    • equals(), hashCode(), toString()
    • Record

クラス設計本格入門

  • オブジェクト指向プログラミングはクラス設計
  • クラス設計はプログラムの分割
  • クラスはロジックとデータの集約
  • 小クラス主義
    • 小さく分割, 変更の影響の局所化, 部品としての再利用
  • 不変
  • 分割と統合の労力
  • リファクタリング (分割の改善) を少しずつ積み立てる
  • 分割したクラスを組み合わせるのは容易
  • 分割
    • メソッド/クラスの抽出 & 名前付け
    • パッケージ/サブパッケージ & 名前付け
  • パッケージ/クラスの名前で分割意図の表現を明確にする
  • クラス/パッケージの凝集度を上げる
  • ビジネスアクションの表現, ビジネスルールの表現
    • ビジネスルールのクラスに void はありえない
  • 対象領域(ドメイン)の関心事で分割する
    • 周りにある関連語彙を増やして知識を広げる
    • 基本の言葉のまわりにはさまざまな決め事 (ビジネスルール) がある
  • 範囲や区分のカプセル化
  • コレクション操作のカプセル化

座談会

  • 不変性を実現するにはハードウェア進化の恩恵が大きい
  • サブタイプ, サブクラス の違い
  • immutable な Collection ライブラリ欲しい
  • String クラスは役割持ちすぎ
  • File IO は役割わけすぎ
    • InputStream, Reader/Writer とか
    • Files でわりとラクになった
  • Stream は微妙に物足りない
  • static or インスタンスメソッド
    • ファクトリーメソッド
    • シグネチャが違うコンストラクタを並べたくない
    • Builder にしたり
    • sort は Collection が持つべき
  • static にすると mock にできない
    • DBUtil, FileUtil, ...
  • 継承可否を言語仕様で提供するのは?
    • 継承できないのがデフォルトでよい
    • 実装継承は使わない方がよい
    • テストしやすいならデフォルト private でよい
    • package private はバランスよい

timestamp 型のカラムに integer 型のカラムの値の加算して date 型で比較する

備忘録。

例えば、created_at カラムに days カラムの値を日数として加算して、その結果を日付で比較するようなケース。使い道があるかは分からないけど。

以下は PostgreSQL の場合。

SELECT * FROM table t
WHERE CAST(t.created_at + CAST(t.days || 'days' AS INTERVAL) AS date) = '2021-06-02'