4 ms·
Command-Query Separation is the term for that. However, I find this statement odd: > having functions that do things without verifying preconditions are exploi
by Jtsummers 1y ago
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.
https://en.wikipedia.org/wiki/Command%E2%80%93query_separation https://en.wikipedia.org/wiki/Command%E2%80%93query_separati...
- jonahx 1y ago> Why would you do this? Performance and re-use are two possible reasons. 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.
- Jtsummers 1y agoPerhaps I was unclear, to add to my comment: 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?
- hinkley 1y agoThe 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.
- Jtsummers 1y agoThat'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.
- hinkley 1y agoAnd I remain confused by your confusion. If A then B() Versus B() 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.
- layer8 1y agoIn 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.
- Jtsummers 1y ago> 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.
- layer8 1y agoWhere 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.
- codebje 1y agoCQRS 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.)
- jakewins 1y agoI 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
- mexicocitinluez 1y agoIt's not about literally doing things (ie logging) it's about the intent. Query and ask are synonyms and represent the same idea in this context.
- jakewins 1y agoThe 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?
- stonemetal12 1y agoThen why the weird assertion that "command" code can only do things and not validate input?
- Jtsummers 1y agoCommands 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.
- 1y ago