انتقل إلى المحتوى الرئيسي

الفصل 1: طبقة الخدمة بالتفصيل

مقدمة​

تعتبر طبقة الخدمة العنصر المركزي في بنية الطبقات الثلاث. إنها تربط بين المتحكم (الذي يدير طلبات HTTP) والمستودع (الذي يصل إلى البيانات). هنا تكمن المنطق التجاري لتطبيقك.

أهداف الفصل​

في نهاية هذا الفصل، ستكون قادرًا على:

  • فهم دور ومسؤوليات طبقة الخدمة
  • فصل الاهتمامات بشكل صحيح بين الطبقات
  • تنفيذ طبقة خدمة احترافية
  • استخدام حقن التبعيات بشكل صحيح
  • تطبيق أفضل الممارسات

بنية الطبقات الثلاث​

نظرة عامة​

مسؤوليات كل طبقة​

الطبقةالمسؤولياتأمثلة
المتحكماستقبال HTTP، التحقق، التحويل، الردود@RestController, @GetMapping
الخدمةالمنطق التجاري، التنسيق، المعاملات، قواعد التحقق@Service, حسابات
المستودعالوصول إلى البيانات، استعلامات قاعدة البيانات، تعيين الكيانات@Repository, JpaRepository

ما هو المنطق التجاري؟​

التعريف​

المنطق التجاري هو مجموعة القواعد والعمليات التي تحدد سلوك تطبيقك.

أمثلة على المنطق التجاري​

❌ ليس منطقًا تجاريًا:

// هذا هو الوصول إلى البيانات
productRepository.findById(id);

// هذا هو إدارة HTTP
return ResponseEntity.ok(product);

✅ منطق تجاري:

// التحقق من المخزون قبل إنشاء الطلب
if (product.getStock() < order.getQuantity()) {
throw new InsufficientStockException();
}

// حساب السعر الإجمالي مع الضرائب
double totalPrice = order.getSubtotal() * (1 + TAX_RATE);

// تطبيق عرض ترويجي
if (customer.isPremium()) {
totalPrice = totalPrice * 0.9; // خصم 10%
}

إنشاء طبقة خدمة​

مثال أساسي​

package com.example.demo.service;

import com.example.demo.model.Product;
import com.example.demo.repository.ProductRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {

private final ProductRepository productRepository;

// حقن عن طريق المنشئ (موصى به)
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

// إنشاء
public Product createProduct(Product product) {
// منطق تجاري: التحقق
if (product.getPrice() < 0) {
throw new IllegalArgumentException("لا يمكن أن يكون السعر سالبًا");
}

// استدعاء المستودع
return productRepository.save(product);
}

// قراءة - الكل
public List<Product> getAllProducts() {
return productRepository.findAll();
}

// قراءة - حسب المعرف
public Product getProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("لم يتم العثور على المنتج بالمعرف: " + id));
}

// تحديث
public Product updateProduct(Long id, Product productDetails) {
Product product = getProductById(id);

// منطق تجاري: التحقق
if (productDetails.getPrice() < 0) {
throw new IllegalArgumentException("لا يمكن أن يكون السعر سالبًا");
}

// تحديث الحقول
product.setName(productDetails.getName());
product.setPrice(productDetails.getPrice());
product.setQuantity(productDetails.getQuantity());

return productRepository.save(product);
}

// حذف
public void deleteProduct(Long id) {
Product product = getProductById(id);
productRepository.delete(product);
}
}

النقاط الرئيسية​

  1. @Service: تمييز الفئة كطبقة خدمة
  2. حقن عن طريق المنشئ: أنظف وأكثر قابلية للاختبار من @Autowired على الحقول
  3. التحقق التجاري: التحقق من القواعد قبل الوصول إلى البيانات
  4. إدارة الأخطاء: إطلاق الاستثناءات المناسبة

حقن التبعيات​

ثلاث طرق للحقن​

1. حقن عن طريق المنشئ (موصى به)​

@Service
public class ProductService {

private final ProductRepository productRepository;

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}

المزايا:

  • ✅ عدم التغيير (final)
  • ✅ قابلية الاختبار بسهولة
  • ✅ إلزامي (خطأ في الترجمة إذا كان مفقودًا)
  • ✅ موصى به من قبل Spring

2. حقن عن طريق الحقل (غير موصى به)​

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;
}

العيوب:

  • ❌ غير قابل للاختبار بسهولة
  • ❌ تبعيات مخفية
  • ❌ غير ثابت

3. حقن عن طريق Setter (نادر الاستخدام)​

@Service
public class ProductService {

private ProductRepository productRepository;

@Autowired
public void setProductRepository(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}

فصل الاهتمامات​

مبدأ SOLID: المسؤولية الواحدة​

يجب أن يكون لكل طبقة مسؤولية واحدة فقط.

❌ مثال سيء: كل شيء في المتحكم​

@RestController
@RequestMapping("/api/products")
public class ProductController {

@Autowired
private ProductRepository productRepository;

@PostMapping
public Product create(@RequestBody Product product) {
// منطق تجاري في المتحكم (سيء!)
if (product.getPrice() < 0) {
throw new IllegalArgumentException("سعر غير صالح");
}

// الوصول المباشر إلى المستودع (سيء!)
return productRepository.save(product);
}
}

المشاكل:

  • ❌ منطق تجاري في المتحكم
  • ❌ صعوبة في الاختبار
  • ❌ صعوبة في إعادة الاستخدام
  • ❌ انتهاك مبدأ المسؤولية الواحدة

✅ مثال جيد: فصل واضح​

// المتحكم: إدارة HTTP فقط
@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

@PostMapping
public Product create(@RequestBody Product product) {
return productService.createProduct(product);
}
}

// الخدمة: منطق تجاري
@Service
public class ProductService {

private final ProductRepository productRepository;

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

public Product createProduct(Product product) {
// تحقق تجاري
if (product.getPrice() < 0) {
throw new IllegalArgumentException("سعر غير صالح");
}

// استدعاء المستودع
return productRepository.save(product);
}
}

// المستودع: الوصول إلى البيانات
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
// يقوم Spring بإنشاء التنفيذ تلقائيًا
}

مثال متقدم: التنسيق​

يمكن لطبقة الخدمة تنسيق عدة مستودعات:

@Service
public class OrderService {

private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final CustomerRepository customerRepository;

public OrderService(OrderRepository orderRepository,
ProductRepository productRepository,
CustomerRepository customerRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
this.customerRepository = customerRepository;
}

public Order createOrder(Long customerId, Long productId, int quantity) {
// 1. التحقق من وجود العميل
Customer customer = customerRepository.findById(customerId)
.orElseThrow(() -> new RuntimeException("لم يتم العثور على العميل"));

// 2. التحقق من وجود المنتج
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("لم يتم العثور على المنتج"));

// 3. التحقق من المخزون
if (product.getStock() < quantity) {
throw new InsufficientStockException("المخزون غير كافٍ");
}

// 4. حساب السعر الإجمالي
double totalPrice = product.getPrice() * quantity;

// 5. تطبيق خصم إذا كان العميل مميزًا
if (customer.isPremium()) {
totalPrice = totalPrice * 0.9;
}

// 6. إنشاء الطلب
Order order = new Order();
order.setCustomer(customer);
order.setProduct(product);
order.setQuantity(quantity);
order.setTotalPrice(totalPrice);

// 7. تقليل المخزون
product.setStock(product.getStock() - quantity);
productRepository.save(product);

// 8. حفظ الطلب
return orderRepository.save(order);
}
}

إدارة المعاملات​

التعليق التوضيحي @Transactional​

@Service
public class OrderService {

@Transactional
public Order createOrder(Long customerId, Long productId, int quantity) {
// جميع العمليات في معاملة واحدة
// إذا تم إطلاق استثناء، يتم إلغاء كل شيء (rollback)

Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("لم يتم العثور على المنتج"));

product.setStock(product.getStock() - quantity);
productRepository.save(product);

Order order = new Order();
// ... إنشاء الطلب
return orderRepository.save(order);
}
}

المزايا:

  • ✅ اتساق البيانات
  • ✅ إلغاء تلقائي في حالة الخطأ
  • ✅ ACID (الذرة، الاتساق، العزل، المتانة)

أفضل الممارسات​

DO (ما يجب فعله)​

✅ حقن عن طريق المنشئ

public ProductService(ProductRepository repository) {
this.repository = repository;
}

✅ طرق بأسماء واضحة

public Product createProduct(Product product)
public void activateUser(Long userId)
public double calculateTotalPrice(Order order)

✅ التحقق التجاري في الخدمة

if (product.getPrice() < 0) {
throw new IllegalArgumentException("سعر غير صالح");
}

✅ إدارة الاستثناءات المناسبة

return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));

DON'T (ما يجب تجنبه)​

❌ منطق تجاري في المتحكم

// سيء: في المتحكم
@PostMapping
public Product create(@RequestBody Product product) {
if (product.getPrice() < 0) { // منطق تجاري!
throw new Exception();
}
return service.create(product);
}

❌ الوصول المباشر إلى المستودع من المتحكم

// سيء
@RestController
public class ProductController {
@Autowired
private ProductRepository repository; // مباشر!
}

❌ حقن عن طريق الحقل

// سيء
@Autowired
private ProductRepository repository;

تمارين عملية​

التمرين 1: خدمة أساسية​

الهدف: إنشاء طبقة خدمة كاملة

التعليمات:

  1. أنشئ كيان Student:
    • Long id
    • String firstName
    • String lastName
    • String email
    • int grade
  2. أنشئ StudentRepository
  3. أنشئ StudentService مع:
    • جميع عمليات CRUD
    • التحقق: الدرجة بين 0 و 100
    • التحقق: بريد إلكتروني صالح
  4. أنشئ StudentController الذي يستخدم الخدمة

التمرين 2: التنسيق​

الهدف: تنسيق عدة مستودعات

التعليمات:

  1. أنشئ Library (id, name, address)
  2. أنشئ Book (id, title, author, libraryId)
  3. أنشئ LibraryService مع طريقة:
    public Library createLibraryWithBooks(Library library, List<Book> books)
    • إنشاء المكتبة
    • إنشاء جميع الكتب المرتبطة
    • كل شيء في معاملة واحدة

ملخص الفصل​

النقاط الرئيسية​

  • تحتوي طبقة الخدمة على المنطق التجاري
  • فصل الاهتمامات: المتحكم → الخدمة → المستودع
  • حقن عن طريق المنشئ (موصى به)
  • @Transactional لاتساق البيانات
  • التحقق في الخدمة، وليس في المتحكم

ما تعرفه الآن​

✅ دور ومسؤوليات طبقة الخدمة
✅ فصل الاهتمامات
✅ حقن التبعيات
✅ تنسيق عدة مستودعات
✅ أفضل الممارسات المهنية


الفصل التالي: التحقق من البيانات

العودة إلى الوحدة 3