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

Prisma ORMを2年運用して培ったノウハウを共有する

tockn
May 10, 2024

Prisma ORMを2年運用して培ったノウハウを共有する

tockn

May 10, 2024
Tweet

More Decks by tockn

Other Decks in Technology

Transcript

  1. 情報も増えつつある © 2024 Cloudbase Inc. A 入門記3 A 採用事例記3 A

    エラー対処記3 A 解説記事 (いつもお世話になっています!)
  2. Cloudbaseの技術スタック © 2024 Cloudbase Inc. RDB API Server GraphDB Data

    Loader Storage スキャナー お客様のクラウド環境 Web Frontend お客様
  3. Cloudbaseの技術スタック © 2024 Cloudbase Inc. RDB API Server GraphDB Data

    Loader Storage スキャナー お客様のクラウド環境 Web Frontend お客様
  4. 操作例: SELECT const = . . ({ { } })

    users await prisma user where: name: findMany "tockn" /* [ { id: 2, email: '[email protected]', name: 'tockn' }, { id: 3, email: '[email protected]', name: 'tockn' } ] */ © 2024 Cloudbase Inc.
  5. 操作例: SELECT const = . . ({ { } })

    users await prisma user where: name: findMany "tockn" /* [ { id: 2, email: '[email protected]', name: 'tockn' }, { id: 3, email: '[email protected]', name: 'tockn' } ] */ © 2024 Cloudbase Inc. 純粋なObjectを返す
  6. 操作例: INSERT , UPDATE, DELETE... // INSERT // UPDATE //

    DELETE // UPSERT await await await await await await . . ({ { , } }) . . ({ { }, { } }) . . ({ { }, { } }) . . ({ { } }) . . ({ { } }) . . ({ { }, { , }, { } }) prisma user data: name: email: prisma user where: email: data: name: prisma user where: name: data: name: prisma user where: email: prisma user where: name: prisma user where: email: create: name: email: update: name: create update updateMany delete deleteMany upsert "tockn" "[email protected]" "[email protected]" "tockn" "sato" "tockn" "[email protected]" "tockn" "[email protected]" "tockn" "[email protected]" "sato" © 2024 Cloudbase Inc.
  7. find系のincludeについて await . . ({ { , }, }); prisma

    user include: post: findFirst true © 2024 Cloudbase Inc.
  8. find系のincludeについて await . . ({ { , }, }); prisma

    user include: post: findFirst true © 2024 Cloudbase Inc. リレーションを持つテーブルを指定
  9. includeして得られる結果 { , , , [ { , , ,

    , , }, ], }; id: email: name: posts: id: title: content: published: authorId: 1 1 11 '[email protected]' 'tockn' 'Cloudbase' 'Cloudbase is a cloud security platform.' false © 2024 Cloudbase Inc.
  10. includeして得られる結果 { , , , [ { , , ,

    , , }, ], }; id: email: name: posts: id: title: content: published: authorId: 1 1 11 '[email protected]' 'tockn' 'Cloudbase' 'Cloudbase is a cloud security platform.' false © 2024 Cloudbase Inc. リレーション先のデータも取得
  11. find系のincludeで発行されるSQL © 2024 Cloudbase Inc. -- 1. Userの取得 SELECT FROM

    WHERE LIMIT . . , . . , . . . . . = ? ? OFFSET ?; `main` `User` `id` `main` `User` `email` `main` `User` `name` `main` `User` `main` `User` `name` -- 2. 取得したUserが持つPost取得 SELECT FROM WHERE IN LIMIT . . , . . , . . , . . , . . . . . (?, ?, ?) ? OFFSET ?; `main` `Post` `id` `main` `Post` `title` `main` `Post` `content` `main` `Post` `published` `main` `Post` `authorId` `main` `Post` `main` `Post` `authorId`
  12. find系のincludeで発行されるSQL © 2024 Cloudbase Inc. -- 1. Userの取得 SELECT FROM

    WHERE LIMIT . . , . . , . . . . . = ? ? OFFSET ?; `main` `User` `id` `main` `User` `email` `main` `User` `name` `main` `User` `main` `User` `name` -- 2. 取得したUserが持つPost取得 SELECT FROM WHERE IN LIMIT . . , . . , . . , . . , . . . . . (?, ?, ?) ? OFFSET ?; `main` `Post` `id` `main` `Post` `title` `main` `Post` `content` `main` `Post` `published` `main` `Post` `authorId` `main` `Post` `main` `Post` `authorId` 最初にuserを取得
  13. find系のincludeで発行されるSQL © 2024 Cloudbase Inc. -- 1. Userの取得 SELECT FROM

    WHERE LIMIT . . , . . , . . . . . = ? ? OFFSET ?; `main` `User` `id` `main` `User` `email` `main` `User` `name` `main` `User` `main` `User` `name` -- 2. 取得したUserが持つPost取得 SELECT FROM WHERE IN LIMIT . . , . . , . . , . . , . . . . . (?, ?, ?) ? OFFSET ?; `main` `Post` `id` `main` `Post` `title` `main` `Post` `content` `main` `Post` `published` `main` `Post` `authorId` `main` `Post` `main` `Post` `authorId` 取得したuser_idで Postを取得
  14. find系のincludeで発行されるSQL © 2024 Cloudbase Inc. -- 1. Userの取得 SELECT FROM

    WHERE LIMIT . . , . . , . . . . . = ? ? OFFSET ?; `main` `User` `id` `main` `User` `email` `main` `User` `name` `main` `User` `main` `User` `name` -- 2. 取得したUserが持つPost取得 SELECT FROM WHERE IN LIMIT . . , . . , . . , . . , . . . . . (?, ?, ?) ? OFFSET ?; `main` `Post` `id` `main` `Post` `title` `main` `Post` `content` `main` `Post` `published` `main` `Post` `authorId` `main` `Post` `main` `Post` `authorId` 取得したuser_idで Postを取得 WHERE IN
  15. © 2024 Cloudbase Inc. Cloudbaseではど う し ているか g 対象データ量が多い場合はqueryRawを使C

    g 少ない場合はincludeを使う await . <{ : ; : }> ; prisma id name $queryRaw number string ` SELECT u.id, u.name FROM user as u INNER JOIN post as p ON u.id = p."authorId" WHERE u.name = 'tockn'`
  16. © 2024 Cloudbase Inc. Cloudbaseではど う し ているか f 原則updateMany,

    deleteManyを使用すa f xxxManyなら余分なクエリが走らない UPDATE DELETE // ️ 原則使わない // 余分なクエリが走らないmanyを使う await await . . ({ { }, { }, }); . . ({ { }, { }, }); prisma user where: email: data: name: prisma user where: name: data: name: update updateMany '[email protected]' 'tockn' 'sato' 'tockn' // 原則使わない // ️ 余分なクエリが走らないmanyを使う await await . . ({ { }, }); . . ({ { }, }); prisma user where: email: prisma user where: name: delete deleteMany '[email protected]' 'tockn'
  17. previewFeature: relationJoins await . . ({ { }, { },

    }); prisma user include: posts: where: name: findMany true 'tockn' © 2024 Cloudbase Inc.
  18. previewFeature: relationJoins await . . ({ , { }, {

    }, }); prisma user relationLoadStrategy: include: posts: where: name: findMany 'join' 'tockn' true © 2024 Cloudbase Inc. joinかqueryか選べるように!
  19. find系のincludeで発行されるSQL © 2024 Cloudbase Inc. await . . ({ ,

    { }, { }, }); prisma user relationLoadStrategy: include: posts: where: name: findMany 'join' 'tockn' true SELECT AS FROM AS LEFT JOIN SELECT AS FROM SELECT FROM SELECT AS FROM SELECT FROM AS WHERE AS AS AS AS ON WHERE . , . , . . LATERAL ( (JSONB_AGG( ), ) ( . ( JSONB_BUILD_OBJECT ( , . , , . , , . ) ( .* . . = . ) ) ) ) TRUE . = $ ; "t1" "id" "t1" "name" "User_posts" "__prisma_data__" "posts" "public" "User" "t1" "__prisma_data__" '[]' "__prisma_data__" "t4" "__prisma_data__" 'id' "t3" "id" 'content' "t3" "content" 'authorId' "t3" "authorId" "__prisma_data__" "t2" "public" "Post" "t2" "t1" "id" "t2" "authorId" "t3" "t4" "t5" "User_posts" "t1" "name" COALESCE /* root select */ /* inner select */ /* middle select */ /* outer select */ 1 発行されるSQL
  20. find系のincludeで発行されるSQL © 2024 Cloudbase Inc. await . . ({ ,

    { }, { }, }); prisma user relationLoadStrategy: include: posts: where: name: findMany 'join' 'tockn' true SELECT AS FROM AS LEFT JOIN SELECT AS FROM SELECT FROM SELECT AS FROM SELECT FROM AS WHERE AS AS AS AS ON WHERE . , . , . . LATERAL ( (JSONB_AGG( ), ) ( . ( JSONB_BUILD_OBJECT ( , . , , . , , . ) ( .* . . = . ) ) ) ) TRUE . = $ ; "t1" "id" "t1" "name" "User_posts" "__prisma_data__" "posts" "public" "User" "t1" "__prisma_data__" '[]' "__prisma_data__" "t4" "__prisma_data__" 'id' "t3" "id" 'content' "t3" "content" 'authorId' "t3" "authorId" "__prisma_data__" "t2" "public" "Post" "t2" "t1" "id" "t2" "authorId" "t3" "t4" "t5" "User_posts" "t1" "name" COALESCE /* root select */ /* inner select */ /* middle select */ /* outer select */ 1 発行されるSQL JOINが使われている (少し複雑なSQLだが...)
  21. PrismaClientIssuerについて const new async => async => = ( )

    . (..., ( ) { . . ({ ... }) }) . (..., ( ) { . . ({ ... }) }) issuer PrismaClientIssuser primary create readReplica findMany args issuer tx tx user issuer tx tx user // primaryへのアクセス // read replicaへのアクセス await await await return © 2024 Cloudbase Inc.
  22. PrismaClientIssuerについて const new async => async => = ( )

    . (..., ( ) { . . ({ ... }) }) . (..., ( ) { . . ({ ... }) }) issuer PrismaClientIssuser primary create readReplica findMany args issuer tx tx user issuer tx tx user // primaryへのアクセス // read replicaへのアクセス await await await return © 2024 Cloudbase Inc. primaryを指定
  23. PrismaClientIssuerについて const new async => async => = ( )

    . (..., ( ) { . . ({ ... }) }) . (..., ( ) { . . ({ ... }) }) issuer PrismaClientIssuser primary create readReplica findMany args issuer tx tx user issuer tx tx user // primaryへのアクセス // read replicaへのアクセス await await await return © 2024 Cloudbase Inc. callbackの引数に primaryへ張ったTranasctionClientが来る
  24. PrismaClientIssuerについて const new async => async => = ( )

    . (..., ( ) { . . ({ ... }) }) . (..., ( ) { . . ({ ... }) }) issuer PrismaClientIssuser primary create readReplica findMany args issuer tx tx user issuer tx tx user // primaryへのアクセス // read replicaへのアクセス await await await return © 2024 Cloudbase Inc. primaryへクエリを発行
  25. PrismaClientIssuerについて const new async => async => = ( )

    . (..., ( ) { . . ({ ... }) }) . (..., ( ) { . . ({ ... }) }) issuer PrismaClientIssuser primary create readReplica findMany args issuer tx tx user issuer tx tx user // primaryへのアクセス // read replicaへのアクセス await await await return © 2024 Cloudbase Inc. read replicaも同じ
  26. PrismaClientIssuerについて const async => = ( : . , :

    ) { ... }; handleUser tx user Prisma TransactionClient User © 2024 Cloudbase Inc.
  27. PrismaClientIssuerについて const async => = ( : . , :

    ) { ... }; handleUser tx user Prisma TransactionClient User © 2024 Cloudbase Inc. primaryを渡すべき? readReplicaを渡すべき?
  28. PrismaClientIssuerについて const async => = ( : . , :

    ) { . . ({ }); }; handleUser create tx user tx user data: user Prisma TransactionClient User await © 2024 Cloudbase Inc. 実装を見ないとわからない (この例はcreateしてるのでprimaryが必要)
  29. © 2024 Cloudbase Inc. 2つのTransaction型 ・PrismaのTransactionClientの交差型 ・   ・issuerのprimaryメソッドで渡る型  

    ・ に対しても ・   ・issuerのreadReplicaメソッドで渡る型   ・ に対して CBWritableTransaction CBWritableTransaction CBReadableTransaction CBReadableTransaction 渡せる 渡せない
  30. 独自Transaction型を使う const async => const async => = ( :

    , : ) { . . ({ }); }; = ( : ) { . . (); }; registerUser create listUsers findMany tx user tx user data: user tx tx user CBWritableTransaction User CBReadableTransaction await return © 2024 Cloudbase Inc.
  31. 独自Transaction型を使う const async => const async => = ( :

    , : ) { . . ({ }); }; = ( : ) { . . (); }; registerUser create listUsers findMany tx user tx user data: user tx tx user CBWritableTransaction User CBReadableTransaction await return © 2024 Cloudbase Inc. Write系クエリがある場合は CBWritableTranasction
  32. 独自Transaction型を使う const async => const async => = ( :

    , : ) { . . ({ }); }; = ( : ) { . . (); }; registerUser create listUsers findMany tx user tx user data: user tx tx user CBWritableTransaction User CBReadableTransaction await return © 2024 Cloudbase Inc. Read系のみの場合は CBReadableTransaction
  33. PrismaClientIssuerと独自Transaction型(OKパターン) // primary // CBWritableTransaction // CBReadableTransaction // read replica

    // CBReadableTransaction await await return await return . (..., ( ) { ( ) ( ) }) . (..., ( ) { ( ) }) issuer tx tx tx issuer tx tx primary registerUser listUsers readReplica listUsers async => async => © 2024 Cloudbase Inc.
  34. PrismaClientIssuerと独自Transaction型(OKパターン) // primary // CBWritableTransaction // CBReadableTransaction // read replica

    // CBReadableTransaction await await return await return . (..., ( ) { ( ) ( ) }) . (..., ( ) { ( ) }) issuer tx tx tx issuer tx tx primary registerUser listUsers readReplica listUsers async => async => © 2024 Cloudbase Inc. Writable, Readableどちらも呼べる primaryでは CBWritableTransactionが貰える
  35. PrismaClientIssuerと独自Transaction型(OKパターン) // primary // CBWritableTransaction // CBReadableTransaction // read replica

    // CBReadableTransaction await await return await return . (..., ( ) { ( ) ( ) }) . (..., ( ) { ( ) }) issuer tx tx tx issuer tx tx primary registerUser listUsers readReplica listUsers async => async => © 2024 Cloudbase Inc. readReplicaでは CBReadableTransactionが貰える Readableのみ呼べる
  36. PrismaClientIssuerと独自Transaction型(NGパターン) // NG // 型エラー // NG // 型エラー await

    return return . (..., ( ) { ( ) }) = ( : ) { ( ) . . () } issuer tx tx tx tx tx user readReplica registerUser listUsers registerUser findMany async => const async => CBReadableTransaction © 2024 Cloudbase Inc.
  37. PrismaClientIssuerと独自Transaction型(NGパターン) // NG // 型エラー // NG // 型エラー await

    return return . (..., ( ) { ( ) }) = ( : ) { ( ) . . () } issuer tx tx tx tx tx user readReplica registerUser listUsers registerUser findMany async => const async => CBReadableTransaction © 2024 Cloudbase Inc. CBWritableTransactionが必要なメソッドを readReplicaで呼ぶと型エラー
  38. PrismaClientIssuerと独自Transaction型(NGパターン) // NG // 型エラー // NG // 型エラー await

    return return . (..., ( ) { ( ) }) = ( : ) { ( ) . . () } issuer tx tx tx tx tx user readReplica registerUser listUsers registerUser findMany async => const async => CBReadableTransaction © 2024 Cloudbase Inc. メソッド内で渡してしまうことも防止
  39. © 2024 Cloudbase Inc. パフ ォーマンス、 スケーラビリテ ィ ノ ウハウまとめ

    ・発行されるクエリに注意  ・結局queryRawが必要になることも  ・とはいえ、Prismaはクエリ最適化を頑張ってる ・Read Replicaによるスケールアウト  ・PrismaClientをwrapするクラスを作ると便利  ・独自のTransaction型を定義すると便利
  40. whereとundefined const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc.
  41. whereとundefined const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc. これがundefinedを返した場合
  42. whereとundefined const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc. tenant関係なく 全件取得
  43. RLSざっくり解説 © 2024 Cloudbase Inc. どうぞ (SELECT * FROM messagesの結果)

    id 1 こんにちは〜 どうも〜 さようなら〜 1 1 1 6 29 body tenant_id
  44. RLSざっくり解説 © 2024 Cloudbase Inc. どうぞ (SELECT * FROM messagesの結果)

    id 1 こんにちは〜 どうも〜 さようなら〜 1 1 1 6 29 body tenant_id Whereが無くても tenantIdで絞られる
  45. PrismaClientIssuerとRLS await . ( , ( ) { ... })

    issuer accessToken tx primary async => © 2024 Cloudbase Inc.
  46. PrismaClientIssuerとRLS await . ( , ( ) { ... })

    issuer accessToken tx primary async => © 2024 Cloudbase Inc. 認可情報を渡す
  47. PrismaClientIssuerとRLS await . ( , ( ) { ... })

    issuer accessToken tx primary async => © 2024 Cloudbase Inc. RLSが適用された Transaction
  48. © 2024 Cloudbase Inc. このaccessTokenって何者? await . ( , (

    ) { ... }) issuer accessToken tx primary async =>
  49. handlerの実装 async => async => ( , ) { .

    ( . , ( ) { ... }) } req: res: issuer req accessToken tx express.Request express.Response await readReplica © 2024 Cloudbase Inc. reqから取得して 渡すだけ
  50. PrismaClientIssuerの実装 async accessToken callback prisma tx tx accessToken tenantId tx

    ( , ) { ... . ( ( ) { . . ; ( ); }) } readReplica $transaction $executeRaw callback return await return async => ${ } ` SELECT set_config( 'app.rls_config.tenant', , TRUE )` © 2024 Cloudbase Inc.
  51. PrismaClientIssuerの実装 async accessToken callback prisma tx tx accessToken tenantId tx

    ( , ) { ... . ( ( ) { . . ; ( ); }) } readReplica $transaction $executeRaw callback return await return async => ${ } ` SELECT set_config( 'app.rls_config.tenant', , TRUE )` © 2024 Cloudbase Inc. set_configを呼んで callbackに渡してるだけ
  52. © 2024 Cloudbase Inc. 例えば 以下のテーブルがあるとする model User id Int

    name String address String password String { } 個人情報
  53. © 2024 Cloudbase Inc. Responseの型を以下とする type = { : ;

    : ; }[]; ListUsersResponse number string id name
  54. こうなる const async => const = ( , ) {

    : = . . (); . ( ); }; listUsers findMany json req res prisma user res resp resp ListUsersResponse await
  55. こうなる const async => const = ( , ) {

    : = . . (); . ( ); }; listUsers findMany json req res prisma user res resp resp ListUsersResponse await 型的には問題ないが…?
  56. こうなる [ { : , : , : , :

    } ] "id" "name" "address" "password" 1 "tockn" "〒108-0073 東京都港区三田3-2-8" "sugoi-secure-password"
  57. こうなる [ { : , : , : , :

    } ] "id" "name" "address" "password" 1 "tockn" "〒108-0073 東京都港区三田3-2-8" "sugoi-secure-password" 個人情報が丸見えに
  58. © 2024 Cloudbase Inc. zod ・スキーマ定義とバリデーションを行うライブラリ ・z.object() 等を使用してスキーマを定義 ・Schema.parse(obj) で渡したobjのバリデーション

    const type typeof = . ({ . (), . (), }); = . < >; . ( ); User z id: z name: z User User obj object number string parse User z infer // バリデーション
  59. Response型をzodで書き換える const type typeof = . ( . ({ .

    (), . (), }), ); = . < >; ListUsersResponse z z id: z name: z ListUsersResponse array object number string ListUsersResponse z infer
  60. zodのparseを行う const async => const = ( , ) {

    = . ( . . () ); . ( ); }; listUsers parse findMany json req res ListUsersResponse prisma user res resp resp await
  61. zodのparseを行う const async => const = ( , ) {

    = . ( . . () ); . ( ); }; listUsers parse findMany json req res ListUsersResponse prisma user res resp resp await parseメソッドを実行
  62. こうなる [ { : , : } ] "id" "name"

    1 "tockn" 個人情報が切り落とされた! parseは未定義フィールドを切り落とす
  63. © 2024 Cloudbase Inc. セキ ュ リテ ィ ノ ウハウまとめ

    ・マルチテナントにおける行レベルのセキュリティ  ・PrismaでRLSを使う  ・またしてもPrismaClientをwrapするクラスが便利 ・列レベルのセキュリティも忘れてはいけない  ・PrismaはPOJO返し、TypeScriptは構造的型付け  ・ナイーブな実装は情報漏洩になることも  ・zodを使うことで回避
  64. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => =>
  65. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => => シードレコードを 宣言的に定義
  66. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => => シードレコードを 宣言的に定義 DELETEしてから INSERT
  67. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => => シードレコードを 宣言的に定義 外部キー制約も考慮して INSERT DELETEしてから INSERT
  68. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => => テスト対象の メソッドを書く
  69. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => => テスト対象メソッドの 戻り値を定義
  70. 内製Test Runner全体像 it run method _TEST_ONLY_primaryDBWithRLS registerUserInGroup ( , (

    , { { [{ , }], [{ , , }], }, () ( ( ) ( , )), { }, { [{ , , }] ... '指定したgroupId配下にuserが作成される' 'テナント1' 'グループ1' 'tockn' db recordSet: tenant: id: name: group: id: name: tenantId: : tx tx returns: id: mutates: user: id: name: groupId: 1 1 1 1 1 1 1 => => テスト対象メソッド実行後の レコードの状態を定義
  71. © 2024 Cloudbase Inc. 今から使えます! schema.prisma shell npm @cloudbase-inc/ install

    prisma-generator-integration-test-runner -D generator integration test runner provider - - { = } "prisma-generator-integration-test-runner"
  72. © 2024 Cloudbase Inc. テスタビリテ ィ まとめ ・開発生産性に課題  ・テストレコードの用意、検証など ・内製Test

    Runnerが便利  ・宣言的な状態定義 ・OSSとして公開しました  ・ぜひ使ってみてください
  73. トレースをJaegerで可視化 const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc.
  74. トレースをJaegerで可視化 const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc. どのAPIで
  75. トレースをJaegerで可視化 const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc. どのような処理が どのくらい実行されたか
  76. トレースをJaegerで可視化 const = (); . . ({ { } });

    tenantId await await getMyTenantId findMany prisma message where: tenantId © 2024 Cloudbase Inc. どのようなSQLが発行されたか