Command Query Separation (CQS)
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:
loadItems()
method inItemDataProvider
can be considered as a query because it retrieves data without modifying the state.deleteItem(at index: Int)
method inItemDataProvider
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