اختبار: طبقة الخدمة والهندسة المعمارية
اختبر معرفتك بطبقة الخدمة، التحقق، وهندسة MVC.
30 سؤالاً - انقر على كل سؤال لكشف الإجابة.
الأسئلة 1-10: طبقة الخدمة
السؤال 1: ما هو دور طبقة الخدمة في هندسة MVC؟
الإجابة: تحتوي طبقة الخدمة على المنطق التجاري للتطبيق. هي:
- تنسق العمليات بين المتحكمات والمستودعات
- تطبق القواعد التجارية
- تدير المعاملات
- مستقلة عن بروتوكول HTTP
السؤال 2: ما هو التعليق التوضيحي المستخدم لإعلان خدمة Spring؟
الإجابة: @Service - هو نوع متخصص من @Component يشير إلى أن الفئة تحتوي على منطق تجاري.
@Service
public class UserService {
// منطق تجاري
}
السؤال 3: لماذا لا نضع المنطق التجاري في المتحكم؟
الإجابة: فصل المنطق التجاري عن المتحكم يسمح بـ:
- إعادة الاستخدام: يمكن استدعاء الخدمة من قبل عدة متحكمات
- قابلية الاختبار: اختبار المنطق بدون HTTP
- قابلية الصيانة: كود أكثر تنظيماً
- فصل المسؤوليات: كل طبقة لها دور محدد
السؤال 4: كيف يتم حقن مستودع في خدمة؟
الإجابة: عن طريق حقن المنشئ (موصى به):
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
السؤال 5: ما هو DTO (كائن نقل البيانات)؟
الإجابة: DTO هو كائن بسيط يستخدم لنقل البيانات بين الطبقات. هو:
- لا يحتوي على منطق تجاري
- يعرض فقط البيانات الضرورية
- يحمي الكيان JPA من التعرض المباشر
- يسمح بتنسيق البيانات لـ API
السؤال 6: لماذا نستخدم DTOs بدلاً من الكيانات JPA مباشرة؟
الإجابة:
- الأمان: عدم كشف الحقول الحساسة (كلمة المرور)
- الأداء: تجنب التحميل البطيء للعلاقات
- المرونة: تنسيق مختلف عن الكيان
- الفصل: التغييرات الداخلية لا تؤثر على API
- التحقق: قواعد محددة للإدخال/الإخراج
السؤال 7: كيف نحول كيان إلى DTO؟
الإجابة: عدة طرق:
// 1. طريقة يدوية
public UserDto toDto(User user) {
return new UserDto(user.getId(), user.getName(), user.getEmail());
}
// 2. باستخدام MapStruct
@Mapper
public interface UserMapper {
UserDto toDto(User user);
}
// 3. باستخدام ModelMapper
modelMapper.map(user, UserDto.class);
السؤال 8: ما الفرق بين RequestDTO و ResponseDTO؟
الإجابة:
- RequestDTO: بيانات واردة (إنشاء، تحديث)، يحتوي على التحقق
- ResponseDTO: بيانات صادرة، منسقة للعميل
// طلب
public class CreateUserRequest {
@NotBlank
private String email;
@Size(min = 8)
private String password;
}
// استجابة
public class UserResponse {
private Long id;
private String email;
// لا توجد كلمة مرور!
}
السؤال 9: كيف ندير الأخطاء في طبقة الخدمة؟
الإجابة: رفع استثناءات مخصصة:
@Service
public class UserService {
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
}
يتم التعامل مع هذه الاستثناءات بواسطة @ControllerAdvice.
السؤال 10: ما هو @Transactional على خدمة؟
الإجابة: @Transactional يدير المعاملات تلقائيًا:
- يبدأ معاملة في بداية الطريقة
- يلتزم إذا نجح
- يتراجع إذا كان هناك استثناء
@Service
@Transactional
public class OrderService {
// جميع الطرق تكون معاملاتية
}
الأسئلة 11-20: التحقق
السؤال 11: ما هي التبعية التي يجب إضافتها للتحقق؟
الإجابة: spring-boot-starter-validation
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
السؤال 12: كيف يتم تفعيل التحقق على متحكم؟
الإجابة: باستخدام @Valid أو @Validated على المعلمة:
@PostMapping
public ResponseEntity<User> create(@Valid @RequestBody CreateUserRequest request) {
// إذا فشل التحقق، يتم رفع MethodArgumentNotValidException
}
السؤال 13: ما هي التعليقات التوضيحية الشائعة للتحقق؟
الإجابة:
| التعليق التوضيحي | الوصف |
|---|---|
@NotNull | لا يجب أن يكون null |
@NotBlank | غير null، غير فارغ، غير مسافات بيضاء |
@NotEmpty | غير null وغير فارغ |
@Size(min, max) | الحجم بين min و max |
@Min, @Max | قيمة رقمية دنيا/قصوى |
@Email | تنسيق بريد إلكتروني صالح |
@Pattern | تعبير نمطي مخصص |
@Past, @Future | تاريخ في الماضي/المستقبل |
السؤال 14: كيف نخصص رسالة خطأ التحقق؟
الإجابة: باستخدام السمة message:
public class CreateUserRequest {
@NotBlank(message = "البريد الإلكتروني مطلوب")
@Email(message = "تنسيق البريد الإلكتروني غير صالح")
private String email;
@Size(min = 8, message = "يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل")
private String password;
}
السؤال 15: كيف ننشئ تحققًا مخصصًا؟
الإجابة: إنشاء تعليق توضيحي ومحقق:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
String message() default "البريد الإلكتروني مستخدم بالفعل";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
// تحقق مما إذا كان البريد الإلكتروني موجودًا
}
}
السؤال 16: ما الفرق بين @Valid و @Validated؟
الإجابة:
@Valid: معيار Java (JSR-380)، يطبق التحقق المتسلسل@Validated: Spring، يدعم مجموعات التحقق
// مجموعات التحقق
public interface OnCreate {}
public interface OnUpdate {}
@NotNull(groups = OnUpdate.class)
private Long id;
@Validated(OnCreate.class)
public void create(...) { }
السؤال 17: كيف نتحقق من معلمات الطلب (query params)؟
الإجابة: إضافة @Validated على المتحكم:
@RestController
@Validated
public class UserController {
@GetMapping("/users")
public List<User> search(
@RequestParam @Min(0) int page,
@RequestParam @Max(100) int size) {
// ...
}
}
السؤال 18: كيف نتحقق من كائن متداخل؟
الإجابة: استخدام @Valid على الحقل المتداخل:
public class OrderRequest {
@Valid // يطبق التحقق المتسلسل
@NotNull
private AddressDto shippingAddress;
}
public class AddressDto {
@NotBlank
private String street;
@NotBlank
private String city;
}
السؤال 19: كيف نسترجع أخطاء التحقق؟
الإجابة: عبر BindingResult أو @ExceptionHandler:
// الخيار 1: BindingResult
@PostMapping
public ResponseEntity<?> create(@Valid @RequestBody Request req, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().body(result.getAllErrors());
}
}
// الخيار 2: ControllerAdvice (موصى به)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
// استخراج الأخطاء
}
السؤال 20: ما هو التحقق على مستوى الخدمة؟
الإجابة: التحقق من المنطق التجاري (ليس فقط التنسيق):
@Service
public class OrderService {
public Order createOrder(OrderRequest request) {
// تحقق تجاري
if (request.getQuantity() > productService.getStock(request.getProductId())) {
throw new InsufficientStockException("المخزون غير كافٍ");
}
// ...
}
}
الأسئلة 21-30: الهندسة المعمارية
السؤال 21: ما هي الطبقات الثلاث الرئيسية في هندسة MVC؟
الإجابة:
- Controller (العرض): يدير HTTP، التحقق من الإدخال
- Service (التجاري): المنطق التجاري، المعاملات
- Repository (البيانات): الوصول إلى قاعدة البيانات
العميل → المتحكم → الخدمة → المستودع → قاعدة البيانات
السؤال 22: في أي طبقة توضع إدارة المعاملات؟
الإجابة: في طبقة الخدمة. هنا يوجد المنطق التجاري الذي يحدد ما يشكل وحدة عمل ذرية.
@Service
@Transactional
public class TransferService {
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// العملية بأكملها ذرية
}
}
السؤال 23: كيف يتم تنظيم الحزم في مشروع Spring Boot؟
الإجابة: حسب الطبقة أو حسب الوظيفة:
// حسب الطبقة
com.example.app
├── controller/
├── service/
├── repository/
├── model/
└── dto/
// حسب الوظيفة (موصى به للمشاريع الكبيرة)
com.example.app
├── user/
│ ├── UserController
│ ├── UserService
│ └── UserRepository
├── order/
└── product/
السؤال 24: ما هو مبدأ المسؤولية الواحدة (SRP)؟
الإجابة: يجب أن يكون للفئة سبب واحد فقط للتغيير. كل فئة لها مسؤولية واحدة:
- المتحكم: إدارة طلبات HTTP
- الخدمة: المنطق التجاري
- المستودع: الوصول إلى البيانات
مثال سيء: متحكم يقوم بكل شيء (HTTP + تجاري + قاعدة بيانات).
السؤال 25: لماذا نستخدم الواجهات للخدمات؟
الإجابة:
- الفصل: يعتمد العملاء على التجريد
- قابلية الاختبار: سهل المحاكاة
- المرونة: تغيير التنفيذ
- الوكالة: يعمل Spring AOP بشكل أفضل
public interface UserService {
User findById(Long id);
}
@Service
public class UserServiceImpl implements UserService { }
السؤال 26: ما هو @ControllerAdvice؟
الإجابة: مكون يدير الاستثناءات عالميًا لجميع المتحكمات:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException ex) {
return ResponseEntity.status(404).body(new ErrorResponse(ex.getMessage()));
}
}
السؤال 27: كيف ننظم استجابة خطأ قياسية؟
الإجابة: إنشاء DTO خطأ متسق:
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
}
// أو تنسيق ProblemDetail (RFC 7807)
السؤال 28: ما الفرق بين @RestController و @Controller؟
الإجابة:
@Controller: يعيد وجهات نظر (HTML)@RestController: يعيد بيانات (JSON/XML)
@RestController = @Controller + @ResponseBody
@RestController // كل شيء JSON
public class UserController { }
@Controller
public class ViewController {
@GetMapping("/")
public String home() {
return "index"; // وجهة Thymeleaf
}
}
السؤال 29: كيف ندير الرموز HTTP المناسبة؟
الإجابة: استخدام ResponseEntity:
@PostMapping
public ResponseEntity<User> create(@RequestBody UserRequest req) {
User user = userService.create(req);
return ResponseEntity
.status(HttpStatus.CREATED) // 201
.body(user);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build(); // 204
}
السؤال 30: ما هي الرموز HTTP الشائعة في REST؟
الإجابة:
| الرمز | المعنى | الاستخدام |
|---|---|---|
| 200 | OK | GET، PUT ناجح |
| 201 | Created | POST ناجح |
| 204 | No Content | DELETE ناجح |
| 400 | Bad Request | فشل التحقق |
| 401 | Unauthorized | غير مصرح |
| 403 | Forbidden | غير مسموح |
| 404 | Not Found | المورد غير موجود |
| 500 | Server Error | خطأ داخلي |
النتيجة
| النتيجة | المستوى |
|---|---|
| 27-30 | ممتاز - إتقان الهندسة |
| 21-26 | جيد - بعض المراجعات |
| 15-20 | متوسط - اقرأ الفصول مرة أخرى |
| أقل من 15 | بحاجة إلى مراجعة - أعد الأساسيات |