ワンクリックで
kafka
Rapids & Rivers, eventdrevet arkitektur, Kafka-mønstre og schema-design for Nav-applikasjoner
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Rapids & Rivers, eventdrevet arkitektur, Kafka-mønstre og schema-design for Nav-applikasjoner
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | kafka |
| description | Rapids & Rivers, eventdrevet arkitektur, Kafka-mønstre og schema-design for Nav-applikasjoner |
| license | MIT |
| compatibility | Kotlin/JVM application with Kafka on Nais |
| metadata | {"domain":"backend","tags":"kafka rapids-rivers events event-driven nais"} |
Patterns, templates, and procedures for building event-driven systems with Kafka on Nais. Covers Rapids & Rivers framework, event schema design, and consumer/producer patterns.
# Check Kafka env vars are present (names only, not values)
kubectl exec -it <pod> -n <namespace> -- env | grep -o '^KAFKA[^=]*'
# Verify Kafka credentials are mounted
kubectl exec -it <pod> -n <namespace> -- ls -la /var/run/secrets/nais.io/kafka/
# View pod logs for Kafka events
kubectl logs -n <namespace> <pod> --tail=50 | grep -i "event\|kafka\|river"
apiVersion: nais.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
kafka:
pool: nav-dev # or nav-prod
This automatically:
/var/run/secrets/nais.io/kafka/// Environment variables automatically set by Nais
val kafkaConfig = mapOf(
"KAFKA_BROKERS" to System.getenv("KAFKA_BROKERS"),
"KAFKA_TRUSTSTORE_PATH" to System.getenv("KAFKA_TRUSTSTORE_PATH"),
"KAFKA_CREDSTORE_PASSWORD" to System.getenv("KAFKA_CREDSTORE_PASSWORD"),
"KAFKA_KEYSTORE_PATH" to System.getenv("KAFKA_KEYSTORE_PATH"),
"KAFKA_CONSUMER_GROUP_ID" to "my-app-v1",
"KAFKA_RAPID_TOPIC" to "teamname.rapid-v1"
)
import no.nav.helse.rapids_rivers.RapidApplication
import no.nav.helse.rapids_rivers.RapidsConnection
fun main() {
val env = System.getenv()
RapidApplication.create(env).apply {
UserCreatedRiver(this, userRepository)
PaymentProcessedRiver(this, paymentService)
}.start()
}
import no.nav.helse.rapids_rivers.*
class UserCreatedRiver(
rapidsConnection: RapidsConnection,
private val userRepository: UserRepository
) : River.PacketListener {
init {
River(rapidsConnection).apply {
precondition { it.requireValue("@event_name", "user_created") }
validate { it.requireKey("user_id", "email", "name") }
validate { it.require("@created_at", JsonNode::asLocalDateTime) }
validate { it.interestedIn("phone_number") }
}.register(this)
}
override fun onPacket(packet: JsonMessage, context: MessageContext) {
val userId = packet["user_id"].asText()
val email = packet["email"].asText()
val name = packet["name"].asText()
val createdAt = packet["created_at"].asLocalDateTime()
userRepository.save(User(id = userId, email = email, name = name, createdAt = createdAt))
}
override fun onError(problems: MessageProblems, context: MessageContext) {
logger.error("Failed to validate message: ${problems.toExtendedReport()}")
}
companion object {
private val logger = KotlinLogging.logger {}
}
}
// Preconditions — "does this message concern me at all?"
// Failures → onPreconditionError() (silent, not logged — high volume)
precondition { packet ->
packet.requireValue("@event_name", "payment_processed")
packet.forbid("@cancelled")
packet.forbidValue("status", "cancelled")
}
// Validations — "is the message I care about well-formed?"
// Failures → onError() (logged — indicates contract violation)
validate { packet ->
packet.requireKey("transaction_id", "amount")
packet.require("amount", JsonNode::asDouble)
packet.require("processed_at", JsonNode::asLocalDateTime)
packet.requireAny("user_id", "session_id")
packet.interestedIn("metadata", "correlation_id")
}
fun publishUserCreatedEvent(user: User, context: MessageContext) {
val event = JsonMessage.newMessage(
mapOf(
"@event_name" to "user_created",
"@id" to UUID.randomUUID().toString(),
"@created_at" to LocalDateTime.now(),
"@produced_by" to "my-service",
"user_id" to user.id,
"email" to user.email,
"name" to user.name
)
)
context.publish(event.toJson())
}
Always include standard metadata:
"@event_name" to "payment_processed", // Event type
"@id" to UUID.randomUUID().toString(), // Unique event ID
"@created_at" to LocalDateTime.now(), // When event was created
"@produced_by" to "payment-service", // Service that created it
"@correlation_id" to correlationId // Request correlation ID (optional)
// ✅ Good - past tense, specific, immutable facts
"user_created", "payment_processed", "application_approved"
// ❌ Bad - imperative, vague
"create_user", "process", "handle_application"
// Option 1: Version in event name
"@event_name" to "user_created_v2"
// Option 2: Version field
"@event_name" to "user_created",
"@version" to 2
import no.nav.helse.rapids_rivers.testsupport.TestRapid
class UserCreatedRiverTest {
private lateinit var testRapid: TestRapid
private lateinit var userRepository: UserRepository
@BeforeEach
fun setup() {
testRapid = TestRapid()
userRepository = InMemoryUserRepository()
UserCreatedRiver(testRapid, userRepository)
}
@Test
fun `processes user_created event`() {
testRapid.sendTestMessage("""
{
"@event_name": "user_created",
"@id": "550e8400-e29b-41d4-a716-446655440000",
"@created_at": "2024-01-15T10:30:00",
"user_id": "12345",
"email": "user@nav.no",
"name": "Test User"
}
""")
val user = userRepository.findById("12345")
assertEquals("user@nav.no", user.email)
}
@Test
fun `publishes downstream event`() {
testRapid.sendTestMessage(/* ... */)
val published = testRapid.inspektør.message(0)
assertEquals("need_user_permissions", published["@event_name"].asText())
}
}
override fun onPacket(packet: JsonMessage, context: MessageContext) {
try {
processEvent(packet)
} catch (e: TemporaryException) {
throw e // Let Kafka retry
} catch (e: PermanentException) {
logger.error("Permanent error processing event", e)
dlqProducer.send(eventName = "user_created", originalMessage = packet.toJson(), error = e.message)
}
}
override fun onPacket(packet: JsonMessage, context: MessageContext) {
val eventId = packet["@id"].asText()
if (eventRepository.exists(eventId)) {
logger.info("Event $eventId already processed, skipping")
return
}
processEvent(packet)
eventRepository.markProcessed(eventId)
}
private val eventsProcessed = meterRegistry.counter("events_processed_total", "event_name", "user_created")
private val processingDuration = meterRegistry.timer("event_processing_duration_seconds", "event_name", "user_created")
override fun onPacket(packet: JsonMessage, context: MessageContext) {
processingDuration.record {
processEvent(packet)
eventsProcessed.increment()
}
}
KAFKA_CONSUMER_GROUP_ID causes reprocessing of all messagesprecondition failures are silent (high volume) — use for event type filteringvalidate failures call onError() — use for schema validation@id for idempotencyTestRapid — never mock Kafka directlyuser_created, not create_user)@event_name, @id, @created_at)@id before processing)precondition for event type filteringevent_id for traceability@id fieldonError handler in RiversGenerer conventional commit-meldinger med Nav-relevante scopes og breaking change-format
Expert builder for the Aksel design system (Nav / @navikt) React components, design tokens, layout primitives, theming (light/dark), icons, CSS, the Tailwind preset, version migrations, and Figma-to-code. Trigger on any frontend UI task that mentions Aksel, Nav/Navikt, "designsystemet", or @navikt/ds-* / @navikt/aksel-* packages — or that asks to add, create, build, or refactor a component (button, input, modal, table, alert, card, form) or layout, or to implement a design from Figma (a pasted figma.com/design/...?node-id link, "implement this design", "build this from Figma", design-to-code). Strong signals "using/with aksel", "@navikt/ds-react", "design system", a pasted figma.com link. If the work is frontend UI and there is any Aksel signal, invoke this skill unless the user explicitly opts out.
Integrer og konfigurer Nav Dekoratøren – felles header og footer for nav.no-applikasjoner. Bruk når et team skal ta i bruk Dekoratøren, oppdatere konfigurasjon, legge til breadcrumbs/språkvelger/analytics, håndtere samtykke (ekomloven), CSP eller feilsøke integrasjon mot dekoratøren.
Lag responsive layouts med Aksel Design System (v8+) - spacing tokens, layout primitives (Box, HStack, VStack, HGrid, Page, Bleed) og ResponsiveProp
Generer og kjør Playwright E2E-tester for webapplikasjoner med page objects, auth fixtures og tilgjengelighetstester
Kompakt output-stil som kutter fyllord og beholder teknisk substans — spar output-tokens uten å miste nøyaktighet.