I recently decided to try to work with a database without an additional layer like ORMs. The problem that currently bothers me is how to convert flat SQL query results into domain models with nested entities.
Let me show what I mean
I have a domain entity WorkingDay
(with nested entities Appointment[]
, Break[]
) that looks like this in my application
WorkingDay (Entity/AggregateRoot)
- id
- date
- start_time
- end_time
- appointments: Appointment[]
- breaks: Break[]
Appointment (Entity)
- id
- start_time
- end_time
- services: AppointmentService[]
AppointmentService (Entity)
- id
- name
- price
- duration
Break (Entity)
- id
- start_time
- end_time
All the data needed to create this "aggregate" is contained in several SQL tables
- working_days (table)
- appointments (table)
- breaks (table)
- appointment_services (table)
This is the the query that I currently have
SELECT WORKING_DAYS.ID,
WORKING_DAYS.BEAUTY_PAGE_ID,
WORKING_DAYS.SPECIALIST_ID,
WORKING_DAYS.DATE,
WORKING_DAYS.START_TIME,
WORKING_DAYS.END_TIME,
WORKING_DAYS.CREATED_AT,
WORKING_DAYS.UPDATED_AT,
BREAKS.ID AS BREAK_ID,
BREAKS.START_TIME AS BREAK_START_TIME,
BREAKS.END_TIME AS BREAK_END_TIME,
APPOINTMENTS.ID AS APPOINTMENT_ID,
APPOINTMENTS.CLIENT_ID AS APPOINTMENT_CLIENT_ID,
APPOINTMENTS.START_TIME AS APPOINTMENTS_START_TIME,
APPOINTMENTS.END_TIME AS APPOINTMENTS_END_TIME,
APPOINTMENTS.STATUS AS APPOINTMENTS_STATUS,
APPOINTMENT_SERVICES.ID AS APPOINTMENT_SERVICE_ID,
APPOINTMENT_SERVICES.NAME AS APPOINTMENT_SERVICE_NAME,
APPOINTMENT_SERVICES.PRICE AS APPOINTMENT_SERVICE_PRICE,
APPOINTMENT_SERVICES.DURATION AS APPOINTMENT_SERVICE_DURATION
FROM WORKING_DAYS
LEFT OUTER
JOIN BREAKS ON BREAKS.WORKING_DAY_ID = WORKING_DAYS.ID AND BREAKS.REMOVED = FALSE
LEFT OUTER
JOIN APPOINTMENTS ON APPOINTMENTS.WORKING_DAY_ID = WORKING_DAYS.ID AND APPOINTMENTS.STATUS = 'PENDING'
LEFT OUTER
JOIN APPOINTMENT_SERVICES ON APPOINTMENTS.ID = APPOINTMENT_SERVICES.APPOINTMENT_ID AND APPOINTMENT_SERVICES.REMOVED = FALSE
WHERE WORKING_DAYS.SPECIALIST_ID = '91404483-d7dc-4d7b-9781-118c8e31e646'
AND WORKING_DAYS.DATE = '2030-01-01';
And the result is something like this
id | beauty_page_id | specialist_id | date | start_time | end_time | created_at | updated_at | break_id | break_start_time | break_end_time | appointment_id | appointment_client_id | appointment_start_time | appointment_end_time | appointment_status | appointment_service_id | appointment_service_name | appointment_service_price | appointment_service_duration |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
Asterisk (*) is a mocked data
In my application, these rows will be represented as an array of rows and require me manually to traverse this array and make a conversation into a domain model.
It seems like a common issue and all ORMs must handle this under the hood. The question is there a better approach to converting flat SQL response into a nested domain model?
I saw that SQL allows returning JSON which supports nested values, but I'm not sure if this is the correct way.
P.S. I use sqlx + PostgreSQL and Rust in my project