الفصل 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);
}
}
النقاط الرئيسية
- @Service: تمييز الفئة كطبقة خدمة
- حقن عن طريق المنشئ: أنظف وأكثر قابلية للاختبار من @Autowired على الحقول
- التحقق التجاري: التحقق من القواعد قبل الوصول إلى البيانات
- إدارة الأخطاء: إطلاق الاستثناءات المناسبة
حقن التبعيات
ثلاث طرق للحقن
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: خدمة أساسية
الهدف: إنشاء طبقة خدمة كاملة
التعليمات:
- أنشئ كيان
Student:- Long id
- String firstName
- String lastName
- String email
- int grade
- أنشئ
StudentRepository - أنشئ
StudentServiceمع:- جميع عمليات CRUD
- التحقق: الدرجة بين 0 و 100
- التحقق: بريد إلكتروني صالح
- أنشئ
StudentControllerالذي يستخدم الخدمة
التمرين 2: التنسيق
الهدف: تنسيق عدة مستودعات
التعليمات:
- أنشئ
Library(id, name, address) - أنشئ
Book(id, title, author, libraryId) - أنشئ
LibraryServiceمع طريقة:public Library createLibraryWithBooks(Library library, List<Book> books)- إنشاء المكتبة
- إنشاء جميع الكتب المرتبطة
- كل شيء في معاملة واحدة
ملخص الفصل
النقاط الرئيسية
- تحتوي طبقة الخدمة على المنطق التجاري
- فصل الاهتمامات: المتحكم → الخدمة → المستودع
- حقن عن طريق المنشئ (موصى به)
- @Transactional لاتساق البيانات
- التحقق في الخدمة، وليس في المتحكم
ما تعرفه الآن
✅ دور ومسؤوليات طبقة الخدمة
✅ فصل الاهتمامات
✅ حقن التبعيات
✅ تنسيق عدة مستودعات
✅ أفضل الممارسات المهنية