Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .github/instructions/solace.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
applyTo: "**/src/main/**/*.java"
description: "Solace PubSub+ messaging conventions for publishers, subscribers, and config"
---

# Solace PubSub+ messaging

These rules apply in addition to `copilot-instructions.md` when you're
writing or modifying code that publishes or consumes messages via Solace
PubSub+. If a class doesn't touch Solace, ignore this file.

## 1. Library choice

- Use `solace-spring-boot-starter` (JCSMP) for native Solace messaging.
Switch to `solace-jms-spring-boot-starter` only when interop with an
existing JMS contract is required.
- Use `spring-cloud-starter-stream-solace` only for event-driven flows that
already rely on Spring Cloud Stream binders elsewhere β€” not as a default.
- **Do not instantiate `JCSMPSession`, `JCSMPFactory`, or context manually.**
Inject the `SpringJCSMPFactory` / `JCSMPSession` provided by the starter.

## 2. Configuration

- Host, VPN, username, and password live in `application.properties` under
`solace.java.*`, sourced from environment variables.
Never hardcode broker URLs or credentials in Java code.
- Group connection-tuning settings (reconnect retries, ack window,
keepalive) into a `@ConfigurationProperties` class β€” not scattered
`@Value` fields.
- Default to TLS (`smfs://`) for non-local environments. `smf://` is
allowed only on `local` / `dev` profiles.

## 3. Topic naming

- Use slash-delimited, lowercase, hierarchical levels:
`<domain>/<bounded-context>/<version>/<event>` β€”
e.g., `orders/order-mgmt/v1/order-created`.
- Topics are nouns with a past-tense event suffix (`order-created`,
`payment-captured`). Never imperative (`create-order`).
- Bake the schema version into the topic level (`v1`, `v2`). When the
payload shape breaks, publish on `v2` and keep `v1` until consumers
migrate.
- **No environment names in topics** (`prod/…`, `dev/…`). Environments
are isolated by VPN, not by topic.

## 4. Quality of Service

- **Persistent (Guaranteed) messaging** for any state-changing or
business-event message. Use durable queues with topic subscriptions.
- **Direct messaging** only for telemetry/heartbeats where loss is
acceptable. Document the choice in a class-level comment.
- Always set `DeliveryMode.PERSISTENT` explicitly on guaranteed publishes β€”
do not rely on a default.

## 5. Producers

- One producer = one bounded context. Wrap publish calls in a
`@Service`-annotated `*Publisher` class. Controllers do not call
`XMLMessageProducer` directly.
- Build messages via `JCSMPFactory.onlyInstance().createMessage(...)`. Set:
- `applicationMessageId` β€” UUID per message
- `correlationId` β€” request/trace id from MDC
- `applicationMessageType` β€” the event name (matches the topic leaf)
- `HTTPContentType` β€” `application/json`
- Serialize payloads with the project's existing Jackson `ObjectMapper`.
Never `new ObjectMapper()` per call.
- Publish with `@Transactional(propagation = NOT_SUPPORTED)` β€” Solace
publishes are not enrolled in the JPA transaction. When publish-after-
commit semantics are required, use a Spring `TransactionSynchronization`
to defer the publish.

## 6. Consumers

- Use `@JCSMPListener` (starter-provided) on a `*Subscriber` /
`*MessageHandler` class. One listener method per topic subscription.
- **Consumers must be idempotent.** Deduplicate on `applicationMessageId`
when the downstream effect is not naturally idempotent.
- Acknowledge **after** the business effect succeeds. On business failure,
rethrow so the broker redelivers β€” do not `ack` and swallow.
- Consumers extract `correlationId` into MDC at the start of `onMessage`
and clear it in a `finally`.

## 7. Error handling and DMQ

- Every durable queue has a Dead Message Queue and a max-redelivery count
(default: 5). Both live in broker config, not Java.
- A `BusinessException` from a consumer triggers redelivery; after max
redeliveries the broker moves the message to the DMQ. **Do not catch,
log, and ack** β€” that silently drops messages.
- Log at `WARN` on redelivery; `ERROR` only when the message is being
routed to the DMQ (final failure).

## 8. Reconnection and flow control

- Let the starter handle session reconnects. Do not implement custom retry
loops around `session.connect()`.
- Configure `reconnectRetries=-1` (infinite) for long-running consumers.
- Honor backpressure: if `XMLMessageProducer.send` throws
`JCSMPTransportException`, propagate β€” do not buffer in memory.

## 9. Logging and observability

- Log at `INFO` on publish (topic, messageId, correlationId) and on
successful consume. Never log full payloads.
- Expose Solace metrics via the starter's actuator integration. Do not
roll bespoke counters for connection state.

## 10. Testing

- Unit-test publishers by mocking `XMLMessageProducer` / `JCSMPSession`.
Assert on the message built (topic, headers, payload).
- For integration tests, use the Solace PubSub+ Testcontainer
(`solace/solace-pubsub-standard`). Do not mock the broker for end-to-end
consumer flows.
- **Do not** start a real broker in unit tests. **Do not** assert on
redelivery behavior in unit tests β€” that's an integration concern.