Command Query Separation (CQS)

abdul ahad
6 min readMar 28, 2024

--

Photo by Md Mahdi on Unsplash

Command Query Separation states.

Functions that change state should not return values and functions that return values should not change state.

This term was coined by Bertrand Meyer in his book Object Oriented Software Construction.

Command-Query Separation (CQS) is a software design principle that states that methods should be classified as either commands, which perform an action and potentially modify the state of an object, or queries, which return a value without modifying the state. This separation helps improve code clarity, maintainability, and testability by distinguishing between operations that change state and operations that do not.

A Query should only return a result and should not have side-effects (does not change the observable state of the system).

A Command changes the state of a system (side-effects) but does not return a value.

why do we need it?

Command-Query Separation (CQS) programming principle can help you identify functions/methods that do too much and break them down into single-purpose methods that are easy to test, reuse, and maintain.

How does it help us?

Functions have side-effects. At times they change the state of the system when you least expect them to and wreak all kinds of havoc. It’s difficult to get rid of all side-effects in an Object Oriented programming paradigm. We just need to make sure to manage them so that they don’t bite us when we aren’t looking.

Managing Side Effects

One of the good ways to manage side-effects is to create a strong separation between commands and queries. In this context, a command changes the system, it has a side effect. A query returns the value of a computation or the observable state of the system.

Theoritical Example

In the CQS principle, methods are categorized as either commands, which perform actions that may change the state of an object, or queries, which return data without modifying the state. The principle suggests keeping these two types of operations separate to improve clarity and maintainability.

protocol FeedLoader{
func load() async -> [feed]
}

protocol FeedValidation {
func validate(feed : [feed]) async
}

let’s take an example above and look at the usecase. validate is a command and load is a query.

You separate loading and validation into 2 use cases. A great benefit of separating the functionality is that now we can use both actions in distinct contexts.
For example, we can schedule cache validation every 10 minutes or every time the app goes to (or gets back from) the background (instead of only performing it when the user requests to see the feed).

a follow-up common question from above would be?

If I’d like to always validate and load at the same time, should I separate loading and validation? If I separate into two functions, I always have to call retrieve twice and maybe I will forget to call validation

It is recommended to separate operations that can run separately so you allow using them separately too. Especially separating queries from side effects commands. This way you have more freedom to run the operations on their own. To avoid duplication when the operations must be run together, you can create a composition of them and reuse this composition. A decorator pattern will come to our aid. here we augment the validation behaviour on the loading of feed.

class FeedCacheValidationDecorator:FeedLoader {
let decoratee:FeedLoader
let validator:FeedValidation

func load() async {
// cache load quert
let feed = try await decoratee.load()
// cache policy validation command
let validate = validator.validate(feed)
}
}

The load method on FeedCacheValidationDecorator is a query method and you are calling a validate method(command) inside of the load. Why doesn't it break the Command-Query Separation Principle?

The decorator was created to inject the caching behavior into the query method without changing existing implementations. The existing implementations remain just a query

Real-World Example

Let's see a real example with a simple scenario where we can add items and delete items from a list

In this example, we have a protocol ItemDataProvider with a method loadItems() to load items and ItemDataDeleter to deleteItems() . We also provide a mock implementation MockItemDataProvider for demonstration purposes.

In the given implementation:

  1. loadItems() method in ItemDataProvider can be considered as a query because it retrieves data without modifying the state.
  2. deleteItem(at index: Int) method in ItemDataProvider can be considered as a command because it performs an action that changes the state by removing an item from the data.
protocol ItemDataProvider {
func loadItems() -> [String]
}
protocol ItemDataDeleter {
func deleteItem(at index: Int)
}

In this ViolatedItemListViewModel, we inject an instance of ItemDataProvider into the initializer. Upon initialization, the view model loads items using the injected data provider.

class ViolatedMockItemDataProvider: ItemDataProvider {
private var items: [String] = ["Item 1", "Item 2", "Item 3"]

func loadItems() -> [String] {
return items
}

func deleteItem(at index: Int) {
guard index >= 0, index < items.count else { return }
items.remove(at: index)
}
}

import Foundation

class ViolatedItemListViewModel: ObservableObject {
@Published var items: [String] = []
private let dataProvider: ViolatedMockItemDataProvider

init(dataProvider: ViolatedMockItemDataProvider) {
self.dataProvider = dataProvider
self.items = dataProvider.loadItems()
}

func addItem() {
let newItem = "Item \(items.count + 1)"
items.append(newItem)
}

func deleteItem(at index: IndexSet) {
guard let firstIndex = index.first else { return }
dataProvider.deleteItem(at: firstIndex)
items = dataProvider.loadItems()
}
}

Finally, update the view to inject the data provider into the view model:

struct ViolatedCommandQueryView: View {
@ObservedObject var viewModel: ViolatedItemListViewModel

init(viewModel: ViolatedItemListViewModel) {
self.viewModel = viewModel
}

var body: some View {
NavigationView {
VStack {
List {
ForEach(viewModel.items, id: \.self) { item in
Text(item)
}
.onDelete(perform: viewModel.deleteItem)
}

HStack {
Button(action: viewModel.addItem) {
Text("Add Item")
}
.padding()

Spacer()

EditButton()
.padding()
}
}
.navigationTitle("Items")
}
}
}
static var previews: some View {

let violatedDataProvider = ViolatedMockItemDataProvider()
let violatedViewModel = ViolatedItemListViewModel(dataProvider: violatedDataProvider)

return ViolatedCommandQueryView(viewModel: violatedViewModel)

}

we get this simple functionality.

Violation

However, the ItemListViewModel violates the CQS principle because the deleteItem(at:) method, which is supposed to be a command, not only performs the action of deleting an item but also queries the data provider to reload items. This mixing of command and query violates the principle's separation.

To adhere more strictly to the CQS principle, we can refactor the deleteItem(at:) method to only perform the command of deleting the item and leave the query updating part to someone else.

Solution

To respect the Command-Query Separation (CQS) principle we use delegation for updates, we can define a protocol for notifying changes in the item list and make the view model conform to it. Here’s how you can do it:

//delegate 
protocol ItemDataProviderDelegate: AnyObject {
func itemsDidChange(_ items:[String])
}

//has delegate property and notify the delegate when changes occur:
class MockItemDataProvider: ItemDataProvider,ItemDataDeleter {
private var items: [String] = ["Item 1", "Item 2", "Item 3"]
weak var delegate: ItemDataProviderDelegate?


func loadItems() -> [String] {
return items
}

func deleteItem(at index: Int) {
guard index >= 0, index < items.count else { return }
items.remove(at: index)
//notify delegate
delegate?.itemsDidChange(items)
}
}

//conforms to the delegate and recieves updates
class ItemListViewModel: ObservableObject, ItemDataProviderDelegate {



@Published var items: [String] = []
typealias facade = ItemDataProvider & ItemDataDeleter
private let dataProvider: facade


init(dataProvider: facade) {
self.dataProvider = dataProvider
self.items = dataProvider.loadItems()
}

func addItem() {
let newItem = "Item \(items.count + 1)"
items.append(newItem)
}

func deleteItem(at index: IndexSet) {
guard let firstIndex = index.first else { return }
dataProvider.deleteItem(at: firstIndex)

}
//recieves update
func itemsDidChange(_ items: [String]) {
self.items = items
}
}

Now, when changes occur in the data provider using delete, the view model will be notified via delegation, and it will update its state accordingly, which will cause the view to update automatically.

so now the the deleteItem(at:) method from ItemListViewModel only perform the command of deleting the item and leave the query updating part to itemsDidChange(_ items: [String]).

Diagram

Summary

Command-query separation helps create single-purpose components that can be composed using polymorphism in any way or form and that leads to more maintainable, testable, and robust software systems by promoting clarity, predictability, and separation of concerns in code.

Github: https://github.com/abdahad1996/DesignPatterns_Bootcamp

REFERENCE:

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet