Upgrade to Pro — share decks privately, control downloads, hide ads and more …

大規模なORMバージョンアップ作業を 乗り越えた話

Tech Leverages
July 01, 2024
5.4k

大規模なORMバージョンアップ作業を 乗り越えた話

Tech Leverages

July 01, 2024
Tweet

Transcript

  1. 自己紹介 桐生直輝 (24歳) • 入社:2023年3月 • 所属:NALYSYSグループ ◦ バックエンド〜インフラ担当 •

    趣味:コンピュータいじり、自宅サーバー構築 ◦ おうちKubernetes運用中です ◦ HOMENOC(AS59105)加盟
  2. 今回の内容 クソデカ破壊的アップデート 参戦! • よく使うグローバル関数の廃止(影響大) • よく使う関数のシグネチャ変更(影響大) • 型で検知しにくい破壊的変更 ◦

    undefinedのかわりにnullが返ってくるようになったり • etc 変更箇所のリスト(一部)→ https://blog.open.tokyo.jp/2022/05/04/upgrade-typeorm-0-3.html
  3. ts-morphの活用 破壊的変更の一例: findOneメソッド • 引数の形に応じて関数を使い分けるように変更 ◦ whereオプションで条件指定 →そのままでOK ◦ ◦

    ◦ where条件を直接書いている →findOneByに変更 repo.findOne({ where: { id: someId, }, }); repo.findOne({ where: { id: someId, }, }); repo.findOne({ id: someId, }); repo.findOneBy({ id: someId, });
  4. ts-morphの活用 正規表現でのマッチが難しい! • 場合によって引数の形が多様に変化するため repo.findOne({ relation: ["hoge"], where: { id:

    someId, }, }); where以外のオプションが指定されていることがある 中身が更に入れ子になっている可能性がある repo.findOne({ id: someId, rel1: { id: someRel1Id, rel2: { ... }, }, });
  5. ts-morphの活用 普通の文字列置換と何が違うの? • 単純な変更なら、文字列置換でまとめてできる export type SomeType = { ...

    oldNameField : string; ... }; const v: SomeType = { oldNameField : 'value', ... }; v.oldNameField = ... /oldNameField/newNameField/ export type SomeType = { ... newNameField : string; ... }; const v: SomeType = { newNameField : 'value', ... }; v.newNameField = ...
  6. ts-morphの活用 ソースコードを ASTとして操作する • AST(Abstract Syntax Tree) = 抽象構文木 •

    ソースコードの意味的な構造を取り出したもの • プログラム処理しやすい(コンパイラの内部表現)
  7. ts-morphの活用 ソースコードを ASTとして操作する • AST(Abstract Syntax Tree) = 抽象構文木 {

    key1: 'value1', key2: { key3: 'value2' } } ObjectLiteralExpression PropertyAssignment PropertyAssignment StringLiteral value1 Identifier key1 PropertyAssignment ObjectLiteralExpression Identifier key2 StringLiteral value2 Identifier key3
  8. ts-morphの活用 ソースコードを ASTとして操作する • フォーマット等で表現が違っても、 ASTは同じになる ◦ 意味的な操作を扱いやすい ObjectLiteralExpression PropertyAssignment

    PropertyAssignment StringLiteral value1 Identifier key1 PropertyAssignment ObjectLiteralExpression Identifier key2 StringLiteral value2 Identifier key3 { key1: 'value1', key2: { key3: 'value2' } } { key1: 'value1', key2: { key3: 'value2', }, } ASTに変換
  9. ts-morphの活用 ts-morphでのAST操作手順 • プロジェクト(ソースコード)の読み込み • 目的箇所のASTノードの取り出し • AST操作 • 書き出し

    ※ 細かい話なので、ここでは簡単に流します   詳細はスライドの内容をご確認ください
  10. ts-morphの活用 プロジェクト (ソースコード )の読み込み import { Project } from 'ts-morph';

    import path from 'path'; // 初期化 const project = new Project({ tsConfigFilePath: './tsconfig.json', skipAddingFilesFromTsConfig: true, }); // 必要なソースを追加 project.addSourceFilesAtPaths(path.resolve(__dirname, './src/**/*.ts'));
  11. ts-morphの活用 目的箇所の ASTノードの取り出し export function call(obj: object) {} src/call.ts import

    { call } from './call' call({ key1: { key2: 'value', key3: { key4: 'value', }, }, }) src/main.ts
  12. ts-morphの活用 目的箇所の ASTノードの取り出し // call関数の定義を取り出し const callSource = project.getSourceFileOrThrow('src/call.ts'); const

    callFunction = callSource.getFunctionOrThrow('call'); // call関数への参照ノードを列挙 for (const ref of callFunction.findReferencesAsNodes()) { // CallExpressionとして使われている箇所を編集 const callExpression = ref.getFirstAncestorByKind(SyntaxKind.CallExpression); if (!callExpression) continue; // 引数を取り出し const arg0 = callExpression.getArguments()[0]; const argAsObj = arg0?.asKind(SyntaxKind.ObjectLiteralExpression); if (!argAsObj) continue; }
  13. ts-morphの活用 目的箇所の ASTノードの取り出し // call関数の定義を取り出し const callSource = project.getSourceFileOrThrow('src/call.ts'); const

    callFunction = callSource.getFunctionOrThrow('call'); // call関数への参照ノードを列挙 for (const ref of callFunction.findReferencesAsNodes()) { // CallExpressionとして使われている箇所を編集 const callExpression = ref.getFirstAncestorByKind(SyntaxKind.CallExpression); if (!callExpression) continue; // 引数を取り出し const arg0 = callExpression.getArguments()[0]; const argAsObj = arg0?.asKind(SyntaxKind.ObjectLiteralExpression); if (!argAsObj) continue; } call( { key1: { key2: 'value', key3: { key4: 'value', }, }, } )
  14. ts-morphの活用 AST操作 // key1の中身を取り出す const key1 = argAsObj.getProperty('key1')?.asKind(SyntaxKind.PropertyAssignment); if (!key1)

    continue; const key1Value = key1.getInitializerOrThrow().getText(); // 引数の置き換え callExpression.removeArgument(arg0); callExpression.insertArgument(0, key1Value); call({ key1: { key2: 'value', key3: { key4: 'value', }, }, })
  15. ts-morphの活用 AST操作 // key1の中身を取り出す const key1 = argAsObj.getProperty('key1')?.asKind(SyntaxKind.PropertyAssignment); if (!key1)

    continue; const key1Value = key1.getInitializerOrThrow().getText(); // 引数の置き換え callExpression.removeArgument(arg0); callExpression.insertArgument(0, key1Value); call({ key1: { key2: 'value', key3: { key4: 'value', }, }, }) call({ key2: 'value', key3: { key4: 'value', }, })
  16. 漸進的な移行の実現 移行のイメージ 現状 TypeORM 0.2 TypeORM0.2記法 移行中 TypeORM 0.2/0.3 両対応ライブラリ

    0.2 記 法 TypeORM0.3 記法 徐々に移行していく 移行完了 TypeORM0.3記法 TypeORM 0.3 ※ 0.2上で模倣できない機能は   最後にまとめて移行