الگوهای طراحی رایج در Stripe

اگر مقاله قبلی در مورد اهمیت الگوهای طراحی را نخوانده اید، پیشنهاد می کنم قبل از خواندن این مقاله از آنجا شروع کنید. با این حال، اگر قبلاً در مورد الگوهای طراحی متقاعد شدهاید، برای دیدن نمونههایی که در Stripe استفاده میکنیم، برگزیدههای برتر من را بخوانید.
ممکن است با نحوه طراحی Stripe API مخالف باشید و طرحی که در نهایت به آن دست مییابید احتمالاً با آنچه ما استفاده میکنیم متفاوت خواهد بود. این خیلی خوب است، زیرا شرکت های مختلف موارد استفاده متفاوتی دارند. در عوض، من برخی از الگوهای طراحی را در اینجا ارائه می کنم که معتقدم به اندازه کافی عمومی هستند تا برای تقریباً هر کسی در فرآیند طراحی API مفید باشد.
زبان
نامگذاری چیزها سخت است. این در مورد بیشتر چیزها در علوم کامپیوتر صادق است و طراحی API تفاوتی ندارد. مشکل اینجاست که مشابه نام متغیرها و توابع، مسیرها، فیلدها و انواع API را می خواهید که واضح و در عین حال مختصر باشند. انجام این کار در بهترین مواقع سخت است، اما در اینجا چند نکته وجود دارد.
از زبان ساده استفاده کنید
این یک پیشنهاد واضح است، اما در عمل انجام آن بسیار سخت است و علت ریزش زیاد دوچرخه است. سعی کنید یک مفهوم را تا هسته آن تقطیر کنید و از استفاده از اصطلاحنامه برای مترادف ها نترسید.
به عنوان مثال، هنگام ساخت یک پلتفرم، مفهوم کاربر و مشتری را با هم اشتباه نگیرید. کاربر (حداقل در اصطلاح Stripe) طرفی است که مستقیماً از محصول پلتفرم شما استفاده میکند، مشتری (که به عنوان «کاربر نهایی» نیز شناخته میشود) طرفی است که در نهایت کالاها یا خدماتی را خریداری میکند که ممکن است کاربر شما ارائه دهد. شما مجبور نیستید دقیقاً از آن عبارات استفاده کنید (“کاربر” و “کاربر نهایی” کاملاً خوب هستند)، تا زمانی که با زبان خود سازگار باشید.
از اصطلاحات خاص خودداری کنید
صنایع با اصطلاحات مخصوص به خود می آیند. فرض نکنید که کاربر شما همه چیز را در مورد صنعت خاص شما می داند. به عنوان مثال، شماره 16 رقمی که در کارت اعتباری خود می بینید، شماره حساب اصلی یا به اختصار PAN نامیده می شود. هنگامی که در حلقههای فینتک اجرا میشوید، طبیعی است که افراد درباره PAN، DPAN و FPAN صحبت کنند، بنابراین اگر چنین کاری را در API پرداختهای خود انجام دهید از شما معاف میشوید:
card.pan = 4242424242424242;
حتی اگر بدانید PAN مخفف چیست، ممکن است ارتباط بین آن و عدد 16 رقمی روی کارت اعتباری هنوز مشخص نباشد. در عوض از اصطلاحات واژگان دوری کنید و از چیزی استفاده کنید که احتمالاً توسط مخاطبان بیشتری قابل درک است:
card.number = 4242424242424242;
این به ویژه زمانی که به این فکر میکنید که مخاطبان API شما چه کسانی هستند، مهم است. احتمالاً نقش اصلی فردی که API شما را پیادهسازی میکند، نقش توسعهدهندهای است که هیچ دانشی از فینتک یا هر دامنه تخصصی دیگری ندارد. به طور کلی بهتر است تصور کنید که مردم با اصطلاحات تخصصی صنعت شما آشنا نیستند.
ساختار
Enums را به Booleans ترجیح دهید
بیایید تصور کنیم که یک API برای یک مدل اشتراک داریم. به عنوان بخشی از API، ما می خواهیم کاربران بتوانند تعیین کنند که آیا اشتراک مورد نظر فعال است یا لغو شده است. داشتن رابط زیر منطقی به نظر می رسد:
Subscription.canceled={true, false}
این کار می کند، اما بگویید که مدتی پس از اجرای موارد فوق، تصمیم می گیریم یک ویژگی جدید ارائه دهیم: توقف اشتراک ها. اشتراک موقتاً به این معنی است که ما از پرداختها استراحت میکنیم، اما اشتراک همچنان فعال است و لغو نشده است. برای منعکس کردن این موضوع میتوانیم یک فیلد جدید اضافه کنیم:
Subscription.canceled={true, false}
Subscription.paused={true, false}
حال برای مشاهده وضعیت واقعی اشتراک، باید به جای یک فیلد، به دو قسمت نگاه کنیم. این همچنین ما را به سردرگمی بیشتری باز می کند: اگر یک اشتراک داشته باشد چه می شود canceled: true
و paused: true
? آیا اشتراکی که لغو شده است نیز قابل توقف است؟ شاید ما آن را یک اشکال در نظر بگیریم و بگوییم که اشتراک های لغو شده باید داشته باشند paused: false
.
آیا این بدان معناست که اشتراک لغو شده میتواند متوقف شود؟
مشکل فقط با اضافه کردن فیلدها بدتر می شود. به جای اینکه بتوانید یک مقدار را بررسی کنید، به یک پشته گیج کننده از عبارات if/else نیاز دارید تا بفهمید دقیقاً چه اتفاقی در این اشتراک می افتد.
در عوض، بیایید الگوی زیر را در نظر بگیریم:
Subscription.status={"active", "canceled"}
یک فیلد واحد به زبان ساده با استفاده از enums به جای boolean وضعیت شی را به ما می گوید. مزیت دیگر توسعه پذیری و آینده نگری است که این تکنیک به ما می دهد. اگر به مثال قبلی خود در مورد افزودن مکانیک “مکث” برگردیم، تنها کاری که باید انجام دهیم این است که یک عدد اضافی اضافه کنیم:
Subscription.status={"active", "canceled", "paused"}
ما عملکردی را اضافه کردهایم، اما پیچیدگی API را در همان پایه حفظ کردهایم در حالی که توصیفیتر است. اگر زمانی تصمیم بگیریم که قابلیت توقف اشتراک را حذف کنیم، حذف یک enum همیشه راحت تر از حذف یک فیلد خواهد بود.
این بدان معنا نیست که شما هرگز نباید از Booleans در API خود استفاده کنید، زیرا تقریباً به طور قطع موارد لبه وجود دارد که آنها منطقی تر هستند. در عوض، از شما میخواهم که قبل از اضافه کردن آنها، احتمال آینده را در نظر بگیرید که منطق بولی دیگر معنی ندارد (مثلاً داشتن گزینه سوم).
از اشیاء تو در تو برای توسعه پذیری آینده استفاده کنید
در ادامه نکته قبلی: سعی کنید به صورت منطقی فیلدها را با هم گروه بندی کنید. به شرح زیر:
customer.address = {
line1: "Main Street 123",
city: "San Francisco",
postal_code: "12345"
};
بسیار تمیزتر از:
customer.address_line1 = "Main street 123";
customer.address_city = "San Francisco";
customer.address_postal_code: "12345";
گزینه اول اضافه کردن یک فیلد اضافی را بعداً آسانتر می کند (به عنوان مثال، a country
اگر تصمیم دارید کسب و کار خود را به مشتریان خارج از کشور گسترش دهید) و تضمین می کند که نام رشته های شما خیلی طولانی نشود. خوب و تمیز نگه داشتن سطح بالای منابع نه تنها ارجح است، بلکه روح را نیز آرام می کند.
پاسخ
نوع شی را برگردانید
در بیشتر موارد، هنگامی که یک تماس API برقرار می کنید، برای دریافت یا جهش برخی از داده ها است. در مورد دوم، هنجار برگرداندن نمایشی از منبع جهش یافته است. به عنوان مثال، اگر آدرس ایمیل مشتری را بهعنوان بخشی از پاسخ 200 خود بهروزرسانی کنید، انتظار دارید یک کپی از آن مشتری با آدرس ایمیل جدید و بهروز شده دریافت کنید.
برای آسانتر کردن زندگی توسعهدهندگان، در مورد آنچه دقیقاً بازگردانده میشود، صریح باشید. در Stripe API، ما یک object
میدانی در پاسخ که کاملاً روشن میکند که با چه چیزی کار میکنیم. به عنوان مثال، مسیر API
/v1/customers/:customer/payment_methods/:payment_method
یک روش پرداخت را که به یک مشتری خاص متصل شده است برمی گرداند. امیدواریم از مسیر مشخص باشد که باید انتظار بازگشت روش پرداخت را داشته باشید، اما در هر صورت، ما آن را درج می کنیم object
برای اطمینان از اینکه هیچ سردرگمی وجود ندارد:
{
"id": "pm_123",
"object": "payment_method",
"created": 1672217299,
"customer": "cus_123",
"livemode": false,
...
}
این به هنگام غربال کردن لاگ ها یا اضافه کردن برخی برنامه های دفاعی به یکپارچه سازی شما کمک زیادی می کند:
if (response.data.object !== 'payment_method') {
// Not the expected object, bail
return;
}
امنیت
از یک سیستم مجوز استفاده کنید
فرض کنید در حال کار بر روی ویژگی جدیدی برای داشبورد محصول خود هستید، ویژگی که به طور خاص توسط یک مشتری بزرگ درخواست شده است. شما آمادهاید تا آنها آن را بهعنوان نسخه بتا آزمایش کنند تا بازخورد دریافت کنند، بنابراین به آنها اجازه میدهید تا از کدام مسیر درخواست کنند و چگونه از آن استفاده کنند. مسیر جدید در هیچ کجا به صورت عمومی مستند نشده است و هیچ کس جز مشتری شما حتی نباید در مورد آن بداند، بنابراین شما زیاد نگران نباشید.
چند هفته بعد، شما تغییری را در ویژگی اعمال میکنید که به برخی از بازخوردهایی که مشتری بزرگ به شما داده است پاسخ میدهد، فقط برای اینکه یک سری ایمیلهای خشمگین از سایر کاربران دریافت کنید که چرا یکپارچهسازی آنها ناگهان به هم خورده است.
فاجعه، معلوم شد که مسیر API مخفی شما لو رفته است. شاید آن مشتری اولیه آنقدر از ویژگی جدید هیجان زده شد که تصمیم گرفت به دوستان توسعه دهنده خود در مورد آن بگوید. یا شاید کاربران آن مشتری نگاهی به پنل شبکه خود انداختند و این درخواستها را به یک API غیرمستند دیدند و به این نتیجه رسیدند که ظاهر آن ویژگی را برای محصول خود دوست دارند.
نه تنها باید آشفتگی فعلی را پاک کنید، بلکه اکنون ویژگی بتا شما به طور موثر به حالت راه اندازی کشیده شده است. از آنجایی که ایجاد هرگونه تغییر جدید اکنون از شما میخواهد که به هر کاربری که دارید اطلاع دهید، سرعت توسعه شما به یک خزیدن کاهش یافته است.
مهندسی معکوس یک API آنقدرها هم که فکر می کنید دشوار نیست، و اگر اقداماتی برای جلوگیری از آن انجام ندهید، می توانید فرض کنید که مردم این کار را خواهند کرد.
امنیت از طریق ابهام این ایده است که چیزی پنهان بنابراین امن است. همانطور که این برای هدایای کریسمس پنهان شده در کمد صدق نمی کند، در مورد امنیت وب نیز صادق نیست. اگر میخواهید مطمئن شوید که APIهای خصوصی شما خصوصی میمانند، مطمئن شوید که دسترسی به آنها امکانپذیر نیست مگر اینکه کاربر مجوزهای صحیح را داشته باشد. ساده ترین راه برای انجام این کار داشتن یک سیستم مجوز متصل به کلید API است. اگر کلید API مجاز به استفاده از مسیر نیست، زودتر وثیقه بگذارید و یک پیام خطا با وضعیت 403 برگردانید.
شناسه های خود را غیرقابل حدس زدن کنید
من در پست Object IDs به این موضوع اشاره کردم، اما ارزش دیدن مجدد در اینجا را دارد. اگر در حال طراحی یک API هستید که اشیاء را با شناسه های مرتبط با آنها برمی گرداند، مطمئن شوید که آن شناسه ها قابل حدس زدن یا مهندسی معکوس نیستند. اگر شناسههای شما صرفاً متوالی هستند، در بهترین حالت ناخواسته اطلاعاتی در مورد کسبوکارتان به بیرون درز میکنید که ممکن است نخواهید مردم بدانند و در بدترین حالت یک حادثه امنیتی در انتظار ایجاد میکنید.
برای نشان دادن، اگر من در سایت شما خریدی انجام دهم و شناسه سفارش تاییدیه “10” را دریافت کنم، می توانم دو فرض داشته باشم:
- شما تقریباً به اندازه ای که احتمالاً ادعا می کنید تجارت ندارید
- شاید بتوانم در مورد 9 سفارش قبلی (و همه سفارشهای آینده) اطلاعاتی به دست بیاورم که نمیتوانم، چون شناسه آنها را میدانم.
برای این فرض دوم، من میتوانم با سوءاستفاده از API شما به روشهایی که شما قصد نداشتید، درباره سایر مشتریان شما اطلاعات بیشتری کسب کنم:
// If the below route isn't behind a permission system,
// I can guess the ID and get potentially private
// information on your other customers
curl https://api.example.com/v1/orders/9
// Response
{
"id": "9",
"object": "order",
"name": "Lady Jessica",
"email": "jessica@benegesserit.com",
"address": "1 Palace Street, Caladan"
}
در عوض، شناسه های خود را برای مثال با استفاده از UUID غیرقابل حدس زدن کنید. استفاده از چیزی که اساساً رشته ای از اعداد و حروف تصادفی به عنوان شناسه است به این معنی است که هیچ راهی برای حدس زدن شناسه بعدی بر اساس شناسه ای که دارید، وجود ندارد.
آنچه را که در راحتی از دست می دهید (صحبت کردن در مورد “سفارش 42” بسیار ساده تر از “سفارش 123e4567-e89b-12d3-a456-426614174000” است) در مزایای امنیتی جبران خواهید کرد. فراموش نکنید که با افزودن پیشوندهای شی، آن را برای انسان خوانا کنید، اما تولید شناسه های سفارش شما در قالب order_123 زندگی شما و افرادی را که با API شما می سازند آسان تر می کند.
طراحی API برای انسان
منابع زیادی برای اینکه چگونه API خود را طراحی کنید وجود دارد، و من امیدوارم که این مقاله به شما انگیزه ای برای فکر کردن و غواصی عمیق تر در این سوراخ خرگوش داده باشد.
در Stripe ما طراحی API را بسیار جدی میگیریم. در داخل ما یک سند الگوی طراحی داریم که شامل آنچه در بالا در مورد آن نوشته ام و خیلی چیزهای دیگر است. این شامل نمونه هایی از طراحی خوب و بد، استثناهای قابل توجه و حتی راهنمای نحوه اضافه کردن مواردی مانند enums به منابع موجود است. بخش مورد علاقه من بخش “دلسرد” است، که در آن نمونه هایی از طراحی مشکوک که امروز در API ما وجود دارد به عنوان هشداری برای توسعه دهندگان Stripe در آینده برجسته شده است.
اگر از این کار لذت بردید، سایر مقالات مجموعه طراحی APIs برای انسان ها را بررسی کنید. همچنین توصیه میکنم برای افکار بیشتر در مورد طراحی API به APIهایی بپیوندید که از انجمن متنفر نباشید.
درباره نویسنده
پل خاکستر یک مدافع توسعه دهنده در Stripe است که در آن می نویسد، کد می نویسد و با توسعه دهندگان صحبت می کند. خارج از محل کار، او از دم کردن آبجو، درست کردن بیلتانگ و شکست دادن به پسرش در Mario Kart لذت می برد.