Result and EjError
Here is the single move that most separates Fusion code from slop. Domain validation never throws. It returns a Result<T>: either a success carrying a value, or a failure carrying a list of errors. The caller inspects the Result and decides what to do. No try/catch for expected, everyday validation failures.
An error is a small record. EjError has a Field (which part failed) and a Message (why). There are typed subtypes so a caller can branch on the KIND of failure, not on the message text.
Result<T> itself gives you IsSuccess / IsFailure, the Errors list, and a Value. Reading Value on a failed result throws, on purpose: it's a programming error to look at a value that isn't there, so it fails loudly rather than handing you a default. You construct results with the static Success and Failure factories.
Try it yourself
Read a Result correctly
Wire your fingers to check before you access. Sketch this (in a scratch file or on paper) against the real API above.
Tick every step to confirm you did it.
Type over message
This is a named anti-pattern; have it cold.
An endpoint needs to return 404 for a missing flight and 504 for a timeout. What does it branch on, and what must it never branch on?