type holyshared = Engineer<mixed>

技術的なことなど色々

TypeORM 0.3.0の変更点

TypeORM 0.3.0では破壊的な変更がいくつか含まれています。

ConnectionからDataSource

ConnectionからDataSource、ConnectionOptionsからDataSourceOptionsにクラス名が変更になっています。
またcreateConnectionなどのユーティリティ関数も軒並みDeprecatedになっているので、しばらくは大丈夫なのですが、一気に変えてしまった方がよさそうです。

また接続処理の部分は別途変更する必要があります。

この変更はtsで接続設定の型指定が効くので、typoや間違った設定をしにくくなるのでいい変更だと思います。

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./model/entity/User"
import { resolve } from "path"

const migrationsPath = resolve(__dirname, "model/migration")

export const AppDataSource = new DataSource({
  type: "postgres",
  host: "localhost",
  port: 5432,
  username: "example",
  password: "example",
  database: "example",
  entities: [User],
  synchronize: false,
  logging: false,
  migrations: [`${migrationsPath}/*{.js,.ts}`]
})

ormconfigの廃止

データベースの接続情報をormconfig.jsonで持てるようになっていましたが、それが廃止になる予定です。
代わりにDataSourceを使います。

複数のデータベースを扱っている場合は、データベース単位でDataSourceを定義する必要がありそうです。
なぜならマイグレーションを実行する時のデータベース指定がファイルを指定するようになっているためです。

マイグレーションの指定方法

typeorm migration:run -d ./dist/data-source1.js # データソース1
typeorm migration:run -d ./dist/data-source2.js # データソース2

一応、一つのDataSourceでも複数のDBに永続化はできる見たいですが、ドキュメント見る限りエンティティ毎にデータソースを指定する必要があるので面倒なのと、ローカルで検証用のDBとテスト用のを分けてテストしたい時に困りそうです。

AbstractRepositoryを使用したカスタムリポジトリの廃止

カスタムリポジトリを扱いたい場合、AbstractRepositoryを継承することで独自のリポジトリを定義できるようになっていて、比較的自由な実装ができるようになっていましたがそれができなくなりました。

代わりにextendを使用して独自のメソッドを定義できるのみなっています。

import { Repository } from "typeorm"
import { AppDataSource } from "./data-source"
import { User } from "./model/entity/User"

type CustomUserRepository = Repository<User> & {
  example(name: string) : Promise<User>
}

export const UserRepository = AppDataSource.getRepository(User).extend({
  // thisのコンテキストがわかるように型を指定する
  async example(this: CustomUserRepository, name: string) {
    return this.createQueryBuilder().getOne()
  }
})

AbstractRepositoryを継承しなくて良くなった点はいいのですが、設計の自由度が減ってしまいました。
DDDが割と実践しやすかった点がなくなってしまっています。

まとめ

カスタムリポジトリがカスタムとは?という感じになってしまっていましたが、割と健全な方な修正で良かったかなと思います。

R2のファイルアップロード用の署名付きURL

R2の署名付きURLが利用できるか調べていました。
S3互換と言いつつ、使えないものもあるだろうと思っていましたが最低限のAPIが揃っているので困ることはなさそうです。

今回はask-sdkのv3を使用しました。 v2まではS3Client単体で作成できたみたいですが、パッケージが分割されていて必要なものだけでインストールすればOKぽいです。

R2の利用方法

R2を利用するには、Cloudflareにログインして、R2を有効にする必要があります。
R2を有効にした後は、R2のAPIトークンとバケットを作成します。

また、アカウントIDも必要になるのでダッシュポードに掲載されているAccount IDをメモしておきます。

署名付きURLを発行する

署名付きURLを発行するには下記のコードを利用します。
S3Clientのオプションにクレデンシャルとリージョンを指定してクライアントを初期化します。

その後はgetSignedUrl関数を利用してPutObjectに対しての署名付きURLを作成します。
署名の有効期限はexpiresInで指定すればOKのようです。

const s3 = require('@aws-sdk/client-s3')
const presigner = require('@aws-sdk/s3-request-presigner')

const accountId = process.env.R2_ACCOUNT_ID || 'example'
const bucket = process.env.R2_BUCKET || 'example'

const client = new s3.S3Client({
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  },
  region: 'auto',
  endpoint: `https://${accountId}.r2.cloudflarestorage.com`
})

exports.presignedURL = {
  async of(path) {
    const command = new s3.PutObjectCommand({
      Bucket: bucket,
      Key: path,
      ContentType: "image/jpeg",
    })
    return presigner.getSignedUrl(client, command, { expiresIn: 3600 });
  }
}

署名付きURLにファイルをアップロードする

署名付きURLにPUTでファイルを指定すると、ファイルをアップロードできます。
Content-Typeヘッダーは指定しておいた方がよさそうです。

curl -XPUT -H "content-type:image/jpeg" --data-binary @example.jpg [URL]

気になったところ

署名付きURLにはX-Amz-CredentialにAccessKeyIdが含まれるので、S3の場合はセキュリティのために署名付きURLを作成する際はAWS Security Token Serviceで一時的なクレデンシャルを作成し、署名付きURLを作成していました。

この方法の場合、X-Amz-Credentialが一時的なクレデンシャルのものになるので安心でした。
R2の場合はこの方法が取れないので、URLの期限をできるだけ短くするなどしか対策が取れないかもしれないです。

最後に

今回の具体的なコードはここにおいて置いました。 github.com

六本木でFish & Chips食べてきた

先週ぐらいから休んでいるので、ちょこちょこ外で食事をしている。
Fish & Chipsが無性に食べたくなったので、六本木まで行って食べてきた。

ラージサイズを頼んだので、お腹がいっぱいになった。
魚のフライは塩で味付けされていて、ソースつけて食べるよりもいいなと思った。

ソースがいくつか選べたので、今度はカレーソースを試して見たい。

家に帰った後は、近くの酒屋でクラフトビールを買って飲んでいた。
BREWDOG の PUNK IPAとELVIS JUICEを飲んだけど、ELVIS JUICEの方が味的には好みだった。

HAZY JANEがまだ残っているので、明日飲む。

OCaml5.0.0の並列処理モジュールを試す

5.0.0がリリースされてから触ってなかったので、新しく追加された並列処理用のモジュールを試しました。

v2.ocaml.org

環境の更新

まず初めに開発環境を最新にしました。
opamを最新の2.1.4にし、opam switch create 5.0.0で5.0.0に切り替えます。
ライブラリはまだ何も入っていないので、とりあえず下記のものをインストールしました。

ビルド、REPL、VS Code用にLanguage Serverを入れておきます。

  • dune
  • utop
  • ocaml-lsp-server
  • ocamlformat-rpc
  • merlin

まずはDomainsから

DomainはOCamlにおける並列処理の単位らしいです。
ドキュメントではspawn関数でDomainを生成して、joinで処理が終了するまで待つという単純なコードになってます。

コマンドライン引数で整数を受け取って、フィボナッチ数列を並列で処理するものです。

let n = try int_of_string Sys.argv.(1) with _ -> 1

let rec fib n =
  if n < 2 then 1 else begin
    let d1 = Domain.spawn (fun _ -> fib (n - 1)) in
    let d2 = Domain.spawn (fun _ -> fib (n - 2)) in
    Domain.join d1 + Domain.join d2
  end

let main () =
  let r = fib n in
  Printf.printf "fib(%d) = %d\n%!" n r

let _ = main ()

並列処理ライブラリを使う

Domainsは低レベルな機能を提供するシンプルなものでしたが、domainslibという並列処理モジュールを使用すると高度な並列処理を行うことができるようです。

opam install domainslibでインストールすることができます。
現時点のバージョンは0.5.0です。

ドキュメントのサンプルはDomainsと同じく、フィボナッチ数列を計算するものですが、代わりにDomainプールを使用して、並列処理を行うようになっています。

setup_pool関数でDomainの数を指定して、プールを初期化し、run関数でプールと、実行したいタスクを指定すると、処理が完全に終了するまで待機します。

run関数の代わりに、async関数を使用すると、非同期のタスクを返してくれます。
非同期タスクはawait関数を使用して処理が終了するまで待機してくれます。

モジュールのシグニチャを見ると同期的なタスクは'a Task.task 、非同期タスクは'a Task.promiseみたいなので、型レベルで区別されていてわかりやすいです。

タスクの型定義はunit -> 'aなので、引数なしの何かしらの値を返す関数として認識しておくとよさそうです。

let num_domains = int_of_string Sys.argv.(1)

let n = int_of_string Sys.argv.(2)

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

module Task = Domainslib.Task

let rec fib_par pool n =
  if n > 20 then begin
    let a = Task.async pool (fun _ -> fib_par pool (n-1)) in
    let b = Task.async pool (fun _ -> fib_par pool (n-2)) in
    Task.await pool a + Task.await pool b
  end else fib n

let main () =
  let pool = Task.setup_pool ~num_domains:(num_domains - 1) () in
  let res = Task.run pool (fun _ -> fib_par pool n) in
  Task.teardown_pool pool;
  Printf.printf "fib(%d) = %d\n" n res

let _ = main ()

感想

まだ追加されたばかりなので、仕様が変わるかもしれないですが思いのほかよかったです。
試してみたコードはGitHubにあげてあります。

github.com

東京タワー行ってきた

この前スカイツリーに行ったので、東京タワーの600階段のコースで登ってきました。
階段の段差はそんなになく、500段くらいから息切れしてきて、めちゃくちゃ汗をかきました。

帰りは芝公園によろうかと思いましたが、疲れたのでそのまま帰りました。

スカイツリーより並ばなかったのでよかったです。