Command Query Separation (CQS) principle ● Design concept devised by Bertrand Meyer ● We should divide an object’s methods into either Command or Query ○ Commands change the state but return no value ○ Queries return a value but never change the state
Command Query Separation principle fun getCount(): Int { return this.count } fun incrementCount() { this.count++ } fun incrementCount(): Int { return ++this.count } Do. Don’t.
Command Query Separation principle fun getCount(): Int { return this.count } fun incrementCount() { this.count++ } fun incrementCount(): Int { return ++this.count } Do. Don’t. Apply the concept to Architecture level
Command Stack / Query Stack ● Command Stack ○ Change the system state ○ Does not return the state to presentation layer ● Query Stack ○ Never change the system state ○ Returns the presentation models to presentation layer
Complexity is coming by unifying Domain Model ● We have justified ourselves that Domain Model is complex because of Domain’s complexity ● Segregation of Command and Query makes it easier to express Domain in Domain Model ● CQRS lights up a new aspect of Domain
Simple example: Conference app ● User can show sessions, speakers and rooms info ● User can make favorite sessions ● All info are fetched by API ● Use cache for improving user experience
How to construct repositories? class SessionRepository { fun findAll(): List = if (isInvalid) { isInvalid = false sessionApi.getAll().also(sessionDb::store) } else { sessionDb.findAll() } fun invalidate() { isInvald = true } ...
How to construct repositories? class SessionRepository { fun findAll(): List = sessionDb.findAll() fun refresh() { sessionApi.getAll().also(sessionDb::store) } ...
invalidate() or refresh() problem ● Repository exposes the concern to API ○ Against to “Collection-like interface for accessing entities” ● Repository is going to have the domain logic
Segregate models into Command and Query ● Command Models - update the local storage ○ Fetch data from API ○ Write fetched data to the local storage ● Query Models - show the latest info ○ Retrieve the local storage ○ Re-query when the local storage is changed
UseCase as a TransactionScript class RefreshSessions { operator fun invoke() { val newSessions = sessionApi.getAll() sessionDb.store(newSessions) } ...
Query Models as Domain Models data class Session( val startTime: Date, val endTime: Date, … ) { fun getPeriodText(context: Context): String = context.getString(...) ...
Query Models as Domain Models data class Session( val startTime: Date, val endTime: Date, … ) { fun getPeriodText(context: Context): String = context.getString(...) ... Can take Android Context since Query Domain Models concern presentation
Query Models as Domain Models data class Session( val startTime: Date, val endTime: Date, … ) { fun getPeriodText(context: Context): String = context.getString(...) ... Can take Android Context since Query Domain Models concern presentation Avoid PresentationUtils class
Re-query by observing Database changes ● Some ORMs support observable queries ○ Room - LiveData, RxJava ○ Realm - Callback, RxJava ○ Orma - RxJava, LiveData (in the plan) ● Room also supports to use original classes instead of @Entity class in @Query
CQRS vs Flux ● Flux is a derivation of CQRS to optimize JavaScript world ○ Non-complex command models ○ No Database ● Main difference is that the app state is stored on either database or memory (Store)
Summary ● CQRS is an architecture that segregate models into Command Stack and Query Stack ● The separation makes it easier to construct models since understanding of Domain is definitely changed ● CQRS and Flux are relatives