کلید حل این چالش، پیوند زدن مفاهیم اصیل برنامهنویسی شیگرا (OOP) با معماری مبتنی بر منبع (Resource-Oriented Architecture) در REST است. در این مقاله تخصصی، نحوه پیادهسازی و معماری APIهای RESTful را با تکیه بر اصول شیگرایی و اصول SOLID بررسی میکنیم.
برای درک درست موضوع، باید ابتدا تفاوت و شباهت بین «شی» (Object) و «منبع» (Resource) را شفاف کنیم:
در شیگرایی (OOP): شیء ترکیبی از داده (State) و رفتار (Behavior) است که عبارات و قوانین کسبوکار (Business Rules) را کپسوله میکند.
در REST: منبع (Resource) یک مفهوم انتزاعی از یک داده یا مجموعه داده است که از طریق یک URI مشخص شناخته شده و با پروتکل HTTP دستکاری میشود.
چالش اصلی چیست؟
بزرگترین اشتباه در طراحی RESTful API، یکسان فرض کردن موجودیتهای دیتابیس (Entities)، اشیاء دامنه (Domain Objects) و منابع API (API Resources) است.
افشای مستقیم اشیاء دامنه در قالب API، اصول کپسولهسازی را نقض کرده و مشتریان API (Clientها) را به ساختار داخلی سیستم وابسته میسازد.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ API Client │ <---> │ API Resource │ <---> │ Domain Object │
│ (JSON Payload) │ │ (DTO) │ │ (Encapsulated) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
۱. کپسولهسازی (Encapsulation)
کپسولهسازی به معنای مخفیسازی جزئیات پیادهسازی و حفاظت از وضعیت (State) سیستم است.
عدم افشای مدلهای دیتابیس: هرگز نباید موجودیتهای ORM (مانند Entity Framework یا Hibernate) را مستقیماً از طریق API ورودی یا خروجی داد. در عوض از Data Transfer Objects (DTOs) استفاده کنید.
کنترل تغییر وضعیت از طریق رفتارها (Behaviors): به جای اینکه کل شیء را از طریق متد PUT در اختیار کلاینت بگذارید تا هر ستونی را تغییر دهد، عملیاتهای مشخص کسبوکار را مدلسازی کنید (مثلاً استفاده از PATCH یا Endpointهای شبهرفتاری مثل /orders/{id}/cancel).
۲. انتزاع (Abstraction)
انتزاع یعنی نمایش رفتارهای ضروری و پنهانسازی پیچیدگیهای غیرضروری.
قراردادهای شفاف (API Contracts): استفاده از OpenAPI / Swagger برای تعریف واضح ورودیها و خروجیها بدون اینکه کلاینت بداند زیر ساختار سرور از چه دیتابیس یا زبان برنامهنویسی استفاده میکند.
انتزاع خطاهای داخلی: بازگرداندن پاسخهای استاندارد خطا (مانند RFC 7807 Problem Details) به جای افشای Exceptionها و StackTraceهای داخلی.
۳. چندریختی (Polymorphism)
چندریختی در REST به کلاینت اجازه میدهد با اشیاء از انواع مختلف، از طریق یک رابط یکسان تعامل داشته باشد.
پاسخهای چندریختی (Polymorphic Payloads): فرض کنید سیستم پرداختی دارید که انواع مختلف روشهای پرداخت (کارت اعتباری، کیف پول، کریپتو) را پشتیبانی میکند. API باید بتواند پاسخهای متفاوتی بر اساس نوع (Type Discriminator) تولید کند.
// GET /api/v1/payments/101
{
"paymentId": "101",
"amount": 250.00,
"type": "CreditCard",
"details": {
"cardNumberMasked": "****-****-****-1234",
"gateway": "Stripe"
}
}
۴. وراثت (Inheritance) و ترکیب (Composition)
در سطح API، ترکیب بر وراثت ارجحیت دارد (Prefer Composition over Inheritance).
وراثت عمیق در ساختار JSON باعث پیچیدگی شدید در Serialization/Deserialization میشود.
به جای تعریف Hierarchyهای پیچیده در API، از ترکیب (Composition) و ساختارهای مسطح (Flat Payload) استفاده کنید.
اصول پنجگانه SOLID سنگ بنای شیگرایی تمیز هستند. بیایید نحوه اعمال این اصول را در لایه API بررسی کنیم:
۱. اصل مسئولیت واحد (Single Responsibility Principle - SRP)
یک Controller یا API Endpoint باید تنها یک دلیل برای تغییر داشته باشد.
اشتباه رایج: ایجاد یک God Controller (مانند SystemController) که تمام عملیاتهای کاربران، گزارشها، فایلها و تنظیمات را مدیریت میکند.
رویکرد صحیح: تفکیک Controllerها بر اساس منابع کوچک و مستقل (مثلاً OrderQueryController و OrderCommandController در الگوی CQRS).
۲. اصل باز/بسته (Open/Closed Principle - OCP)
کدها و APIها باید برای توسعه باز و برای تغییر بسته باشند.
در سطح API، تغییر نادرتطبیق (Breaking Change) نباید کلاینتهای موجود را از کار بیندازد.
استراتژی Versioning: استفاده از Versioning در URI یا Headerها (/api/v1/products) به شما اجازه میدهد رفتارهای جدید را اضافه کنید (توسعه) بدون اینکه کلاینتهای قدیمی آسیب ببینند (بسته بودن در برابر تغییر).
۳. اصل جانشینی لیسکوف (Liskov Substitution Principle - LSP)
اشیاء کلاسهای فرعی باید بتوانند جایگزین کلاسهای اصلی شوند بدون اینکه خللی در برنامه ایجاد شود.
در API: اگر ساختار پلیمورفیک دارید، تمامی Sub-Resourceها باید به قرارداد کلانی که ارائه دادهاند متعهد باشند.
مثال: اگر endpoint خروجی لیست پرداختیها را میدهد، تمامی متدهای پرداخت باید فیلدهای پایه (id, amount, status) را به طور یکسان و معتبر برگردانند.
۴. اصل تفکیک رابطها (Interface Segregation Principle - ISP)
کلاینتها نباید مجبور شوند به متدها یا دادههایی وابسته شوند که از آنها استفاده نمیکنند.
عدم ارسال دادههای زائد: اگر یک کلاینت موبایل فقط به نام و عکس کاربر نیاز دارد، نباید یک DTO شامل ۵۰ فیلد پروفایل، تنظیمات امنیتی و تاریخچه تراکنشها به او برگردانده شود.
راهکار: استفاده از DTOهای مختص به سناریو (Read Models)، یا پشتیبانی از Sparse Fieldsets (انتخاب فیلدها توسط کلاینت) یا استفاده از الگوی BFF (Backend for Frontend).
۵. اصل وارونگی وابستگی (Dependency Inversion Principle - DIP)
ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند؛ هر دو باید به انتزاع وابسته باشند.
API Controller نباید مستقیماً به دیتابیس، ORM یا کدهای لایه Data Access متصل باشد.
Controller باید تنها به انتزاعها (Interfaces / Use Cases) وابسته باشد.
برای تجسم بهتر، پیادهسازی یک Endpoint ثبت سفارش را در یک محیط شیگرا (با استفاده از زبان C#) بررسی میکنیم.
۱. تعریف Domain Entity (حفظ کپسولهسازی)
public class Order
{
public Guid Id { get; private set; }
public Guid CustomerId { get; private set; }
private readonly List<OrderItem> _items = new();
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
public OrderStatus Status { get; private set; }
// Constructor کپسولهشده
public Order(Guid customerId)
{
Id = Guid.NewGuid();
CustomerId = customerId;
Status = OrderStatus.Pending;
}
// رفتارDomain به جای Setterهای عمومی
public void AddItem(Guid productId, int quantity, decimal unitPrice)
{
if (quantity <= 0)
throw new DomainException("تعداد کالا باید بزرگتر از صفر باشد.");
_items.Add(new OrderItem(productId, quantity, unitPrice));
}
}
۲. تعریف DTOها (اصل تفکیک و کپسولهسازی API)
// ورودی خالص API - بدون منطق دیتابیس
public record CreateOrderRequest(
Guid CustomerId,
List<OrderItemDto> Items
);
public record OrderItemDto(
Guid ProductId,
int Quantity
);
// خروجی کنترلشده API
public record OrderResponse(
Guid OrderId,
string Status,
decimal TotalAmount
);
۳. کنترلر API (رعایت DIP و SRP)
[ApiController]
[Route("api/v1/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService; // وابسته به انتزاع
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
// نگاشت DTO ورودی به لایه سرویس/دامنه
var result = await _orderService.CreateOrderAsync(request);
if (!result.IsSuccess)
{
return BadRequest(new ProblemDetails
{
Title = "خطا در ثبت سفارش",
Detail = result.ErrorMessage
});
}
return CreatedAtAction(
nameof(GetOrderById),
new { id = result.Value.OrderId },
result.Value
);
}
}
|
اشتباه رایج |
پیامد منفی |
راهکار اصلاحی شیگرا |
|
Anemic Domain Model (مدلهای کمخون بدون رفتار) |
تبدیل سرویس به اسکریپتهای رویهای (Procedural) |
انتقال منطق کسبوکار به داخل Entityها و Value Objectها |
|
Exposing Entities directly |
افشای دیتابیس و شکستن Encapsulation |
استفاده از DTOها و AutoMapper/Mapster |
|
RPC-style Endpoints (/POST /updateUserAddress) |
نقض معماری منبعمحور REST |
استفاده از متدهای استاندارد HTTP یا تعریف منبع فرعی (/PUT /users/{id}/address) |
|
Tightly Coupled Controllers |
تستناپذیری و سخت شدن تغییرات |
تزریق وابستگی (DI) و استفاده از الگوی Mediator |
طراحی APIهای RESTful موفق نیازمند نگاهی فراتر از صرفاً تعریف مسیرها (Routes) و متدهای HTTP است. زمانی که اصول شیگرایی (Encapsulation, Abstraction, Polymorphism) و قوانین SOLID را در لایهبندی API خود به کار میگیرید:
API پایدارتر میشود: تغییرات داخلی دیتابیس یا منطق کسبوکار، کلاینتها را خراب نمیکند.
کد قابل تستتر میشود: لایهها از هم جدا شده و Mock کردن وابستگیها ساده میشود.
توسعهپذیری افزایش مییابد: افزودن ویژگیهای جدید بدون دستکاری کدهای قبلی (بر اساس اصل OCP) امکانپذیر خواهد بود.
در نهایت، API شما تنها رابط ارتباطی سیستم با دنیای بیرون نیست؛ بلکه بازتابی از کیفیت معماری داخلی و تفکر شیگرایانه شماست.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.