mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			81 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			81 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
---
 | 
						|
title: "ADR: Write Model vs Read Models"
 | 
						|
---
 | 
						|
 | 
						|
## Background
 | 
						|
 | 
						|
We keep solving 3 problems in each of the business modules:
 | 
						|
* changing the state of the system through actions
 | 
						|
* displaying UI 
 | 
						|
* asking other modules for information
 | 
						|
 | 
						|
In the past we had one pattern of `service + store` for handling all 3 of those. Store was responsible for changing state and returning UI data. Cross module collaboration
 | 
						|
happened by calling services in other modules. While it was convenient and reduced the number of building blocks to learn, it also led to coupling between
 | 
						|
services and overloaded stores - responsible for writing data, reading complex UI data and answering questions from other modules.
 | 
						|
 | 
						|
## Decision
 | 
						|
 | 
						|
To address these challenges categorize your problem as one of 3 models:
 | 
						|
 | 
						|
[3 types of models](/img/write-model-vs-read-models.png)
 | 
						|
 | 
						|
We have 3 types of models serving 3 different purposes:
 | 
						|
* I need to take action that modifies a state of the system (Write Model) 
 | 
						|
 | 
						|
Solution: Go through the Command stack aka the *Write Model*. *Application logic* with the use case/workflow/steps resides in the *Application Service*. Application service can delegate
 | 
						|
  business logic handling to the *Domain Model* if needed (only for complex subdomains like Change Requests). Simple domain logic (very few business if statements)
 | 
						|
  can be handled together with the application logic. Application service calls the store that is responsible for the *generic CRUD operations* serving the write model. 
 | 
						|
 | 
						|
* I need to read data for UI display (External Read Model)
 | 
						|
  
 | 
						|
Solution: Expose *External Read Model* aka *View Model* that returns all the data in one query so that frontend doesn't have to ask multiple write models for data.
 | 
						|
  View model will typically join data across a few DB tables since most UI screens required more than one table of data. If we keep the *complex queries* for UI
 | 
						|
  in the external read model our stores can be generic and focused on the main table operations.
 | 
						|
 | 
						|
* I need information from another module to proceed with my module application logic (Internal Read Model)
 | 
						|
  
 | 
						|
Solution: Cross module queries can be handled with the *Internal Read Model*. Internal means it's used internally between our modules and not exposed to the UI.
 | 
						|
  Internal read models are typically narrowly focused on answering one question and usually require *simple queries* compared to external read models. By introducing internal
 | 
						|
  read model other business modules are not coupled to our module's service/store/write model and can't call write model methods from another module by accident.
 | 
						|
 | 
						|
## Example
 | 
						|
 | 
						|
Before (one multi-purpose class)
 | 
						|
```typescript
 | 
						|
class SegmentStore {
 | 
						|
    // used to perform actions on segment
 | 
						|
    create(segment: Segment): Promise<Segment> {}
 | 
						|
    get(id: number): Promise<Segment> {}
 | 
						|
    update(id: number, segment: Segment): Promise<Segment> {}
 | 
						|
    delete(id: number): Promise<void> {}
 | 
						|
    
 | 
						|
    // used by UI
 | 
						|
    getAll(): Promise<SegmentWithUsageInfo[]> {}
 | 
						|
    
 | 
						|
    // used by another module checking existing names
 | 
						|
    getSegmentNames(): Promise<string[]> {}
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
After (3 role-based classes so clients depends only on method they use)
 | 
						|
```typescript
 | 
						|
// used to perform actions on segment
 | 
						|
class SegmentStore {
 | 
						|
    create(segment: Segment): Promise<Segment> {}
 | 
						|
    get(id: number): Promise<Segment> {}
 | 
						|
    update(id: number, segment: Segment): Promise<Segment> {}
 | 
						|
    delete(id: number): Promise<void> {}
 | 
						|
}
 | 
						|
 | 
						|
// used by UI
 | 
						|
class SegmentViewModel {
 | 
						|
    getAll(): Promise<SegmentWithUsageInfo[]> {}
 | 
						|
}
 | 
						|
 | 
						|
// used by another module checking existing names
 | 
						|
class SegmentReadModel {
 | 
						|
    getSegmentNames(): Promise<string[]> {}
 | 
						|
}
 | 
						|
```
 | 
						|
 |