Skip to main content

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 GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub