بالدرس السابق بنينا الـ Controller مع الـ Error Handling. اليوم رح ننتقل على الـ Service Layer ونفهم ليش هي ضرورية وكيف نبنيها.
بالدروس السابقة، الـ Controller كان يعمل حرفياً كل شي - يحفظ البيانات، يبحث، يعدّل. هذا غلط لأن كل Layer لازم يكون عنده وظيفة واضحة.
وظيفة الـ Controller هي:
وظيفة الـ Service هي التعامل مع الـ Business Requirements - يعني الخطوات اللي البزنس بيطلبها.
مثلاً بـ Spotify لما شخص جديد يشترك:
هاد المجموعة من الخطوات هي الـ Business Requirements. وين لازم تصير؟ بالـ Service، مش بالـ Controller.
قبل ما نبني الـ Service، لازم نفهم مفهوم Dependency Injection.
المشكلة: إذا عنا كلاس يعتمد على كلاس ثاني بشكل مباشر، بصير الكود مرتبط ببعض (Tightly Coupled). مثلاً:
public class OrderService {
private PaypalService paypal = new PaypalService(); // مرتبط بـ PayPal فقط
}
لو بدنا ندعم Credit Card، لازم نغير الكود كله.
الحل هو استخدام Interface:
public interface PaymentService {
void processPayment(double
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
هيك الـ OrderService بيعتمد على الـ Interface مش على تطبيق معين، وبنقدر نغير الـ Implementation بدون ما نغير الكود.
Spring Boot بيعمل هالـ Injection تلقائياً - هذا ما نسميه Inversion of Control (IoC).
بننشئ Package اسمها abstracts وفيها Interface:
public interface EmployeeService {
ArrayList<Employee> findAll();
Employee findOne(UUID employeeId);
Employee create(Employee employee);
Employee updateOne(UUID employeeId, Employee employee);
void deleteOne(UUID employeeId);
}الـ Interface هو "العقد" - كل Implementation لازم يلتزم بهالـ Methods.
بننشئ Package اسمها services وفيها الـ Implementation:
@Service
public class EmployeeServiceImpl implements EmployeeService {
private ArrayList<Employee> employees = new ArrayList<>();
@Override
public ArrayList<Employee> findAll() {
return employees;
}
@Override
public Employee findOne(UUID employeeId) {
return employees.stream()
.filter(emp -> emp.getId().equals(employeeId))
.findFirst()
.orElseThrow(() -> CustomResponseException.resourceNotFound(
"Employee with ID " + employeeId + " not found"
));
}
@Override
public Employee create(Employee employee) {
employee.setId(UUID.randomUUID());
employees.add(employee);
return employee;
}
@Override
public Employee updateOne(UUID employeeId, Employee updatedEmployee) {
Employee existing = findOne(employeeId);
existing.setFirstName(updatedEmployee.getFirstName());
existing.setLastName(updatedEmployee.getLastName());
existing.setEmail(updatedEmployee.getEmail());
existing.setPosition(updatedEmployee.getPosition());
return existing;
}
@Override
public void deleteOne(UUID employeeId) {
employees.removeIf(emp -> emp.getId().equals(employeeId));
}
}@Service - Annotation بتخبر Spring Boot إنو يسجّل هذا الكلاس بالـ IoC Container، وعندما يكون في كلاس ثاني يحتاجه يعمل Injection تلقائياًفي طريقتين لعمل الـ Injection:
Constructor Injection (المفضلة):
@RestController
@RequestMapping("/employees")
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
}Field Injection:
@RestController
@RequestMapping("/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
}كلا الطريقتين صح، الاختيار يرجع لك.
بعد ما نتقل كل المنطق للـ Service، الـ Controller صار نظيف:
@RestController
@RequestMapping("/employees")
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@GetMapping
public ResponseEntity<GlobalResponse<ArrayList<Employee>>> findAll() {
var employees = employeeService.findAll();
return new ResponseEntity<>(new GlobalResponse<>(employees), HttpStatus.OK);
}
@GetMapping("/{employeeId}")
public ResponseEntity<GlobalResponse<Employee>> findOne(@PathVariable UUID employeeId) {
var employee = employeeService.findOne(employeeId);
return new ResponseEntity<>(new GlobalResponse<>(employee), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<GlobalResponse<Employee>> create(
@Valid @RequestBody Employee employee) {
var created = employeeService.create(employee);
return new ResponseEntity<>(new GlobalResponse<>(created), HttpStatus.CREATED);
}
@PutMapping("/{employeeId}")
public ResponseEntity<GlobalResponse<Employee>> update(
@PathVariable UUID employeeId,
@Valid @RequestBody Employee employee) {
var updated = employeeService.updateOne(employeeId, employee);
return new ResponseEntity<>(new GlobalResponse<>(updated), HttpStatus.OK);
}
@DeleteMapping("/{employeeId}")
public ResponseEntity<Void> delete(@PathVariable UUID employeeId) {
employeeService.deleteOne(employeeId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}الـ Controller الآن مسؤول فقط عن:
بالدرس الجاي رح نتصل فعلياً بقاعدة البيانات.