Type-safe S3 پرس و جوها را با Kysely انتخاب کنید

S3 Select یک ویژگی Amazon S3 است که امکان بازیابی زیرمجموعه های محتوای S3 Objects را از طریق عبارات SQL فراهم می کند. میتوانید از بندهایی مانند SELECT و WHERE برای واکشی دادهها از فایلهای CSV، JSON یا Apache Parquet استفاده کنید، حتی اگر با GZIP و/یا رمزگذاری شده در سمت سرور فشرده شده باشند.
S3 Select برای استفاده ساده، مقرون به صرفه است و بسته به درخواست شما می تواند عملکرد برنامه شما را به شدت بهبود بخشد. به طور کلی یک افزودنی عالی به جعبه ابزار توسعه دهنده بدون سرور است، به ویژه زمانی که:
- شما باید به حجم زیادی از داده ها دسترسی داشته باشید (که آن را برای راه حل های ذخیره سازی دیگر مانند DynamoDB نامناسب می کند)
- شما باید آن را به روشی پیچیده و/یا پویا پرس و جو و فیلتر کنید
- شما نیازی به استفاده از بند JOIN ندارید (زیرا S3 Select فقط می تواند یک فایل را پرس و جو کند) یا برای به روز رسانی یک رکورد از داده ها (به آن S3 Select می گویند نه S3 Insert 🙂)
در این مقاله، نحوه اجرای دستورات S3 Select در تایپ اسکریپت را یاد خواهیم گرفت و چگونه می توانیم DevX و ایمنی نوع خود را با استفاده از Kysely بهبود دهیم.
پرس و جو با S3 Select
بیایید بگوییم که ما یک DB از پوکمون ها به شکل CSV داریم که در جایی در یک سطل S3 ذخیره شده است:
id ; name ; customName ; type ; level ; generation
1 ; pikachu ; ; electric ; 42 ; 1
2 ; charizard ; ; fire ; 54 ; 1
3 ; meganium ; plantyDino ; grass ; 26 ; 2
...
اگر بخواهیم پوکمون های آتش را از نسل 1 و 2 بازیابی کنیم چه؟ خوب، S3 Select اجازه دهید این کار را با کوئری زیر انجام دهیم:
import { S3Client, SelectObjectContentCommand } from '@aws-sdk/client-s3';
export const s3Client = new S3Client({
region: 'us-east-1', // <= Your region here
});
const { Payload } = await s3Client.send(
new SelectObjectContentCommand({
// 👇 Those first params are required but don't mind them
ExpressionType: 'SQL',
OutputSerialization: {
JSON: {
RecordDelimiter: ',',
},
},
// 👇 Those depends on the CSV
InputSerialization: {
CSV: {
FileHeaderInfo: 'USE',
FieldDelimiter: ';',
QuoteCharacter: '"',
},
},
// 👇 Those are the most important
Bucket: 'my-super-bucket-name',
Key: 'pokedex.csv',
Expression: `
select "id", "name", "customName", "type" as "pokemonType", "level"
from "S3Object"
where
"generation" in ('1', '2')
and "type" = 'fire'
`,
}),
);
// Note that this command requires the s3:GetObject permission
این عالی است و همه چیز به جز ما می توانیم آن را بهتر کنیم:
- 📝 برای استفاده از پاسخ باید مقداری تجزیه سفارشی اضافه کنیم
Payload
- 👍 ما میتوانیم از Kysely برای تولید عبارت SQL به روشی ایمن و سازگار با devX استفاده کنیم
- 🌈 همچنین می توانیم از آن برای تایپ نتیجه دستور استفاده کنیم
تجزیه پاسخ پرس و جو
این Payload
یک شی جاوا اسکریپت ساده نیست بلکه نمونه ای از آن است AsyncInterable
. حالا چه جهنمی است AsyncIterable
شما از من بپرسید؟ خوب، میتوانید به دنبال اسناد رسمی Async Iterator در MDN بگردید یا فقط میتوانید از پرسیدن سؤال خودداری کنید و از راهنما زیر استفاده کنید:
import type { SelectObjectContentEventStream } from '@aws-sdk/client-s3';
// 👇 TextDecoder is a native Node/Browser class
const textDecoder = new TextDecoder();
const parseS3SelectEventStream = async (
s3SelectEventStream:
| AsyncIterable<SelectObjectContentEventStream>
| undefined,
): Promise<unknown[]> => {
if (!s3SelectEventStream) {
return [];
}
const stringifiedJSONOutputs: string[] = [];
for await (const event of s3SelectEventStream) {
if (event.Records) {
const stringifiedJSONOutput = textDecoder.decode(event.Records.Payload);
stringifiedJSONOutputs.push(stringifiedJSONOutput);
}
}
const rows = JSON.parse(
'[' + stringifiedJSONOutputs.join('').slice(0, -1) + ']',
) as unknown[];
return rows;
};
حالا می توانیم خروجی را به صورت زیر تجزیه کنیم:
const { Payload: s3SelectEventStream } = await s3Client.send(
new SelectObjectContentCommand({
// ...
}),
);
// 🎉 Ta-da!
const myPokemons: unknown[] = await parseS3SelectEventStream(
s3SelectEventStream,
);
ساخت پرس و جو با Kysely
بیایید با آن روبرو شویم، نوشتن پرس و جوهای SQL با دست دردناک و مستعد خطا است. علاوه بر این، اگر CSV ناگهان تغییر شکل دهد، خوب نیست که یک خطای نوع داشته باشیم؟
اینجاست که Kysely به کمک می آید: Kysely یک سازنده پرس و جوی SQL تایپ اسکریپت سازگار با نوع و devX است. این برای کار با PostgreSQL و MySQL طراحی شده بود، اما چند کلاس را در معرض دید قرار میدهد که میتوانند به ما اجازه نوشتن پرسوجوها را بدون اتصال به یک پایگاه داده رابطهای واقعی بدهند.
بیایید با طراحی نوع CSV خود شروع کنیم:
enum PokemonType {
Water = 'water',
Grass = 'grass',
Fire = 'fire',
// ...
}
interface PokemonCSV {
id: string;
name: string;
customName?: string;
type: PokemonType;
// 👇 In CSVs everything is a string
level: string;
generation: '1' | '2'; // ...up to 9
}
بعد، بیایید Kysely را نصب کنیم و a را نمونه کنیم Kysely
پایگاه داده:
# npm
npm install kysely
# yarn
yarn add kysely
import {
Kysely,
DummyDriver,
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler,
} from 'kysely';
interface Database {
S3Object: PokemonCSV;
}
const db = new Kysely<Database>({
dialect: {
createAdapter: () => new SqliteAdapter(),
createDriver: () => new DummyDriver(),
createIntrospector: ($db: Kysely<unknown>) => new SqliteIntrospector($db),
createQueryCompiler: () => new SqliteQueryCompiler(),
},
});
// That’s it 🎉
ساده، نه؟ اکنون، بیایید همان پرس و جو را بنویسیم، اما در حالی که از ایمنی نوع و تکمیل خودکار لذت می بریم:
const kyselyQuery = db
.selectFrom('S3Object')
.select([
'id',
'name',
'customName',
// 🙌 You can rename columns as you like
'type as pokemonType',
'level',
// 💥 Will trigger an error:
'unexistingColumn',
])
// 🙌 Every method is type-safe!
.where('generation', 'in', ['1', '2'])
.where('type', '=', PokemonType.Fire);
برای محافظت از ما در برابر تزریق های نامناسب SQL، Kysely مستقیماً عبارت SQL را در اختیار ما قرار نمی دهد، بلکه یک sql
رشته و parameters
آرایه:
const {
sql, // 👈 SQL query with '?' as placeholders
parameters, // 👈 Array of parameters
} = kyselyQuery.compile();
از آنجایی که S3 Select پارامترها را در API خود نمی پذیرد، باید خودمان پارامترها را هیدراته کنیم:
const dangerouslyHydrateSQLParameters = (
sql: string,
parameters: readonly unknown[],
): string => {
for (const parameter of parameters) {
sql = sql.replace('?', `'${String(parameter)}'`);
}
return sql;
};
const { sql, parameters } = kyselyQuery.compile();
// ⛔️ **BE SURE TO VALIDATE DYNAMIC PARAMETERS FIRST** ⛔️
const sqlExpression = dangerouslyHydrateSQLParameters(sql, parameters);
console.log(sqlExpression);
// 👇 We retrieve the same expression as above:
// select "id", "name", "customName", "type" as "pokemonType", "level"
// from "S3Object"
// where
// "generation" in ('1', '2')
// and "type" = 'fire'
ما فقط باید آن را به دستور S3 Select و voilà خود ارائه دهیم! انجام شد!
تقریباً: توجه کنید که ما فقط عبارت پرس و جوی SQL خود را تایپ کردیم! در مورد پاسخی که از S3 می آید چطور؟
استنباط نوع پاسخ
در ابتدا، Kysely نه تنها برای ساخت پرس و جو ساخته شد، بلکه آنها را نیز اجرا کرد. ما فقط می توانیم با بازرسی از نوع استنباط شده بهره مند شویم execute
ویژگی پرس و جو Kysely ما. این را می توان با کمک برخی از جادوگری TS انجام داد:
import type { SelectQueryBuilder } from 'kysely';
type QueryResponseRow<
// 👇 Add a large type constraint
KyselyQuery extends SelectQueryBuilder<
Record<string, unknown>,
string,
unknown
>,
> =
// 👇 Remove the Promise wrapper
Awaited<
// 👇 Get the return type of the execute method
ReturnType<KyselyQuery['execute']>
// 👇 Unpack the array
>[number];
type Pokemon = QueryResponseRow<typeof kyselyQuery>;
// 👇 Equivalent to:
type Pokemon = {
id: string;
name: string;
// 👍 customName is indeed possibly undefined
customName: string | undefined;
level: string;
// 🙌 "type" property has been renamed
pokemonType: PokemonType;
};
اکنون می توانیم از این نوع پاسخ خود استفاده کنیم parseS3SelectEventStream
Util و voilà! انجام شد! برای خیر این بار 🙂
نتیجه
هر دو S3 Select و Kysely ابزارهای بسیار خوبی هستند. با پیوستن به هر دوی آنها، میتوانیم پرسوجوهای عملکردی، مقیاسپذیر و مقرونبهصرفه را اجرا کنیم و در عین حال از ایمنی و استنتاج نوع قوی و خشک بهرهمند شویم.
توجه داشته باشید که یک اشکال کوچک وجود دارد، اما: Kysely 120 کیلوبایت به بستههای Lambdas شما اضافه میکند (برای کمک به من در این مورد 🙌). این مقدار زیادی نیست، اما قابل چشم پوشی هم نیست، زیرا بسته های NodeJS Lambdas بالای 5 مگابایت بر شروع سرد آنها تأثیر منفی می گذارد. بنابراین ممکن است بخواهید افزودن Kysely به بستههای خود را مجدداً ارزیابی کنید، اگر درخواست شما اغلب تغییر نمیکند.