ساخت REST API همیشه معضل ها و چالش های خودش را دارد اما استانداردی به نام OPEN API برای توسعه این API ها وجود دارد که مشکل و سردرگمی های احتمالی شما را حل می کند. ما می خواهیم در این مقاله ابتدا با چالش های طراحی REST API آشنا شده و سپس به معرفی OPEN API بپردازیم. در نظر داشته باشید که ما این مقاله را با Express.js می نویسیم بنابراین آشنایی با آن لازم است.
نکته: این مقاله برای کاربران تازه کار نوشته نشده است بنابراین مباحث ساده در آن توضیح داده نمی شود.
با اینکه در این مقاله از Express.js استفاده می کنیم اما چالش های طراحی REST API اختصاصی به Express.js ندارند و با هر زبان برنامه نویسی و فریم ورکی همراه هستند. همچنین چالش های مطرح شده در این بخش به هیچ هدف خاصی اختصاص ندارند و چه API را برای یک برنامه موبایل طراحی کرده باشید یا چه آن را برای وب سایت ها یا میکروسرویس ها ساخته باشید باز هم با این چالش ها روبرو خواهید بود.
اگر برنامه شما از REST API ها استفاده می کند، ایجاد تغییر در هر دو سمت (سمت سرور و سمت کلاینت) مشکل آسان نخواهد بود. به طور مثال ممکن است شما Endpoint خاصی را داشته باشید که نام یک کاربر را برمی گرداند اما در آینده نیاز می شود که سن کاربر را نیز برگرداند. مشکل اینجاست که برنامه شما در سمت کلاینت (چه برنامه موبایلی و چه وب سایت) انتظار دریافت چنین مقداری را ندارد و احتمال دارد به طور کامل از کار بیفتد. مثلا شما فقط نام کاربر را به صورت یک رشته برمی گرداندید اما حالا یک شیء برمی گردد که دو خصوصیت name (نام) و age (سن) را دارد. طبیعتا سمت کلاینت نیز باید به همین شکل ویرایش شود.
معمولا تیم های توسعه برای حل این مشکل شروع به نوشتن تست می کنند اما در انتهای کار باز هم نیاز است که تیم توسعه به صورت دستی اشتباهات را تصحیح کرده وسمت کلاینت را ویرایش کند. به زبان ساده با هر تغییر در API احتمال از کار افتادگی سمت کلاینت وجود دارد.
تمام وب سایت هایی که از REST API استفاده می کنند باید نوعی Documentation داشته باشند. در صورتی که API شما عمومی است، documentation برای افرادی خواهد بود که می خواهند از خدمات شما استفاده کنند و اگر API شما خصوصی است Documentation برای توسعه دهندگان تیم شما است. یادتان نرود که توسعه دهندگان نیز برای کمک به پیشرفت پروژه به Documentation نیاز دارند و حتی کمپانی هایی مانند گوگل و فیسبوک نیز از این موضوع مستثنی نیستند و برای توسعه دهندگانشان documentation مخصوص دارند.
فرآیند توسعه یا به روز رسانی و ایجاد تغییرات در یک API بدین شکل است که همه چیز از سورس کد شروع می شود یعنی تیم توسعه همیشه قابلیت جدید را اضافه می کند یا تغییر جدید را ایجاد می کند و سپس آن را به documentation اضافه می کند. مسئله اینجاست که تیم توسعه به اندازه کافی درگیری دارد و با کدها درگیر است و اضافه کردن Documentation و نوشتن توضیحات برای آن ها باری اضافی و سنگین است. مهم ترین نکته منفی اینجاست که محول کردن چنین وظیفه ای به تیم توسعه باعث کاهش سرعت توسعه نیز خواهد شد. از طرف دیگر کاملا طبیعی است که بین اضافه شدن یک قابلیت یا ایجاد یک تغییر در API و اضافه کردن توضیحات آن در documentation فاصله زمانی وجود خواهد داشت. این فاصله زمانی باعث سردرگمی تیم توسعه شما نیز خواهد شد چرا که توسعه دهندگان بر اساس توضیحات documentation جلو می روند اما با خطا روبرو می شوند و زمان زیادی برای شناسایی مشکل تلف خواهد شد.
API بسیاری از برنامه های تحت وب یا به طور کامل عمومی است و یا اینکه بخشی برای توسعه دهندگان دیگر (به صورت عمومی) دارد، مخصوصا اگر وب سایت شما سرویس و خدمات خاصی را به دیگر توسعه دهندگان ارائه کند. این API های عمومی یک محدودیت بسیار بزرگ دارند: ما نمی توانیم آن ها را با سرعت API های خصوصی به روز رسانی کنیم! چرا؟ به دلیل اینکه سرویس ها و وب سایت های زیادی از API ما استفاده می کنند و ایجاد تغییر ناگهانی در API باعث از کار افتادن تمام این وب سایت ها و سرویس ها می شوند.
معمولا برای ایجاد تغییر در API عمومی از چند ماه قبل به توسعه دهندگان عمومی هشدار داده می شود که در فلان تاریخ ساختار API تغییر خواهد کرد. حتی برخی از وب سایت ها یک نسخه قدیمی از API خود را با پسوند v2 یا v1 یا v3 و امثال آن باقی می گذارند تا همه مجبور به آپدیت برنامه هایشان نشوند!
برنامه شما در طول زمان رشد می کند و تعداد کاربران شما اضافه خواهند شد. در چنین حالتی چیزی که کاربران از API شما انتظار دارند و چیزی که API شما ارائه می دهد تفاوت خواهند داشت. این مسئله در برنامه های کوچک مشکلی ندارد اما زمانی که برنامه شما رشد می کند، برای ایجاد تغییر در یک endpoint باید تاثیرات آن را در طول تمام برنامه رهگیری کنید تا قسمت دیگری از برنامه را خراب نکنید و مطمئن شوید آنچه که API ارائه می دهد همان چیزی است که کاربران انتظارش را دارند. معمولا برای حل این مشکل از تست های یکپارچگی سیستم (integration tests) استفاده می کنند اما نوشتن این تست ها به شدت زمان بر بوده و علاوه بر آن تضمیمی ندارند.
ما در بخش قبل چند چالش جدی در طراحی REST API را بررسی کردیم اما راه حل چیست؟ ما در این بخش یک API بسیار ساده را با استفاده از فریم ورک Express.js طراحی می کنیم و چالش های مطرح شده در آن را حل می کنیم.
چالش هایی که در این مقاله مطالعه کردید، از قدیم الایام همراه توسعه دهندگان بوده اند و مسئله ای جدید نیستند بنابراین در حال حاضر چندین راه حل برای این مشکلات داریم. طبیعتا عاقلانه تر است که به سراغ این راه حل ها برویم تا اینکه بخواهیم خودمان راه حل جدیدی را اختراع کنیم. تمام راه حل های ارائه شده برای چالش های طراحی API در جهت استاندارد سازی آن بوده اند که به آن API Standardization گفته می شود. از بین استانداردهای ارائه شده معمولا سه استاندارد از بقیه مشهور تر هستند: RAML و JsonAPI و OpenAPI.
هدف ارائه این استانداردها یکپارچه سازی ساختار API ها (در تمام زبان ها) است تا فارغ از تکنولوژی استفاده شده، انتظارات ما از یک API و endpoint های آن همیشه یکسان باشد. این استاندارد سازی چند مزیت بسیار بزرگ دارد، آیا می توانید خودتان آن ها را پیدا کنید؟
فرض کنید ما می خواهیم یک برنامه ساده todo را بنویسیم. برنامه های todo برنامه هایی هستند که شما در آن لیستی از کار هایی که باید انجام بدهید را می نویسید تا بعدا یادتان نرود. مثلا فردا یک ساعت کتاب بخوانم، خرید های خانه را انجام بدهم، به احمد در اسباب کشی کمک کنم و غیره. در این برنامه کاربران می توانند todo های جدید را ایجاد کنند، آن ها را دریافت کنند، آن ها را ویرایش کنند و نهایتا در صورت تمایل آن ها را حذف کنند. با این حساب endpoint های شما بدین شکل خواهند بود:
ما با ایجاد این برنامه ساده می توانیم روش حل چالش های موجود را به صورت واقعی ببینیم.
همانطور که گفتم ما از OpenAPI استفاده خواهیم کرد بنابراین به پکیج express-openapi نیاز خواهیم داشت. البته شما می توانید از کتابخانه های دیگری نیز استفاده کنید. همچنین در نظر داشته باشید که این کتابخانه قابلیت های پیشرفته ای مانند response validation (اعتبارسنجی پاسخ های سرور) و authentication (احراز هویت) را نیز دارد اما تمرکز ما روی طراحی API است و با این قابلیت ها کاری نخواهیم داشت.
نکته: هدف ما آموزش ساخت API با Express.js نیست بنابراین من کدهای Express.js را توضیح نخواهم داد. همچنین زمانی که مسیر یک فایل را به شما می دهم و آن فایل وجود ندارد یعنی خودتان باید آن مسیر و آن فایل را بسازید. از شما انتظار می رود که با طراحی API از قبل آشنا باشید. به همین خاطر من فایل کامل پروژه را از همین ابتدا در اختیار شما قرار می دهم تا نیازی به توضیحات اضافه نباشد: دانلود پروژه کامل.
همچنین برای سرعت بخشیدن به کار از express-generator استفاده خواهیم کرد. پکیج express-generator یک boilerplate برای پروژه های Express.js است. یعنی چه؟ یعنی ساختار اولیه برنامه های Express را به سادگی برایتان آماده کرده است تا نیازی به انجام تنظیمات اولیه نداشته باشید. این پکیج در همان ابتدا چند پوشه زیر را ساخته و در اختیار شما قرار می دهد:
البته ما به برخی از پوشه ها مانند views احتیاجی نداریم بنابراین درون دستور نصب مشخص می کنیم که این موارد حضور نداشته باشن. برای شروع کار دستورات زیر را به ترتیب نوشته شده در ترمینال خود وارد کرده و اجرا کنید:
npx express-generator --no-view --git todo-app cd ./todo-app git init git add .; git commit -m "Initial commit"; در این زمان باید محتویات فایل app.js شما بدین شکل باشد: var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); module.exports = app;
این ساختار اولیه برنامه express.js ما است. حالا باید کتابخانه OpenAPI را به پروژه اضافه کنیم. برای این کار ابتدا باید آن را نصب کنیم:
npm i express-openapi -s
در مرحله بعدی به فایل app.js رفته و کدهای زیر را اضافه می کنیم:
// ./app.js ... app.listen(3030); ... // OpenAPI routes initialize({ app, apiDoc: require("./api/api-doc"), paths: "./api/paths", }); module.exports = app;
با انجام این کار از OpenAPI نمونه سازی کرده ایم. در مرحله بعدی schema را برای هر todo در مسیر api/api-doc.js اضافه می کنیم:
// ./api/api-doc.js const apiDoc = { swagger: "2.0", basePath: "/", info: { title: "Todo app API.", version: "1.0.0", }, definitions: { Todo: { type: "object", properties: { id: { type: "number", }, message: { type: "string", }, }, required: ["id", "message"], }, }, paths: {}, }; module.exports = apiDoc;
شیء apiDoc ساختار documentation برای API شما را مشخص می کند. من در اینجا توضیح داده ام که documentation من چه ساختاری خواهد داشت و نوع هر todo را مشخص کرده ام. شما می توانید کدهای کامل پروژه را از فایلی که قرار داده ام دانلود کنید. در مرحله بعدی باید route handler هایی را برای مسیرهایمان تعریف کنیم. هر کدام از این route hanlder ها متد های پشتیبانی شده را تعریف می کنند، callback های هر عملیات را مشخص می کنند و نهایتا apiDoc یا همان schema را نیز می خواهند. من این کار را در مسیر api/paths/todos/index.js انجام می دهم:
// ./api/paths/todos/index.js module.exports = function () { let operations = { GET, POST, PUT, DELETE, }; function GET(req, res, next) { res.status(200).json([ { id: 0, message: "First todo" }, { id: 1, message: "Second todo" }, ]); } function POST(req, res, next) { console.log(`About to create todo: ${JSON.stringify(req.body)}`); res.status(201).send(); } function PUT(req, res, next) { console.log(`About to update todo id: ${req.query.id}`); res.status(200).send(); } function DELETE(req, res, next) { console.log(`About to delete todo id: ${req.query.id}`); res.status(200).send(); } GET.apiDoc = { summary: "Fetch todos.", operationId: "getTodos", responses: { 200: { description: "List of todos.", schema: { type: "array", items: { $ref: "#/definitions/Todo", }, }, }, }, }; POST.apiDoc = { summary: "Create todo.", operationId: "createTodo", consumes: ["application/json"], parameters: [ { in: "body", name: "todo", schema: { $ref: "#/definitions/Todo", }, }, ], responses: { 201: { description: "Created", }, }, }; PUT.apiDoc = { summary: "Update todo.", operationId: "updateTodo", parameters: [ { in: "query", name: "id", required: true, type: "string", }, { in: "body", name: "todo", schema: { $ref: "#/definitions/Todo", }, }, ], responses: { 200: { description: "Updated ok", }, }, }; DELETE.apiDoc = { summary: "Delete todo.", operationId: "deleteTodo", consumes: ["application/json"], parameters: [ { in: "query", name: "id", required: true, type: "string", }, ], responses: { 200: { description: "Delete", }, }, }; return operations; };
بخش summary خلاصه کار یک endpoint را مشخص می کند. بخش consumes نوع محتوای دریافتی توسط یک endpoint خاص را مشخص می کند. parameters نیز پارامترهای لازم برای انجام عملیات در آن endpoint را توضیح می دهد. بخش response به کاربر می گوید که اگر کار خود را درست انجام داده باشد چه نتیجه ای را دریافت خواهد کرد. در صورتی که می خواهید با OpenAPI بیشتر کار کنید می توانید به documentation آن مراجعه کرده و آن را مطالعه کنید. حالا به یک UI نیاز داریم تا documentation ما را به صورت خودکار بسازد. برای این کار از پکیج swagger-ui-express استفاده می کنیم:
npm i swagger-ui-express -s
حالا به app.js می رویم و مسیر این UI را نیز مشخص می کنیم:
// ./app.js ... // OpenAPI UI app.use( "/api-documentation", swaggerUi.serve, swaggerUi.setup(null, { swaggerOptions: { url: "http://localhost:3030/api-docs", }, }) ); module.exports = app; در حال حاضر محتوای کامل فایل app.js شما باید شبیه به کد زیر باشد: var express = require("express"); var path = require("path"); var cookieParser = require("cookie-parser"); var logger = require("morgan"); var { initialize } = require("express-openapi"); var swaggerUi = require("swagger-ui-express"); var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); var app = express(); app.listen(3030); app.use(logger("dev")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); app.use("/", indexRouter); app.use("/users", usersRouter); // OpenAPI routes initialize({ app, apiDoc: require("./api/api-doc"), paths: "./api/paths", }); // OpenAPI UI app.use( "/api-documentation", swaggerUi.serve, swaggerUi.setup(null, { swaggerOptions: { url: "http://localhost:3030/api-docs", }, }) ); module.exports = app;
حالا اگر برنامه خود را در مسیر http://localhost:3030/api-docs باز کنید می توانید انواع تست ها و تایپ ها و کلاینت های جدید را بسازید! همچنین documentation شما در آدرس http://localhost:3030/api-documentation به شکل زیر آماده خواهد بود:
همانطور که می بینید ظاهر این برنامه بسیار زیبا و بدون مشکل است. به همین راحتی برنامه ای داریم که به صورت کامل با OpenAPI سازگار است. دوباره تکرار می کنم، در صورتی که می خواهید کدهای کامل برنامه را مطالعه کنید می توانید آن ها را از این لینک دانلود کنید. امیدوارم این توضیحات به درک بهتر شما از OpenAPI کمک کرده باشد اما یادتان باشد که این مقاله فقط مقدمه ای به OpenAPI و Swagger بود و در صورتی که می خواهید به طور جدی با این تکنولوژی ها کار کنید مطالعه documentation آن ها ضروری خواهد بود.
منبع: وب سایت freecodecamp
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.