It sounds like you have a problem with the "API" first approach (not the design pattern, but the knowledge of the infrastructure).
If you forget that you have an API, how would you handle this? The HTTP layer shouldn't know anything about the validation. If you decide to expose the functionality over gRPC for example, you should not have to change anything other than core method calls.
I imagine you have some sort of filter/middleware that makes a DB request and aborts if the request fails. Stay away from such approaches as they fracture your domain logic and hide it away from you.
Which brings me to the main point: validation is not a layer. It is a domain rule and as such should be encapsulated inside the domain logic. Instead of thinking about the API like this: http request -> validation -> (db ->) (http controller ->) business logic -> db -> http response
, think of it like http request -> business -> http response
. The API layer should not know about the validation. It should only map the parameters to the business logic calls.
If you know NodeJS for example, don't do the following:
app.post('path', validateStuff, (req, res) => {
// req mapping
business(mappedParam)
})
instead do:
app.post('path', (req, res) => {
// req mapping
const result = business(mappedParam)
// result mapping (mostly for exception handling)
res.json(mappedResult)
})
function business(param) {
validateStuff(param)
// business stuff
return result
}
Having this leads you to fetch the data first and then validate it.
Another thing to think about is that even if your architecture is a bit backwards, and even if it does too much requests to the DB, does it matter? Is the impact significant enough that you have to optimize it? Because introducing caching is way more complex and refactoring is sometimes just not worth the effort. Remember, the current code works, if it doesn't interfere with nothing else, why change it, just take a note that what you did was not optimal and do not repeat the mistake.