Northline Equipment Rentals: a three-year-old inheritance hierarchy
Northline Equipment Rentals runs a Mendix 10 application on a single PostgreSQL database. The domain model has a Vehicle entity (plate, dailyRate, notes) that is generalized into Car, Van and Truck; Reservation is associated with Vehicle, MaintenanceRecord is associated with Vehicle, and Inspection exists only for trucks. Over three years the team added 14 attributes to Vehicle that only one specialization uses - for example axles and tonnage, which only trucks need. The fleet is 2,400 vehicles and Reservation holds 380,000 rows.
The fleet overview page performs three separate retrieves (all Cars, all Vans, all Trucks), merges the three lists in a microflow, and then filters them by an availability date range before handing the result to the data grid. The page takes 11 seconds to open. Separately, deleting a retired truck fails whenever inspections exist, but when the same action is performed on a truck without inspections it succeeds and silently removes that truck's maintenance history.
1. Assess whether generalizing `Vehicle` into `Car`, `Van` and `Truck` is the right structure, and decide where the 14 type-specific attributes belong. Explain the consequences of your choice for the overview page and for validation.
Read the worked answer
Keep the generalization. It is what lets Reservation and MaintenanceRecord be associated once with a single Vehicle instead of three times, and it lets the overview page retrieve one entity rather than merging three lists. Mendix has no interface construct, so a generalization is the only built-in polymorphism mechanism; retrieving on Vehicle returns all subtypes and specialization pages give you the type-specific forms.
Move the 14 attributes onto the specializations. They are currently polluting the base entity:
- The overview grid shows columns that are empty for most rows, and nobody can tell from the domain model which type actually uses a field.
- Validation rules and event handlers defined on
Vehiclefire for every subtype, so a truck-only rule accidentally blocks van edits. - Every vehicle row carries nullable columns it will never use.
The cost you must plan for: a retrieve on Vehicle pulls together base and specialization data, so filtering on a subtype-only attribute in a base-entity XPath is not possible. Where the overview needs tonnage, either retrieve Truck directly, or add a small base attribute that encodes the discriminator (an enumeration VehicleType) and constrain on that. Type-specific edit pages should have the specialization as their context entity so the form engine binds the right attributes.
Finally, Inspection being truck-only is correct where it is: a 1–* association from Truck to Inspection expresses the constraint; putting it on Vehicle would not.
2. The overview takes 11 seconds. Propose a retrieval and filtering design that eliminates the three-way merge and the microflow-side date filtering, and state which indexes you would create and why.
Read the worked answer
Eliminate the merge first. Three retrieves plus a list merge forces the whole fleet into memory and destroys database-side sorting and paging; the data grid can no longer apply offset/limit efficiently. Use one retrieve on Vehicle with an XPath that expresses both the type and the availability condition, and let the data grid's XPath data source do the paging.
Availability. The condition is 'no overlapping reservation'. Two viable designs:
- XPath sub-constraint - constrain on the associated reservations, e.g. a bracket on the
Reservationassociation that excludes vehicles with an overlap for$Start/$End. This is simple and works at 2,400 vehicles, but association-heavy XPath in a security or data-source constraint is expensive to evaluate per row. - Materialized availability - a scheduled event (nightly, plus on reservation change) writes one row per vehicle per day or per week into a persisted
VehicleAvailabilityentity with a boolean and a date. The overview then filters on a flat, indexed attribute. This is the design that survives growth to 20,000 vehicles and to real-time refreshes.
Indexes:
Reservation: composite index on (Vehicleassociation,StartDate,EndDate) - this is what makes both the overlap check and any reservation history screen fast.VehicleAvailability: composite on (Date,Available).Vehicle: index onStatusif the default page filter uses it, and on the sort column used by the grid (e.g.plate), because sorting an unindexed column on every page load forces a full sort.
Only add indexes you can justify: each one slows inserts and consumes space, and an index on a low-selectivity column such as a two-value status rarely pays off unless it is part of a composite.
3. Design the delete behavior for `Vehicle` with respect to `Reservation`, `MaintenanceRecord` and `Inspection`, and identify the risks of the current setting.
Read the worked answer
Reservation: prevent deletion. Reservations are financial and contractual records; deleting a vehicle must not destroy them, and the user who deletes a vehicle from the overview may not even have read access to its reservations. Set the delete behavior so a vehicle with reservations cannot be removed, and handle the resulting error in a microflow that explains why ("3 open reservations; archive the vehicle instead").
MaintenanceRecord and Inspection: cascade, but only because the history is meaningless without the machine. If the business needs the history for compliance, archive it to a VehicleHistory entity in a microflow before deleting, then cascade.
The current behavior - failing on inspections but silently wiping maintenance history - is the opposite of what the business wants in both directions. It suggests the delete behavior was set per association by whoever created it, without a policy.
Risks to call out:
- Cascading deletes are executed by the runtime without running your business logic, and without a per-object permission check on the children. A user with delete rights on
Vehiclecan therefore remove child records they cannot see. - Because the cascade is a bulk database operation, you cannot rely on it to trigger the children's own event handlers or their own 'prevent' rules further down the chain. Anything that must happen on child deletion (file cleanup, audit logging) has to be done explicitly in a microflow before the delete.
- Restrict the delete right itself: give delete access on
Vehicleto the Admin module role only, and expose aACT_ArchiveVehiclemicroflow that performs the validation, archival and deletion in one auditable step.