@klok-integration/cli 0.3.4 → 0.5.0

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.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: klok-route
3
- description: Write or change a Camel route on the Klok Quarkus extension 2.0 (onramp, processing, offramp, REST API handler, async task worker, custom route) with the correct naming, configuration, error handling, tests and klok-docs. Use for any change inside a Klok integration project.
3
+ description: Write or change a Camel route on the Klok Quarkus extension 2.0 (onramp, processing, offramp, REST API handler, async task worker, custom route) with the correct naming, topics and topic purpose, configuration, error handling, tests and klok-docs. Use for any change inside a Klok integration project.
4
4
  ---
5
5
 
6
6
  # Writing a Klok route
7
7
 
8
- Read https://docs.klok.dev/klok-framework-klok-quarkus-extension/llm.txt first (pages `patterns`, `naming`,
8
+ Read `klok docs read klok-framework-klok-quarkus-extension` first (pages `patterns`, `naming`,
9
9
  `config`, `error-handling`, `testing`); read the `klok-docs` skill for other pages.
10
10
 
11
11
  ## Steps
@@ -17,23 +17,77 @@ Read https://docs.klok.dev/klok-framework-klok-quarkus-extension/llm.txt first (
17
17
  2. **Name it.** `<TENANTSHORT><NNN><LEG><seq>_<Description>`, one class per route, the solution number from the
18
18
  portal registry. Sub routes are `S`, timers `T`.
19
19
  3. **Bind the datamodels** as type parameters; take them from the catalog bundle, never hand written DTOs.
20
- 4. **Configure** through `routeConfig("key", default)` / `requiredRouteConfig("key")` and document every key in
20
+ 4. **Choose the topic purpose** of every topic the route publishes or consumes (next section).
21
+ 5. **Configure** through `routeConfig("key", default)` / `requiredRouteConfig("key")` and document every key in
21
22
  `application.properties` as `<RouteId>.<key>`. Reserved keys: `enabled`, `logevents.*`, `retry.*`, `dlq.*`,
22
23
  `topic.*`, `subscription.*`, `claimcheck.*`.
23
- 5. **Errors.** Throw `Klok4XXException` for caller errors, let retryable failures propagate; the base route logs,
24
+ 6. **Errors.** Throw `Klok4XXException` for caller errors, let retryable failures propagate; the base route logs,
24
25
  retries with backoff, republishes to `<topic>_DLQ` and emits the failure CloudEvent. Override `failureHandled()`
25
26
  only when the route owns its failures (custom routes that answer HTTP themselves).
26
- 6. **Capabilities** as interfaces: `SplitIncomingMessage`, `AggregateOutgoingMessages`, `ClaimCheck`,
27
+ 7. **Capabilities** as interfaces: `SplitIncomingMessage`, `AggregateOutgoingMessages`, `ClaimCheck`,
27
28
  `KlokEmitCloudEventOnSuccess/Failure`, `PersistOutgoingMessages`, `KlokAsyncTaskInitiator`.
28
- 7. **Test** with `@QuarkusTest` and the recording broker client factory: assert the route status is `Started`,
29
- send a message, assert the published topic, body and `klok-*` properties.
30
- 8. **Document** in `klok-docs/`: the route in `routes.md`, decisions in `design-decisions.md`, operations notes.
31
- 9. Build (`./gradlew build`) in the task worktree (`klok-worktree` skill), push its `agent/<task-id>` branch,
32
- open a merge request ready for review, check the pipeline.
29
+ 8. **Test** with `@QuarkusTest` and the recording broker client factory: assert the route status is `Started`,
30
+ send a message, assert the published topic (the full name, purpose included), body and `klok-*` properties.
31
+ 9. **Document** in `klok-docs/`: the route in `routes.md` with the purpose of each topic and the project on its other
32
+ end, decisions in `design-decisions.md`, operations notes.
33
+ Annotate central classes with `@KlokDocs` (`dev.klok.framework.common.docs.KlokDocs`, klok-common): configuration
34
+ or standards several routes read, shared mappers, domain logic used by several routes. Each gets its own generated
35
+ page; write its Javadoc for a reader who has not seen the code. Not every class: the classes a route calls
36
+ directly reach its generated documentation without the annotation.
37
+ 10. Build (`./gradlew build`) in the task worktree (`klok-worktree` skill), push its `agent/<task-id>` branch,
38
+ open a merge request ready for review, check the pipeline.
39
+
40
+ ## Topic purpose
41
+
42
+ A topic is named after its datamodel: `<Model>[_<purpose>]`. The purpose (called *producer intention* in older code)
43
+ is a suffix that the producer and the consumer agree on and each set on their own route, so one datamodel serves
44
+ several flows: `CallAiResponseV1_documentation` and `CallAiResponseV1_invoicecoding` both carry the typed
45
+ `CallAiResponseV1`, and each consumer receives only its own flow. The framework builds the full topic name on both
46
+ sides from the model and the purpose.
47
+
48
+ * **When.** Give a flow a purpose when its datamodel is, or may be, used by another flow. Without one, every consumer
49
+ of the model receives every message. No purpose only when every consumer wants every message.
50
+ * **Ingress** (onramps, webhook onramp): decide which flow the data belongs to and publish with that purpose.
51
+ **Egress** (offramps): consume the purpose the producer publishes on; find it in the producer's `klok-docs/` or its
52
+ route configuration, and ask when it is not written down. Never guess a purpose.
53
+ * **Where it is set.** Configuration wins over code.
54
+
55
+ | Pattern | Code | Configuration |
56
+ |---|---|---|
57
+ | onramps, `KlokWebhookOnrampRoute`, offramps, channel adapters, `Sync` variants | `topicPurpose()` | `<RouteId>.topic.purpose` |
58
+ | `KlokOfframpRouteWithResult` | `topicPurpose()`, `resultTopicPurpose()` (default: the request's) | `.topic.purpose`, `.topic.result-purpose` |
59
+ | `KlokProcessingRoute` | `incomingTopicPurpose()`, `outgoingTopicPurpose()` | `.topic.purpose`, `.topic.outgoing-purpose` |
60
+ | `KlokAsyncTaskWorkerRoute` | `taskType()`: the task type is the request topic's purpose | |
61
+ | REST handler with `task.initiate` | the `klok-task-type` header in `transformBody` | `.task.type` |
62
+
63
+ Put it in code when the purpose is part of what the route is; put it in configuration when the same route serves
64
+ several flows. Set `<SolutionId>.topic.purpose` only when every route of the solution is on that flow.
65
+ * **Name** the flow as both sides understand it (`created`, `reminder`, `documentation`): letters, digits, `-` and
66
+ `.`, never `_`, and no version, tenant or environment. Task types are `<domain>.<verb>-<object>`. A topic read by
67
+ another tenant appends the reader after the purpose: `<Model>_<purpose>_<reader>`.
68
+ * **A mismatch fails silently**: the two routes end up on different topics and nothing arrives. Check the start log
69
+ (`Route <RouteId> subscribes to <topic>`, `Route <RouteId> publishes <Model> to <topic>`) and assert the full topic
70
+ in the test.
71
+ * **Changing a purpose** moves the flow to a new topic: move the consumers first, then the producers, and let the old
72
+ topic drain.
73
+ * **Limit.** A route has one purpose, fixed when it starts. A shared service that answers each caller on the purpose
74
+ the caller chose is not supported yet (klok/framework/klok-quarkus-extension#32); do not write a subclass per
75
+ caller in the shared service, say so in the merge request instead.
76
+
77
+ The user guide page *Topic Purpose: One Datamodel, Several Flows* (`klok docs read klok-user-guide`)
78
+ explains the reasons and the naming in full.
33
79
 
34
80
  ## Things that are always wrong
35
81
 
36
82
  * A route id that does not start with the solution id, or two routes with the same id.
37
83
  * Publishing to a hand written topic string; use the resolver and the datamodel.
84
+ * Consuming a shared topic and discarding the messages of other flows (a filter on a payload field, an ODL lookup
85
+ per message) where the producer could publish the flow with a topic purpose.
86
+ * A purpose with `_`, a version, a tenant or an environment in it, or a purpose guessed instead of agreed with the
87
+ other side.
88
+ * Choosing a tenant, index, repository or account from a field in the message; the topic decides the tenant.
89
+ * Sharing broker credentials between services, or granting a whole namespace of another domain or tenant.
90
+ * Publishing into another tenant's namespace, with `<RouteId>.topic.tenant-override` or otherwise; a route consumes
91
+ another tenant's topic with the override and a `consume` grant, and publishes only in its own tenant.
38
92
  * Catching exceptions to return HTTP 200 with an error text.
39
93
  * Pinning a framework or datamodel version in `gradle/klok-libs.versions.toml` by hand.
@@ -8,7 +8,7 @@ description: Create, use, hand over and clean up the per task Git worktree and b
8
8
  The rule is in `KLOK-AGENTS.md` (*One task, one worktree, one branch*): every task that changes repository files runs
9
9
  in its own Git worktree on its own branch, and the canonical checkout is left exactly as found. This skill is the
10
10
  procedure. The principle behind it, *Git is the truth*, is described in
11
- https://docs.klok.dev/klok-integration-strategy-and-tactics/llm.txt.
11
+ `klok docs read klok-integration-strategy-and-tactics`.
12
12
 
13
13
  ## Directory contract
14
14