هندسة الواجهة الخلفية متعددة الطبقات
السؤال الأساسي: كيف تنظم الكود عندما يصبح فوضوياً بشكل متزايد؟
عندما يتوسع المشروع من بضع عشرات من الأسطر إلى عشرات الآلاف، ومن مطور واحد إلى فريق كامل، ومن عمليات CRUD بسيطة إلى منطق أعمال معقد، فإن طريقة تنظيم الكود تحدد بشكل مباشر مصير المشروع. الهندسة متعددة الطبقات ليست للتباهي أو اتباع العقائد، بل لحل تناقض جوهري في هندسة البرمجيات: النمو الطبيعي لتعقيد الأعمال مقابل محدودية القدرة الإدراكية البشرية.
1. لماذا نحتاج إلى الطبقات؟
1.1 جذور المشكلة
النسخة الأولية (100 سطر من الكود):
@PostMapping("/register")
public Result register(@RequestBody User user) {
// 1. التحقق من عدم تكرار اسم المستخدم
if (userRepository.findByUsername(user.getUsername()) != null) {
return Result.error("اسم المستخدم موجود مسبقاً");
}
// 2. تشفير كلمة المرور
user.setPassword(encrypt(user.getPassword()));
// 3. حفظ المستخدم
userRepository.save(user);
// 4. إرسال بريد الترحيب
emailService.sendWelcome(user.getEmail());
// 5. تسجيل السجل
log.info("User registered: {}", user.getUsername());
return Result.success();
}بعد 6 أشهر (500 سطر من الكود):
- تمت إضافة التحقق من رقم الهاتف
- تمت إضافة التحقق من الهوية الحقيقية
- تمت إضافة مكافآت الدعوة
- تمت إضافة فحص المخاطر
- ...
الآن هذا الميثود يحتوي على 500 سطر، وكل تعديل يثير القلق، لأن:
- المنطق مختلط، وتعديل جزء قد يؤثر على وظائف أخرى
- صعوبة في الاختبار، حيث يتطلب كل اختبار محاكاة طلب HTTP كامل
- المطورون الجدد لا يفهمونه، لأن كل المنطق مكدس معاً
جوهر المشكلة: الكود يفتقر إلى "الحدود"، وجميع المسؤوليات مختلطة معاً.
التأثير التراكمي للديون التقنية:
- ❌ الاقتران العالي: منطق الأعمال مقترن بالوصول إلى البيانات وبروتوكول HTTP، وأي تعديل يؤثر على النظام بأكمله
- ❌ التماسك المنخفض: ميثود واحد يتحمل مسؤوليات متعددة، مما ينتهك مبدأ المسؤولية الواحدة
- ❌ صعوبة الاختبار: لا يمكن اختبار منطق الأعمال بشكل مستقل، يجب تشغيل حاوية HTTP كاملة
- ❌ صعوبة إعادة الاستخدام: منطق الأعمال مرتبط بطلب HTTP، ولا يمكن إعادة استخدامه في المهام المجدولة أو طوابير الرسائل
- ❌ الحمل الإدراكي: يحتاج المطور إلى فهم تفاصيل جميع الطبقات في وقت واحد، دون القدرة على التركيز
1.2 الفكرة الأساسية للطبقات
هندسة الطبقات ترسم حدوداً واضحة للكود:
┌─────────────────────────────────────┐
│ استقبال الطلب ← Controller │ مسؤول فقط عن "استلام الطلب"
├─────────────────────────────────────┤
│ تنسيق الأعمال ← Service │ مسؤول فقط عن "الطهي"
├─────────────────────────────────────┤
│ الوصول إلى البيانات ← Repository │ مسؤول فقط عن "جلب المكونات"
├─────────────────────────────────────┤
│ تعريف الأعمال ← Domain │ مسؤول فقط عن "معايير الوصفة"
└─────────────────────────────────────┘المبادئ الأساسية:
- كل طبقة تقوم بعملها فقط
- التواصل بين الطبقات عبر واجهات واضحة
- منطق الأعمال يتركز في Service و Domain
- منطق الوصول إلى البيانات يتركز في Repository
القيمة الهندسية لهندسة الطبقات:
- تقليل الحمل الإدراكي: يمكن للمطور التركيز على مسؤوليات الطبقة الحالية دون فهم التفاصيل العامة
- تحسين قابلية الاختبار: يمكن اختبار كل طبقة بشكل مستقل عبر اختبارات الوحدة باستخدام Mock للاعتماديات
- تعزيز قابلية الصيانة: عند تغيير المتطلبات، يكون نطاق التعديل واضحاً، مما يقلل المخاطر
- تعزيز إعادة استخدام الكود: منطق الأعمال لا يعتمد على HTTP، ويمكن إعادة استخدامه في المهام المجدولة وطوابير الرسائل
- دعم التعاون الجماعي: يمكن لمطورين مختلفين العمل على طبقات مختلفة بالتوازي، مما يقلل التعارضات
- إطالة عمر الكود: الحدود الواضحة تجعل إعادة الهيكلة والتطور أسهل
2. شرح مفصل للهندسة رباعية الطبقات
2.1 الهيكل العام
جوهر هندسة الطبقات هو فصل الاهتمامات (Separation of Concerns) والتحكم في اتجاه الاعتماديات:
┌─────────────────────────────────────────────────────┐
│ طلب الواجهة الأمامية │
└────────────────────┬────────────────────────────────┘
│ HTTP Request
▼
┌─────────────────────────────────────────────────────┐
│ Controller (طبقة المتحكم) │
│ - استقبال الطلب، التحقق من المعاملات │
│ - تحويل DTO │
│ - استدعاء Service │
│ - إرجاع الاستجابة │
└────────────────────┬────────────────────────────────┘
│ استدعاء الأعمال
▼
┌─────────────────────────────────────────────────────┐
│ Service (طبقة منطق الأعمال) │
│ - تنسيق منطق الأعمال │
│ - إدارة المعاملات │
│ - تنسيق عدة Repository │
│ - التنسيق عبر الوحدات │
└────────────────────┬────────────────────────────────┘
│ الوصول إلى البيانات
▼
┌─────────────────────────────────────────────────────┐
│ Repository (طبقة الوصول إلى البيانات) │
│ - عمليات CRUD على قاعدة البيانات │
│ - تغليف الاستعلامات │
│ - تعيين ORM │
└────────────────────┬────────────────────────────────┘
│ كائنات المجال
▼
┌─────────────────────────────────────────────────────┐
│ Domain (طبقة نموذج المجال) │
│ - الكيانات (Entity) │
│ - كائنات القيمة (Value Object) │
│ - قواعد الأعمال │
└─────────────────────────────────────────────────────┘اتجاه الاعتماديات: يجب أن تشير اعتماديات الكود نحو الاتجاه الأكثر استقراراً وتجريداً
- Controller يعتمد على واجهة Service (تجريد)
- Service يعتمد على واجهة Repository (تجريد)
- جميع الطبقات تعتمد على Domain (نواة الأعمال، الأكثر استقراراً)
- لا يُسمح بالاعتماديات العكسية (مثل اعتماد Repository على Service)
2.2 طبقة Controller
المسؤولية: "موظف الاستقبال" للطلبات
- استقبال طلبات HTTP، تحليل المعاملات
- التحقق من المعاملات (التنسيق، الحقول المطلوبة، إلخ)
- تحويل DTO (Request → Param)
- استدعاء Service لتنفيذ الأعمال
- تحويل DTO (Result → Response)
- إرجاع استجابة HTTP
ما لا يجب فعله:
- كتابة منطق الأعمال مباشرة
- التعامل مع قاعدة البيانات مباشرة
- إدارة المعاملات
فلسفة التصميم: Controller هو "واجهة" النظام، ويؤدي دور المحول — تكييف بروتوكول HTTP الخارجي إلى استدعاءات الأعمال الداخلية. يجب ألا يحتوي على أي قرارات أعمال، لأن قرارات الأعمال هي تجسيد للمعرفة المجالية ويجب فصلها عن بروتوكول النقل.
مثال:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@PostMapping
public UserResponse createUser(
@RequestBody @Valid UserRequest request) {
// 1. Request DTO → Param DTO
UserParam param = UserParam.builder()
.username(request.getUsername())
.password(encrypt(request.getPassword()))
.email(request.getEmail())
.build();
// 2. استدعاء Service
User user = userService.createUser(param);
// 3. Entity → Response DTO
return UserResponse.from(user);
}
}النقاط الرئيسية:
- استخدام
@Validللتحقق التلقائي من المعاملات - استخدام DTO لعزل هياكل بيانات الواجهة الأمامية والخلفية
- القيام فقط بـ"الترجمة" و"الجدولة"، دون احتواء منطق الأعمال
POST /api/users/register
Content-Type: application/json
{ "username": "Alice", "email": "alice@example.com", "password": "123456" }@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<UserDTO> register(
@RequestBody @Valid UserRegisterRequest request) {
UserDTO user = userService.register(request);
return ResponseEntity.ok(user);
}
}public class UserRegisterRequest {
@NotBlank(message = "Username is required")
@Size(min = 2, max = 20) private String username;
@Email(message = "Invalid email format") private String email;
@Size(min = 6, message = "Password needs at least 6 characters") private String password;
}HTTP/1.1 200 OK
{ "code": 200, "message": "Registered",
"data": { "id": 10001, "username": "Alice", "email": "alice@example.com" } }2.3 طبقة Service
المسؤولية: "طاهي" الأعمال
- تنفيذ منطق الأعمال الأساسي
- تنسيق عمليات عدة Repository
- إدارة حدود المعاملات
- معالجة التنسيق عبر الوحدات
ما لا يجب فعله:
- كتابة SQL مباشرة (اتركها لـ Repository)
- معالجة الأمور المتعلقة بـ HTTP
- إرجاع كيانات قاعدة البيانات إلى Controller
فلسفة التصميم: طبقة Service هي حامل منطق الأعمال، ويجب أن تبقى نقية. لا تعتمد على أي إطار عمل أو بروتوكول نقل، وهذا يمكنها من:
- الاختبار المستقل عن طبقة الويب عبر اختبارات الوحدة
- إعادة الاستخدام في المهام المجدولة ومستهلكي طوابير الرسائل
- تجنب تأثير تغييرات المكدس التقني على منطق الأعمال
مثال:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
@Transactional
public User createUser(UserParam param) {
// 1. قاعدة الأعمال: التحقق من عدم تكرار اسم المستخدم
if (userRepository.existsByUsername(param.getUsername())) {
throw new UserAlreadyExistsException();
}
// 2. إنشاء كيان المستخدم
User user = new User();
user.setUsername(param.getUsername());
user.setPassword(param.getPassword());
user.setEmail(param.getEmail());
// 3. الحفظ في قاعدة البيانات
userRepository.save(user);
// 4. إرسال بريد الترحيب (تنسيق عبر الوحدات)
emailService.sendWelcomeEmail(user);
return user;
}
}النقاط الرئيسية:
- استخدام
@Transactionalلضمان اتساق المعاملات - رمي استثناءات الأعمال، ليقوم Controller بمعالجتها بشكل موحد
- عدم الاعتماد على مفاهيم HTTP، مما يسمح بإعادة الاستخدام
@PostMapping("/orders")
public ResponseEntity<OrderDTO> createOrder(
@RequestBody @Valid CreateOrderRequest request) {
OrderDTO order = orderService.createOrder(request);
return ResponseEntity.ok(order);
}@Transactional
public OrderDTO createOrder(CreateOrderRequest request) {
inventoryService.checkAndDeduct(request.getSkuId(), request.getQuantity());
Order order = new Order();
order.setUserId(request.getUserId());
order.setTotalAmount(calculateTotal(request));
orderRepository.save(order);
Payment payment = createPayment(order);
paymentRepository.save(payment);
return convertToDTO(order);
}public interface OrderRepository extends JpaRepository<Order, Long> {
// Basic CRUD is built in
}UserService handles users, OrderService handles ordersPut @Transactional on Service methodsA→B→A creates a cyclereturn new UserDTO(user)2.4 طبقة Repository
المسؤولية: "أمين المستودع" للبيانات
- تغليف كل منطق الوصول إلى البيانات
- تنفيذ عمليات CRUD
- معالجة تعيين ORM
- تغليف شروط الاستعلام
ما لا يجب فعله:
- كتابة منطق الأعمال
- معالجة المعاملات (تديرها طبقة Service)
- الاعتماد على الوحدات العليا
فلسفة التصميم: Repository هو طبقة تجريد للوصول إلى البيانات، تخفي تفاصيل قاعدة البيانات الأساسية. قيمة هذا التجريد تكمن في:
- عند تغيير قاعدة البيانات، يكفي تعديل تنفيذ Repository فقط، دون تغيير منطق الأعمال
- سهولة استخدام Mock لاختبارات الوحدة
- إدارة مركزية لمنطق الاستعلام، لتجنب تكرار الكود
مثال:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPA تنفذ تلقائياً
Optional<User> findByUsername(String username);
boolean existsByUsername(String username);
// استعلام معقد مخصص
@Query("SELECT u FROM User u WHERE u.email = :email AND u.deleted = false")
Optional<User> findActiveByEmail(@Param("email") String email);
}النقاط الرئيسية:
- Repository هو واجهة، لا تحتوي على منطق الأعمال
- استخدام أسماء الميثود للتعبير عن نية الاستعلام
- يمكن استخدام
@Queryللاستعلامات المعقدة المخصصة
// Repository interface definition
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// ✅ Query generated from method name
List<Order> findByUserIdAndDeletedFalse(Long userId);
// ✅ Custom JPQL
@Query("SELECT o FROM Order o WHERE o.createdAt BETWEEN :start AND :end")
List<Order> findByDateRange(@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end);
}
// Service layer with pure business logic
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository; // ✅ Depend on interface
public List<OrderDTO> getUserOrders(Long userId) {
List<Order> orders = orderRepository.findByUserIdAndDeletedFalse(userId);
return orders.stream().map(OrderDTO::from).collect(Collectors.toList());
}
}- Separation of concerns: Service handles business, Repository handles data
- High testability: mocks can replace the real database
- Code reuse: common queries are defined once and reused
- Low switching cost: changing database mostly affects Repository implementation
| Implementation | Pros | Cons | Best for |
|---|---|---|---|
| Spring Data JPA Mainstream | Method-name query derivation, built-in pagination | Complex queries may be less efficient | Fast development, standard CRUD |
| MyBatis / MyBatis-Plus SQL control | Full SQL control and strong dynamic SQL | Requires handwritten SQL | Complex queries, performance-sensitive paths |
| Spring Data JDBC Lightweight | Simple, lightweight, fast startup | No complex mapping | Microservices, simple aggregate roots |
2.5 طبقة Domain
المسؤولية: "معايير الوصفة" للأعمال
- تعريف كيانات الأعمال (Entity)
- تعريف كائنات القيمة (Value Object)
- تغليف قواعد الأعمال
- العمل كاعتمادية مشتركة لجميع الطبقات
خصائص مهمة:
- طبقة Domain لا تعتمد على أي طبقة أخرى
- جميع الطبقات تعتمد على طبقة Domain
- هي أساس هندسة الطبقات
فلسفة التصميم: طبقة Domain هي نواة الأعمال للنظام بأكمله، وتعبر عن المعرفة المجالية وقواعد الأعمال. نقاؤها أمر بالغ الأهمية:
- عدم الاعتماد على الإطار يعني أن منطق الأعمال ليس مقيداً بالمكدس التقني
- اعتماد جميع الطبقات عليها يضمن توحيد قواعد الأعمال
- سهولة التطور على المدى الطويل، حيث يمكن استبدال المكدس التقني بينما تبقى قواعد الأعمال مستقرة نسبياً
مثال:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
// ✅ ميثود الأعمال: تغليف قواعد الأعمال
public boolean isPasswordCorrect(String rawPassword) {
return BCrypt.checkpw(rawPassword, this.password);
}
public void changePassword(String oldPassword, String newPassword) {
if (!isPasswordCorrect(oldPassword)) {
throw new IncorrectPasswordException();
}
this.password = BCrypt.hashpw(newPassword);
}
}النقاط الرئيسية:
- Entity لها معرف فريد
- قواعد الأعمال مغلفة في كائنات Domain
- طبقة Domain هي منطق أعمال خالص، لا تعتمد على الإطار
@Entity
public class Order {
@Id private Long id;
private BigDecimal totalAmount;
private OrderStatus status;
// Only getters/setters, no business logic
public Long getId() { return id; }
public void setStatus(OrderStatus s) { this.status = s; }
}@Service
public class OrderService {
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
// Anemic model: business logic is scattered in Service
if (order.getStatus() == OrderStatus.SHIPPED)
throw new IllegalStateException("Shipped order cannot be cancelled");
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
}
}- Violates object orientation: objects have data but no behavior
- Scattered logic: same rule may repeat in multiple Services
- Hard to maintain: changing a rule requires finding all usages
@Entity
public class Order {
@Id private Long id;
private BigDecimal totalAmount;
private OrderStatus status;
// Business behavior is encapsulated in the entity
public void cancel() {
if (this.status == OrderStatus.SHIPPED)
throw new IllegalStateException("Shipped order cannot be cancelled");
this.status = OrderStatus.CANCELLED;
registerEvent(new OrderCancelledEvent(this.id));
}
public void pay(Payment payment) {
if (this.status != OrderStatus.PENDING_PAYMENT)
throw new IllegalStateException("Invalid order state");
this.status = OrderStatus.PAID;
}
}@Service
public class OrderService {
@Transactional
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
order.cancel(); // Call domain behavior
orderRepository.save(order);
}
}- Object-oriented: data and behavior are encapsulated together
- Business cohesion: rules stay with objects and change in one place
- Testable: domain objects are in-memory and do not require database
- Expressive: order.cancel() is more natural than orderService.cancel(order)
3. DTO: "المترجم" بين الطبقات
3.1 لماذا نحتاج إلى DTO؟
المشكلة: إذا قمنا بإرجاع كيان قاعدة البيانات مباشرة إلى الواجهة الأمامية:
// ❌ خطأ: إرجاع Entity مباشرة
@Entity
public class User {
private Long id;
private String username;
private String password; // معلومات حساسة!
private Boolean isDeleted; // حقل داخلي!
}الواجهة الأمامية ستتلقى حقولاً لا يجب كشفها، مما يشكل خطراً أمنياً.
الحل: استخدام DTO "للترجمة"
كيان قاعدة البيانات → Service Param/Result → Controller Request/Response → الواجهة الأمامية3.2 أنواع DTO
| النوع | الغرض | مثال |
|---|---|---|
| Request DTO | استقبال المعاملات في Controller | UserCreateRequest |
| Response DTO | إرجاع البيانات من Controller | UserResponse |
| Param DTO | معاملات ميثود Service | UserParam |
| Result DTO | نتيجة الإرجاع من Service | UserResult |
| Entity | تعيين قاعدة البيانات | User |
المبادئ الأساسية: كل طبقة تستخدم DTO الخاص بها، ولا تمرر Entity مباشرة، ويحتوي DTO فقط على الحقول الضرورية، مما يتجنب كشف تفاصيل التنفيذ الداخلية ويضمن استقلالية كل طبقة.
// Receive Request DTO
public ResponseEntity<UserDTO> createUser(
@RequestBody @Valid UserCreateRequest request) { ... }public UserDTO createUser(UserCreateParam param) {
User user = param.toEntity(); // Convert to Entity
userRepository.save(user);
return UserDTO.from(user); // Entity → DTO
}public interface UserRepository
extends JpaRepository<User, Long> { }{ "id": 10001, "username": "Alice",
"email": "alice@example.com", "createdAt": "2024-01-15T10:30:00Z" }| Layer | DTO type | Responsibility | Example |
|---|---|---|---|
| Controller | Request / Response DTO | Define API contract and validation | UserCreateRequest |
| Service | Param / Result DTO | Wrap business method parameters and decouple layers | UserCreateParam |
| Repository | Entity / DO | Map database table structure | UserEntity |
4. اتجاه الاعتماديات: القانون الحديدي لهندسة الطبقات
4.1 مبدأ عكس الاعتماديات
الطريقة الخاطئة:
Controller → UserServiceImpl → UserDaoImpl → UserEntityالطريقة الصحيحة:
Controller → UserService(واجهة) → UserRepository(واجهة) → UserEntityاتجاه الاعتماديات:
اتجاه الاعتماديات الصحيح هو أن جميع الطبقات تعتمد على الطبقات الأكثر تجريداً واستقراراً. بشكل ملموس، Controller يعتمد على واجهة Service، و Service يعتمد على واجهة Repository، وجميع الطبقات تعتمد على طبقة Domain، بينما طبقة Domain لا تعتمد على أي طبقة أخرى. يضمن اتجاه الاعتماديات هذا استقلالية منطق الأعمال وقابليته للاختبار.
تشمل الممارسات الخاطئة اعتماد Service مباشرة على فئة تنفيذ Repository، أو تعامل Controller مباشرة مع قاعدة البيانات، أو اعتماد طبقة Domain على طبقات أخرى، وكلها تؤدي إلى زيادة الاقتران وتقليل قابلية الصيانة.
4.2 مثال على الكود
// ✅ صحيح: الاعتماد على الواجهة
@Service
public class OrderService {
private final OrderRepository orderRepository; // واجهة
private final PaymentService paymentService; // واجهة
}
// ✅ فئة التنفيذ تُحقن تلقائياً عبر Spring
@Repository
public class OrderRepositoryImpl implements OrderRepository {
// تفاصيل التنفيذ
}High-level modules should not depend on low-level implementation details. They should depend on abstractions.
5. حالة عملية: نظام طلبات التجارة الإلكترونية
5.1 المتطلبات
إنشاء طلب:
- يختار المستخدم المنتج
- التحقق من المخزون
- حساب المبلغ
- إنشاء الطلب
- خصم المخزون
5.2 تنفيذ الكود
طبقة Domain:
@Entity
public class Order {
@Id
private Long id;
private Long userId;
private List<OrderItem> items;
private Money totalAmount;
private OrderStatus status;
public void calculateTotal() {
Money total = Money.zero();
for (OrderItem item : items) {
total = total.add(item.getSubTotal());
}
this.totalAmount = total;
}
public void cancel() {
if (this.status != OrderStatus.PENDING_PAYMENT) {
throw new IllegalStateException("يمكن فقط إلغاء الطلبات المعلقة للدفع");
}
this.status = OrderStatus.CANCELLED;
}
}طبقة Repository:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByUserIdOrderByCreatedAtDesc(Long userId);
}طبقة Service:
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
@Transactional
public OrderDTO createOrder(OrderParam param) {
// 1. التحقق من المنتج وخصم المخزون
for (OrderItemParam item : param.getItems()) {
inventoryService.reserveStock(item.getProductId(), item.getQuantity());
}
// 2. إنشاء الطلب
Order order = new Order();
order.setUserId(param.getUserId());
order.calculateTotal();
// 3. حفظ الطلب
orderRepository.save(order);
return OrderDTO.from(order);
}
}طبقة Controller:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@PostMapping
public OrderResponse createOrder(@RequestBody @Valid OrderRequest request) {
OrderParam param = OrderParam.builder()
.userId(request.getUserId())
.items(request.getItems())
.build();
OrderDTO order = orderService.createOrder(param);
return OrderResponse.from(order);
}
}6. الأسئلة الشائعة
6.1 هل يمكن لـ Controller كتابة منطق الأعمال؟
يجب ألا يكتب Controller منطق الأعمال، فهو مسؤول فقط عن استقبال الطلبات وإرجاع الاستجابات. يجب تغليف منطق الأعمال في طبقة Service، والفائدة من ذلك هي إمكانية إعادة استخدام الكود، على سبيل المثال يمكن للمهام المجدولة أو مستهلكي طوابير الرسائل استدعاء Service مباشرة دون المرور عبر طلب HTTP. في الوقت نفسه، تركيز منطق الأعمال في مكان واحد يجعله أسهل في الاختبار والصيانة، ويتجنب مشاكل عدم الاتساق الناتجة عن تشتت المنطق.
6.2 ما هو النموذج الفقير والنموذج الغني؟
النموذج الفقير (Anemic Model) يعني أن فئة الكيان تحتوي فقط على الخصائص وطرق getter/setter المقابلة، دون أي منطق أعمال، وتوضع جميع قواعد الأعمال في طبقة Service. هذا النموذج بسيط الهيكل وسهل الفهم، وهو النهج المعتمد في معظم المشاريع.
النموذج الغني (Rich Model) يعني أن فئة الكيان لا تحتوي فقط على الخصائص، بل أيضاً على ميثودات الأعمال المتعلقة بالكيان، مما يغلف قواعد الأعمال داخل الكيان نفسه. هذا النهج أكثر توافقاً مع فكر التصميم الموجه للكائنات، حيث يجمع البيانات والسلوك معاً، مما يزيد من تماسك الكود.
يُنصح باختيار النموذج المناسب بناءً على الخلفية التقنية للفريق وتعقيد المشروع، ولكن أياً كان الاختيار، يجب الحفاظ على الاتساق، ويجب أن تحتوي طبقة Domain على الأقل على ميثودات سلوك الأعمال الأساسية، بدلاً من أن تكون مجرد قشرة فارغة تماماً.
6.3 كيفية التعامل مع المعاملات عبر عدة Service؟
عندما تحتاج عملية أعمال إلى عبور عدة Service، يجب استخدام تعليق المعاملة @Transactional في Service العلوي، واستدعاء عدة Service سفلية بالتسلسل داخل هذا الميثود. هذا يضمن تنفيذ جميع العمليات في نفس سياق المعاملة، فإما أن تنجح جميعها أو تفشل جميعها، مما يضمن اتساق البيانات. يجب الانتباه إلى أن حدود المعاملة يجب أن تكون صغيرة قدر الإمكان، وتحتوي فقط على العمليات الضرورية، لتجنب الاحتفاظ بأقفال قاعدة البيانات لفترة طويلة مما يؤثر على الأداء التزامني.
7. ملخص
| الطبقة | المسؤولية | الكلمة المفتاحية |
|---|---|---|
| Controller | استقبال الطلب، التحقق من المعاملات، استدعاء Service، إرجاع الاستجابة | موظف الاستقبال |
| Service | تنسيق منطق الأعمال، إدارة المعاملات، تنسيق Repository | الطاهي |
| Repository | الوصول إلى البيانات، تعيين ORM، تغليف الاستعلامات | أمين المستودع |
| Domain | تعريف الكيانات، قواعد الأعمال، كائنات القيمة | معايير الوصفة |
المبادئ الأساسية:
- كل طبقة تقوم بعملها فقط
- التواصل بين الطبقات عبر واجهات
- منطق الأعمال يتركز في Service و Domain
- منطق الوصول إلى البيانات يتركز في Repository
- استخدام DTO لعزل هياكل البيانات بين الطبقات
8. المزيد من أنماط الهندسة
تقدم هذه المقالة الهندسة متعددة الطبقات (Layered Architecture)، وهي النمط الأكثر شيوعاً وسهولة في البداية لهندسة الواجهة الخلفية. لكن هندسة الواجهة الخلفية لا تقتصر على هذا النمط، فهناك أنماط أخرى تستحق المعرفة حسب سيناريوهات الأعمال المختلفة:
8.1 أنماط الهندسة الشائعة الأخرى
| نمط الهندسة | السيناريو المناسب | الخصائص |
|---|---|---|
| الهندسة الأحادية | المشاريع الصغيرة، MVP | جميع الوظائف في تطبيق واحد، نشر بسيط |
| هندسة الخدمات المصغرة | الأنظمة الكبيرة المعقدة | تقسيم إلى خدمات مستقلة متعددة، كل خدمة يمكن نشرها بشكل مستقل |
| الهندسة المدفوعة بالأحداث | التزامن العالي، المعالجة غير المتزامنة | معالجة التدفق عبر الأحداث، درجة عالية من فك الاقتران |
| الهندسة النظيفة | أنظمة الأعمال المعقدة | منطق الأعمال في المركز، الاعتماديات تتجه للداخل فقط، الإطار في الطبقة الخارجية |
| الهندسة السداسية | الحاجة إلى محولات خارجية متعددة | عزل النواة عن الأنظمة الخارجية عبر المنافذ والمحولات |
| هندسة البصل | التصميم الموجه بالمجال | طبقات دائرية متحدة المركز، نموذج المجال في الداخل، البنية التحتية في الخارج |
فيما يلي شرح مفصل لكل منها:
الهندسة الأحادية (Monolithic)
جميع الوظائف مجمعة في تطبيق واحد، تشترك في نفس قاعدة البيانات والعملية.
┌──────────────────────────────┐
│ تطبيق أحادي │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │مستخدم│ │طلب │ │دفع │ ... │
│ └──┬─┘ └──┬─┘ └──┬─┘ │
│ └──────┼──────┘ │
│ قاعدة بيانات مشتركة │
└──────────────────────────────┘- المزايا: تطوير بسيط، نشر سهل، تصحيح محلي مريح
- العيوب: اقتران عالٍ للكود، صعوبة في التوسع، مشكلة في وحدة واحدة قد تؤثر على النظام بأكمله
- مناسب لـ: مشاريع الشركات الناشئة المبكرة، تطوير فريق واحد، التحقق السريع من النموذج الأولي
هندسة الخدمات المصغرة (Microservices)
تقسيم النظام إلى خدمات مستقلة متعددة، لكل خدمة بياناتها ومنطق أعمالها الخاص، ويمكن نشرها وتوسيعها بشكل مستقل.
┌────────┐ ┌────────┐ ┌────────┐
│خدمة │ │خدمة │ │خدمة │
│المستخدم│ │الطلبات │ │الدفع │
│ DB-1 │ │ DB-2 │ │ DB-3 │
└───┬────┘ └───┬────┘ └───┬────┘
└───────────┼───────────┘
API Gateway- المزايا: نشر وتوسيع مستقل، مرونة في المكدس التقني، عزل الأعطال
- العيوب: تعقيد التواصل بين الخدمات، صعوبة اتساق البيانات الموزعة، الحاجة إلى قدرات DevOps ناضجة
- مناسب لـ: الأنظمة الكبيرة المعقدة، التعاون متعدد الفرق، السيناريوهات التي تحتاج توسعاً مستقلاً
الهندسة المدفوعة بالأحداث (Event-Driven)
التواصل عبر الأحداث غير المتزامنة، المنتج يصدر الأحداث، والمستهلك يستجيب لها، والمكونات عالية الفصل.
منتج ──→ [ناقل الأحداث/طابور الرسائل] ──→ مستهلك أ
──→ مستهلك ب
──→ مستهلك ج- المزايا: فصل عالٍ، دعم طبيعي للتوسع، مناسب للمعالجة الفورية
- العيوب: صعوبة في التصحيح، ترتيب الأحداث والعامل المثالي يحتاجان معالجة إضافية
- مناسب لـ: تحليل البيانات الفوري، أنظمة IoT، التواصل غير المتزامن بين الخدمات المصغرة
الهندسة النظيفة (Clean Architecture)
قدمها Robert C. Martin، تقسم النظام إلى أربع طبقات دائرية متحدة المركز، الاعتماديات تتجه فقط من الخارج إلى الداخل:
┌─────────────────────────────────────┐
│ Frameworks & Drivers (الأطر والمشغلات) │
│ ┌─────────────────────────────┐ │
│ │ Interface Adapters (المحولات) │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ Use Cases (حالات الاستخدام)│ │ │
│ │ │ ┌─────────────┐ │ │ │
│ │ │ │ Entities │ │ │ │
│ │ │ │ (الكيانات/المجال)│ │ │ │
│ │ │ └─────────────┘ │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
اتجاه الاعتماديات: الخارج → الداخل- القاعدة الأساسية: الطبقات الداخلية لا تعرف وجود الطبقات الخارجية، منطق الأعمال مستقل تماماً عن الإطار وقاعدة البيانات
- المزايا: قابلية اختبار عالية، المكدس التقني قابل للاستبدال، منطق الأعمال واضح
- العيوب: تكلفة تطوير أولية عالية، كود تعيين كثير بين الطبقات، قد تكون مبالغة في التصميم للمشاريع الصغيرة
- مناسب لـ: أنظمة الأعمال المعقدة، المشاريع التي تحتاج صيانة طويلة الأمد
- Vertical dependency: upper layers directly depend on lower layers
- Simple and intuitive: clear structure, easy to understand
- Good for small and medium projects: quick development
- Potential issue: lower-layer changes may affect upper layers
الهندسة السداسية (Hexagonal / Ports & Adapters)
تعريف واجهات الإدخال والإخراج للأعمال الأساسية عبر "المنافذ"، والاتصال بالأنظمة الخارجية عبر "المحولات":
┌─────────────┐
HTTP ──→ Port │
CLI ──→ (منفذ إدخال)│ منطق الأعمال الأساسي │ (منفذ إخراج) ──→ قاعدة البيانات
MQ ──→ │ │ Port ──→ API خارجي
└─────────────┘- الفكرة الأساسية: منطق الأعمال لا يعتمد على أي تقنية خارجية، الأنظمة الخارجية تتصل عبر المحولات
- المزايا: الأنظمة الخارجية قابلة للاستبدال بحرية، يمكن استخدام محولات Mock للاختبار
- مناسب لـ: السيناريوهات التي تحتاج الاتصال بأنظمة خارجية متعددة
هندسة البصل (Onion Architecture)
مشابهة للهندسة النظيفة، تؤكد على أن نموذج المجال في الطبقة الأعمق، والبنية التحتية في الطبقة الخارجية، والاعتماديات تتجه للداخل فقط:
┌──────────────────────────────┐
│ Infrastructure (البنية التحتية) │
│ ┌────────────────────────┐ │
│ │ Application Services │ │
│ │ ┌──────────────────┐ │ │
│ │ │ Domain Services │ │ │
│ │ │ ┌────────────┐ │ │ │
│ │ │ │Domain Model│ │ │ │
│ │ │ └────────────┘ │ │ │
│ │ └──────────────────┘ │ │
│ └────────────────────────┘ │
└──────────────────────────────┘- الفكرة الأساسية: نموذج المجال هو نواة النظام، جميع الاعتماديات تشير إليه
- الفرق عن الهندسة النظيفة: هندسة البصل تؤكد أكثر على طبقة خدمات المجال، بينما الهندسة النظيفة تؤكد أكثر على طبقة حالات الاستخدام
- مناسب لـ: المشاريع التي تتبنى التصميم الموجه بالمجال (DDD)
8.2 مسار تطور الهندسة
هذه الهندسات ليست بدائل عن بعضها البعض، بل هي تطور تدريجي:
الهندسة التقليدية متعددة الطبقات (N-Layered)
│ المشكلة: اقتران بين الطبقات، صعوبة استبدال الاعتماديات الخارجية
▼
الهندسة السداسية (Ports & Adapters)
│ التحسين: عزل الأنظمة الخارجية بالمنافذ والمحولات
▼
هندسة البصل (Onion)
│ التحسين: طبقات دائرية واضحة، نموذج المجال في المركز
▼
الهندسة النظيفة (Clean Architecture)
│ التحسين: توحيد قواعد الاعتماديات، توضيح مسؤوليات الطبقات الأربع
▼
اختيار الهندسة المناسبة حسب احتياجات الأعمال8.3 دليل اختيار نمط الهندسة
عدد المستخدمين < 1k، حجم الكود < 5000 سطر
↓
الهندسة الأحادية + طبقات بسيطة
↓
عدد المستخدمين 1k-100k، الحاجة إلى تعاون متعدد الفرق
↓
الهندسة متعددة الطبقات (المقدمة في هذه المقالة)
↓
عدد المستخدمين > 100k، تعقيد أعمال مرتفع
↓
هندسة الخدمات المصغرة / الهندسة المدفوعة بالأحداثأبعاد اختيار أكثر تفصيلاً:
| عامل الاعتبار | طبقات بسيطة | هندسة نظيفة/سداسية | خدمات مصغرة |
|---|---|---|---|
| حجم الفريق | 1-5 أشخاص | 5-20 شخص | 20+ شخص |
| تعقيد الأعمال | منخفض | متوسط-مرتفع | مرتفع |
| تكرار النشر | منخفض | متوسط | مرتفع (نشر مستقل) |
| تنوع المكدس التقني | واحد | واحد | يمكن أن يكون متنوعاً |
| تكلفة التشغيل | منخفضة | متوسطة | مرتفعة |
8.4 قراءات موصى بها
- الهندسة الأحادية: راجع المقالة الشقيقة
backend-project-architecture.mdلفهم التطور من السكربت إلى الأحادي - هندسة الخدمات المصغرة: راجع التطور من الأحادي إلى الخدمات المصغرة
- الهندسة النظيفة: كتاب "Clean Architecture" لـ Robert C. Martin — العمل الكلاسيكي الذي يقدم قواعد الاعتماديات ونموذج الطبقات الأربع متحدة المركز
- أنماط هندسة المؤسسات: كتاب "Patterns of Enterprise Application Architecture" لـ Martin Fowler — المرجع الموثوق لهندسة الطبقات وتنظيم منطق المجال
8.5 كيف تختار؟
تذكر هذا المبدأ: الهندسة تخدم الأعمال، وليس الهندسة من أجل الهندسة.
- المشاريع الصغيرة تستخدم هندسة بسيطة، للإطلاق السريع والتحقق
- المشاريع الكبيرة تفكر في الهندسة المعقدة لاحقاً، لتجنب المبالغة في التصميم
- ألفة الفريق مهمة جداً أيضاً، اختر حلاً يمكن للجميع فهمه
9. ملخص
| الطبقة | المسؤولية | الكلمة المفتاحية |
|---|---|---|
| Controller | استقبال الطلب، التحقق من المعاملات، استدعاء Service، إرجاع الاستجابة | موظف الاستقبال |
| Service | تنسيق منطق الأعمال، إدارة المعاملات، تنسيق Repository | الطاهي |
| Repository | الوصول إلى البيانات، تعيين ORM، تغليف الاستعلامات | أمين المستودع |
| Domain | تعريف الكيانات، قواعد الأعمال، كائنات القيمة | معايير الوصفة |
المبادئ الأساسية:
جوهر هندسة الطبقات يكمن في التقسيم الواضح للمسؤوليات والتحكم في اتجاه الاعتماديات. كل طبقة تركز فقط على مسؤولياتها، وتتواصل مع الطبقات المجاورة عبر الواجهات، ويتركز منطق الأعمال في طبقتي Service و Domain، ويتركز منطق الوصول إلى البيانات في طبقة Repository، وتعزل الطبقات هياكل البيانات فيما بينها عبر DTO، لتجنب الكشف المباشر عن تفاصيل التنفيذ الداخلي. هذا التصميم يجعل النظام أسهل في الفهم والاختبار والصيانة، وقادراً على مواجهة التطور المستمر للأعمال.
المراجع
- Catalog of Patterns of Enterprise Application Architecture - Martin Fowler — دليل Martin Fowler لأنماط هندسة تطبيقات المؤسسات، مرجع كلاسيكي لهندسة الطبقات
- Backend Side Architecture Evolution (N-layered, DDD, Hexagon, Onion, Clean Architecture) — رحلة التطور من هندسة N من الطبقات إلى الهندسة النظيفة، لفهم أسباب نشأة كل هندسة
- Complete Guide to Clean Architecture - GeeksforGeeks — دليل شامل للهندسة النظيفة، شرح مفصل للطبقات وقواعد الاعتماديات وفصل الاهتمامات
- Understanding Hexagonal, Clean, Onion, and Traditional Layered Architectures: A Deep Dive — مقارنة معمقة بين الهندسة السداسية والنظيفة والبصل والطبقات التقليدية
- Building Clean Architectures in Modern Backend Frameworks — دليل عملي لتطبيق الهندسة النظيفة في أطر الواجهة الخلفية الحديثة
- Backend Architecture Patterns: From Monoliths to Microservices — نظرة شاملة على أنماط هندسة الواجهة الخلفية من الأحادي إلى الخدمات المصغرة
- MVC شرح مفصل لحالة الهندسة ثلاثية الطبقات — العلاقة بين MVC والهندسة ثلاثية الطبقات مع حالة عملية، مناسبة للمبتدئين الناطقين بالصينية