From 47d324f20ab7e21bf6553ad2c14a3973826c55b4 Mon Sep 17 00:00:00 2001 From: sumitkc Date: Tue, 28 Apr 2026 21:51:56 -0400 Subject: [PATCH] Add Solace PubSub+ instructions for Copilot Adds .github/instructions/solace.instructions.md so Copilot applies the team's Solace messaging conventions (library choice, topic naming, QoS, producer/consumer shape, DMQ handling, testing) when editing Java code. Co-Authored-By: Claude Opus 4.7 --- .github/instructions/solace.instructions.md | 116 ++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/instructions/solace.instructions.md diff --git a/.github/instructions/solace.instructions.md b/.github/instructions/solace.instructions.md new file mode 100644 index 0000000..1347860 --- /dev/null +++ b/.github/instructions/solace.instructions.md @@ -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: + `///` — + 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.