@rashidee/co2 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# Messaging Patterns — RabbitMQ Pub/Sub
|
|
2
|
+
|
|
3
|
+
This reference describes the standalone RabbitMQ pub/sub messaging layer for inter-system
|
|
4
|
+
communication. This is **independent** from Spring Modulith's internal application events
|
|
5
|
+
and from Spring Batch remote partitioning — it provides explicit publisher/consumer
|
|
6
|
+
services that modules call to communicate with external systems.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Architecture Overview
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌─────────────────────────────────────────────┐
|
|
14
|
+
│ RabbitMQ Broker │
|
|
15
|
+
│ │
|
|
16
|
+
┌──────────┐ │ ┌──────────────────┐ ┌───────────────┐ │ ┌──────────────┐
|
|
17
|
+
│ Module │ publish │ │ app.events │ │ app.commands │ │ consume │ External │
|
|
18
|
+
│ │────────►│ │ (TopicExchange) │ │ (DirectExch.) │ │────────►│ System A │
|
|
19
|
+
│ Service │ │ └────────┬─────────┘ └───────┬───────┘ │ └──────────────┘
|
|
20
|
+
└──────────┘ │ │ │ │
|
|
21
|
+
│ ┌─────▼──────┐ ┌─────▼──────┐ │
|
|
22
|
+
│ │ Queue: │ │ Queue: │ │
|
|
23
|
+
│ │ order.* │ │ inventory │ │
|
|
24
|
+
│ └─────┬──────┘ │ .sync │ │
|
|
25
|
+
│ │ └─────┬──────┘ │
|
|
26
|
+
┌──────────┐ consume │ │ │ │ ┌──────────────┐
|
|
27
|
+
│ This │◄─────────│───────────┘ └─────────│────────►│ External │
|
|
28
|
+
│ App │ │ │ │ System B │
|
|
29
|
+
└──────────┘ │ ┌──────────────────┐ │ └──────────────┘
|
|
30
|
+
│ │ app.dlx │ │
|
|
31
|
+
│ │ (Dead Letter) │ │
|
|
32
|
+
│ └──────────────────┘ │
|
|
33
|
+
└─────────────────────────────────────────────┘
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Key concepts:**
|
|
37
|
+
- **Topic Exchange** (`app.events.exchange`) — pub/sub event broadcasting with routing key
|
|
38
|
+
patterns (e.g., `order.exported`, `order.cancelled`)
|
|
39
|
+
- **Direct Exchange** (`app.commands.exchange`) — point-to-point command delivery to a
|
|
40
|
+
specific queue (e.g., `inventory.sync`)
|
|
41
|
+
- **Dead Letter Exchange** (`app.dlx.exchange`) — failed messages routed here after max
|
|
42
|
+
retries for investigation and replay
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Maven Dependencies
|
|
47
|
+
|
|
48
|
+
```xml
|
|
49
|
+
<!-- RabbitMQ Messaging (included when Messaging = yes) -->
|
|
50
|
+
<dependency>
|
|
51
|
+
<groupId>org.springframework.boot</groupId>
|
|
52
|
+
<artifactId>spring-boot-starter-amqp</artifactId>
|
|
53
|
+
</dependency>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **Note:** If Remote Partitioning is also selected, `spring-boot-starter-amqp` is already
|
|
57
|
+
> present — do not duplicate. The same RabbitMQ broker connection is shared.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Application Configuration
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
# [If Messaging = yes] add to application.yml:
|
|
65
|
+
spring:
|
|
66
|
+
rabbitmq:
|
|
67
|
+
host: localhost
|
|
68
|
+
port: 5672
|
|
69
|
+
username: guest
|
|
70
|
+
password: guest
|
|
71
|
+
virtual-host: /
|
|
72
|
+
|
|
73
|
+
app:
|
|
74
|
+
messaging:
|
|
75
|
+
events-exchange: app.events.exchange
|
|
76
|
+
commands-exchange: app.commands.exchange
|
|
77
|
+
dead-letter-exchange: app.dlx.exchange
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> **Shared broker config:** If Remote Partitioning is also selected, the `spring.rabbitmq.*`
|
|
81
|
+
> connection block is shared — include it only once. The batch partition queues
|
|
82
|
+
> (`batch.partition.*`) and messaging queues (`app.*`) use different exchanges and routing
|
|
83
|
+
> keys, so they do not conflict.
|
|
84
|
+
|
|
85
|
+
### application-prod.yml overrides
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
spring:
|
|
89
|
+
rabbitmq:
|
|
90
|
+
host: ${RABBITMQ_HOST}
|
|
91
|
+
port: ${RABBITMQ_PORT:5672}
|
|
92
|
+
username: ${RABBITMQ_USERNAME}
|
|
93
|
+
password: ${RABBITMQ_PASSWORD}
|
|
94
|
+
virtual-host: ${RABBITMQ_VHOST:/}
|
|
95
|
+
connection-timeout: 10000
|
|
96
|
+
requested-heartbeat: 30
|
|
97
|
+
publisher-confirm-type: correlated
|
|
98
|
+
publisher-returns: true
|
|
99
|
+
listener:
|
|
100
|
+
simple:
|
|
101
|
+
prefetch: 10
|
|
102
|
+
concurrency: 2
|
|
103
|
+
max-concurrency: 5
|
|
104
|
+
retry:
|
|
105
|
+
enabled: true
|
|
106
|
+
initial-interval: 1000
|
|
107
|
+
max-attempts: 3
|
|
108
|
+
max-interval: 10000
|
|
109
|
+
multiplier: 2.0
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Package Structure
|
|
115
|
+
|
|
116
|
+
Messaging artifacts split across two locations to honor Spring Modulith boundaries:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
shared/
|
|
120
|
+
└── messaging/ # Shared infrastructure ONLY (no business logic)
|
|
121
|
+
├── RabbitMQMessagingConfig.java # Exchange, queue, binding declarations
|
|
122
|
+
├── RabbitMQPublisher.java # Generic publisher service
|
|
123
|
+
├── MessageConverterConfig.java # Jackson2JsonMessageConverter bean
|
|
124
|
+
├── OrderExportedEvent.java # Inter-system event/command DTOs
|
|
125
|
+
└── InventorySyncCommand.java
|
|
126
|
+
|
|
127
|
+
{{module}}/ # Module — PUBLIC API
|
|
128
|
+
└── internal/ # INTERNAL — hidden from other modules
|
|
129
|
+
├── {{Module}}ServiceImpl.java
|
|
130
|
+
├── ...
|
|
131
|
+
└── {{Module}}EventConsumer.java # @RabbitListener — MUST live here, NOT in shared/
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
> **Convention — MQ listeners are module-internal.**
|
|
135
|
+
> Every `@RabbitListener` (and any other inbound MQ adapter) is a module-specific
|
|
136
|
+
> **inbound adapter** — it belongs inside the owning module's `internal/` package, never
|
|
137
|
+
> under `shared/messaging/consumer/` or any other shared location. This mirrors how
|
|
138
|
+
> `@RestController` and `@Controller` adapters are placed: inside `internal/`. The shared
|
|
139
|
+
> `messaging/` package holds infrastructure beans only (config, publisher, converter,
|
|
140
|
+
> cross-module DTOs) — it must contain **no** `@RabbitListener` classes.
|
|
141
|
+
>
|
|
142
|
+
> Reasoning:
|
|
143
|
+
> - A consumer that calls `OrderService` belongs to the `order` module's bounded context.
|
|
144
|
+
> - Placing it in `shared/` lets it bypass module boundaries and inject any `*ServiceImpl`,
|
|
145
|
+
> silently breaking Spring Modulith encapsulation and making `ApplicationModules.verify()`
|
|
146
|
+
> results misleading.
|
|
147
|
+
> - Module-internal placement keeps the listener, the service it drives, and the entity
|
|
148
|
+
> it persists colocated and independently verifiable.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## RabbitMQ Infrastructure Configuration
|
|
153
|
+
|
|
154
|
+
```java
|
|
155
|
+
package {{BASE_PACKAGE}}.shared.messaging;
|
|
156
|
+
|
|
157
|
+
import org.springframework.amqp.core.Binding;
|
|
158
|
+
import org.springframework.amqp.core.BindingBuilder;
|
|
159
|
+
import org.springframework.amqp.core.DirectExchange;
|
|
160
|
+
import org.springframework.amqp.core.FanoutExchange;
|
|
161
|
+
import org.springframework.amqp.core.Queue;
|
|
162
|
+
import org.springframework.amqp.core.QueueBuilder;
|
|
163
|
+
import org.springframework.amqp.core.TopicExchange;
|
|
164
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
165
|
+
import org.springframework.context.annotation.Bean;
|
|
166
|
+
import org.springframework.context.annotation.Configuration;
|
|
167
|
+
|
|
168
|
+
@Configuration
|
|
169
|
+
public class RabbitMQMessagingConfig {
|
|
170
|
+
|
|
171
|
+
@Value("${app.messaging.events-exchange}")
|
|
172
|
+
private String eventsExchangeName;
|
|
173
|
+
|
|
174
|
+
@Value("${app.messaging.commands-exchange}")
|
|
175
|
+
private String commandsExchangeName;
|
|
176
|
+
|
|
177
|
+
@Value("${app.messaging.dead-letter-exchange}")
|
|
178
|
+
private String dlxExchangeName;
|
|
179
|
+
|
|
180
|
+
// ── Exchanges ────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
@Bean
|
|
183
|
+
TopicExchange eventsExchange() {
|
|
184
|
+
return new TopicExchange(eventsExchangeName, true, false);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@Bean
|
|
188
|
+
DirectExchange commandsExchange() {
|
|
189
|
+
return new DirectExchange(commandsExchangeName, true, false);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@Bean
|
|
193
|
+
FanoutExchange deadLetterExchange() {
|
|
194
|
+
return new FanoutExchange(dlxExchangeName, true, false);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Dead Letter Queue ────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
@Bean
|
|
200
|
+
Queue deadLetterQueue() {
|
|
201
|
+
return QueueBuilder.durable("app.dlq").build();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@Bean
|
|
205
|
+
Binding deadLetterBinding(Queue deadLetterQueue, FanoutExchange deadLetterExchange) {
|
|
206
|
+
return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── Sample Event Queue (Topic Exchange) ──────────────────────────────
|
|
210
|
+
// Subscribe to all order-related events via routing key pattern "order.*"
|
|
211
|
+
|
|
212
|
+
@Bean
|
|
213
|
+
Queue orderEventsQueue() {
|
|
214
|
+
return QueueBuilder.durable("app.events.order")
|
|
215
|
+
.withArgument("x-dead-letter-exchange", dlxExchangeName)
|
|
216
|
+
.build();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@Bean
|
|
220
|
+
Binding orderEventsBinding(Queue orderEventsQueue, TopicExchange eventsExchange) {
|
|
221
|
+
return BindingBuilder.bind(orderEventsQueue).to(eventsExchange).with("order.*");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Sample Command Queue (Direct Exchange) ───────────────────────────
|
|
225
|
+
// Point-to-point command delivery for inventory sync
|
|
226
|
+
|
|
227
|
+
@Bean
|
|
228
|
+
Queue inventorySyncQueue() {
|
|
229
|
+
return QueueBuilder.durable("app.commands.inventory-sync")
|
|
230
|
+
.withArgument("x-dead-letter-exchange", dlxExchangeName)
|
|
231
|
+
.build();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@Bean
|
|
235
|
+
Binding inventorySyncBinding(Queue inventorySyncQueue, DirectExchange commandsExchange) {
|
|
236
|
+
return BindingBuilder.bind(inventorySyncQueue).to(commandsExchange).with("inventory.sync");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Message Converter
|
|
244
|
+
|
|
245
|
+
```java
|
|
246
|
+
package {{BASE_PACKAGE}}.shared.messaging;
|
|
247
|
+
|
|
248
|
+
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
|
249
|
+
import org.springframework.amqp.support.converter.MessageConverter;
|
|
250
|
+
import org.springframework.context.annotation.Bean;
|
|
251
|
+
import org.springframework.context.annotation.Configuration;
|
|
252
|
+
|
|
253
|
+
@Configuration
|
|
254
|
+
public class MessageConverterConfig {
|
|
255
|
+
|
|
256
|
+
@Bean
|
|
257
|
+
MessageConverter jackson2JsonMessageConverter() {
|
|
258
|
+
return new Jackson2JsonMessageConverter();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Publisher Service
|
|
266
|
+
|
|
267
|
+
```java
|
|
268
|
+
package {{BASE_PACKAGE}}.shared.messaging;
|
|
269
|
+
|
|
270
|
+
import lombok.RequiredArgsConstructor;
|
|
271
|
+
import lombok.extern.slf4j.Slf4j;
|
|
272
|
+
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
|
273
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
274
|
+
import org.springframework.stereotype.Service;
|
|
275
|
+
|
|
276
|
+
@Service
|
|
277
|
+
@RequiredArgsConstructor
|
|
278
|
+
@Slf4j
|
|
279
|
+
public class RabbitMQPublisher {
|
|
280
|
+
|
|
281
|
+
private final RabbitTemplate rabbitTemplate;
|
|
282
|
+
|
|
283
|
+
@Value("${app.messaging.events-exchange}")
|
|
284
|
+
private String eventsExchange;
|
|
285
|
+
|
|
286
|
+
@Value("${app.messaging.commands-exchange}")
|
|
287
|
+
private String commandsExchange;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Publish a domain event to the topic exchange.
|
|
291
|
+
* Consumers subscribe via routing key patterns (e.g., "order.*").
|
|
292
|
+
*
|
|
293
|
+
* @param routingKey the routing key (e.g., "order.exported", "order.cancelled")
|
|
294
|
+
* @param event the event payload — serialized to JSON automatically
|
|
295
|
+
*/
|
|
296
|
+
public void publishEvent(String routingKey, Object event) {
|
|
297
|
+
log.info("Publishing event [{}]: {}", routingKey, event.getClass().getSimpleName());
|
|
298
|
+
rabbitTemplate.convertAndSend(eventsExchange, routingKey, event);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Send a command to the direct exchange for point-to-point delivery.
|
|
303
|
+
*
|
|
304
|
+
* @param routingKey the routing key matching a specific queue (e.g., "inventory.sync")
|
|
305
|
+
* @param command the command payload — serialized to JSON automatically
|
|
306
|
+
*/
|
|
307
|
+
public void sendCommand(String routingKey, Object command) {
|
|
308
|
+
log.info("Sending command [{}]: {}", routingKey, command.getClass().getSimpleName());
|
|
309
|
+
rabbitTemplate.convertAndSend(commandsExchange, routingKey, command);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Sample Event / Command DTOs
|
|
317
|
+
|
|
318
|
+
```java
|
|
319
|
+
package {{BASE_PACKAGE}}.shared.messaging;
|
|
320
|
+
|
|
321
|
+
import java.time.Instant;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Sample domain event broadcast to external systems via the topic exchange.
|
|
325
|
+
*/
|
|
326
|
+
public record OrderExportedEvent(
|
|
327
|
+
String orderId,
|
|
328
|
+
String customerId,
|
|
329
|
+
Instant exportedAt
|
|
330
|
+
) {}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Sample point-to-point command sent via the direct exchange.
|
|
334
|
+
*/
|
|
335
|
+
public record InventorySyncCommand(
|
|
336
|
+
String productId,
|
|
337
|
+
int quantity,
|
|
338
|
+
String warehouseCode
|
|
339
|
+
) {}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Consumer Service (module-internal)
|
|
345
|
+
|
|
346
|
+
The consumer lives inside the owning module's `internal/` package — **never** in
|
|
347
|
+
`shared/messaging/`. Use package-private visibility (no `public` modifier) so Spring
|
|
348
|
+
Modulith treats it as internal-only.
|
|
349
|
+
|
|
350
|
+
```java
|
|
351
|
+
package {{BASE_PACKAGE}}.order.internal;
|
|
352
|
+
|
|
353
|
+
import {{BASE_PACKAGE}}.order.OrderService;
|
|
354
|
+
import {{BASE_PACKAGE}}.shared.messaging.OrderExportedEvent;
|
|
355
|
+
import lombok.RequiredArgsConstructor;
|
|
356
|
+
import lombok.extern.slf4j.Slf4j;
|
|
357
|
+
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
|
358
|
+
import org.springframework.stereotype.Component;
|
|
359
|
+
|
|
360
|
+
@Component
|
|
361
|
+
@RequiredArgsConstructor
|
|
362
|
+
@Slf4j
|
|
363
|
+
class OrderEventConsumer {
|
|
364
|
+
|
|
365
|
+
private final OrderService orderService;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Listens to order events from the topic exchange.
|
|
369
|
+
* The queue "app.events.order" is bound with routing key pattern "order.*".
|
|
370
|
+
*
|
|
371
|
+
* Inbound adapter — adapts a RabbitMQ message into a call against the module's
|
|
372
|
+
* public service interface (OrderService). Same role as OrderController, but for
|
|
373
|
+
* MQ instead of HTTP.
|
|
374
|
+
*/
|
|
375
|
+
@RabbitListener(queues = "app.events.order")
|
|
376
|
+
void handleOrderEvent(OrderExportedEvent event) {
|
|
377
|
+
log.info("Received order exported event: orderId={}, customerId={}",
|
|
378
|
+
event.orderId(), event.customerId());
|
|
379
|
+
// Delegate to the module's public service interface.
|
|
380
|
+
// Do NOT inject another module's *ServiceImpl or repository here.
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
> **Placement rule** — for every `@RabbitListener` you generate:
|
|
386
|
+
> 1. Identify the **bounded context** it serves (which module's data/logic it drives).
|
|
387
|
+
> 2. Put the class in `{{BASE_PACKAGE}}.<that-module>.internal`.
|
|
388
|
+
> 3. Make it package-private (drop `public`) so other modules cannot reference it.
|
|
389
|
+
> 4. Inject only that module's own components or other modules' **public service interfaces** — never another module's `*ServiceImpl`, `*Repository`, or `*Entity`.
|
|
390
|
+
> 5. If a single message would fan out work across multiple modules, publish a Spring Modulith `ApplicationEvent` from the listener and let each module react via `@ApplicationModuleListener` in its own `internal/` package.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Error Handling
|
|
395
|
+
|
|
396
|
+
### Retry with Dead Letter Queue
|
|
397
|
+
|
|
398
|
+
Queues are configured with `x-dead-letter-exchange` pointing to `app.dlx.exchange`.
|
|
399
|
+
When a message fails after the configured retry attempts, Spring AMQP routes it to the
|
|
400
|
+
dead letter queue (`app.dlq`) for manual investigation.
|
|
401
|
+
|
|
402
|
+
### Custom Error Handler (optional)
|
|
403
|
+
|
|
404
|
+
```java
|
|
405
|
+
package {{BASE_PACKAGE}}.shared.messaging;
|
|
406
|
+
|
|
407
|
+
import lombok.extern.slf4j.Slf4j;
|
|
408
|
+
import org.springframework.amqp.core.Message;
|
|
409
|
+
import org.springframework.amqp.rabbit.listener.api.RabbitListenerErrorHandler;
|
|
410
|
+
import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException;
|
|
411
|
+
import org.springframework.stereotype.Component;
|
|
412
|
+
|
|
413
|
+
@Component("messagingErrorHandler")
|
|
414
|
+
@Slf4j
|
|
415
|
+
public class MessagingErrorHandler implements RabbitListenerErrorHandler {
|
|
416
|
+
|
|
417
|
+
@Override
|
|
418
|
+
public Object handleError(Message amqpMessage,
|
|
419
|
+
org.springframework.messaging.Message<?> message,
|
|
420
|
+
ListenerExecutionFailedException exception) {
|
|
421
|
+
log.error("Failed to process message from queue [{}]: {}",
|
|
422
|
+
amqpMessage.getMessageProperties().getConsumerQueue(),
|
|
423
|
+
exception.getCause().getMessage(),
|
|
424
|
+
exception);
|
|
425
|
+
// Optionally: send alert, record metric, etc.
|
|
426
|
+
// Re-throw to trigger retry / DLQ routing
|
|
427
|
+
throw exception;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Usage in a consumer:
|
|
433
|
+
```java
|
|
434
|
+
@RabbitListener(queues = "app.events.order", errorHandler = "messagingErrorHandler")
|
|
435
|
+
public void handleOrderEvent(OrderExportedEvent event) {
|
|
436
|
+
// ...
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Retry Configuration (application.yml)
|
|
441
|
+
|
|
442
|
+
```yaml
|
|
443
|
+
spring:
|
|
444
|
+
rabbitmq:
|
|
445
|
+
listener:
|
|
446
|
+
simple:
|
|
447
|
+
retry:
|
|
448
|
+
enabled: true
|
|
449
|
+
initial-interval: 1000
|
|
450
|
+
max-attempts: 3
|
|
451
|
+
max-interval: 10000
|
|
452
|
+
multiplier: 2.0
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Usage from Modules
|
|
458
|
+
|
|
459
|
+
Modules inject `RabbitMQPublisher` to send messages to external systems.
|
|
460
|
+
This is **separate** from Spring Modulith's `ApplicationEventPublisher`, which handles
|
|
461
|
+
in-process inter-module events.
|
|
462
|
+
|
|
463
|
+
```java
|
|
464
|
+
package {{BASE_PACKAGE}}.order.internal;
|
|
465
|
+
|
|
466
|
+
import {{BASE_PACKAGE}}.shared.messaging.OrderExportedEvent;
|
|
467
|
+
import {{BASE_PACKAGE}}.shared.messaging.RabbitMQPublisher;
|
|
468
|
+
import lombok.RequiredArgsConstructor;
|
|
469
|
+
import lombok.extern.slf4j.Slf4j;
|
|
470
|
+
import org.springframework.stereotype.Service;
|
|
471
|
+
import java.time.Instant;
|
|
472
|
+
|
|
473
|
+
@Service
|
|
474
|
+
@RequiredArgsConstructor
|
|
475
|
+
@Slf4j
|
|
476
|
+
class OrderServiceImpl implements OrderService {
|
|
477
|
+
|
|
478
|
+
private final OrderRepository repository;
|
|
479
|
+
private final RabbitMQPublisher messagingPublisher;
|
|
480
|
+
|
|
481
|
+
public void exportOrder(String orderId) {
|
|
482
|
+
var order = repository.findById(orderId)
|
|
483
|
+
.orElseThrow(() -> new OrderException.NotFound(orderId));
|
|
484
|
+
|
|
485
|
+
// ... export logic ...
|
|
486
|
+
|
|
487
|
+
// Publish event to external systems via RabbitMQ
|
|
488
|
+
messagingPublisher.publishEvent("order.exported",
|
|
489
|
+
new OrderExportedEvent(order.getId(), order.getCustomerId(), Instant.now()));
|
|
490
|
+
log.info("Order {} exported and event published", orderId);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Operational Notes
|
|
498
|
+
|
|
499
|
+
- **Connection pooling**: Spring Boot auto-configures a `CachingConnectionFactory`.
|
|
500
|
+
Default channel cache size is 25. Adjust `spring.rabbitmq.cache.channel.size` for
|
|
501
|
+
high-throughput scenarios.
|
|
502
|
+
- **Heartbeat**: Set `spring.rabbitmq.requested-heartbeat: 30` in production to detect
|
|
503
|
+
dropped connections within 60 seconds (2x heartbeat interval).
|
|
504
|
+
- **Consumer prefetch**: `spring.rabbitmq.listener.simple.prefetch` controls how many
|
|
505
|
+
messages a consumer fetches at once. Default is 250; set to 1-10 for slow consumers
|
|
506
|
+
to avoid message starvation across instances.
|
|
507
|
+
- **Publisher confirms**: Enable `spring.rabbitmq.publisher-confirm-type: correlated` in
|
|
508
|
+
production to guarantee message delivery to the broker.
|
|
509
|
+
- **Monitoring**: Monitor the dead letter queue (`app.dlq`) for failed messages.
|
|
510
|
+
Integrate with your alerting system to notify on DLQ message accumulation.
|
|
511
|
+
- **Queue naming convention**: All messaging queues use the `app.*` prefix. Batch
|
|
512
|
+
partition queues use `batch.partition.*`. These namespaces must not overlap.
|