I'm refactoring a settlements module in a large healthcare application. I'm trying to follow DDD.
Let me give you a short and simplified description of the refactored code:
- A patient has an appointment during which they undergo several medical procedures. Every procedure has its price.
- The patient can pay money to the clinic (either for the procedures, or some medical products)
- When they pay for procedures, the procedures become accounted
- An invoice or a receipt is issued
- Some of the money the patient pays is assigned to the doctor
The points above basically define the main processes and my refactoring scopes processes 2-5.
The processes are more complicated then it seems (loads of domain logic). In order to decouple the logic I decided to treat every process as a small sub-system. And for every sub-system I created a separate aggregate. Let me present it with some pseudo-code:
// Patient pays for whatever they're charged for
class PatientForPayments : AggregateRoot {
private Payment[] Payments
public Pay(amount){...}
}
// You can use the payment to account procedures in an appointment
class Appointment : AggregateRoot {
private Procedure[] Procedures
public IsFullyAccounted(){...}
public Account(payment){...} // use the payment to account as many procedures as possible with the money paid
}
// An invoice can be issued
class PatientForInvoices : AggregateRoot {
private Invoice[] Invoices
public IssueInvoice(payment, procedures[]){...}
}
// Settle the money with a doctor
class ProcedureForSettlements : AggregateRoot {
private Payment Payment
private Doctor Doctor
private Procedure Procedure
private decimal Value
}
I'm wondering what's the best way to orchestrate it.
- Some of the steps are optional - they depend on user input (e.g. issuing an invoice)
- There is still some temporal coupling (e.g. you cannot issue an invoice for a payment which doesn't yet exist)
- Some of the operations should go together (e.g. when a procedure gets accounted, we should settle with the doctor)
For now I orchestrated it in a domain service, called directly form the UI (code behind in a legacy WinForms app). Some more pseudo-code:
class PatientPaymentDomainService {
public void HandlePayment(bool isInvoiceNeeded, PaymentDto paymentDto){
payment = CreatePayment(paymentDto)
database.Save(payment)
appointment = database.GetAppointment(paymentDto.AppointmentId)
appointment.Account(payment)
database.Save(appointment)
if(isInvoiceNeeded){
patient = database.GetPatient(paymentDto.PatientId)
patient.IssueInvoice(payment)
database.Save(patient)
}
for(procedureDto in paymendDto.ProceduresDtos) {
doctorSettlement = CreateDoctorSettlement(procedureDto, payment)
database.Save(doctorSettlement)
}
}
...
}
The code above definitely encapsulates some domain logic (the workflow), so I believe it belongs to the domain layer and I would not put it the ViewModel / CodeBehind.
Putting everything in a single aggregate doesn't make much sense. I'd probably end up where I started - a huge blob of tangled and coupled logic.
However orchestrating it with domain events and handlers kind of makes sense.
What do you think? How would you model it?