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

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

Avatar for Tech Leverages Tech Leverages
July 01, 2024
6.1k

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

Avatar for Tech Leverages

Tech Leverages

July 01, 2024
Tweet

More Decks by Tech Leverages

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上で模倣できない機能は   最後にまとめて移行