Strengthening Domain Logic: An Amelioration Journey in Java's rdv_Medecin Project
Project Context: rdv_Medecin
The rdv_Medecin project is an application designed to manage doctor appointments. As with any system handling critical scheduling and patient data, ensuring data integrity and consistent application of business rules is paramount. Recent development focused on an "amelioration" effort, aiming to enhance the robustness and maintainability of the core domain logic, particularly around appointment scheduling and related entities.
The Problem
Initially, business logic and data validation could be found scattered across various layers of the application. This often led to:
- Duplication: Similar validation checks implemented in multiple places.
- Inconsistency: Subtle differences in how business rules were applied, leading to potential data corruption or unexpected behavior.
- Rigidity: Making changes to a business rule often required modifying several different files, increasing the risk of introducing new bugs.
- Anemic Domain Models: Core domain entities lacked the behavior they should encapsulate, making services overly complex.
The Approach
Our "amelioration" tackled these issues by embracing principles from Domain-Driven Design (DDD). The goal was to centralize validation, encapsulate business rules directly within the domain model, and improve the overall clarity and maintainability of the rdv_Medecin application.
Phase 1: Centralizing Input Validation
The first step involved ensuring that invalid data couldn't even reach the core domain. We introduced dedicated request objects for API endpoints and consolidated validation logic within a dedicated service layer or by using annotations, ensuring that raw, unvalidated input is transformed into valid domain-specific parameters.
public class ScheduleAppointmentRequest {
@NotNull(message = "Patient ID is required")
private String patientId;
@NotNull(message = "Doctor ID is required")
private String doctorId;
@NotNull(message = "Start time is required")
private LocalDateTime startTime;
@NotNull(message = "End time is required")
private LocalDateTime endTime;
// Getters and Setters
}
// In a service method:
public Appointment schedule(ScheduleAppointmentRequest request) {
// Validation logic can be external or via annotations handled by framework
// ... then convert request to domain objects ...
}
This approach ensures that initial data integrity checks are performed upfront, reducing complexity in the core domain logic.
Phase 2: Encapsulating Business Rules within Entities
To combat anemic domain models, core business rules were moved directly into the Appointment and Doctor entities (or their aggregate roots). For instance, the logic to check for slot availability or to reschedule an appointment now resides within the Appointment entity itself, rather than being managed by a separate service.
public class Appointment {
private String appointmentId;
private String patientId;
private String doctorId;
private TimeSlot timeSlot;
private AppointmentStatus status;
public boolean canBeScheduled(Doctor doctor, List<Appointment> existingAppointments) {
// Rule: Check if doctor is available in time slot
if (!doctor.isAvailable(timeSlot)) {
return false;
}
// Rule: Check for overlapping appointments
for (Appointment existing : existingAppointments) {
if (timeSlot.overlapsWith(existing.getTimeSlot())) {
return false;
}
}
return true;
}
public void reschedule(TimeSlot newTimeSlot, Doctor doctor, List<Appointment> existingAppointments) {
if (!canBeScheduled(doctor, existingAppointments)) {
throw new IllegalArgumentException("New time slot is not valid.");
}
this.timeSlot = newTimeSlot;
// ... update status or other fields ...
}
}
By placing business logic where it belongs, the domain model becomes a true representation of the business, making it more expressive and less prone to errors.
Phase 3: Leveraging Value Objects for Immutability
For concepts that represent descriptive aspects of the domain and lack identity, such as a TimeSlot or a PatientId, Value Objects were introduced. These are immutable objects that ensure consistency and prevent invalid states by design, simplifying their usage and comparison.
public record TimeSlot(LocalDateTime start, LocalDateTime end) {
public TimeSlot {
if (start.isAfter(end)) {
throw new IllegalArgumentException("Start time cannot be after end time");
}
}
public boolean overlapsWith(TimeSlot other) {
return !this.end.isBefore(other.start) && !this.start.isAfter(other.end);
}
}
public record PatientId(String value) {
public PatientId {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("Patient ID cannot be empty");
}
// Add format validation if needed
}
}
Value Objects make the code safer and more readable, as their validity is guaranteed upon creation.
Key Insight
The "amelioration" highlighted that consistent application of Domain-Driven Design principles, even in iterative steps, dramatically improves the maintainability and reliability of a core application like rdv_Medecin. By centralizing validation, encapsulating business rules, and using Value Objects, we've built a domain model that is robust, expressive, and easier to evolve.
Actionable Takeaway: Start small by identifying a critical business rule or a frequently validated data point. Implement a Value Object or move the rule into its corresponding domain entity. Observe how this change simplifies surrounding code and expand from there. Consistent application of DDD principles leads to significantly more stable and understandable software over time.
Generated with Gitvlg.com