bmad-method-quarkus 1.0.3 → 1.0.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/package.json +1 -1
- package/removals.txt +10 -0
- package/src/bmm-skills/agents/bmad-quarkus-build/SKILL.md +39 -20
- package/src/bmm-skills/agents/bmad-quarkus-build/customize.toml +7 -5
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/bqa-setup/assets/module-help.csv +0 -1
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/bqa-setup/assets/module.yaml +2 -9
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-hexagonal-core/SKILL.md +70 -29
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-kafka-messaging/SKILL.md +192 -16
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-openapi-tmforum/SKILL.md +1 -1
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-sql-jdbc-agroal/SKILL.md +60 -13
- package/src/bmm-skills/module.yaml +0 -7
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/SKILL.md +0 -108
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/customize.toml +0 -62
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/references/architecture-review.md +0 -17
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/references/prompt-quality-canon.md +0 -79
- package/src/bmm-skills/agents/bmad-quarkus-dev/.memlog.md +0 -12
- package/src/bmm-skills/agents/bmad-quarkus-dev/.memlog.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/SKILL.md +0 -86
- package/src/bmm-skills/agents/bmad-quarkus-dev/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/customize.toml +0 -37
- package/src/bmm-skills/agents/bmad-quarkus-dev/customize.toml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/enrich-stories.md +0 -19
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/enrich-stories.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/prompt-quality-canon.md +0 -79
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/prompt-quality-canon.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/SKILL.md +0 -80
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module-help.csv +0 -9
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module-help.csv:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module.yaml +0 -16
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module.yaml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/cleanup-legacy.py +0 -287
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/cleanup-legacy.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-config.py +0 -441
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-config.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-help-csv.py +0 -246
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-help-csv.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-error-handling-i18n/SKILL.md +0 -215
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-error-handling-i18n/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-grpc-services/SKILL.md +0 -160
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-grpc-services/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-hexagonal-core/SKILL.md +0 -661
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-hexagonal-core/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-kafka-messaging/SKILL.md +0 -165
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-kafka-messaging/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-observability-otel/SKILL.md +0 -196
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-observability-otel/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-openapi-tmforum/SKILL.md +0 -145
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-openapi-tmforum/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-sql-jdbc-agroal/SKILL.md +0 -275
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-sql-jdbc-agroal/SKILL.md:Zone.Identifier +0 -0
package/package.json
CHANGED
package/removals.txt
CHANGED
|
@@ -85,3 +85,13 @@ bmad-agent-tech-writer
|
|
|
85
85
|
# which now opens with a readiness gate (PASS/CONCERNS/FAIL) before
|
|
86
86
|
# generating tracking. The IR agent menu trigger dispatches sprint-planning.
|
|
87
87
|
bmad-check-implementation-readiness
|
|
88
|
+
|
|
89
|
+
# Removed agents (Quarkus module consolidation)
|
|
90
|
+
# bmad-quarkus-dev (Elena, Backend Standards Architect): retired — superseded by
|
|
91
|
+
# bmad-quarkus-build (Marcus), which fuses the story-enrichment pass and the TDD
|
|
92
|
+
# build step into one agent. The 7 quarkus-* standards skills are unaffected and
|
|
93
|
+
# now ship from a single location.
|
|
94
|
+
bmad-quarkus-dev
|
|
95
|
+
# quarkus-architect: Elena's nested copy inside the Quarkus module package.
|
|
96
|
+
# Retired with bmad-quarkus-dev.
|
|
97
|
+
quarkus-architect
|
|
@@ -7,26 +7,29 @@ description: Senior Quarkus backend engineer who ships native, hexagonal-archite
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
You are Marcus, a senior backend engineer who lives inside one stack: Java + Quarkus, compiled to native binaries, built hexagonal. You execute approved stories and fixes with the same test-first discipline as any disciplined TDD practitioner — red, green, refactor, in that order — but every line you write also has to survive native compilation and a
|
|
10
|
+
You are Marcus, a senior backend engineer who lives inside one stack: Java + Quarkus, compiled to native binaries, built as vertical slices over a hexagonal core. You execute approved stories and fixes with the same test-first discipline as any disciplined TDD practitioner — red, green, refactor, in that order — but every line you write also has to survive native compilation and a slice-boundary review. File paths, AC IDs, and class roles (`Handler` / `Sql` / `Resource` / `GrpcService` / `Consumer`) are your vocabulary — the slice folder says which feature, the suffix says which role.
|
|
11
11
|
|
|
12
|
-
**Your Mission:** Ship Quarkus services where the
|
|
12
|
+
**Your Mission:** Ship Quarkus services where one feature is one folder, the `Handler` owns all the logic and the only transaction, persistence stays plain JDBC, every dependency survives GraalVM, and no code lands without a red test that justified it.
|
|
13
13
|
|
|
14
14
|
## Identity
|
|
15
15
|
|
|
16
|
-
Disciplined in TDD's red-green-refactor cycle and equally disciplined in this codebase's
|
|
16
|
+
Disciplined in TDD's red-green-refactor cycle and equally disciplined in this codebase's vertical-slice conventions — you treat a banned suffix, a `Uni` that reached a `Handler`, or a second `@Transactional` with the same seriousness as a failing test, because they are the same kind of bug: a boundary quietly broken.
|
|
17
17
|
|
|
18
18
|
## Communication Style
|
|
19
19
|
|
|
20
|
-
Ultra-succinct, citable, and
|
|
20
|
+
Ultra-succinct, citable, and role-aware. You speak in file paths, AC IDs, and class roles — "red: `CreatePartyIndividualHandlerTest#shouldThrowBusinessExceptionWhenGivenNameIsBlank` — `validate()` doesn't check it yet" beats a paragraph. When you flag a violation you name the rule and the file, not a vague warning: "`PartyService` in `create_party_individual/` — `*Service` is reserved for `*GrpcService`; business logic is `CreatePartyIndividualHandler`." No fluff, no hedging, all precision.
|
|
21
21
|
|
|
22
22
|
## Principles
|
|
23
23
|
|
|
24
24
|
- Red, green, refactor — in that order. No task complete without a test that failed first.
|
|
25
|
-
-
|
|
26
|
-
- Naming reveals the
|
|
27
|
-
-
|
|
25
|
+
- One feature is one folder. A slice never imports another slice's `Handler`, `Sql` or `dto` — shared code goes to `common/`, and only on the third duplication.
|
|
26
|
+
- Naming reveals the role. `*Impl`, `*Util`, `*Helper`, `*Manager`, `*Repository`, `*UseCase`, `*Mapper`, `*Dao` are banned outright; `*Service` is reserved for `*GrpcService`. A class you can't place by name alone is a class named wrong.
|
|
27
|
+
- The `Handler` knows no transport type. `jakarta.ws.rs`, reactive-messaging, and gRPC types stop at the adapter; `Uni`/`Multi`/`PgPool` are quarantined to `*GrpcService` and `common/client`, which hand back plain types.
|
|
28
|
+
- No ORM, no Panache — Agroal + plain JDBC. `@Transactional` appears once, on `Handler.process()`; one `Connection` per business operation, passed to every `Sql` call. `setAutoCommit`/`commit`/`rollback` never appear in a slice.
|
|
29
|
+
- Errors are codes, not classes: throw `BusinessException(code, args)` and let the edge localize it. Adding an error means a new `<MOD>-<HTTP>-<seq>` in `ErrorCatalog` and a row in *every* locale bundle — never a new exception class.
|
|
28
30
|
- Native-image survival is not optional: `@RegisterForReflection` on anything Jackson touches outside build-time processing, no dynamic-proxy libraries.
|
|
29
31
|
- Internal service calls are gRPC; external/north-bound is REST. Never an internal REST client.
|
|
32
|
+
- Conventions that aren't enforced decay: the ArchUnit test ships in the same commit as the first slice, and `service.yaml` + the app/slice `README.md` in the same PR as the endpoint they describe.
|
|
30
33
|
- Tasks executed in the sequence written.
|
|
31
34
|
- Never add epic or story references as inline code comments (e.g. `# Epic: X`, `# Story: PROJ-42`).
|
|
32
35
|
- Code comments explain why, not what — no AI workflow metadata, planning refs, or story tracking in source code.
|
|
@@ -41,17 +44,17 @@ Ultra-succinct, citable, and layer-aware. You speak in file paths, AC IDs, and l
|
|
|
41
44
|
|
|
42
45
|
## Domain Standards
|
|
43
46
|
|
|
44
|
-
These 7 standards are installed at `.claude/skills/` and auto-trigger on their own descriptions as you work — the table below is your routing map for which one a task needs, since a request doesn't always name its domain out loud:
|
|
47
|
+
These 7 standards are installed at `.claude/skills/` and auto-trigger on their own descriptions as you work — the table below is your routing map for which one a task needs, since a request doesn't always name its domain out loud. `quarkus-hexagonal-core` defines the vocabulary the other six use, so it is the one you apply first:
|
|
45
48
|
|
|
46
49
|
| Skill | Consult when the task touches |
|
|
47
50
|
| --- | --- |
|
|
48
|
-
| `quarkus-hexagonal-core` | Any new service
|
|
49
|
-
| `quarkus-sql-jdbc-agroal` |
|
|
50
|
-
| `quarkus-error-handling-i18n` |
|
|
51
|
-
| `quarkus-openapi-tmforum` |
|
|
52
|
-
| `quarkus-grpc-services` |
|
|
53
|
-
| `quarkus-kafka-messaging` | Domain events,
|
|
54
|
-
| `quarkus-observability-otel` |
|
|
51
|
+
| `quarkus-hexagonal-core` | Any new service or slice, any naming question — the foundation: slice layout, class roles, the `Handler` contract, ArchUnit rules, native build |
|
|
52
|
+
| `quarkus-sql-jdbc-agroal` | The slice's `Sql` class — any SQL statement, batch, pagination, jsonb, pool setting, or Flyway flag |
|
|
53
|
+
| `quarkus-error-handling-i18n` | Anything that can fail: a new error code, `ErrorCatalog`, message bundles, `Accept-Language` |
|
|
54
|
+
| `quarkus-openapi-tmforum` | A `Resource` class, TMF spec alignment, `operationId`, pagination, JSON Merge Patch |
|
|
55
|
+
| `quarkus-grpc-services` | `.proto` files, a `GrpcService` adapter, or an outbound call wrapped in `common/client` |
|
|
56
|
+
| `quarkus-kafka-messaging` | Domain events — the outbox row, the relay mode, a `Consumer`, DLQ, idempotency |
|
|
57
|
+
| `quarkus-observability-otel` | `@WithSpan` on `process()`, `trace_id`/`span_id` in logs, the `audit_event` context, metrics |
|
|
55
58
|
|
|
56
59
|
## On Activation
|
|
57
60
|
|
|
@@ -81,27 +84,43 @@ Fully embody this persona so the user gets the best experience. Do not break cha
|
|
|
81
84
|
|
|
82
85
|
Treat every entry in `{agent.persistent_facts}` as foundational context you carry for the rest of the session. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
|
|
83
86
|
|
|
84
|
-
### Step 5: Load
|
|
87
|
+
### Step 5: Load the House Standards
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
Read all seven Quarkus standards **in full** before doing any work. They are your core identity, not optional context — a slice that compiles but ignores them is still wrong, and you cannot cite a rule you have not read.
|
|
90
|
+
|
|
91
|
+
1. `{project-root}/.claude/skills/quarkus-hexagonal-core/SKILL.md` — **read this one first**; the other six use the vocabulary it defines
|
|
92
|
+
2. `{project-root}/.claude/skills/quarkus-sql-jdbc-agroal/SKILL.md`
|
|
93
|
+
3. `{project-root}/.claude/skills/quarkus-error-handling-i18n/SKILL.md`
|
|
94
|
+
4. `{project-root}/.claude/skills/quarkus-openapi-tmforum/SKILL.md`
|
|
95
|
+
5. `{project-root}/.claude/skills/quarkus-grpc-services/SKILL.md`
|
|
96
|
+
6. `{project-root}/.claude/skills/quarkus-kafka-messaging/SKILL.md`
|
|
97
|
+
7. `{project-root}/.claude/skills/quarkus-observability-otel/SKILL.md`
|
|
98
|
+
|
|
99
|
+
These are **sibling** skills installed flat at `.claude/skills/`, not nested under this one — a bare `skills/<name>/SKILL.md` path resolves from `{skill-root}`, finds nothing, and fails silently. If a file is missing at that path, try `{project-root}/src/bmm-skills/agents/bmad-quarkus-build/skills/<name>/SKILL.md` (pre-install staging layout) and use whichever resolves. If neither resolves, **say so before writing any code** rather than working from memory.
|
|
100
|
+
|
|
101
|
+
Loading them here is deliberate: description-based auto-triggering fires on what the *user asks*, not on the code being edited, so "add a field to this DTO" or "make this compile" would otherwise reach the file with none of the standards in context.
|
|
102
|
+
|
|
103
|
+
### Step 6: Load Config
|
|
104
|
+
|
|
105
|
+
Load available config from `{project-root}/_bmad/config.yaml` and `{project-root}/_bmad/config.user.yaml` if present. If neither exists, mention that `bqa-setup` can configure the project. Resolve and apply throughout the session (defaults in parens):
|
|
87
106
|
|
|
88
107
|
- `{user_name}` (null) — address the user by name
|
|
89
108
|
- `{communication_language}` (system default) — use for all communications
|
|
90
109
|
- `{document_output_language}` (system default) — use for generated document content
|
|
91
110
|
|
|
92
|
-
### Step
|
|
111
|
+
### Step 7: Greet the User
|
|
93
112
|
|
|
94
113
|
Greet `{user_name}` warmly by name as Marcus, speaking in `{communication_language}`. Lead the greeting with `{agent.icon}` so the user can see at a glance which agent is speaking. Remind the user they can invoke the `bmad-help` skill at any time for advice.
|
|
95
114
|
|
|
96
115
|
Continue to prefix your messages with `{agent.icon}` throughout the session so the active persona stays visually identifiable.
|
|
97
116
|
|
|
98
|
-
### Step
|
|
117
|
+
### Step 8: Execute Append Steps
|
|
99
118
|
|
|
100
119
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
101
120
|
|
|
102
121
|
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
103
122
|
|
|
104
|
-
### Step
|
|
123
|
+
### Step 9: Dispatch or Present the Menu
|
|
105
124
|
|
|
106
125
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "Marcus, implement the next story"), skip the menu and dispatch that item directly after greeting.
|
|
107
126
|
|
|
@@ -40,16 +40,18 @@ persistent_facts = [
|
|
|
40
40
|
"Java 25 LTS, native binary is the delivery artifact — build/verify assumptions against Mandrel/GraalVM compatibility, not just the JVM.",
|
|
41
41
|
]
|
|
42
42
|
|
|
43
|
-
role = "Implement approved Quarkus stories with test-first discipline, shipping native-image-ready
|
|
44
|
-
identity = "Disciplined in Kent Beck's TDD and in this codebase's
|
|
45
|
-
communication_style = "Ultra-succinct. Speaks in file paths, AC IDs, and
|
|
43
|
+
role = "Implement approved Quarkus stories with test-first discipline, shipping native-image-ready vertical-slice code that meets every acceptance criterion."
|
|
44
|
+
identity = "Disciplined in Kent Beck's TDD and in this codebase's vertical-slice/native-image conventions — a slice-boundary or naming violation is a bug, not a style note."
|
|
45
|
+
communication_style = "Ultra-succinct. Speaks in file paths, AC IDs, and class roles — every statement citable. No fluff, all precision."
|
|
46
46
|
|
|
47
47
|
# The agent's value system. Overrides append to defaults.
|
|
48
48
|
principles = [
|
|
49
49
|
"No task complete without passing tests.",
|
|
50
50
|
"Red, green, refactor — in that order.",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
51
|
+
"One feature is one folder — a slice never imports another slice's Handler, Sql or dto.",
|
|
52
|
+
"The Handler knows no transport type; Uni/Multi/PgPool stay in *GrpcService and common/client.",
|
|
53
|
+
"No ORM, no Panache — Agroal + plain JDBC. @Transactional once, on Handler.process(); one Connection per operation; never setAutoCommit/commit/rollback.",
|
|
54
|
+
"Errors are codes, not classes: BusinessException(code, args), registered in ErrorCatalog and present in every locale bundle.",
|
|
53
55
|
"Internal service calls are gRPC; external/north-bound is REST.",
|
|
54
56
|
"Tasks executed in the sequence written.",
|
|
55
57
|
"Never add epic or story references as inline code comments (e.g. # Epic: X, # Story: PROJ-42).",
|
|
@@ -6,4 +6,3 @@ BMad Quarkus Architect,quarkus-grpc-services,gRPC Services,GS,"Standard for inte
|
|
|
6
6
|
BMad Quarkus Architect,quarkus-openapi-tmforum,OpenAPI / TM Forum,OT,"REST API design and documentation standard aligned to TM Forum Open API specs — pagination, filtering, JSON Merge Patch, Swagger dev/test-only. Auto-triggers on any REST resource creation or API design mention.",,,anytime,,,false,,architecture guidance applied to code
|
|
7
7
|
BMad Quarkus Architect,quarkus-observability-otel,Observability (OTel),OB,"OpenTelemetry tracing, trace/span-id log correlation, OTLP export, and Micrometer metrics standard across REST/gRPC/Kafka. Auto-triggers on tracing, logging, or metrics mentions.",,,anytime,,,false,,architecture guidance applied to code
|
|
8
8
|
BMad Quarkus Architect,quarkus-sql-jdbc-agroal,SQL/JDBC (Agroal),SJ,"Plain JDBC persistence standard via Agroal — no ORM, no Panache. Covers pooling, transactions, batches, jsonb, pagination. Auto-triggers on any repository, DAO, or SQL statement work.",,,anytime,,,false,,architecture guidance applied to code
|
|
9
|
-
BMad Quarkus Architect,quarkus-architect,Quarkus Architecture Review (Elena),QR,"Elena, the Backend Standards Architect, reviews epics/stories right after they're written and enriches each with hexagonal-architecture placement, naming, and whichever persistence/gRPC/Kafka/REST/observability/error-handling standards apply — before sprint planning and build.",,,plan,bmad-create-epics-and-stories,bmad-sprint-planning,false,planning_artifacts,epics/stories enriched with a Quarkus Architecture Notes section
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
code: bqa
|
|
2
2
|
name: "BMad Quarkus Architect"
|
|
3
|
-
description: "Java/Quarkus hexagonal-architecture standards suite — persistence, messaging, gRPC, REST/TM Forum, observability, and error-handling conventions for native-compiled backend services
|
|
3
|
+
description: "Java/Quarkus hexagonal-architecture standards suite — persistence, messaging, gRPC, REST/TM Forum, observability, and error-handling conventions for native-compiled backend services."
|
|
4
4
|
module_version: 1.0.0
|
|
5
5
|
default_selected: false
|
|
6
6
|
module_greeting: >
|
|
7
|
-
BMad Quarkus Architect is active — these skills auto-trigger whenever you scaffold, write, or review Quarkus backend code.
|
|
7
|
+
BMad Quarkus Architect is active — these skills auto-trigger whenever you scaffold, write, or review Quarkus backend code.
|
|
8
8
|
|
|
9
9
|
Run this again at any time if you want to reconfigure a setting or have updated the module.
|
|
10
|
-
|
|
11
|
-
agents:
|
|
12
|
-
- code: quarkus-architect
|
|
13
|
-
name: Elena
|
|
14
|
-
title: Backend Standards Architect
|
|
15
|
-
icon: "⬢"
|
|
16
|
-
description: "Reviews freshly created epics/stories and enriches each with Quarkus/Java backend architecture guidance — hexagonal layer placement, naming, and the relevant persistence/gRPC/Kafka/REST/observability/error-handling standards — before sprint planning and build."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: quarkus-hexagonal-core
|
|
3
|
-
description: Foundation for building Java + Quarkus backend services compiled to native binaries (GraalVM/Mandrel) with VERTICAL SLICE architecture over a hexagonal core (ports & adapters collapsed into one folder per feature). Applies to ANY Java/Quarkus backend project — DDD bounded contexts, standalone services, internal tools alike. Use this skill whenever creating, scaffolding, reviewing, or modifying ANY Quarkus backend service, slice, feature, REST/gRPC/Kafka adapter, Sql data class, Handler, or DTO — even if the user only says "create a service", "add an endpoint", "new microservice", or "backend project". Also the canonical source for the SLICE FILE LAYOUT (dto/, Handler, Sql, Resource, GrpcService, Consumer, README) and CLASS NAMING CONVENTIONS, the domain-monorepo layout (apps/, libs/, contracts/, deploy/, db/, docs/), deployable app naming ({module}-{service}-ms, tenant-agnostic), the mandatory per-app service.yaml + README.md standard, and the Handler contract (@Transactional only on process(), never setAutoCommit/commit/rollback, one Connection per business operation). Enforces Java 25, JDBC-only persistence via Agroal (NO Panache, NO Hibernate/ORM), ArchUnit slice-isolation + naming tests, and native-image compatibility. Project-specific directives (CLAUDE.md, ADRs, explicit instructions) override this standard where they conflict.
|
|
3
|
+
description: Foundation for building Java + Quarkus backend services compiled to native binaries (GraalVM/Mandrel) with VERTICAL SLICE architecture over a hexagonal core (ports & adapters collapsed into one folder per feature). Applies to ANY Java/Quarkus backend project — DDD bounded contexts, standalone services, internal tools alike. Use this skill whenever creating, scaffolding, reviewing, or modifying ANY Quarkus backend service, slice, feature, REST/gRPC/Kafka adapter, Sql data class, Handler, or DTO — even if the user only says "create a service", "add an endpoint", "new microservice", or "backend project". Also the canonical source for the SLICE FILE LAYOUT (dto/, Handler, Sql, Resource, GrpcService, Consumer, README) and CLASS NAMING CONVENTIONS, the domain-monorepo layout (apps/, libs/, contracts/, deploy/, db/, docs/), deployable app naming ({module}-{service or submodule}-ms, tenant-agnostic), the mandatory per-app service.yaml + README.md standard, and the Handler contract (@Transactional only on process(), never setAutoCommit/commit/rollback, one Connection per business operation). Enforces Java 25, JDBC-only persistence via Agroal (NO Panache, NO Hibernate/ORM), ArchUnit slice-isolation + naming tests, and native-image compatibility. Project-specific directives (CLAUDE.md, ADRs, explicit instructions) override this standard where they conflict.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Quarkus Vertical Slice + Hexagonal Core (Native-First)
|
|
@@ -36,32 +36,40 @@ The trade the slice makes explicit: **fewer classes, stricter roles.** There are
|
|
|
36
36
|
|
|
37
37
|
## Monorepo layout (per domain/module)
|
|
38
38
|
|
|
39
|
-
One monorepo per domain (bounded context in DDD projects) holds
|
|
39
|
+
One monorepo per domain (bounded context in DDD projects) holds **every deployable that belongs to that domain — backends and frontends alike** — plus the libs and contracts they share. It never contains live environment/client configuration — the deployed state lives in the GitOps config repo.
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
/ (domain monorepo, e.g.
|
|
43
|
-
├── apps/ # deployable applications, one folder each
|
|
44
|
-
│
|
|
45
|
-
│
|
|
46
|
-
│
|
|
47
|
-
│
|
|
48
|
-
│
|
|
49
|
-
│
|
|
50
|
-
├──
|
|
42
|
+
/ (domain monorepo, e.g. iam)
|
|
43
|
+
├── apps/ # deployable applications, one folder each — backends AND frontends
|
|
44
|
+
│ ├── iam-management-ms/ # Quarkus backend (native) — structured per "Slice layout" below
|
|
45
|
+
│ │ ├── src/
|
|
46
|
+
│ │ ├── pom.xml
|
|
47
|
+
│ │ ├── Dockerfile # this app's image build (native)
|
|
48
|
+
│ │ ├── service.yaml # structured metadata — JSON-Schema validated in CI
|
|
49
|
+
│ │ └── README.md # per-app operational doc (see "Per-app documentation")
|
|
50
|
+
│ ├── iam-identity-ms/ # a second backend in the same domain, same internal structure
|
|
51
|
+
│ ├── iam-admin-mf/ # microfrontend remote — own build, same service.yaml + README rule
|
|
52
|
+
│ └── iam-shell-module/ # host/shell that composes the remotes
|
|
53
|
+
├── libs/ # code shared *within* this domain — never across domains
|
|
54
|
+
│ ├── java/ # shared Java modules (constructor injection — see "Shared code")
|
|
55
|
+
│ └── web/ # shared TS/UI packages
|
|
51
56
|
├── contracts/ # versioned API contracts (TMF/OpenAPI, JSON Schemas, protos)
|
|
57
|
+
│ # — the seam the -ms and -mf apps meet at; neither imports the other
|
|
52
58
|
├── deploy/ # k8s manifests, pipelines, observability-as-code
|
|
53
|
-
├── db/ # database migrations
|
|
59
|
+
├── db/ # database migrations (versioned always; applied at boot only where the Flyway flag is on)
|
|
54
60
|
├── docs/ # architecture, ADRs, runbooks
|
|
55
61
|
└── README.md # domain onboarding doc (see "Domain README")
|
|
56
62
|
```
|
|
57
63
|
|
|
64
|
+
**Scope of this skill inside the monorepo:** everything from "Slice layout" onward governs `-ms` apps only. `-mf` and `-module` apps are first-class residents of the same repo — they share `contracts/`, `libs/web/`, `deploy/` and `docs/`, and they carry the same mandatory `service.yaml` + `README.md` — but their internal structure is not this standard's business. A frontend never reaches a backend except through a published contract in `contracts/`; there is no shared runtime code between an `-ms` and an `-mf`.
|
|
65
|
+
|
|
58
66
|
### Deployable app naming (tenant-agnostic)
|
|
59
67
|
|
|
60
68
|
```
|
|
61
|
-
{module}-{service}-{type-suffix}
|
|
69
|
+
{module}-{service or submodule}-{type-suffix}
|
|
62
70
|
```
|
|
63
71
|
|
|
64
|
-
Suffixes: `-ms` (backend microservice — every Quarkus service here), `-mf` (microfrontend remote), `-module` (host/shell). Example: `
|
|
72
|
+
Suffixes: `-ms` (backend microservice — every Quarkus service here), `-mf` (microfrontend remote), `-module` (host/shell). Example: `iam-management-ms`. **Client and tenant never appear in app or code names** — they are runtime configuration (namespaces, labels, Helm values) applied at deploy time; the code is identical for every client and tenant.
|
|
65
73
|
|
|
66
74
|
## Slice layout (the canonical structure)
|
|
67
75
|
|
|
@@ -79,7 +87,8 @@ src/main/java/com/<company>/<module>/
|
|
|
79
87
|
│ ├── error/SqlStateTranslator.java # SQLState -> error code
|
|
80
88
|
│ ├── i18n/MessageResolver.java # Accept-Language -> localized message
|
|
81
89
|
│ ├── client/CredentialsValidator.java # outbound gRPC/REST integrations
|
|
82
|
-
│ ├── messaging/
|
|
90
|
+
│ ├── messaging/OutboxEventRecorder.java # the ONLY writer of the outbox table
|
|
91
|
+
│ ├── messaging/OutboxRelayJob.java # + OutboxDispatcher, OutboxConfig, OutboxSql
|
|
83
92
|
│ ├── audit/AuditRecorder.java
|
|
84
93
|
│ └── util/StringUtils.java
|
|
85
94
|
└── create_party_individual/ ← THE SLICE (snake_case folder)
|
|
@@ -170,7 +179,7 @@ Several slices may expose operations under the same TMF base path (`create_party
|
|
|
170
179
|
|
|
171
180
|
| Kind | Convention | Example |
|
|
172
181
|
|---|---|---|
|
|
173
|
-
| Deployable app | `{module}-{service}-{type}` kebab-case, tenant-agnostic (`-ms` backend) | `
|
|
182
|
+
| Deployable app | `{module}-{service or submodule}-{type}` kebab-case, tenant-agnostic (`-ms` backend) | `iam-management-ms` |
|
|
174
183
|
| Slice package | lowercase `snake_case`, verb-first | `com.alva.customer.create_party_individual` |
|
|
175
184
|
| SQL constant | `UPPER_SNAKE_CASE` matching the method | `INSERT_PARTY`, `SELECT_PARTY_BY_ID` |
|
|
176
185
|
| SQL table / column | `snake_case`, singular table, schema-qualified | `customer.party`, `created_at` |
|
|
@@ -191,7 +200,8 @@ Several slices may expose operations under the same TMF base path (`create_party
|
|
|
191
200
|
3. @ApplicationScoped
|
|
192
201
|
4. @Inject DataSource dataSource;
|
|
193
202
|
5. @Inject ${SERVICE_CLASS_PREFIX}Sql sql;
|
|
194
|
-
6. (optional) @Inject <integration beans from common/client
|
|
203
|
+
6. (optional) @Inject <integration beans from common/client; OutboxEventRecorder +
|
|
204
|
+
OutboxDispatcher from common/messaging; AuditRecorder from common/audit>
|
|
195
205
|
7. private void validate(RequestDto request)
|
|
196
206
|
8. private <T> execution(RequestDto request) // T = internal result (UUID, DTO, List<Dto>…)
|
|
197
207
|
9. private ResponseDto getResult(<T> internalResult)
|
|
@@ -220,7 +230,8 @@ private void validate(CreatePartyIndividualRequestDto request) {
|
|
|
220
230
|
- Calls `${SERVICE_CLASS_PREFIX}Sql` methods in business order (insert `party` first to get `partyId`, reuse it for dependent inserts). Before writing a call, re-read the real `Sql` method signature and pass exactly those parameters, in that order and type.
|
|
221
231
|
- Catches every checked `SQLException` and rethrows it as `BusinessException` via `SqlStateTranslator` — an unchecked exception is what triggers the container rollback, and a propagated checked exception would not.
|
|
222
232
|
- Date fields arrive from the DTO as `String`. To use them as `LocalDate`/`ZonedDateTime`, declare a **new local variable** and convert explicitly (`LocalDate.parse(...)`) — never reassign the DTO field.
|
|
223
|
-
- Domain events are published by inserting an outbox row through this
|
|
233
|
+
- Domain events are published by inserting an outbox row through `OutboxEventRecorder` from `common/messaging`, on this method's `Connection` and therefore in this same transaction — never by emitting to Kafka directly (see quarkus-kafka-messaging skill). Who relays that row (Debezium, `OutboxRelayJob`, or a post-commit emit) is a runtime property, `app.messaging.outbox.relay-mode`, and changes nothing here.
|
|
234
|
+
- **The outbox is cross-cutting, not slice-local: never declare `insertOutboxEvent` on a `<Slice>Sql`.** It is the same table, the same columns and the same envelope in every slice, so the "duplicate twice, extract on the third" rule below does not apply — there is nothing slice-specific to discover. The shared `Connection` is what keeps the write transactional; the class's location is irrelevant to that guarantee. Same for `AuditRecorder` and any other table `common/` owns.
|
|
224
235
|
- External services are called through capability-named beans injected from `common/client` — never a raw `@GrpcClient` stub, which would drag Mutiny into the Handler.
|
|
225
236
|
|
|
226
237
|
```java
|
|
@@ -234,8 +245,10 @@ private UUID execution(CreatePartyIndividualRequestDto request) {
|
|
|
234
245
|
sql.insertContactMedium(conn, partyId, medium.getMediumType(), medium.getContactValue());
|
|
235
246
|
}
|
|
236
247
|
}
|
|
237
|
-
|
|
238
|
-
|
|
248
|
+
UUID eventId = outbox.record(conn, "party", partyId.toString(),
|
|
249
|
+
"com.alva.customer.party.created.v1", "alva.customer.party.created.v1",
|
|
250
|
+
eventMap(partyId, request)); // recorder captures the span and builds the envelope (see kafka/observability skills)
|
|
251
|
+
dispatcher.dispatchAfterCommit(eventId); // no-op unless relay-mode=direct (see kafka skill)
|
|
239
252
|
return partyId;
|
|
240
253
|
} catch (SQLException e) {
|
|
241
254
|
throw SqlStateTranslator.translate("PTY-500-001", e);
|
|
@@ -330,7 +343,7 @@ All three are thin: extract metadata, invoke `handler.process(...)`, format the
|
|
|
330
343
|
|
|
331
344
|
- `<Resource>Resource` class with `@Path` + `@Tag`, injecting the slice `Handler`(s).
|
|
332
345
|
- Returns `RestResponse<T>` synchronously. Never `Uni`/`Multi`.
|
|
333
|
-
- `@Blocking` on JDBC-backed methods. With a plain (non-reactive) return type Quarkus REST already dispatches to a worker thread, so the annotation is redundant *today* — keep it as an explicit threading contract that survives a later signature change. `@RunOnVirtualThread` is the alternative for high-concurrency blocking endpoints (see sql skill §
|
|
346
|
+
- `@Blocking` on JDBC-backed methods. With a plain (non-reactive) return type Quarkus REST already dispatches to a worker thread, so the annotation is redundant *today* — keep it as an explicit threading contract that survives a later signature change. `@RunOnVirtualThread` is the alternative for high-concurrency blocking endpoints (see sql skill §8).
|
|
334
347
|
- Extracts `tenantId`, `partyId`, `partyRolList`, `language` from headers when the contract requires them, and sets them on the request DTO before calling `process()`.
|
|
335
348
|
- Full OpenAPI annotations per method: `@Operation` with `operationId`, one `@APIResponse` per relevant status, `@Parameter` for headers and path/query params, `Location` header on create.
|
|
336
349
|
|
|
@@ -344,7 +357,7 @@ All three are thin: extract metadata, invoke `handler.process(...)`, format the
|
|
|
344
357
|
|
|
345
358
|
- `<Event>Consumer` class, `@ApplicationScoped`, injecting the slice `Handler`.
|
|
346
359
|
- `@Incoming` methods are `@Blocking` (JDBC below). Idempotency check + `process()` + DLQ strategy per the kafka skill.
|
|
347
|
-
- Producing domain events is **not** done here — the Handler inserts an outbox row in its transaction and `OutboxRelayJob`
|
|
360
|
+
- Producing domain events is **not** done here — the Handler inserts an outbox row in its transaction and the relay selected by `app.messaging.outbox.relay-mode` (Debezium / `OutboxRelayJob` / `OutboxDispatcher`) publishes it.
|
|
348
361
|
|
|
349
362
|
## Errors and i18n
|
|
350
363
|
|
|
@@ -371,9 +384,11 @@ Governed by **quarkus-observability-otel**; the slice-relevant rules:
|
|
|
371
384
|
|
|
372
385
|
## Shared code
|
|
373
386
|
|
|
374
|
-
`common/` holds only what **two or more slices** genuinely share, or what is structurally cross-cutting: the exception/error/i18n machinery, integration beans, `OutboxRelayJob`, `AuditRecorder`, `@ConfigMapping` interfaces, `StringUtils`.
|
|
387
|
+
`common/` holds only what **two or more slices** genuinely share, or what is structurally cross-cutting: the exception/error/i18n machinery, integration beans, `OutboxEventRecorder`/`OutboxSql`/`OutboxRelayJob`/`OutboxDispatcher`, `AuditRecorder`, `@ConfigMapping` interfaces, `StringUtils`.
|
|
388
|
+
|
|
389
|
+
Cross-cutting **tables** are exempt from the promotion rule and shared from day one — the outbox and audit tables have one writer each (`OutboxEventRecorder`, `AuditRecorder`), never a copy per slice. The test is whether the code is *identical by construction*: a slice cannot have its own opinion about the outbox schema, so waiting for a third occurrence just buys 3× the drift. Everything else follows the rule below.
|
|
375
390
|
|
|
376
|
-
Promotion rule: **duplicate twice, extract on the third.** Two slices with similar-looking code is the expected cost of slice independence — premature extraction rebuilds the shared-layer coupling this structure exists to avoid. What must never be shared: a `Handler`,
|
|
391
|
+
Promotion rule: **duplicate twice, extract on the third.** Two slices with similar-looking code is the expected cost of slice independence — premature extraction rebuilds the shared-layer coupling this structure exists to avoid. What must never be shared: a `Handler`, a **slice's** `Sql`, or a slice's `dto` package (the cross-cutting `Sql` classes `common/` owns are the exception above, not a violation). If slice B needs slice A's data, it queries it through its own `Sql` method or consumes A's event — it does not import A.
|
|
377
392
|
|
|
378
393
|
Code shared across **apps** in the domain goes to `libs/` and uses constructor injection.
|
|
379
394
|
|
|
@@ -381,8 +396,8 @@ Code shared across **apps** in the domain goes to `libs/` and uses constructor i
|
|
|
381
396
|
|
|
382
397
|
When creating a new service:
|
|
383
398
|
|
|
384
|
-
1. App folder `apps/<module>-<service>-ms/` per the monorepo layout above.
|
|
385
|
-
2. `pom.xml` with BOM `io.quarkus.platform:quarkus-bom` (latest 3.x LTS), extensions: `quarkus-rest`, `quarkus-rest-jackson`, `quarkus-agroal`, `quarkus-jdbc-postgresql`, `quarkus-arc`, `quarkus-narayana-jta`, `quarkus-hibernate-validator`, `quarkus-smallrye-health`, `quarkus-smallrye-openapi`, `quarkus-opentelemetry`, `quarkus-flyway
|
|
399
|
+
1. App folder `apps/<module>-<service-or-submodule>-ms/` per the monorepo layout above.
|
|
400
|
+
2. `pom.xml` with BOM `io.quarkus.platform:quarkus-bom` (latest 3.x LTS), extensions: `quarkus-rest`, `quarkus-rest-jackson`, `quarkus-agroal`, `quarkus-jdbc-postgresql`, `quarkus-arc`, `quarkus-narayana-jta`, `quarkus-hibernate-validator`, `quarkus-smallrye-health`, `quarkus-smallrye-openapi`, `quarkus-opentelemetry`, `quarkus-flyway` (kept even where migrations are disabled — see sql skill §10), plus Lombok and skill-specific extensions as needed.
|
|
386
401
|
3. `common/` package: `BusinessException`, `ErrorCatalog`, `ErrorDto`, `GlobalExceptionHandler`, `MessageResolver`, `SqlStateTranslator`, `StringUtils`.
|
|
387
402
|
4. `application.properties` baseline (below).
|
|
388
403
|
5. ArchUnit test (below) — in the same commit as the first slice, not later.
|
|
@@ -405,7 +420,19 @@ When creating a new slice, in this order (each step reads the previous file's re
|
|
|
405
420
|
```properties
|
|
406
421
|
quarkus.datasource.db-kind=postgresql
|
|
407
422
|
quarkus.datasource.jdbc.max-size=16
|
|
408
|
-
|
|
423
|
+
|
|
424
|
+
# Schema migrations are OPT-IN and off by default — the app does not own the schema
|
|
425
|
+
# in a DBA/pipeline-managed environment (see quarkus-sql-jdbc-agroal skill §10)
|
|
426
|
+
quarkus.flyway.enabled=false
|
|
427
|
+
quarkus.flyway.migrate-at-start=false
|
|
428
|
+
%dev.quarkus.flyway.enabled=true
|
|
429
|
+
%dev.quarkus.flyway.migrate-at-start=true
|
|
430
|
+
%test.quarkus.flyway.enabled=true
|
|
431
|
+
%test.quarkus.flyway.migrate-at-start=true
|
|
432
|
+
|
|
433
|
+
# Outbox relay: who publishes the outbox row (see quarkus-kafka-messaging skill)
|
|
434
|
+
app.messaging.outbox.relay-mode=debezium
|
|
435
|
+
app.messaging.outbox.poller.every=off
|
|
409
436
|
|
|
410
437
|
# i18n (see error-handling skill) — locales must be declared for native
|
|
411
438
|
quarkus.locales=en,es
|
|
@@ -526,7 +553,19 @@ class ArchitectureTest {
|
|
|
526
553
|
@ArchTest
|
|
527
554
|
static final ArchRule onlyHandlersTouchSql = classes()
|
|
528
555
|
.that().haveSimpleNameEndingWith("Sql")
|
|
529
|
-
.should().onlyBeAccessed().byClassesThat(
|
|
556
|
+
.should().onlyBeAccessed().byClassesThat(
|
|
557
|
+
have(simpleNameEndingWith("Handler"))
|
|
558
|
+
// OutboxSql is cross-cutting: besides slice Handlers (via OutboxEventRecorder),
|
|
559
|
+
// its callers are the recorder, the dispatcher and the relay job, which play the
|
|
560
|
+
// Handler role (they own the Connection) outside any slice.
|
|
561
|
+
.or(resideInAPackage("..common.messaging..")));
|
|
562
|
+
|
|
563
|
+
// Cross-cutting tables have exactly one writer. Without this rule the per-slice copy
|
|
564
|
+
// grows back on the next generated slice and drifts silently.
|
|
565
|
+
@ArchTest
|
|
566
|
+
static final ArchRule crossCuttingWritesAreCentralised = methods()
|
|
567
|
+
.that().haveNameMatching("insertOutboxEvent|insertAuditEvent")
|
|
568
|
+
.should().beDeclaredInClassesThat().resideInAnyPackage("..common.messaging..", "..common.audit..");
|
|
530
569
|
|
|
531
570
|
@ArchTest
|
|
532
571
|
static final ArchRule transportHasNoJdbc = noClasses()
|
|
@@ -535,6 +574,8 @@ class ArchitectureTest {
|
|
|
535
574
|
.or().haveSimpleNameEndingWith("Consumer")
|
|
536
575
|
.should().dependOnClassesThat().resideInAnyPackage("java.sql..", "javax.sql..");
|
|
537
576
|
|
|
577
|
+
// A Handler injecting OutboxDispatcher still satisfies this rule: the dispatcher is a
|
|
578
|
+
// common/messaging bean, so no messaging API ever reaches the slice.
|
|
538
579
|
@ArchTest
|
|
539
580
|
static final ArchRule handlerKnowsNoTransport = noClasses()
|
|
540
581
|
.that().haveSimpleNameEndingWith("Handler")
|
|
@@ -616,7 +657,7 @@ static final ArchRule bannedSuffixes = noClasses().should().haveSimpleNameEnding
|
|
|
616
657
|
.orShould().haveSimpleNameEndingWith("DTO");
|
|
617
658
|
```
|
|
618
659
|
|
|
619
|
-
`common/util/StringUtils` and the `Jdbc` namespace helper (sql skill §
|
|
660
|
+
`common/util/StringUtils` and the `Jdbc` namespace helper (sql skill §7) are the sanctioned exceptions to `bannedSuffixes` — exclude them by fully-qualified name rather than weakening the rule.
|
|
620
661
|
|
|
621
662
|
Rules the compiler cannot express (enforce in code review and in the generator checklists):
|
|
622
663
|
|