bmad-method-quarkus 1.0.2 → 1.0.3
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/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-error-handling-i18n/SKILL.md +61 -27
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-grpc-services/SKILL.md +60 -41
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-hexagonal-core/SKILL.md +482 -213
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-kafka-messaging/SKILL.md +46 -29
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-observability-otel/SKILL.md +47 -31
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-openapi-tmforum/SKILL.md +42 -25
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-sql-jdbc-agroal/SKILL.md +115 -85
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-error-handling-i18n/SKILL.md +61 -27
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-grpc-services/SKILL.md +60 -41
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-hexagonal-core/SKILL.md +482 -213
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-kafka-messaging/SKILL.md +46 -29
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-observability-otel/SKILL.md +47 -31
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-openapi-tmforum/SKILL.md +42 -25
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-sql-jdbc-agroal/SKILL.md +115 -85
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: quarkus-grpc-services
|
|
3
|
-
description: Standard for internal microservice-to-microservice communication using gRPC in Quarkus native services — proto conventions, Mutiny services, clients, deadlines, error mapping to
|
|
3
|
+
description: Standard for internal microservice-to-microservice communication using gRPC in Quarkus native services — proto conventions, Mutiny services, clients, deadlines, error mapping to BusinessException codes, health checks, and native-image setup. Use this skill whenever the user mentions gRPC, protobuf, .proto files, internal service calls, synchronous inter-service communication, service clients, or "call service X from service Y" — internal synchronous calls are ALWAYS gRPC, never internal REST. Includes proto and gRPC adapter/client naming conventions for the vertical-slice layout (the GrpcService lives in the slice folder and delegates to the slice Handler; outbound stubs are wrapped by capability-named beans in common/client).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# gRPC Standard for Inter-Service Communication (Quarkus)
|
|
@@ -11,12 +11,12 @@ Extension: `quarkus-grpc` (fully native-compatible; code generation is build-tim
|
|
|
11
11
|
|
|
12
12
|
## Proto conventions
|
|
13
13
|
|
|
14
|
-
Location: `src/main/proto
|
|
14
|
+
Location: `src/main/proto/<module>/v<major>/`, one file per capability group, published to the monorepo `contracts/` folder. One proto package per service/module + major version (the module segment is the module's semantic name — `iam`, `customer` — see quarkus-hexagonal-core skill, or the service name):
|
|
15
15
|
|
|
16
16
|
```protobuf
|
|
17
17
|
syntax = "proto3";
|
|
18
|
-
package alva.
|
|
19
|
-
option java_package = "com.alva.
|
|
18
|
+
package alva.iam.v1;
|
|
19
|
+
option java_package = "com.alva.iam.grpc.v1";
|
|
20
20
|
option java_multiple_files = true;
|
|
21
21
|
|
|
22
22
|
service UserService {
|
|
@@ -36,27 +36,32 @@ Rules:
|
|
|
36
36
|
- Proto files are contracts: keep them in a shared `contracts/` module (or a dedicated repo) consumed by both sides; do NOT copy-paste protos between services.
|
|
37
37
|
- Streaming RPCs only with a justified use case (bulk export, watch); default is unary.
|
|
38
38
|
|
|
39
|
-
## Server (inbound adapter,
|
|
39
|
+
## Server (inbound adapter, in the slice folder)
|
|
40
40
|
|
|
41
41
|
```java
|
|
42
42
|
@GrpcService
|
|
43
43
|
public class UserGrpcService implements UserService { // Mutiny-generated interface
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
public UserGrpcService(GetUserUseCase getUser) { this.getUser = getUser; }
|
|
45
|
+
@Inject FindUserHandler findUser; // the slice's Handler, blocking
|
|
48
46
|
|
|
49
47
|
@Override
|
|
50
48
|
public Uni<GetUserResponse> getUser(GetUserRequest req) {
|
|
51
|
-
return Uni.createFrom().item(() ->
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
return Uni.createFrom().item(() -> {
|
|
50
|
+
var request = FindUserRequestDto.builder().userId(req.getUserId()).build();
|
|
51
|
+
var result = findUser.process(request); // blocking JDBC
|
|
52
|
+
return GetUserResponse.newBuilder()
|
|
53
|
+
.setUser(User.newBuilder().setId(result.getId()).setEmail(result.getEmail()))
|
|
54
|
+
.build();
|
|
55
|
+
})
|
|
56
|
+
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool()); // JDBC → worker thread
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
- gRPC service = thin adapter:
|
|
59
|
-
-
|
|
61
|
+
- gRPC service = thin adapter: convert proto ↔ DTO, delegate to the slice `Handler`. No business logic, no JDBC, no `Connection`.
|
|
62
|
+
- **Proto ↔ DTO conversion happens inline, inside the `item(() -> ...)` lambda.** There is no `*GrpcMapper` class — `*Mapper` is a banned suffix (see quarkus-hexagonal-core skill).
|
|
63
|
+
- This class and the integration beans in `common/client` are the **only** two places Mutiny is allowed; the ArchUnit rule `reactiveIsQuarantined` enforces it. Never let `Uni`/`Multi` reach a `Handler` or `Sql`.
|
|
64
|
+
- Blocking work (JDBC) either `@Blocking` on the method or the explicit worker pool as above.
|
|
60
65
|
|
|
61
66
|
### Naming
|
|
62
67
|
|
|
@@ -68,60 +73,73 @@ Canonical rules in the quarkus-hexagonal-core skill; gRPC specifics:
|
|
|
68
73
|
| Proto RPC | `<Verb><Entity>` | `GetUser`, `ValidateCredentials` |
|
|
69
74
|
| Proto messages | `<Rpc>Request` / `<Rpc>Response`, one pair per RPC | `GetUserRequest` |
|
|
70
75
|
| Proto fields | `snake_case` | `user_id` |
|
|
71
|
-
| Server adapter | `<ProtoService>GrpcService
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
76
|
+
| Server adapter | `<ProtoService>GrpcService`, in the slice folder | `UserGrpcService` |
|
|
77
|
+
| Outbound integration bean | capability noun, **no technology**, in `common/client` | `CredentialsValidator` |
|
|
78
|
+
| Global error interceptor | `GrpcExceptionInterceptor`, in `common/error` | |
|
|
79
|
+
| Client config key | `<module>-<entity>` kebab-case, matching `@GrpcClient` | `iam-users` |
|
|
80
|
+
|
|
81
|
+
`*GrpcService` is the **only** class allowed to end in `Service` under the slice standard — it mirrors the proto service name, which is not ours to rename (ArchUnit rule `serviceSuffixIsReserved`). There is no `Grpc<Port>` client adapter and no `*GrpcMapper`: the outbound bean is named after the capability it provides, and only its internals reveal that gRPC is the transport.
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
- Separate gRPC server port (default `9000`) or unified with HTTP via `quarkus.grpc.server.use-separate-server=false` — pick one per platform and keep it consistent.
|
|
83
|
+
Separate gRPC server port (default `9000`) or unified with HTTP via `quarkus.grpc.server.use-separate-server=false` — pick one per platform and keep it consistent.
|
|
79
84
|
|
|
80
|
-
## Error mapping (
|
|
85
|
+
## Error mapping (`BusinessException` → gRPC status)
|
|
81
86
|
|
|
82
|
-
One `ExceptionHandlerProvider`-style mapping, symmetric to the REST unified handler:
|
|
87
|
+
One `ExceptionHandlerProvider`-style mapping, symmetric to the REST unified handler. The status is derived from the code's entry in `ErrorCatalog`, not from the exception class:
|
|
83
88
|
|
|
84
|
-
|
|
|
89
|
+
| Situation (per `ErrorCatalog`) | gRPC status |
|
|
85
90
|
|---|---|
|
|
86
91
|
| Not found | `NOT_FOUND` |
|
|
87
92
|
| Validation / bad argument | `INVALID_ARGUMENT` |
|
|
88
93
|
| Business rule conflict | `FAILED_PRECONDITION` |
|
|
89
|
-
| Duplicate
|
|
94
|
+
| Duplicate | `ALREADY_EXISTS` |
|
|
95
|
+
| Stale version / concurrency (`StaleVersionException`) | `ABORTED` |
|
|
90
96
|
| Auth | `UNAUTHENTICATED` / `PERMISSION_DENIED` |
|
|
91
97
|
| Anything unexpected | `INTERNAL` (no details leaked) |
|
|
92
98
|
|
|
93
|
-
Attach the stable error code in trailers metadata key `error-code` so callers can map back
|
|
99
|
+
Attach the stable error code in trailers metadata key `error-code` so callers can map it back through their own `ErrorCatalog`. Implement once as a global gRPC exception interceptor (`GrpcExceptionInterceptor` in `common/error`, the counterpart of `GlobalExceptionHandler` — see quarkus-error-handling-i18n skill); it resolves the localized message the same way, from the request's locale metadata. Individual services never build `StatusRuntimeException` by hand, and no `GrpcService` catches `BusinessException`.
|
|
94
100
|
|
|
95
|
-
## Client (outbound
|
|
101
|
+
## Client (outbound integration bean, `common/client`)
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
A slice `Handler` must never hold a `@GrpcClient` stub: the generated stub returns `Uni`, which would drag Mutiny into the business logic and break the `reactiveIsQuarantined` rule. Wrap it in a capability-named bean that `await()`s internally and exposes plain types:
|
|
98
104
|
|
|
99
105
|
```java
|
|
106
|
+
// common/client — named for what it provides, not for how
|
|
100
107
|
@ApplicationScoped
|
|
101
|
-
public class
|
|
108
|
+
public class CredentialsValidator {
|
|
102
109
|
|
|
103
|
-
@GrpcClient("
|
|
110
|
+
@GrpcClient("iam-users") UserService client; // Mutiny stub, contained here
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
public boolean validate(String username, String password) {
|
|
113
|
+
return client.validateCredentials(
|
|
114
|
+
ValidateCredentialsRequest.newBuilder()
|
|
115
|
+
.setUsername(username).setPassword(password).build())
|
|
108
116
|
.map(ValidateCredentialsResponse::getValid)
|
|
109
|
-
.await().atMost(Duration.ofSeconds(2));
|
|
117
|
+
.await().atMost(Duration.ofSeconds(2)); // deadline also set in config
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
120
|
```
|
|
113
121
|
|
|
122
|
+
The `Handler` then injects it like any other collaborator:
|
|
123
|
+
|
|
124
|
+
```java
|
|
125
|
+
@Inject CredentialsValidator credentials; // returns boolean, knows nothing about gRPC
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The bean always lives in `common/client`, even when only one slice calls it today — that is the package the `reactiveIsQuarantined` ArchUnit rule exempts (see quarkus-hexagonal-core skill), and it saves relocating the bean the day a second slice needs it. It is mocked in `HandlerTest` exactly like the `Sql` class.
|
|
129
|
+
|
|
130
|
+
An interface is warranted only under the core skill's "when to add an interface back" rule — a second real implementation, a cross-module boundary, or a `common/` consumer that must not see the slice.
|
|
131
|
+
|
|
114
132
|
```properties
|
|
115
|
-
quarkus.grpc.clients.
|
|
116
|
-
quarkus.grpc.clients.
|
|
117
|
-
quarkus.grpc.clients.
|
|
133
|
+
quarkus.grpc.clients.iam-users.host=iam-users.internal
|
|
134
|
+
quarkus.grpc.clients.iam-users.port=9000
|
|
135
|
+
quarkus.grpc.clients.iam-users.deadline=2s
|
|
118
136
|
```
|
|
119
137
|
|
|
120
138
|
Rules:
|
|
121
139
|
- **Deadline on every client** (config `deadline` or per-call). No unbounded internal calls, ever — cascading hangs kill native pods just as well.
|
|
122
140
|
- Retries only for idempotent RPCs and only `UNAVAILABLE`/`DEADLINE_EXCEEDED`, with budget — prefer platform/mesh retry policy over hand-rolled loops.
|
|
123
141
|
- TLS/mTLS per platform standard (`quarkus.grpc.clients.*.ssl.*` / mesh-provided).
|
|
124
|
-
- Translate `StatusRuntimeException`
|
|
142
|
+
- Translate `StatusRuntimeException` into a `BusinessException` carrying the caller's own error code, inside the integration bean — gRPC types (and `Uni`) never reach a `Handler`. Read the upstream code from the `error-code` trailer and map it through the caller's `ErrorCatalog`.
|
|
125
143
|
|
|
126
144
|
## Health & reflection
|
|
127
145
|
|
|
@@ -134,8 +152,9 @@ Tracing is automatic when `quarkus-opentelemetry` is present — `traceparent` p
|
|
|
134
152
|
|
|
135
153
|
## Checklist for a new RPC
|
|
136
154
|
|
|
137
|
-
1. Proto in
|
|
138
|
-
2.
|
|
139
|
-
3.
|
|
140
|
-
4. Error mapping covered by the global interceptor (
|
|
155
|
+
1. Proto in `src/main/proto/<module>/v<major>/`, published to `contracts/`, dedicated Request/Response pair.
|
|
156
|
+
2. `<ProtoService>GrpcService` in the slice folder delegating to the slice `Handler`; proto ↔ DTO conversion inline; blocking handled by `runSubscriptionOn` or `@Blocking`.
|
|
157
|
+
3. Outbound calls wrapped in a capability-named bean in `common/client` that `await()`s and returns plain types; deadline configured.
|
|
158
|
+
4. Error mapping covered by the global interceptor (register any new code in `ErrorCatalog` + all locale bundles).
|
|
141
159
|
5. Contract check in CI (buf breaking-change detection or equivalent).
|
|
160
|
+
6. No `Uni` outside the `GrpcService` and the integration bean — `reactiveIsQuarantined` proves it.
|