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

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

اختبر معرفتك بطبقة الخدمة، التحقق، وهندسة 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 مباشرة؟

الإجابة:

  1. الأمان: عدم كشف الحقول الحساسة (كلمة المرور)
  2. الأداء: تجنب التحميل البطيء للعلاقات
  3. المرونة: تنسيق مختلف عن الكيان
  4. الفصل: التغييرات الداخلية لا تؤثر على API
  5. التحقق: قواعد محددة للإدخال/الإخراج

السؤال 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؟

الإجابة:

  1. Controller (العرض): يدير HTTP، التحقق من الإدخال
  2. Service (التجاري): المنطق التجاري، المعاملات
  3. 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: لماذا نستخدم الواجهات للخدمات؟

الإجابة:

  1. الفصل: يعتمد العملاء على التجريد
  2. قابلية الاختبار: سهل المحاكاة
  3. المرونة: تغيير التنفيذ
  4. الوكالة: يعمل 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؟

الإجابة:

الرمزالمعنىالاستخدام
200OKGET، PUT ناجح
201CreatedPOST ناجح
204No ContentDELETE ناجح
400Bad Requestفشل التحقق
401Unauthorizedغير مصرح
403Forbiddenغير مسموح
404Not Foundالمورد غير موجود
500Server Errorخطأ داخلي

النتيجة​

النتيجةالمستوى
27-30ممتاز - إتقان الهندسة
21-26جيد - بعض المراجعات
15-20متوسط - اقرأ الفصول مرة أخرى
أقل من 15بحاجة إلى مراجعة - أعد الأساسيات

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