I've been struggling to figure out a clean way to validate things when objects have complex relationships and the validation requires expensive operations (e.g., making DB queries or other RPCs).
Looking around, I couldn't quite find something that answers this question, so here goes. I've read a bit about domain driven design, domain models, service layers, data mappers, transaction scripts, and anemic vs rich domain models; there's a lot and it's overwhelming with the various opinions.
As an example, let's say we have an Document
object, and it has a list of usernames, but you're only allowed to have usernames for active users. You check if a user is active by calling to an external UserService
. This example can easily grow as you add e.g., attachments, sharing settings, etc, that can only be used with a document under certain conditions and also have their own internal invariants/validation, or require more calls to the database or other external services.
But, just where does/should that validation take place?
- At the level where user input is collected? Leaving the chance for other input points or more internal code to ignore this rule (or more likely, copy/paste slightly different version of it around)?
- On the
Document
object itself? Which results in a strange binding between theDocument
object and the heavy-weight dependency (e.g., as a constructor arg, or a required arg to someisValid
function) - Way down in a persistence layer, so it's always enforced, but muddling persistence with business logic?
- In some middle layer? Still leaving the chance for more internal code to go directly to the persistence layer and skip the rule?
As complexity grows, -- let's say saving a Document
now has a feature where it'll automatically create an Attachment
(which can be created independently and also have their own internal invariants/validation) -- how do you structure/design/architect the application to avoid the sort of problems/short-comings as mentioned above as things grow and change?