Kotlin: شرح تصميم مشاريع كوتلن
آخر تحديث: 2026-08-26
جميع الدروس الأربعة والعشرون تتلاقى هنا — يبدأ Charlie من تحليل المتطلبات ويصمم نموذج نطاق OrderProcessor، والبنية المعمارية الطبقية، واختيار مجموعة التقنيات، وعقود واجهة برمجة التطبيقات. هذه هي الخطوة الأولى للمشروع الحقيقي.
1. ما ستتعلمه
- تحليل المتطلبات ونمذجة النطاق: آلة حالة الطلبات، مصدر الأحداث
- البنية المعمارية الطبقية: وحدة تحكم ← خدمة ← مستودع ← نطاق
- مجموعة التقنيات: Spring Boot + كوروتينات + kotlinx.serialization + عميل Ktor
- تصميم API: تخطيط نقاط نهاية RESTful + توثيق OpenAPI
- تطبيق Charlie: مخطط بنية OrderProcessor ومخطط قاعدة البيانات
2. قصة مهندس معمارية حقيقي
(1) المشكلة: التصميم بدون خطة يضمن إعادة العمل
فريق Charlie تخطى التصميم وبدأ البرمجة مباشرة مرة واحدة. بعد ثلاثة أشهر، نموذج النطاق لم يتطابق مع متطلبات العمل — أُعيد كتابة 60% من الكود. الخسارة: 3 أشهر تطوير + شهران إعادة عمل.
(2) نهج التصميم أولاً
TEXT
📖 للعرض فقط
الأسبوع 1: المتطلبات ← نموذج النطاق ← آلة الحالة
الأسبوع 2: البنية المعمارية ← مجموعة التقنيات ← عقد API
الأسبوع 3: مخطط قاعدة البيانات ← حدود الوحدات ← خطة CI/CD
الأسبوع 4+: التطوير (بمخطط واضح)
استثمر 3 أسابيع في التصميم، قلل إعادة العمل بنسبة 60%. "التطوير السريع" بدون تصميم هو أبطأ طريق.
3. تحليل المتطلبات ونمذجة النطاق
(1) المتطلبات الأساسية
| المتطلب | الوصف | الأولوية |
|---|---|---|
| إنشاء الطلب | العميل يضع طلبًا، يُولّد طلبًا معلقًا | P0 |
| دفع الطلب | تأكيد الطلب بعد دفع ناجح | P0 |
| شحن الطلب | المستودع يشحن، يُولّد رقم تتبع | P0 |
| إلغاء الطلب | العميل/النظام يلغي الطلب، يُطلق استرداد | P0 |
| توجيه الطلبات عالية القيمة | الطلبات > 10,000 دولار تمر عبر خط VIP | P1 |
| المعالجة الدفعية | دعم إنتاجية 5,000 طلب/ثانية | P1 |
| مصدر الأحداث | تغييرات حالة الطلب تُنتج أحداث نطاق | P2 |
(2) نموذج النطاق
KOTLIN
// كيانات النطاق الأساسية
data class Order(
val id: String,
val total: Double,
var status: OrderStatus,
val customerId: String,
val items: List<OrderItem>,
val createdAt: String,
var updatedAt: String?
)
data class OrderItem(val sku: String, val quantity: Int, val unitPrice: Double)
sealed class OrderStatus {
object Pending : OrderStatus()
data class Processing(val step: Int) : OrderStatus()
object Paid : OrderStatus()
object Shipped : OrderStatus()
object Delivered : OrderStatus()
data class Cancelled(val reason: String) : OrderStatus()
}
sealed class OrderEvent {
data class Created(val orderId: String, val total: Double) : OrderEvent()
data class PaymentReceived(val orderId: String, val amount: Double) : OrderEvent()
data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}
(3) آلة حالة الطلب
stateDiagram-v2
[*] --> Pending: إنشاء طلب
Pending --> Processing: بدء المعالجة
Pending --> Cancelled: إلغاء العميل
Processing --> Paid: نجاح الدفع
Processing --> Cancelled: فشل الدفع
Paid --> Shipped: شحن الطلب
Shipped --> Delivered: تأكيد التسليم
Delivered --> [*]
Cancelled --> [*]
4. البنية المعمارية الطبقية
(1) بنية من أربع طبقات
flowchart TD
A[طبقة وحدة التحكم<br/>REST API / التحقق من الطلبات] --> B[طبقة الخدمة<br/>منطق الأعمال / آلة الحالة]
B --> C[طبقة المستودع<br/>الوصول للبيانات / الاستمرارية]
B --> D[طبقة التكامل<br/>استدعاءات API خارجية]
C --> E[(قاعدة البيانات)]
D --> F[خدمة الدفع]
D --> G[خدمة المخزون]
(2) مسؤوليات الطبقات
| الطبقة | المسؤولية | التقنية الأساسية |
|---|---|---|
| وحدة التحكم | معالجة طلبات HTTP، التحقق، الاستجابة | Spring WebFlux، suspend |
| الخدمة | منطق الأعمال، آلة الحالة، نشر الأحداث | الكوروتينات، sealed class |
| المستودع | استمرارية البيانات، الاستعلامات | R2DBC، JPA |
| التكامل | استدعاءات الخدمات الخارجية | عميل Ktor، Resilience4j |
(3) هيكل وحدات المشروع
TEXT
📖 للعرض فقط
order-processor/
├── build.gradle.kts
├── src/main/kotlin/com/order/
│ ├── Application.kt # نقطة دخول Spring Boot
│ ├── controller/
│ │ └── OrderController.kt # نقاط نهاية REST
│ ├── service/
│ │ ├── OrderService.kt # منطق الأعمال
│ │ └── OrderStateMachine.kt # انتقالات الحالة
│ ├── repository/
│ │ ├── OrderRepository.kt # الوصول للبيانات
│ │ └── EventRepository.kt # مخزن الأحداث
│ ├── integration/
│ │ ├── PaymentClient.kt # API الدفع
│ │ └── InventoryClient.kt # API المخزون
│ ├── domain/
│ │ ├── Order.kt # الكيان
│ │ ├── OrderStatus.kt # تعداد الحالة
│ │ └── OrderEvent.kt # أحداث النطاق
│ ├── config/
│ │ └── AppConfig.kt # التهيئة
│ └── exception/
│ └── OrderExceptions.kt # استثناءات مخصصة
└── src/test/kotlin/com/order/
└── ... # الاختبارات
5. اختيار مجموعة التقنيات
| النطاق | الاختيار التقني | السبب |
|---|---|---|
| إطار العمل | Spring Boot 3.2 + WebFlux | نظام بيئي ناضج، دعم الكوروتينات |
| اللغة | كوتلن 1.9 | أمان null، الكوروتينات، فئة data |
| غير متزامن | kotlinx.coroutines | تزامن مهيكل، suspend |
| التسلسل | kotlinx.serialization | أمان وقت الترجمة، تنسيقات متعددة |
| قاعدة البيانات | PostgreSQL + R2DBC | مشغل تفاعلي، صديق للكوروتينات |
| عميل HTTP | عميل Ktor | أصلي لكوتلن، دعم الكوروتينات |
| التخزين المؤقت | Redis + kotlinx.coroutines | تخزين مؤقت غير متزامن |
| الاختبار | JUnit 5 + MockK | محاكاة أصيلة لكوتلن |
| المراقبة | Micrometer + Prometheus | جمع المقاييس |
6. تصميم API
(1) نقاط نهاية RESTful
KOTLIN
// نقاط نهاية API
// POST /api/v1/orders - إنشاء طلب
// GET /api/v1/orders - قائمة الطلبات (مع ترقيم الصفحات)
// GET /api/v1/orders/{id} - الحصول على طلب بالمعرف
// PATCH /api/v1/orders/{id}/status - تحديث حالة الطلب
// DELETE /api/v1/orders/{id} - إلغاء طلب
// GET /api/v1/orders/{id}/events - الحصول على سجل أحداث الطلب
(2) DTOs الطلب/الاستجابة
KOTLIN
// طلب
data class CreateOrderRequest(
val customerId: String,
val items: List<CreateOrderItemRequest>,
val shippingAddress: AddressRequest?
)
data class CreateOrderItemRequest(
val sku: String,
val quantity: Int,
val unitPrice: Double
)
// استجابة
data class OrderResponse(
val id: String,
val total: Double,
val status: String,
val customerId: String,
val items: List<OrderItemResponse>,
val createdAt: String,
val updatedAt: String?
)
// خطأ
data class ErrorResponse(
val code: String,
val message: String,
val details: Map<String, String>? = null
)
(3) مبادئ تصميم API
| المبدأ | الممارسة |
|---|---|
| RESTful | أسماء الموارد + دلالات طرق HTTP |
| الإصدارات | بادئة /api/v1/ |
| ترقيم الصفحات | ?page=0&size=20 |
| التصفية | ?status=CONFIRMED&customer=C001 |
| HATEOAS | تضمين روابط ذات صلة في الاستجابة (اختياري) |
| تنسيق الخطأ | ErrorResponse موحد |
7. مخطط قاعدة البيانات
SQL
CREATE TABLE orders (
id VARCHAR(20) PRIMARY KEY,
customer_id VARCHAR(20) NOT NULL,
total DECIMAL(12,2) NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP
);
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id VARCHAR(20) NOT NULL REFERENCES orders(id),
sku VARCHAR(50) NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL
);
CREATE TABLE order_events (
id SERIAL PRIMARY KEY,
order_id VARCHAR(20) NOT NULL,
event_type VARCHAR(30) NOT NULL,
payload JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_events_order ON order_events(order_id);
8. مثال كامل: وثيقة تصميم بنية OrderProcessor
KOTLIN
// ============================================
// OrderProcessor - تصميم البنية المعمارية
// الميزة: مخطط معماري كامل
// ============================================
// --- طبقة النطاق ---
data class OrderItem(val sku: String, val qty: Int, val unitPrice: Double) {
val subtotal: Double get() = qty * unitPrice
}
sealed class OrderStatus {
object Pending : OrderStatus()
object Confirmed : OrderStatus()
object Shipped : OrderStatus()
object Delivered : OrderStatus()
data class Cancelled(val reason: String) : OrderStatus()
}
sealed class OrderEvent {
data class Created(val orderId: String, val total: Double, val customerId: String) : OrderEvent()
data class Confirmed(val orderId: String) : OrderEvent()
data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
data class Delivered(val orderId: String) : OrderEvent()
data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}
data class Order(
val id: String,
val total: Double,
var status: OrderStatus,
val customerId: String,
val items: List<OrderItem>
)
// --- آلة الحالة ---
object OrderStateMachine {
fun transition(current: OrderStatus, event: OrderEvent): OrderStatus = when {
current is OrderStatus.Pending && event is OrderEvent.Created -> OrderStatus.Pending
current is OrderStatus.Pending && event is OrderEvent.Confirmed -> OrderStatus.Confirmed
current is OrderStatus.Confirmed && event is OrderEvent.Shipped -> OrderStatus.Shipped
current is OrderStatus.Shipped && event is OrderEvent.Delivered -> OrderStatus.Delivered
current is OrderStatus.Pending && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason)
current is OrderStatus.Confirmed && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason)
else -> throw IllegalStateException("انتقال غير صالح: $current + $event")
}
}
// --- ملخص البنية المعمارية ---
fun main() {
println("=== تصميم بنية OrderProcessor ===\n")
println("مجموعة التقنيات:")
println(" إطار العمل: Spring Boot 3.2 + WebFlux")
println(" اللغة: كوتلن 1.9")
println(" غير متزامن: kotlinx.coroutines")
println(" قاعدة البيانات: PostgreSQL + R2DBC")
println(" التخزين المؤقت: Redis")
println(" HTTP: عميل Ktor")
println(" الاختبار: JUnit 5 + MockK")
println(" المراقبة: Micrometer + Prometheus")
println("\nآلة حالة الطلب:")
val order = Order("ORD-001", 299.99, OrderStatus.Pending, "CUST-001", emptyList())
val confirmed = OrderStateMachine.transition(order.status, OrderEvent.Confirmed("ORD-001"))
println(" Pending + Confirmed = $confirmed")
val shipped = OrderStateMachine.transition(confirmed, OrderEvent.Shipped("ORD-001", "TRK-ABC"))
println(" Confirmed + Shipped = $shipped")
val delivered = OrderStateMachine.transition(shipped, OrderEvent.Delivered("ORD-001"))
println(" Shipped + Delivered = $delivered")
println("\nنقاط نهاية API:")
println(" POST /api/v1/orders - إنشاء طلب")
println(" GET /api/v1/orders - قائمة الطلبات")
println(" GET /api/v1/orders/{id} - الحصول على طلب")
println(" PATCH /api/v1/orders/{id}/status - تحديث الحالة")
println(" DELETE /api/v1/orders/{id} - إلغاء طلب")
println(" GET /api/v1/orders/{id}/events - سجل الأحداث")
}
الإخراج:
TEXT
📖 للعرض فقط
=== تصميم بنية OrderProcessor ===
مجموعة التقنيات:
إطار العمل: Spring Boot 3.2 + WebFlux
اللغة: كوتلن 1.9
غير متزامن: kotlinx.coroutines
قاعدة البيانات: PostgreSQL + R2DBC
التخزين المؤقت: Redis
HTTP: عميل Ktor
الاختبار: JUnit 5 + MockK
المراقبة: Micrometer + Prometheus
آلة حالة الطلب:
Pending + Confirmed = Confirmed
Confirmed + Shipped = Shipped
Shipped + Delivered = Delivered
نقاط نهاية API:
POST /api/v1/orders - إنشاء طلب
GET /api/v1/orders - قائمة الطلبات
GET /api/v1/orders/{id} - الحصول على طلب
PATCH /api/v1/orders/{id}/status - تحديث الحالة
DELETE /api/v1/orders/{id} - إلغاء طلب
GET /api/v1/orders/{id}/events - سجل الأحداث
9. أمثلة عملية سريعة
▶ مثال: تصميم Domain Model
KOTLIN
// نمط DDD: Aggregates, Entities, Value Objects
// Aggregate Root: Order
data class Order(
val id: OrderId, // Value Object
val customerId: CustomerId, // Value Object
val items: List<OrderItem>, // Entities
val status: OrderStatus, // Value Object (enum)
val createdAt: Instant,
val updatedAt: Instant
) {
init {
require(items.isNotEmpty()) { "Order must have at least one item" }
require(items.size <= 100) { "Order cannot have more than 100 items" }
}
fun total(): Money = items.fold(Money.ZERO) { acc, item -> acc + item.subtotal() }
fun confirm(): Order {
require(status == OrderStatus.PENDING) { "Only PENDING orders can be confirmed" }
return copy(status = OrderStatus.CONFIRMED, updatedAt = Instant.now())
}
fun cancel(): Order {
require(status in setOf(OrderStatus.PENDING, OrderStatus.CONFIRMED)) {
"Cannot cancel a ${status} order"
}
return copy(status = OrderStatus.CANCELLED, updatedAt = Instant.now())
}
fun ship(): Order {
require(status == OrderStatus.CONFIRMED) { "Only CONFIRMED orders can be shipped" }
return copy(status = OrderStatus.SHIPPED, updatedAt = Instant.now())
}
}
// Value Objects
@JvmInline
value class OrderId(val value: String) {
init { require(value.matches(Regex("^ORD-\\d{6}$"))) { "Invalid OrderId: $value" } }
}
@JvmInline
value class CustomerId(val value: String) {
init { require(value.isNotBlank()) }
}
enum class OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }
data class OrderItem(
val productId: String,
val quantity: Int,
val unitPrice: Money
) {
init { require(quantity > 0) }
fun subtotal(): Money = unitPrice * quantity
}
@JvmInline
value class Money(val cents: Long) : Comparable<Money> {
operator fun plus(other: Money) = Money(cents + other.cents)
operator fun times(qty: Int) = Money(cents * qty)
override fun compareTo(other: Money) = cents.compareTo(other.cents)
companion object {
val ZERO = Money(0)
}
}
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Repository Pattern
KOTLIN
// Repository interface in domain layer
interface OrderRepository {
suspend fun save(order: Order): Order
suspend fun findById(id: OrderId): Order?
suspend fun findByCustomer(customerId: CustomerId): List<Order>
suspend fun findByStatus(status: OrderStatus): List<Order>
suspend fun delete(id: OrderId): Boolean
fun observeAll(): Flow<Order> // للوقت الحقيقي
}
// تطبيق باستخدام JPA
@Repository
class JpaOrderRepository(
private val jpaRepo: JpaOrderEntityRepository,
private val mapper: OrderMapper
) : OrderRepository {
override suspend fun save(order: Order): Order {
val entity = mapper.toEntity(order)
val saved = jpaRepo.save(entity)
return mapper.toDomain(saved)
}
override suspend fun findById(id: OrderId): Order? =
jpaRepo.findById(id.value)
.map { mapper.toDomain(it) }
.orElse(null)
override suspend fun findByCustomer(customerId: CustomerId): List<Order> =
jpaRepo.findByCustomerId(customerId.value)
.map { mapper.toDomain(it) }
override suspend fun findByStatus(status: OrderStatus): List<Order> =
jpaRepo.findByStatus(status.name)
.map { mapper.toDomain(it) }
override suspend fun delete(id: OrderId): Boolean {
return if (jpaRepo.existsById(id.value)) {
jpaRepo.deleteById(id.value)
true
} else false
}
override fun observeAll(): Flow<Order> =
jpaRepo.findAll().map { mapper.toDomain(it) }.asFlow()
}
// في الذاكرة للاختبارات
class InMemoryOrderRepository : OrderRepository {
private val store = mutableMapOf<OrderId, Order>()
override suspend fun save(order: Order): Order {
store[order.id] = order
return order
}
override suspend fun findById(id: OrderId): Order? = store[id]
override suspend fun findByCustomer(customerId: CustomerId): List<Order> =
store.values.filter { it.customerId == customerId }
override suspend fun findByStatus(status: OrderStatus): List<Order> =
store.values.filter { it.status == status }
override suspend fun delete(id: OrderId): Boolean = store.remove(id) != null
override fun observeAll(): Flow<Order> = store.values.asFlow()
}
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Application Service
KOTLIN
@Service
class OrderApplicationService(
private val orderRepo: OrderRepository,
private val eventPublisher: EventPublisher
) {
suspend fun placeOrder(command: PlaceOrderCommand): Order {
// 1. بناء Aggregate
val order = Order(
id = OrderId.generate(),
customerId = CustomerId(command.customerId),
items = command.items.map {
OrderItem(it.productId, it.quantity, Money(it.unitPriceCents))
},
status = OrderStatus.PENDING,
createdAt = Instant.now(),
updatedAt = Instant.now()
)
// 2. حفظ
val saved = orderRepo.save(order)
// 3. نشر حدث
eventPublisher.publish(OrderPlacedEvent(saved.id, saved.total()))
return saved
}
suspend fun confirmOrder(orderId: OrderId): Order {
val order = orderRepo.findById(orderId)
?: throw OrderNotFoundException(orderId)
val confirmed = order.confirm()
val saved = orderRepo.save(confirmed)
eventPublisher.publish(OrderConfirmedEvent(saved.id))
return saved
}
suspend fun cancelOrder(orderId: OrderId): Order {
val order = orderRepo.findById(orderId)
?: throw OrderNotFoundException(orderId)
val cancelled = order.cancel()
val saved = orderRepo.save(cancelled)
eventPublisher.publish(OrderCancelledEvent(saved.id))
return saved
}
}
data class PlaceOrderCommand(
val customerId: String,
val items: List<OrderItemDto>
) {
data class OrderItemDto(
val productId: String,
val quantity: Int,
val unitPriceCents: Long
)
}
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Event-driven architecture
KOTLIN
// Domain Event base
interface DomainEvent {
val occurredAt: Instant
val aggregateId: String
}
data class OrderPlacedEvent(
val orderId: OrderId,
val total: Money,
override val occurredAt: Instant = Instant.now()
) : DomainEvent {
override val aggregateId: String get() = orderId.value
}
data class OrderConfirmedEvent(
val orderId: OrderId,
override val occurredAt: Instant = Instant.now()
) : DomainEvent {
override val aggregateId: String get() = orderId.value
}
// Event Publisher interface
interface EventPublisher {
suspend fun publish(event: DomainEvent)
}
// تطبيق In-Memory
@Service
class InMemoryEventPublisher : EventPublisher {
private val handlers = mutableMapOf<Class<*>, MutableList<suspend (Any) -> Unit>>()
override suspend fun publish(event: DomainEvent) {
handlers[event::class.java]?.forEach { it(event) }
}
fun <T : DomainEvent> subscribe(type: Class<T>, handler: suspend (T) -> Unit) {
handlers.getOrPut(type) { mutableListOf() }.add { handler(it as T) }
}
}
// Event Handler
@Service
class OrderEventHandler(
private val emailService: EmailService,
private val analyticsService: AnalyticsService
) {
fun registerHandlers(publisher: InMemoryEventPublisher) {
publisher.subscribe(OrderPlacedEvent::class.java) { event ->
emailService.sendOrderConfirmation(event.orderId)
analyticsService.trackOrderPlaced(event.total)
}
publisher.subscribe(OrderConfirmedEvent::class.java) { event ->
analyticsService.trackOrderConfirmed(event.orderId)
}
}
}
interface EmailService {
suspend fun sendOrderConfirmation(orderId: OrderId)
}
interface AnalyticsService {
suspend fun trackOrderPlaced(total: Money)
suspend fun trackOrderConfirmed(orderId: OrderId)
}
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: REST API versioning
KOTLIN
// v1 - الإصدار الحالي
@RestController
@RequestMapping("/api/v1/orders")
class OrderControllerV1(private val service: OrderApplicationService) {
@GetMapping
suspend fun list(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "20") size: Int
): Page<OrderResponseV1> {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
suspend fun create(@Valid @RequestBody req: CreateOrderRequestV1): OrderResponseV1 {
// ...
}
}
// v2 - إضافة حقول جديدة بدون كسر v1
@RestController
@RequestMapping("/api/v2/orders")
class OrderControllerV2(private val service: OrderApplicationService) {
@GetMapping
suspend fun list(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "20") size: Int,
@RequestParam(required = false) status: OrderStatus?
): Page<OrderResponseV2> { // V2 له حقول إضافية
// ...
}
}
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Validation و Error handling موحد
KOTLIN
// تعريف أخطاء المجال
sealed class DomainError(message: String) : RuntimeException(message) {
class NotFound(id: Any) : DomainError("Resource $id not found")
class InvalidOperation(msg: String) : DomainError(msg)
class ValidationFailed(val violations: List<String>) : DomainError("Validation failed")
class Unauthorized(reason: String) : DomainError("Unauthorized: $reason")
}
// معالجة موحدة
@ControllerAdvice
class GlobalErrorHandler {
@ExceptionHandler(DomainError.NotFound::class)
@ResponseStatus(HttpStatus.NOT_FOUND)
fun handleNotFound(ex: DomainError.NotFound) = ErrorResponse(
code = "NOT_FOUND",
message = ex.message ?: "Resource not found",
timestamp = Instant.now()
)
@ExceptionHandler(DomainError.InvalidOperation::class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleInvalid(ex: DomainError.InvalidOperation) = ErrorResponse(
code = "INVALID_OPERATION",
message = ex.message ?: "Invalid operation"
)
@ExceptionHandler(DomainError.ValidationFailed::class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
fun handleValidation(ex: DomainError.ValidationFailed) = ErrorResponse(
code = "VALIDATION_FAILED",
message = ex.message ?: "Validation failed",
details = ex.violations
)
@ExceptionHandler(DomainError.Unauthorized::class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
fun handleUnauthorized(ex: DomainError.Unauthorized) = ErrorResponse(
code = "UNAUTHORIZED",
message = ex.message ?: "Unauthorized"
)
@ExceptionHandler(Exception::class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
fun handleGeneric(ex: Exception) = ErrorResponse(
code = "INTERNAL_ERROR",
message = ex.message ?: "An unexpected error occurred"
)
}
data class ErrorResponse(
val code: String,
val message: String,
val timestamp: Instant = Instant.now(),
val details: List<String>? = null
)
**الإخراج:
TEXT
📖 للعرض فقط
404 Not Found: {"code":"NOT_FOUND","message":"Order ORD-999 not found","timestamp":"..."}
422 Unprocessable: {"code":"VALIDATION_FAILED","message":"...","details":["amount must be > 0"]}
❓ أسئلة شائعة
س تصميم قاعدة البيانات أولاً أم واجهة برمجة التطبيقات أولاً؟
ج صمم نموذج النطاق أولاً (نهج DDD). عندها تكون كل من API وقاعدة البيانات إسقاطات لنموذج النطاق. نموذج النطاق هو الأساس؛ وAPI والمخطط هوما الطرفيات.
س ما الفرق بين مصدر الأحداث و CRUD؟
ج CRUD يخزن الحالة الحالية فقط. مصدر الأحداث يخزن جميع أحداث تغيير الحالة. مصدر الأحداث يدعم التدقيق وإعادة التشغيل والسفر عبر الزمن، لكن تعقيده أعلى. ابدأ بـ CRUD؛ أضف الأحداث عند الحاجة.
س R2DBC أم JPA - كيف تختار؟
ج مشروع جديد + WebFlux + كوروتينات ← R2DBC (غير حظرية). مشروع JPA حالي أو فريق غير ملم بالتفاعلية ← JPA + امتدادات الكوروتينات.
س ما حجم الخدمة المصغرة المناسب؟
ج خدمة مصغرة واحدة لكل سياق محدد. OrderProcessor هو سياق "معالجة الطلبات" — لا تضع الدفع والمخزون فيه أيضًا.
س كيف تتعامل مع إصدارات API؟
ج إصدارات مسار URL (
/api/v1/) هي الأبسط والأكثر بديهية. الإصدارات القائمة على الرأس أكثر توافقًا مع REST لكن أكثر تعقيدًا. يُوصى بإصدارات URL.س كيف تضمن أن التصميم المعماري قابل للتنفيذ؟
ج كل قرار معماري يجب أن يكون له كود POC (إثبات المفهوم). بنية على الورق + نموذج أولي قابل للتشغيل = تصميم قابل للتنفيذ.
📖 ملخص
- المتطلبات ← نمذجة النطاق ← آلة الحالة ← البنية المعمارية ← مجموعة التقنيات ← API ← قاعدة البيانات
- البنية المعمارية الطبقية: وحدة تحكم ← خدمة ← مستودع ← تكامل
- الفئات المختومة (sealed classes) تنمذج آلة حالة الطلب مع فحوصات إحاطية يفرضها المُترجم
- مجموعة التقنيات: Spring Boot + WebFlux + كوروتينات + R2DBC
- تصميم API RESTful: أسماء الموارد + طرق HTTP + إصدارات + تنسيق خطأ موحد
- مصدر الأحداث: تخزين أحداث تغيير الحالة للتدقيق وإعادة التشغيل
📝 تمارين
- مبتدئ (⭐): استخدم
sealed classلتصميم آلة حالة لنظام إدارة مهام بسيط (Todo ← InProgress ← Done / Cancelled). تلميح:sealed class TaskStatus - متوسط (⭐⭐): صمم عقد API RESTful كامل مع فئات data للطلب/الاستجابة لـ 5 نقاط نهاية. تلميح:
CreateOrderRequest/OrderResponse - متقدم (⭐⭐⭐): صمم وثيقة بنية معمارية طبقية كاملة لنظام تجارة إلكترونية، بما في ذلك نموذج النطاق، آلة الحالة، API، مخطط قاعدة البيانات، ومجموعة التقنيات. تلميح: راجع جميع أقسام هذا الدرس