Command-Query Separation is the term for that. However, I find this statement odd:
> having functions that do things without verifying preconditions are exploitable
Why would you do this? The separation between commands and queries does not mean that executing a command must succeed. It can still fail. Put queries inside the commands (but do not return the query results, that's the job of the query itself) and branch based on the results. After executing a command which may fail, you can follow it with a query to see if it succeeded and, if not, why not.
I think CQRS is something different than what’s being described here. “Query” code in CQRS can still “do stuff”: call an external database, grab locks, audit trail recording etc.
What’s being described here is something lower level, that you keep as much code as you can as a side-effect-free “pure functional core”. That pattern is useful both for the “command” and “query” side of a CQRS system, and is not the same thing as CQRS
If by "described here" you mean the article, yes, it is not about CQRS or CQS. I was responding to hinkley who was referencing CQS as defined by (or at least popularized by) Meyer in his Eiffel language and books on OO programming.
The fact that “query” and “ask” are synonyms in English does not make the patterns the same.
The key design goal in this thread was to create a pure functional core, which you can “ask” things of. That pattern is useful on both the command and query side of a CQRS system, and a different thing from splitting up mutating and reading operations as CQRS proposes
Maybe I misunderstand you though. Say you have a CQRS system that reads and writes to a database. Are you proposing the query side be implemented in pure side-effect-free functional code? How should the pure code make the network calls to the database?
That is not something that’s necessary for all CQRS systems, but maybe is something you’ve heard for the subset that people call “Event Sourcing”? There it’s a design goal that the system only records events that are occurring, so there’s no domain level validation that can be done on the command path - the user pressed the button whether we like it or not, so to speak. Whether the event has the intended effect is worked out after the event is recorded.
But there’s nothing in the more general idea of “separate reads from writes” that mandates “no validation on writes”
Commands can validate their input in CQS. What they don't do, in strict CQS, is return values. They can set state which can then be queried after execution which can let you retrieve an updated result or check to see if an error occurred during execution or whatever.
In asynchronous environments, you may not be able to repeat the same query with the same result (unless you control a cache of results, which has its own issues). If some condition is determined by the command’s implementation that subsequent code is interested in (a condition that isn’t preventing the command from succeeding), it’s generally more robust for the command to return that information to the caller, who then can make use of it. But now the command is also a query.
I can’t decide if this really is the biggest problem with CQS. Certainly the wiki page claims it is, and it’s a reasonable argument. For some simpler cases you could dodge it by wrapping the function pairs/tuples in a lock. Database calls are a bit sketchy, because a transaction only “fixes” the problem if you ignore the elephant in the room which is reduced system parallelism by a measurable amount because even in an MVCC database transactions aren’t free. They’re just cheaper.
Caches always mess up computational models because they turn all reads into writes. Which makes things you could say with static analysis no longer true. I know a lot of tricks for making systems faster and I’ve hardly ever seen anyone apply most of them to systems after caching was introduced. It has one upside and dozens of downsides as bad or worse than this one.
> it’s generally more robust for the command to return that information to the caller, who then can make use of it. But now the command is also a query.
You don't need the command to return anything (though it can be more efficient or convenient). It can set state indicating, "Hey, I was called but by the time I tried to do the thing the world and had changed and I couldn't. Try using a lock next time."
if (query(?)) {
command(x)
result := status(x) // ShouldHaveUsedALockError
}
The caller can still obtain a result following the command, though it does mean the caller now has to explicitly retrieve a status rather than getting it in the return value.
Where is that state stored, in an environment where the same command could be executed with the same parameters but resulting in a different status, possibly in parallel? How do you connect the particular command execution with the particular resulting status? And if you manage to do so, what is actually won over the command just returning the status?
I’d argue that the separation makes things worse here, because it creates additional hidden state.
Also, as I stated, this is not about error handling.
CQRS should really only guide you to designing separate query and command interfaces. If your processing is asynchronous then you have no choice but to have state about processing-in-flight, and your commands should return an acknowledgement of successful receipt of valid commands with a unique identifier for querying progress or results. If your processing is synchronous make your life easier by just returning the result. Purity of CQRS void-only commands is presentation fodder, not practicality.
(One might argue that all RPC is asynchronous; all such arguments eventually lead to message buses, at-least-once delivery, and the reply-queue pattern, but maybe that's also just presentation fodder.)
You may have a command sub-routine that is used by multiple higher-level commands, or even called multiple times within by a higher-level command. If the validation lives in the subroutine, that validation will be called multiple times, even when it only needs to be called once.
So you are forced to choose either efficiency or the security of colocating validation, which makes it impossible to call the sub-routine with unvalidated input.
hinkley poses this as a fault in CQS, but CQS does not require your commands to always succeed. Command-Query Separation means your queries return values, but produce no effects, and your commands produce effects, but return no values. Nothing in that requires you to have a command which always succeeds or commands which don't make use of queries (queries cannot make use of commands, though). So a better question than what I originally posed:
My "Why would you do this?" is better expanded to: Why would you use CQS in a way that makes your system less secure (or safe or whatever) when CQS doesn't actually require that?
The example in the wiki page is far more rudimentary than the ones I encountered when I was shown this concept. Trivial, in fact.
CQS will rely on composition to do any If A Then B work, rather than entangling the two. Nothing forces composition except information hiding. So if you get your interface wrong someone can skip over a query that is meant to short circuit the command. The constraint system in Eiffel I don’t think is up to providing that sort of protection on its own (and the examples I was given very much assumed not). Elixir’s might end up better, but not by a transformative degree. And it remains to be seen how legible that code will be seen as by posterity.
That's still not really answering my question for you, which was less clear than intended. To restate it:
> The one place this advice falls down is security - having functions that do things without verifying preconditions are exploitable
My understanding of your comment was that "this advice" is CQS. So you're saying that CQS commands do not verify preconditions and that this is a weakness in CQS, in particular.
Where did you get the idea that CQS commands don't verify preconditions? I've never seen anything in any discussion of it, including my (admittedly 20 years ago) study of Eiffel.
Somewhere there’s a B without the associated query. Call it what you want, at the bottom of the tree two roads diverge. Otherwise there is no Separation in your CQS.
ETA: once you get down to the mutation point you aren’t just dealing with immutable data. You’re moving things around, often plural.
> having functions that do things without verifying preconditions are exploitable
Why would you do this? The separation between commands and queries does not mean that executing a command must succeed. It can still fail. Put queries inside the commands (but do not return the query results, that's the job of the query itself) and branch based on the results. After executing a command which may fail, you can follow it with a query to see if it succeeded and, if not, why not.
https://en.wikipedia.org/wiki/Command%E2%80%93query_separati...