Opinions about command-query separation

You guys and girls have any opinions about this design principle?

Wikipedia link

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer .[1] More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

A counter-example would a function like read_file. It’s not referential transparent, but it does at least not change the answer (writes anything).

Another interesting topic is how to enforce this principle in language semantics.

NB that even in Haskell, nothing prevents the coder from spamming IO monad everywhere.

Seems incompatible with (modern) attempts/approaches at addressing time-of-check to time-of-use (TOCTOU) bugs?

Say in any concurrent environment (doesn’t really matter whether the environment is parallel or not), you have a store which manages some kind of resource. Then a nice way would be to have a API of roughly the form val get : ?timeout:int -> store -> resource option, which both answers the query “is there a free instance of the resource?” and also changes the state (breaking the principle if I didn’t misunderstand it).

If we are to adhere to the principle strictly then I’m guessing the code would read closer to

if free store then
  let x = get store in
  ...

but then you have the TOCTOU problem. Of course one can argue that surrounding the (critical) section with locks would mitigate the problem, but I think the general consensus has been we are not very good at writing or analysing code written that way.

EDIT: Bumped into SCOOP (software) - Wikipedia while browsing further, which addresses my concern I guess, but I haven’t given much thought into it.

Concurrency always messes things up. :joy:

1 Like

This command/query principle is violated when doing high-performance lexing. If the current lexeme is exactly what you demanded then you also want the next lexeme to be read, automatically, not manually.

Sure. Iterators also do this when going through files, for example.

“How many times has this question been asked?” :slight_smile:

I guess some questions really do change the answer in the asking, just by the nature of the question :wink:

Also, iiuc, referential transparency doesn’t entail purity (but the entailment does work the other way around). Memorization is the usual example to consider for this.

IMO, CQRS is a useful pattern in imperative and OOPy contexts, where patterns are often your best bet at getting reasonable code. But in contexts where we can use tools like equational reasoning, parameticity, denotational semantics, and algebraic semiotics, stuff like DDD and CQRS ends up looking mostly like ad hoc rules with some value from a pragmatic view, but not generally sound as principles.

1 Like

Consider a database suitable for concurrent access. It enforces two rules (when used with suitably high isolation level):

(1) -within- a transaction, the user is unaware of locks and dirty pages; reads go thru as if there’s nobody else in the universe, and are idempotent. They are also side-effect-free.
(2) viewed from -outside- the transaction, they’re not side-effect-free at all.

“referential transparency” … “depends”. It’s not as simple as “write side-effect-free code” b/c you can’t do that very much in real systems. Instead, it’s about being able to know a priori what sorts of side-effects can be ignored, and how to do so, in order to be able to reason about code easily. And then, you jump to another level, and those same side-effects might actually be the critical objects of interest.

Also, though, the design principle you state fails rapidly: there are entire classes of operations that need to both perform a side-effect and return a value. Standard examples are “the shoe store problem” and “the serial number problem”.

That said, when you can do it, yeah, I think it’s a good thing. Just not a hard-and-fast rule.

1 Like

What good is it, then? I mean what’s the point of a rule that says ~ no values returned from an effectful operation ~, if you’re going to cheerfully return values from an effectful operation when it makes sense to do so? If there’s something we’re trying for here, maybe it could be rethought into something that could actually serve as a rule.

1 Like

Two thoughts:

(1) there is value in the rule you state, and value in knowing why you’re breaking it. Also, it’s not as common as you think, that one wants to break it. But OTOH, the two examples I cite come up over and over in data-backed systems. And, frankly, in other systems too.

(2) Especially when designing systems where data is accessed via RPC, there are STRONG reasons for breaking the rule, viz: a seminal paper by Jim Gray, where he explains why one must never provide direct SQL (or query) access to a remote database, but instead provide a stored-procedure level of access. That is to say, do not break apart operations that naturally go together, simply in order to abide by the stated rule.

System design isn’t a simple thing, and there are lots of conflicting tensions.

1 Like

I think the principle is good but the definition is wrong, or misses the mark.

Example:

let orders = get_orders () in 
let processed_orders = process_orders orders in
apply_changes processed_orders

This is a good separation, because you can unit-test process_orders easily. How to phrase this type of separation? Processing behaviour should not have side-effects? Do we need a better taxonomy for different kinds of behaviour? Or is it just “domain/business logic”?