حل مشکلات REST API با استاندارد OPEN API

?How to Build Better APIs in Express with OpenAPI

حل مشکلات REST API با استاندارد OPEN API

ساخت REST API همیشه معضل ها و چالش های خودش را دارد اما استانداردی به نام OPEN API برای توسعه این API ها وجود دارد که مشکل و سردرگمی های احتمالی شما را حل می کند. ما می خواهیم در این مقاله ابتدا با چالش های طراحی REST API آشنا شده و سپس به معرفی OPEN API بپردازیم. در نظر داشته باشید که ما این مقاله را با Express.js می نویسیم بنابراین آشنایی با آن لازم است.

نکته: این مقاله برای کاربران تازه کار نوشته نشده است بنابراین مباحث ساده در آن توضیح داده نمی شود.

چالش های طراحی REST API

با اینکه در این مقاله از Express.js استفاده می کنیم اما چالش های طراحی REST API اختصاصی به Express.js ندارند و با هر زبان برنامه نویسی و فریم ورکی همراه هستند. همچنین چالش های مطرح شده در این بخش به هیچ هدف خاصی اختصاص ندارند و چه API را برای یک برنامه موبایل طراحی کرده باشید یا چه آن را برای وب سایت ها یا میکروسرویس ها ساخته باشید باز هم با این چالش ها روبرو خواهید بود.

ایجاد تغییرات و به روز رسانی سخت تر است

اگر برنامه شما از REST API ها استفاده می کند، ایجاد تغییر در هر دو سمت (سمت سرور و سمت کلاینت) مشکل آسان نخواهد بود. به طور مثال ممکن است شما Endpoint خاصی را داشته باشید که نام یک کاربر را برمی گرداند اما در آینده نیاز می شود که سن کاربر را نیز برگرداند. مشکل اینجاست که برنامه شما در سمت کلاینت (چه برنامه موبایلی و چه وب سایت) انتظار دریافت چنین مقداری را ندارد و احتمال دارد به طور کامل از کار بیفتد. مثلا شما فقط نام کاربر را به صورت یک رشته برمی گرداندید اما حالا یک شیء برمی گردد که دو خصوصیت name (نام) و age (سن) را دارد. طبیعتا سمت کلاینت نیز باید به همین شکل ویرایش شود.

معمولا تیم های توسعه برای حل این مشکل شروع به نوشتن تست می کنند اما در انتهای کار باز هم نیاز است که تیم توسعه به صورت دستی اشتباهات را تصحیح کرده وسمت کلاینت را ویرایش کند. به زبان ساده با هر تغییر در API احتمال از کار افتادگی سمت کلاینت وجود دارد.

کمبود documentation به روز

تمام وب سایت هایی که از REST API استفاده می کنند باید نوعی Documentation داشته باشند. در صورتی که API شما عمومی است، documentation برای افرادی خواهد بود که می خواهند از خدمات شما استفاده کنند و اگر API شما خصوصی است Documentation برای توسعه دهندگان تیم شما است. یادتان نرود که توسعه دهندگان نیز برای کمک به پیشرفت پروژه به Documentation نیاز دارند و حتی کمپانی هایی مانند گوگل و فیسبوک نیز از این موضوع مستثنی نیستند و برای توسعه دهندگانشان documentation مخصوص دارند.

فرآیند توسعه یا به روز رسانی و ایجاد تغییرات در یک API بدین شکل است که همه چیز از سورس کد شروع می شود یعنی تیم توسعه همیشه قابلیت جدید را اضافه می کند یا تغییر جدید را ایجاد می کند و سپس آن را به documentation اضافه می کند. مسئله اینجاست که تیم توسعه به اندازه کافی درگیری دارد و با کدها درگیر است و اضافه کردن Documentation و نوشتن توضیحات برای آن ها باری اضافی و سنگین است. مهم ترین نکته منفی اینجاست که محول کردن چنین وظیفه ای به تیم توسعه باعث کاهش سرعت توسعه نیز خواهد شد. از طرف دیگر کاملا طبیعی است که بین اضافه شدن یک قابلیت یا ایجاد یک تغییر در API و اضافه کردن توضیحات آن در documentation فاصله زمانی وجود خواهد داشت. این فاصله زمانی باعث سردرگمی تیم توسعه شما نیز خواهد شد چرا که توسعه دهندگان بر اساس توضیحات documentation جلو می روند اما با خطا روبرو می شوند و زمان زیادی برای شناسایی مشکل تلف خواهد شد.

محدودیت های API های عمومی

API بسیاری از برنامه های تحت وب یا به طور کامل عمومی است و یا اینکه بخشی برای توسعه دهندگان دیگر (به صورت عمومی) دارد، مخصوصا اگر وب سایت شما سرویس و خدمات خاصی را به دیگر توسعه دهندگان ارائه کند. این API های عمومی یک محدودیت بسیار بزرگ دارند: ما نمی توانیم آن ها را با سرعت API های خصوصی به روز رسانی کنیم! چرا؟ به دلیل اینکه سرویس ها و وب سایت های زیادی از API ما استفاده می کنند و ایجاد تغییر ناگهانی در API باعث از کار افتادن تمام این وب سایت ها و سرویس ها می شوند.

معمولا برای ایجاد تغییر در API عمومی از چند ماه قبل به توسعه دهندگان عمومی هشدار داده می شود که در فلان تاریخ ساختار API تغییر خواهد کرد. حتی برخی از وب سایت ها یک نسخه قدیمی از API خود را با پسوند v2 یا v1 یا v3 و امثال آن باقی می گذارند تا همه مجبور به آپدیت برنامه هایشان نشوند!

تست های یکپارچگی

برنامه شما در طول زمان رشد می کند و تعداد کاربران شما اضافه خواهند شد. در چنین حالتی چیزی که کاربران از API شما انتظار دارند و چیزی که API شما ارائه می دهد تفاوت خواهند داشت. این مسئله در برنامه های کوچک مشکلی ندارد اما زمانی که برنامه شما رشد می کند، برای ایجاد تغییر در یک endpoint باید تاثیرات آن را در طول تمام برنامه رهگیری کنید تا قسمت دیگری از برنامه را خراب نکنید و مطمئن شوید آنچه که API ارائه می دهد همان چیزی است که کاربران انتظارش را دارند. معمولا برای حل این مشکل از تست های یکپارچگی سیستم (integration tests) استفاده می کنند اما نوشتن این تست ها به شدت زمان بر بوده و علاوه بر آن تضمیمی ندارند.

مبارزه با چالش های طراحی API

ما در بخش قبل چند چالش جدی در طراحی REST API را بررسی کردیم اما راه حل چیست؟ ما در این بخش یک API بسیار ساده را با استفاده از فریم ورک Express.js طراحی می کنیم و چالش های مطرح شده در آن را حل می کنیم.

OPEN API چیست؟

چالش هایی که در این مقاله مطالعه کردید، از قدیم الایام همراه توسعه دهندگان بوده اند و مسئله ای جدید نیستند بنابراین در حال حاضر چندین راه حل برای این مشکلات داریم. طبیعتا عاقلانه تر است که به سراغ این راه حل ها برویم تا اینکه بخواهیم خودمان راه حل جدیدی را اختراع کنیم. تمام راه حل های ارائه شده برای چالش های طراحی API در جهت استاندارد سازی آن بوده اند که به آن API Standardization گفته می شود. از بین استانداردهای ارائه شده معمولا سه استاندارد از بقیه مشهور تر هستند: RAML و JsonAPI و OpenAPI.

هدف ارائه این استانداردها یکپارچه سازی ساختار API ها (در تمام زبان ها) است تا فارغ از تکنولوژی استفاده شده، انتظارات ما از یک API و endpoint های آن همیشه یکسان باشد. این استاندارد سازی چند مزیت بسیار بزرگ دارد، آیا می توانید خودتان آن ها را پیدا کنید؟

  • رفع سردرگمی توسعه دهندگان: اگر تمام API های ما استاندارد نوشته شده باشند و همه بر اساس قرارداد خاصی API هایشان را بنویسند، توسعه دهندگان قبل از شروع پروژه های جدید با آن پروژه ها آشنا خواهند بود و نیازی نیست وقت خودشان را صرف مطالعه و درک API کنند.
  • سرعت بالای توسعه: از آنجایی که توسعه دهندگان ساختار پروژه را می دانند، سریعا دست کار شده و کار توسعه بدون وقت تلف کردن تمام خواهد شد.
  • documentation آماده: OpenAPI استانداردی برای طراحی REST API است اما Swagger ابزاری است که OPEN API را به صورت خودکار پیاده سازی می کند. یعنی چه؟ مثلا برای پروژهی ما که با Express.js نوشته خواهد شد، Swagger ابزاری است که documentation مربوط به API شما را به صورت خودکار آماده می کند! با استفاده از این ابزار دیگر نیازی به نوشتن documentation به صورت دستی نیست و طبیعتا زمان بسیار زیادی را برای خودمان حفظ می کنیم. البته در نظر داشته باشید که swagger مخصوص پکیج express.js نیست و با فریم ورک های دیگری مانند fastify نیز کار می کند.

توضیحات پروژه ما

فرض کنید ما می خواهیم یک برنامه ساده todo را بنویسیم. برنامه های todo برنامه هایی هستند که شما در آن لیستی از کار هایی که باید انجام بدهید را می نویسید تا بعدا یادتان نرود. مثلا فردا یک ساعت کتاب بخوانم، خرید های خانه را انجام بدهم، به احمد در اسباب کشی کمک کنم و غیره. در این برنامه کاربران می توانند todo های جدید را ایجاد کنند، آن ها را دریافت کنند، آن ها را ویرایش کنند و نهایتا در صورت تمایل آن ها را حذف کنند. با این حساب endpoint های شما بدین شکل خواهند بود:

  • دریافت todo از طریق درخواست GET به todos/
  • ساخت todo از طریق درخواست POST به todos/
  • ویرایش todo از طریق درخواست POST به todos/:id
  • حذف todo از طریق درخواست DELETE به todos/:id

ما با ایجاد این برنامه ساده می توانیم روش حل چالش های موجود را به صورت واقعی ببینیم.

پیاده سازی پروژه

همانطور که گفتم ما از OpenAPI استفاده خواهیم کرد بنابراین به پکیج express-openapi نیاز خواهیم داشت. البته شما می توانید از کتابخانه های دیگری نیز استفاده کنید. همچنین در نظر داشته باشید که این کتابخانه قابلیت های پیشرفته ای مانند response validation (اعتبارسنجی پاسخ های سرور) و authentication (احراز هویت) را نیز دارد اما تمرکز ما روی طراحی API است و با این قابلیت ها کاری نخواهیم داشت.

نکته: هدف ما آموزش ساخت API با Express.js نیست بنابراین من کدهای Express.js را توضیح نخواهم داد. همچنین زمانی که مسیر یک فایل را به شما می دهم و آن فایل وجود ندارد یعنی خودتان باید آن مسیر و آن فایل را بسازید. از شما انتظار می رود که با طراحی API از قبل آشنا باشید. به همین خاطر من فایل کامل پروژه را از همین ابتدا در اختیار شما قرار می دهم تا نیازی به توضیحات اضافه نباشد: دانلود پروژه کامل.

همچنین برای سرعت بخشیدن به کار از express-generator استفاده خواهیم کرد. پکیج express-generator یک boilerplate برای پروژه های Express.js است. یعنی چه؟ یعنی ساختار اولیه برنامه های Express را به سادگی برایتان آماده کرده است تا نیازی به انجام تنظیمات اولیه نداشته باشید. این پکیج در همان ابتدا چند پوشه زیر را ساخته و در اختیار شما قرار می دهد:

  • پوشه bin: فایل های اجرایی برای شروع برنامه در این قسمت قرار خواهند داشت (مثل شروع سرور روی پورت ۳۰۰۰).
  • پوشه public: محتویات این پوشه در دسترس عموم خواهد بود بنابراین محل نگهداری فایل های CSS و تصاویر و دیگر فایل های عمومی است.
  • پوشه routes: این پوشه محل تعریف و نگهداری مسیر های برنامه (routes) می باشد.
  • پوشه views: این پوشه محل فایل های html یا فایل های templating engine شما است.

البته ما به برخی از پوشه ها مانند 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 به شکل زیر آماده خواهد بود:

ظاهر Swagger و نمایش جزئیات برنامه
ظاهر Swagger و نمایش جزئیات برنامه

همانطور که می بینید ظاهر این برنامه بسیار زیبا و بدون مشکل است. به همین راحتی برنامه ای داریم که به صورت کامل با OpenAPI سازگار است. دوباره تکرار می کنم، در صورتی که می خواهید کدهای کامل برنامه را مطالعه کنید می توانید آن ها را از این لینک دانلود کنید. امیدوارم این توضیحات به درک بهتر شما از OpenAPI کمک کرده باشد اما یادتان باشد که این مقاله فقط مقدمه ای به OpenAPI و Swagger بود و در صورتی که می خواهید به طور جدی با این تکنولوژی ها کار کنید مطالعه documentation آن ها ضروری خواهد بود.


منبع: وب سایت freecodecamp

نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.