state-view-slice
Knows how to build state change slices
Source facts
- Repository
- dilgerma/ralph-loop-demo
- Last source activity
- March 11, 2026 at 16:29
- Detected SKILL.md language
- English
- Stars
- 1
- Forks
- 0
Install options
The review-first prompt is selected by default. You can switch to a direct command or download a local copy.
Review the source files
Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.
Showing SKILL.md
SKILL.md
Source instructions · Read-only preview- name
- state view slice
- description
- Knows how to build state change slices
# STATE_VIEW Slice Skill
## Overview
A STATE_VIEW slice represents a read-only projection/view of data. It does NOT contain commands - only read models that are populated by events from other slices. This is the "Query" side of CQRS.
## Input Structure (from config.json)
```json
{
"sliceType": "STATE_VIEW",
"title": "slice: <Name>",
"context": "<BoundedContext>",
"commands": [], // Always empty for STATE_VIEW
"events": [], // Always empty for STATE_VIEW
"readmodels": [...],
"screens": [...],
"specifications": [...]
}
```
## Key Characteristics
1. **No commands** - STATE_VIEW slices only display data
2. **No events owned** - Events come from other STATE_CHANGE slices
3. **ReadModels reference external events** - Dependencies with INBOUND type point to events
4. **Screens display data** - May link to commands in other slices
## Flow Pattern
```
Events (from STATE_CHANGE slices) → @EventHandler Projector → ReadModel Entity → @QueryHandler → Screen
```
## Code Generation Steps
### Step 1: Identify Source Events
Look at the readmodel's dependencies with `type: "INBOUND"` and `elementType: "EVENT"`:
```json
"dependencies": [
{
"type": "INBOUND",
"title": "Law Firm Created",
"elementType": "EVENT"
},
{
"type": "INBOUND",
"title": "Law Firm Updated",
"elementType": "EVENT"
}
]
```
These events must already exist (from STATE_CHANGE slices).
### Step 2: Determine Persistence Strategy
Before creating entities, determine which persistence pattern to use based on the readmodel's fields:
**Decision Criteria:**
| Scenario | Pattern | When to Use |
|----------|---------|-------------|
| Single `idAttribute: true` field | Single Key | Entity represents one unique aggregate/record |
| Multiple `idAttribute: true` fields | Multi Key (Composite) | Entity represents a combination (e.g., assignment, membership) |
| `listElement: true` on readmodel | Multi Key (Composite) | ReadModel returns a list of related entities |
| Has parent/child relationship | Multi Key (Composite) | Entity belongs to another entity (e.g., person-to-case) |
---
## Persistence Pattern A: Single Key
Use when the readmodel has exactly ONE `idAttribute: true` field (typically `aggregateId` or similar).
### A.1: Create Single-Key Entity
**Location:** `src/main/kotlin/de/alex/<module>/<readmodelname>/<ReadModelName>ReadModel.kt`
```kotlin
package de.alex.<module>.<readmodelname>
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.util.UUID
data class <ReadModelName>ReadModelQuery(val aggregateId: UUID)
@Table(name = "<table_name>_read_model_entity", schema = "public")
@Entity
class <ReadModelName>ReadModelEntity {
@Id
@Column(name = "<id_field_name>")
var <idFieldName>: UUID? = null
// Add fields from readmodel definition (non-id fields)
@Column(name = "<field_name>")
var <fieldName>: <Type>? = null
// Common: lawfirmId for multi-tenancy
@Column(name = "lawfirm_id")
var lawfirmId: UUID? = null
}
data class <ReadModelName>ReadModel(val data: <ReadModelName>ReadModelEntity)
```
**Real Example (CaseDetailsReadModel):**
```kotlin
package de.alex.cases.casedetails
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.math.BigDecimal
import java.util.UUID
data class CaseDetailsReadModelQuery(val aggregateId: UUID)
@Table(name = "case_details_read_model_entity", schema = "public")
@Entity
class CaseDetailsReadModelEntity {
@Id @Column(name = "caseId") var caseId: UUID? = null
@Column(name = "ablagenummer") var ablagenummer: String? = null
@Column(name = "description") var description: String? = null
@Column(name = "disputeAmount") var disputeAmount: BigDecimal? = null
@Column(name = "active_case") var activeCase: Boolean? = null
@Column(name = "lawfirm_id") var lawfirmId: UUID? = null
}
data class CaseDetailsReadModel(val data: CaseDetailsReadModelEntity)
```
### A.2: Create Single-Key Repository
**Location:** `src/main/kotlin/de/alex/<module>/<readmodelname>/internal/<ReadModelName>ReadModelRepository.kt` (or inline in Projector)
```kotlin
package de.alex.<module>.<readmodelname>.internal
import de.alex.<module>.<readmodelname>.<ReadModelName>ReadModelEntity
import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID
interface <ReadModelName>ReadModelRepository : JpaRepository<<ReadModelName>ReadModelEntity, UUID> {
// Optional: query methods for filtering
fun findByLawfirmId(lawfirmId: UUID): List<<ReadModelName>ReadModelEntity>
}
```
### A.3: Create Single-Key Projector
```kotlin
package de.alex.<module>.<readmodelname>.internal
import de.alex.<module>.ProcessingGroups
import de.alex.<module>.<readmodelname>.<ReadModelName>ReadModelEntity
import de.alex.events.*
import org.axonframework.config.ProcessingGroup
import org.axonframework.eventhandling.EventHandler
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Component
import java.util.UUID
interface <ReadModelName>ReadModelRepository : JpaRepository<<ReadModelName>ReadModelEntity, UUID>
@ProcessingGroup(ProcessingGroups.<MODULE>)
@Component
class <ReadModelName>ReadModelProjector(var repository: <ReadModelName>ReadModelRepository) {
@EventHandler
fun on(event: <CreatedOrUpdatedEvent>) {
// findById returns Optional, use orElse for upsert pattern
val entity = repository.findById(event.aggregateId).orElse(<ReadModelName>ReadModelEntity())
entity.apply {
<idFieldName> = event.aggregateId
<fieldName> = event.<eventFieldName>
}.also { repository.save(it) }
}
@EventHandler
fun on(event: <DeleteEvent>) {
// Simple delete by primary key
repository.deleteById(event.aggregateId)
}
}
```
### A.4: Create Single-Key QueryHandler
```kotlin
package de.alex.<module>.<readmodelname>.internal
import de.alex.<module>.<readmodelname>.<ReadModelName>ReadModel
import de.alex.<module>.<readmodelname>.<ReadModelName>ReadModelQuery
import org.axonframework.queryhandling.QueryHandler
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Component
@Component
class <ReadModelName>ReadModelQueryHandler(
private val repository: <ReadModelName>ReadModelRepository
) {
@QueryHandler
fun handleQuery(query: <ReadModelName>ReadModelQuery): <ReadModelName>ReadModel? {
val entity = repository.findByIdOrNull(query.aggregateId) ?: return null
return <ReadModelName>ReadModel(entity)
}
}
```
### A.5: Single-Key Database Migration
```sql
CREATE TABLE IF NOT EXISTS public.<table_name>_read_model_entity (
"<id_column>" UUID NOT NULL,
"<field_column>" <SQL_TYPE>,
"lawfirm_id" UUID,
PRIMARY KEY ("<id_column>")
);
CREATE INDEX IF NOT EXISTS idx_<table>_lawfirmid ON public.<table_name>_read_model_entity("lawfirm_id");
```
---
## Persistence Pattern B: Multi Key (Composite Key)
Use when the readmodel has MULTIPLE `idAttribute: true` fields or represents a many-to-many/parent-child relationship.
### B.1: Choose Key Class Style
There are two common approaches for composite keys:
**Option 1: Simple class with @NoArgConstructor** (preferred for simple cases)
```kotlin
import de.alex.common.NoArgConstructor
@NoArgConstructor
class <ReadModelName>Key(
var <firstIdField>: UUID,
var <secondIdField>: UUID
)
```
**Option 2: @Embeddable data class** (when using embedded queries)
```kotlin
import jakarta.persistence.Embeddable
@Embeddable
data class <ReadModelName>Key(
var <firstIdField>: UUID,
var <secondIdField>: <Type> // Can be UUID, String, etc.
)
```
### B.2: Create Multi-Key Entity
**Location:** `src/main/kotlin/de/alex/<module>/<readmodelname>/<ReadModelName>ReadModel.kt`
```kotlin
package de.alex.<module>.<readmodelname>
import de.alex.common.NoArgConstructor
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.IdClass
import jakarta.persistence.Table
import java.util.UUID
class <ReadModelName>ReadModelQuery() // Often empty for list queries
@NoArgConstructor
class <ReadModelName>Key(
var <firstIdField>: UUID,
var <secondIdField>: UUID
)
@IdClass(<ReadModelName>Key::class)
@Table(name = "<table_name>_read_model_entity", schema = "public")
@Entity
class <ReadModelName>ReadModelEntity {
@Id @Column(name = "<first_id>")
var <firstIdField>: UUID? = null
@Id @Column(name = "<second_id>")
var <secondIdField>: UUID? = null
// Add remaining non-id fields
@Column(name = "<field_name>")
var <fieldName>: <Type>? = null
@Column(name = "lawfirmId")
var lawfirmId: UUID? = null
}
// For composite keys, ReadModel typically wraps a List
data class <ReadModelName>ReadModel(val data: List<<ReadModelName>ReadModelEntity>)
```
**Real Example (CaseAssignmentsReadModel):**
```kotlin
package de.alex.cases.caseassignments
import de.alex.common.NoArgConstructor
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.IdClass
import jakarta.persistence.Table
import java.util.UUID
class CaseAssignmentsReadModelQuery()
@NoArgConstructor
class CaseAssignmentsKey(var caseId: UUID, var assignedPersonId: UUID)
@IdClass(CaseAssignmentsKey::class)
@Table(name = "case_assignments_read_model_entity", schema = "public")
@Entity
class CaseAssignmentsReadModelEntity {
@Id @Column(name = "caseId") var caseId: UUID? = null
@Id @Column(name = "assigned_person") var assignedPersonId: UUID? = null
@Column(name = "assigned_role") var assignedRole: String? = null
@Column(name = "lawfirmId") var lawfirmId: UUID? = null
}
data class CaseAssignmentsReadModel(val data: List<CaseAssignmentsReadModelEntity>)
```
**Real Example with @Embeddable (TaskReadModel):**
```kotlin
package de.alex.tasks.tasks
import jakarta.persistence.Column
import jakarta.persistence.Embeddable
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.IdClass
import jakarta.persistence.Table
import java.util.UUID
@Embeddable
data class TaskReadModelKey(var taskId: UUID, var caseId: UUID)
@IdClass(TaskReadModelKey::class)
@Table(name = "tasks_read_model_entity", schema = "public")
@Entity
class TaskReadModelEntity {
@Id @Column(name = "taskId") var taskId: UUID? = null
@Id @Column(name = "caseId") var caseId: UUID? = null
@Column(name = "title") var title: String? = null
@Column(name = "lawfirm_id") var lawFirmId: UUID? = null
}
data class TaskReadModel(val data: TaskReadModelEntity)
```
### B.3: Create Multi-Key Repository
```kotlin
package de.alex.<module>.<readmodelname>.internal
import de.alex.<module>.<readmodelname>.<ReadModelName>Key
import de.alex.<module>.<readmodelname>.<ReadModelName>ReadModelEntity
import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID
interface <ReadModelName>ReadModelRepository :
JpaRepository<<ReadModelName>ReadModelEntity, <ReadModelName>Key> {
// Query by first part of composite key
fun findBy<FirstIdField>(<firstIdField>: UUID): List<<ReadModelName>ReadModelEntity>
// Query by second part of composite key
fun findBy<SecondIdField>(<secondIdField>: UUID): List<<ReadModelName>ReadModelEntity>
// Query by lawfirmId for multi-tenancy
fun findByLawfirmId(lawfirmId: UUID): List<<ReadModelName>ReadModelEntity>
}
```
### B.4: Create Multi-Key Projector
```kotlin
package de.alex.<module>.<readmodelname>.internal
import de.alex.<module>.ProcessingGroups
import de.alex.<module>.<readmodelname>.<ReadModelName>Key
import de.alex.<module>.<readmodelname>.<ReadModelName>ReadModelEntity
import de.alex.events.*
import org.axonframework.config.ProcessingGroup
import org.axonframework.eventhandling.EventHandler
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Component
import java.util.UUID
interface <ReadModelName>ReadModelRepository :
JpaRepository<<ReadModelName>ReadModelEntity, <ReadModelName>Key>
@ProcessingGroup(ProcessingGroups.<MODULE>)
@Component
class <ReadModelName>ReadModelProjector(var repository: <ReadModelName>ReadModelRepository) {
@EventHandler
fun on(event: <AddedEvent>) {
// Construct composite key for lookup
View on GitHubThis SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub