@shortlink-org/portolan 0.2.4 → 0.4.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.
- package/README.md +174 -6
- package/catalog/enum_test.go +46 -0
- package/catalog/evidence_test.go +35 -0
- package/catalog/model.go +1213 -0
- package/catalog/roundtrip_test.go +210 -0
- package/catalog/via_test.go +38 -0
- package/cli/init.test.mjs +6 -1
- package/cli/portolan.mjs +14 -1
- package/cli/portolan.test.mjs +49 -0
- package/go.mod +21 -0
- package/go.sum +34 -0
- package/internal/gocall/README.md +19 -0
- package/internal/gocall/analyze.go +189 -0
- package/internal/gocall/analyze_test.go +107 -0
- package/internal/gohttp/analyze.go +2562 -0
- package/internal/gohttp/destination.go +373 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +96 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/index.go +629 -0
- package/internal/goscan/index_test.go +66 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +37 -0
- package/internal/goscan/tree.go +284 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +70 -0
- package/internal/wsdl/parse.go +949 -0
- package/internal/wsdl/parse_test.go +170 -0
- package/package.json +28 -15
- package/plugin/describe.go +156 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +141 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +404 -50
- package/plugins/cmd/portolan-http-clients/main.go +19 -0
- package/plugins/extract-argocd/options.schema.json +44 -0
- package/plugins/extract-celery/extract.py +0 -2
- package/plugins/extract-celery/extract_test.py +1 -1
- package/plugins/extract-celery/main.py +1 -0
- package/plugins/extract-csharp-ddd/README.md +213 -0
- package/plugins/extract-csharp-ddd/options.schema.json +55 -0
- package/plugins/extract-django/README.md +52 -19
- package/plugins/extract-django/choices.py +47 -0
- package/plugins/extract-django/domain.py +30 -18
- package/plugins/extract-django/extract.py +21 -7
- package/plugins/extract-django/extract_test.py +68 -2
- package/plugins/extract-django/lifecycle.py +4 -28
- package/plugins/extract-django/main.py +1 -0
- package/plugins/extract-django/operations.py +1 -1
- package/plugins/extract-django/routing_test.py +109 -1
- package/plugins/extract-django/rules.py +154 -0
- package/plugins/extract-django/rules_test.py +158 -0
- package/plugins/extract-django/store.py +1 -1
- package/plugins/extract-django/transport.py +101 -55
- package/plugins/extract-django/verbs.py +241 -0
- package/plugins/extract-go/README.md +47 -0
- package/plugins/extract-go-sqs/options.schema.json +21 -0
- package/plugins/extract-http-clients/describe.go +20 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +740 -0
- package/plugins/extract-http-clients/extract_test.go +1561 -0
- package/plugins/extract-http-clients/main.go +41 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-k8s/options.schema.json +32 -0
- package/plugins/extract-laravel/Cargo.lock +962 -0
- package/plugins/extract-laravel/Cargo.toml +20 -0
- package/plugins/extract-laravel/README.md +200 -0
- package/plugins/extract-laravel/options.schema.json +68 -0
- package/plugins/extract-laravel/rustfmt.toml +1 -0
- package/plugins/extract-php-ddd/Cargo.lock +972 -0
- package/plugins/extract-php-ddd/Cargo.toml +22 -0
- package/plugins/extract-php-ddd/README.md +141 -0
- package/plugins/extract-php-ddd/options.schema.json +50 -0
- package/plugins/extract-php-ddd/rustfmt.toml +1 -0
- package/plugins/extract-python-kafka/extract.py +0 -2
- package/plugins/extract-python-kafka/extract_test.py +1 -1
- package/plugins/extract-python-kafka/main.py +1 -0
- package/plugins/extract-terraform/options.schema.json +26 -0
- package/plugins/extract-ts/extract.test.ts +2 -2
- package/plugins/extract-ts/extract.ts +4 -5
- package/plugins/extract-ts/graphql.test.ts +1 -1
- package/plugins/extract-ts/main.ts +1 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/phpscan/Cargo.lock +953 -0
- package/plugins/phpscan/Cargo.toml +19 -0
- package/plugins/phpscan/rustfmt.toml +1 -0
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/catalog.py +24 -1
- package/plugins/pyplugin/protocol.py +1 -5
- package/plugins/verify-otel/options.schema.json +12 -0
- package/portolan.json +132 -4
- package/rules/builtin.json +218 -0
- package/schema/portolan.schema.json +905 -4
- package/scripts/README.md +21 -13
- package/scripts/catalog-sources.mjs +6 -0
- package/scripts/delivery-presets.mjs +57 -14
- package/scripts/diff.mjs +5 -1
- package/scripts/django-aggregates.test.mjs +58 -0
- package/scripts/gen-likec4.mjs +150 -17
- package/scripts/gen-likec4.test.mjs +96 -0
- package/scripts/gen.mjs +148 -118
- package/scripts/gitops-example.test.mjs +108 -0
- package/scripts/go-discovery.test.mjs +30 -0
- package/scripts/history.mjs +186 -3
- package/scripts/history.test.mjs +1 -1
- package/scripts/host-plugins/fetch-argocd.mjs +338 -0
- package/scripts/host-plugins/fetch-argocd.options.json +49 -0
- package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
- package/scripts/host-plugins/fetch-bsr.mjs +1 -0
- package/scripts/host-plugins/fetch-csr.mjs +1 -0
- package/scripts/host-plugins/fetch-git.mjs +78 -21
- package/scripts/host-plugins/fetch-git.test.mjs +62 -8
- package/scripts/host-plugins/fetch-k8s.mjs +263 -0
- package/scripts/host-plugins/fetch-k8s.options.json +50 -0
- package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
- package/scripts/host-plugins/k8s-topology.mjs +183 -0
- package/scripts/json-format.mjs +192 -0
- package/scripts/json-format.test.mjs +97 -0
- package/scripts/local-api.mjs +287 -12
- package/scripts/local-api.test.mjs +150 -5
- package/scripts/local-discovery.mjs +94 -9
- package/scripts/manifest.mjs +14 -3
- package/scripts/manifest.test.mjs +24 -0
- package/scripts/output-diff.mjs +94 -0
- package/scripts/output-diff.test.mjs +36 -0
- package/scripts/package-smoke.mjs +62 -4
- package/scripts/plugin-host.mjs +45 -3
- package/scripts/plugin-host.test.mjs +9 -0
- package/scripts/plugin-wasm-worker.mjs +4 -1
- package/scripts/plugins-fresh.mjs +97 -0
- package/scripts/plugins-fresh.test.mjs +64 -0
- package/scripts/provenance.mjs +72 -0
- package/scripts/provenance.test.mjs +149 -0
- package/scripts/run-builtin.mjs +62 -7
- package/scripts/schema.mjs +167 -19
- package/scripts/trace-trials.mjs +176 -0
- package/scripts/trace-trials.test.mjs +142 -0
- package/scripts/warning-policy.mjs +167 -0
- package/scripts/warning-policy.test.mjs +93 -0
- package/src/app/Breadcrumbs.test.ts +3 -0
- package/src/app/Breadcrumbs.tsx +3 -0
- package/src/app/CatalogApp.tsx +2 -0
- package/src/app/Sidebar.tsx +3 -3
- package/src/app/SidebarFooter.tsx +20 -4
- package/src/catalog-docs.test.ts +64 -0
- package/src/catalog-docs.ts +35 -0
- package/src/catalog-error.test.ts +15 -0
- package/src/catalog-index.ts +25 -0
- package/src/catalog-model.ts +293 -5
- package/src/catalog-validation.ts +113 -2
- package/src/catalog.test.ts +40 -1
- package/src/chat/Starter.tsx +5 -11
- package/src/chat/tools.test.ts +27 -0
- package/src/chat/tools.ts +5 -9
- package/src/components/CatalogStamp.tsx +10 -8
- package/src/components/ChannelRows.test.tsx +76 -1
- package/src/components/ChannelRows.tsx +223 -60
- package/src/components/DeploymentRows.tsx +131 -0
- package/src/components/DocsLinks.test.tsx +27 -0
- package/src/components/DocsLinks.tsx +56 -0
- package/src/components/FieldTree.tsx +5 -2
- package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
- package/src/components/HTTPDestinationEvidence.tsx +31 -0
- package/src/components/Integrations.tsx +1 -1
- package/src/components/MachineDocs.tsx +6 -5
- package/src/components/MethodRows.tsx +9 -2
- package/src/components/PluginIcon.tsx +77 -0
- package/src/components/ProblemRow.tsx +173 -153
- package/src/components/RelationEvidence.test.tsx +14 -0
- package/src/components/RelationEvidence.tsx +53 -0
- package/src/components/RuleMarks.tsx +22 -0
- package/src/components/ShapeRows.tsx +24 -20
- package/src/data.ts +46 -7
- package/src/enrich.test.ts +459 -4
- package/src/enrich.ts +308 -7
- package/src/er/ErCanvas.tsx +218 -12
- package/src/er/GroupNode.tsx +57 -0
- package/src/er/StoreHeader.tsx +1 -0
- package/src/er/layout.test.ts +85 -2
- package/src/er/layout.ts +140 -5
- package/src/er/spec.test.ts +17 -0
- package/src/er/spec.ts +23 -10
- package/src/flow/Recordings.test.tsx +52 -0
- package/src/flow/Recordings.tsx +236 -0
- package/src/flow/StepDetail.tsx +59 -0
- package/src/flow/TraceTrial.tsx +419 -0
- package/src/flow/evidence.test.ts +16 -0
- package/src/flow/evidence.ts +34 -0
- package/src/flow/examples.test.ts +33 -0
- package/src/flow/examples.ts +37 -0
- package/src/flow/outline.test.ts +28 -0
- package/src/flow/outline.ts +7 -2
- package/src/flow/trace-trial-resume.test.ts +42 -0
- package/src/flow/trace-trial-resume.ts +74 -0
- package/src/graph/elk.ts +78 -0
- package/src/index.css +68 -0
- package/src/landing/DraggableReveal.tsx +3 -2
- package/src/landing/EvidencePipeline.tsx +105 -0
- package/src/landing/LandingPage.tsx +17 -70
- package/src/landing/ProductTour.tsx +6 -6
- package/src/lib/all-problems.ts +27 -17
- package/src/lib/catalog-diff.ts +1 -1
- package/src/lib/centrality.test.ts +251 -0
- package/src/lib/centrality.ts +232 -0
- package/src/lib/confluence.test.ts +37 -0
- package/src/lib/confluence.ts +41 -0
- package/src/lib/context-color.ts +1 -1
- package/src/lib/deployment-drift.ts +22 -0
- package/src/lib/derive.ts +43 -69
- package/src/lib/django-aggregates.d.mts +9 -0
- package/src/lib/django-aggregates.mjs +36 -0
- package/src/lib/django-aggregates.test.ts +29 -0
- package/src/lib/django-aggregates.ts +5 -0
- package/src/lib/environments.test.ts +83 -0
- package/src/lib/environments.ts +57 -0
- package/src/lib/integration-url.test.ts +30 -0
- package/src/lib/integration-url.ts +63 -0
- package/src/lib/kafka-ui.ts +3 -45
- package/src/lib/local-api.ts +116 -4
- package/src/lib/notion.ts +13 -0
- package/src/lib/plugin-index.json +2765 -0
- package/src/lib/plugins.test.ts +68 -0
- package/src/lib/plugins.ts +264 -0
- package/src/lib/problem-flows.test.ts +61 -0
- package/src/lib/problem-flows.ts +78 -0
- package/src/lib/problem-rules-cel.d.mts +43 -0
- package/src/lib/problem-rules-cel.mjs +407 -0
- package/src/lib/problem-rules.test.ts +287 -0
- package/src/lib/problem-rules.ts +271 -0
- package/src/lib/problem-subjects.ts +737 -0
- package/src/lib/rule-entries.ts +39 -0
- package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
- package/src/lib/rules-deploy.test.ts +133 -0
- package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
- package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
- package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
- package/src/lib/rules.test.ts +51 -0
- package/src/lib/rules.ts +86 -0
- package/src/lib/setup-info.test.ts +17 -0
- package/src/lib/setup-info.ts +58 -0
- package/src/lib/shape.test.ts +32 -0
- package/src/lib/shape.ts +30 -6
- package/src/lib/tech.ts +16 -0
- package/src/lib/trace-project.test.ts +34 -0
- package/src/lib/trace-project.ts +50 -0
- package/src/lib/use-problems.ts +23 -0
- package/src/lib/warnings.test.ts +63 -0
- package/src/lib/warnings.ts +260 -0
- package/src/likec4/ids.test.ts +6 -2
- package/src/likec4/ids.ts +43 -0
- package/src/main.tsx +23 -0
- package/src/map/ContextMapGraph.tsx +76 -32
- package/src/merge-deployments.test.ts +127 -0
- package/src/merge.test.ts +82 -0
- package/src/merge.ts +189 -18
- package/src/pages/AggregatePage.tsx +65 -14
- package/src/pages/ContextMap.tsx +45 -3
- package/src/pages/ContextPage.tsx +8 -5
- package/src/pages/EventPage.tsx +15 -5
- package/src/pages/FlowDetail.tsx +23 -2
- package/src/pages/GraphPage.tsx +40 -3
- package/src/pages/Overview.tsx +152 -12
- package/src/pages/PluginIndex.tsx +190 -0
- package/src/pages/Problems.tsx +396 -128
- package/src/pages/ServicePage.tsx +62 -5
- package/src/pages/Settings.tsx +217 -43
- package/src/pages/settings/AboutSettings.tsx +8 -1
- package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
- package/src/pages/settings/IntegrationsSettings.tsx +63 -17
- package/src/pages/settings/RecordingSettings.tsx +138 -0
- package/src/pages/settings/RulesSettings.tsx +825 -0
- package/src/routes.test.ts +9 -0
- package/src/routes.ts +22 -1
- package/src/selection/DetailPanel.tsx +15 -0
- package/src/virtual-provenance.d.ts +11 -0
- package/vite.config.ts +5 -0
- package/scripts/vendor-lock.mjs +0 -58
- package/scripts/vendor-lock.test.mjs +0 -69
- package/src/lib/data-problems.ts +0 -314
- package/src/lib/proto-problems.ts +0 -237
- package/src/lib/wire-problems.ts +0 -342
package/README.md
CHANGED
|
@@ -87,8 +87,9 @@ reference).
|
|
|
87
87
|
- **Diagrams** — LikeC4 C4 views (estate landscape, every container in the
|
|
88
88
|
estate with its technology and the protocol on each edge, one per context,
|
|
89
89
|
two per service, one dynamic view per flow), an ELK-routed dependency graph,
|
|
90
|
-
and a context map. The app never draws these itself; `npm run
|
|
91
|
-
|
|
90
|
+
and a context map. The app never draws these itself; `npm run gen` writes
|
|
91
|
+
the model from the catalog as its last step, and holds it to the catalog in
|
|
92
|
+
`gen:check`, and `npm run likec4:gen` writes it on its own before `dev`.
|
|
92
93
|
- **ER canvases** — per store: tables, views, keys and crow's feet, plus column
|
|
93
94
|
lineage (`from`) drawn dashed; hovering a column lights the whole chain back
|
|
94
95
|
to where the value came from.
|
|
@@ -116,7 +117,43 @@ first:
|
|
|
116
117
|
with a second writer, a table that no longer holds the aggregate it claims,
|
|
117
118
|
a column whose type has drifted from its field's, an outbox with no payload;
|
|
118
119
|
- a channel with a second publisher, an event on a channel its service does not
|
|
119
|
-
declare, a declared channel no event names, a subscription nothing publishes
|
|
120
|
+
declare, a declared channel no event names, a subscription nothing publishes;
|
|
121
|
+
- what the deployer runs from a place no service lives at, and where the GitOps
|
|
122
|
+
tree and the deployer disagree.
|
|
123
|
+
|
|
124
|
+
Each check is a rule in `rules/builtin.json`: a passport - id, severity, what
|
|
125
|
+
it looks for and what to do about a row - and the rule itself, in CEL over one
|
|
126
|
+
subject. A subject is one row with every fact a rule would otherwise have to
|
|
127
|
+
look up already on it - a table knows who writes it, a channel knows who else
|
|
128
|
+
publishes there, a call knows whether its peer is in the estate - so a rule is
|
|
129
|
+
one line a reader can read, copy and tighten. **Settings → Rules** lists them
|
|
130
|
+
with the rows each produces now. A shipped rule can be switched off or
|
|
131
|
+
re-graded in `portolan.json`, with a reason, and a rule of your own is written
|
|
132
|
+
there the same way - over a service, a call, a copy, an event, a consumer, a
|
|
133
|
+
channel, a subscription, a table, a column, a deployment, a flow or an
|
|
134
|
+
aggregate - and runs in the page the moment it is saved:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"problemRules": [
|
|
139
|
+
{ "id": "shared-store", "enabled": false, "reason": "the estate shares one database by design" },
|
|
140
|
+
{
|
|
141
|
+
"id": "team.quiet-event",
|
|
142
|
+
"over": "event",
|
|
143
|
+
"severity": "warning",
|
|
144
|
+
"title": "Event nobody consumes",
|
|
145
|
+
"when": "size(event.consumers) == 0 && !event.name.endsWith('Audit')",
|
|
146
|
+
"message": "'nothing consumes ' + event.id",
|
|
147
|
+
"peer": "event.service"
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Expressions are type-checked against the subject's fields when the manifest is
|
|
154
|
+
read - `event.nme` is refused, not shown as an empty page - and again in the
|
|
155
|
+
page, by the same module. The Rules page writes the entry after a preview of
|
|
156
|
+
the rows it would add (portolan.0016, portolan.0017).
|
|
120
157
|
|
|
121
158
|
## Where the facts come from
|
|
122
159
|
|
|
@@ -125,17 +162,47 @@ Plugins, one JSON message in and one out (`plugins/README.md`), declared in
|
|
|
125
162
|
|
|
126
163
|
| phase | plugins |
|
|
127
164
|
| --- | --- |
|
|
128
|
-
| extract | `extract-project`, `extract-go`, `extract-ts`, `extract-rust`, `extract-java`, `extract-django`, `extract-celery`, `extract-python-kafka`, `extract-openapi`, `extract-wsdl`, `extract-http-clients`, `extract-redis`, `extract-asyncapi`, `extract-graphql`, `extract-proto`, `extract-river`, `extract-watermill`, `extract-go-nats`, `extract-csr`, `extract-sql`, `extract-flows`, `extract-adr`, `extract-glossary`, `extract-commands` |
|
|
165
|
+
| extract | `extract-project`, `extract-go`, `extract-ts`, `extract-rust`, `extract-java`, `extract-django`, `extract-laravel`, `extract-php-ddd`, `extract-csharp-ddd`, `extract-celery`, `extract-python-kafka`, `extract-openapi`, `extract-wsdl`, `extract-http-clients`, `extract-redis`, `extract-asyncapi`, `extract-graphql`, `extract-proto`, `extract-river`, `extract-watermill`, `extract-go-nats`, `extract-go-sqs`, `extract-csr`, `extract-sql`, `extract-flows`, `extract-adr`, `extract-glossary`, `extract-commands`, `extract-k8s`, `extract-argocd` |
|
|
129
166
|
| verify | `verify-otel` — reads traces, marks the hops they show as `verified`; `verify-codeowners` — reads CODEOWNERS, says who to ask about each service |
|
|
130
167
|
| generate | `gen-markdown` — `docs/`, `gen-mermaid` — standalone flow diagrams, `gen-backstage` — Backstage entities |
|
|
131
168
|
|
|
132
169
|
`fetch-git`, `fetch-bsr` and `fetch-csr` bring in sources from other
|
|
133
170
|
repositories, the Buf Schema Registry and a Confluent Schema Registry, against a
|
|
134
|
-
lock, so a later build can reproduce them without a socket.
|
|
171
|
+
lock, so a later build can reproduce them without a socket. `fetch-k8s` reads a
|
|
172
|
+
live cluster through `kubectl` into the names each service answers on and
|
|
173
|
+
dials, and replays the committed fragment when there is no cluster to ask.
|
|
174
|
+
`fetch-argocd` reads the applications an Argo CD server manages into a snapshot
|
|
175
|
+
of where each service runs - environment, cluster, namespace, revision, images -
|
|
176
|
+
against the same kind of lock; the service page shows it under "Where it runs".
|
|
135
177
|
|
|
136
178
|
Each plugin describes its own options; `npm run schema` asks all of them and
|
|
137
179
|
composes `schema/portolan.schema.json`, which editors complete against and `gen`
|
|
138
|
-
checks before running anything.
|
|
180
|
+
checks before running anything. The same answers are rendered as the site's
|
|
181
|
+
plugin reference - `/plugins` on any generated site, grouped by what each reads
|
|
182
|
+
or makes, with the options it takes - so the list above is the short form.
|
|
183
|
+
|
|
184
|
+
Non-fatal extractor diagnostics remain attached to their pipeline step. The
|
|
185
|
+
Settings page groups repetitions by plugin, stable rule and severity, and shows
|
|
186
|
+
the recommended next action. A reviewed limitation can be hidden from the
|
|
187
|
+
active view without discarding it:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"warningPolicies": [
|
|
192
|
+
{
|
|
193
|
+
"when": "plugin == 'openapi' && rule == 'openapi.missing-operation-id' && project == 'aviacore'",
|
|
194
|
+
"action": "suppress",
|
|
195
|
+
"reason": "The partner-owned contract cannot be changed in this repository."
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The rule id is shown beside the warning. CEL expressions can read `plugin`,
|
|
202
|
+
`rule`, `severity`, `project`, `phase`, `ref`, `message`, and the integer
|
|
203
|
+
`count` for that rule in the step. Expressions are type-checked when the
|
|
204
|
+
manifest is read, must return `bool`, and run during generation. A suppression
|
|
205
|
+
requires a reason and remains available through the `suppressed` filter.
|
|
139
206
|
|
|
140
207
|
## Use it in your project
|
|
141
208
|
|
|
@@ -234,6 +301,107 @@ live in a dedicated architecture repository: `fetch-git` pins the service
|
|
|
234
301
|
repositories at immutable commits and the normal merge, check, diff, and build
|
|
235
302
|
commands operate on the combined estate.
|
|
236
303
|
|
|
304
|
+
### A real one: Bagisto
|
|
305
|
+
|
|
306
|
+
The services under `examples/` are small on purpose. For what the catalog
|
|
307
|
+
looks like on a real application, the site ships a third profile: pick
|
|
308
|
+
**Bagisto** in the catalog selector. [Bagisto](https://github.com/bagisto/bagisto)
|
|
309
|
+
is an open-source e-commerce platform on Laravel - 41 packages, one per
|
|
310
|
+
module, talking to each other through named events - read by
|
|
311
|
+
`extract-laravel` alone, with nothing annotated for the catalog.
|
|
312
|
+
|
|
313
|
+
What comes out of Bagisto 2.4, pinned in `vendor/repos/bagisto/bagisto`:
|
|
314
|
+
|
|
315
|
+
| | |
|
|
316
|
+
| --- | --- |
|
|
317
|
+
| model groups | 28, one per package with models, 125 Eloquent models and 22 enums between them |
|
|
318
|
+
| events | 309, most of them named - `Event::dispatch('sales.order.cancel.after', …)` - owned by the package their name says, 50 with a listener |
|
|
319
|
+
| HTTP | 520 operations in 10 interfaces, one per route file, with an inferred OpenAPI document |
|
|
320
|
+
| database | 138 tables and 1300 columns replayed from 189 migrations, 185 foreign keys, 120 places the code reads or writes them |
|
|
321
|
+
| queues | 17 jobs on one queue, each a hop in the flow that dispatches it and a worker flow of its own |
|
|
322
|
+
| flows | 610 |
|
|
323
|
+
|
|
324
|
+
Go to the service and read it the way the tabs are ordered: **bus** for the
|
|
325
|
+
queue, **data** for the schema laid out by model group - the picker beside
|
|
326
|
+
the arrangement toggle switches groups off, the search brings a table up
|
|
327
|
+
close - and **flows** for `shop-checkout-onepage-orders-store`, the checkout
|
|
328
|
+
followed from the controller through the repository it holds to the events
|
|
329
|
+
and the job it ends in. Every "view source" link opens the file on GitHub at
|
|
330
|
+
the pinned commit.
|
|
331
|
+
|
|
332
|
+
The warnings the extractor left are findings about Bagisto, not about the
|
|
333
|
+
tool: 218 events under the admin package are dispatched by name and declare
|
|
334
|
+
no payload; `Settings\CurrencyController` answers admin routes and does not
|
|
335
|
+
exist; the sitemap job reads a `pages` table no migration creates; a
|
|
336
|
+
foreign key points at a `theme_customizations` table no migration creates;
|
|
337
|
+
one listener waits for `bagisto.shop.products.price.after`, which nothing
|
|
338
|
+
dispatches, and one for the repository events of a vendored package. How the
|
|
339
|
+
fragments were made, and how to refresh them, is in
|
|
340
|
+
`vendor/repos/bagisto/bagisto/README.md`; the reading rules are in
|
|
341
|
+
`plugins/extract-laravel/README.md`.
|
|
342
|
+
|
|
343
|
+
### The small one, by the book: CodelyTV php-ddd-example
|
|
344
|
+
|
|
345
|
+
The fourth profile, **CodelyTV php-ddd-example**, is the opposite kind of
|
|
346
|
+
real: [php-ddd-example](https://github.com/CodelyTV/php-ddd-example) is
|
|
347
|
+
CodelyTV's reference for DDD, hexagonal architecture and CQRS in PHP, a
|
|
348
|
+
Symfony monorepository laid out as `src/<Context>/<Module>/{Domain,Application,Infrastructure}`
|
|
349
|
+
with the deployables under `apps/`, read by `extract-php-ddd` alone - the
|
|
350
|
+
layout is the claim, and nothing is annotated. What comes out, pinned in
|
|
351
|
+
`vendor/repos/CodelyTV/php-ddd-example`: four contexts (Mooc, Backoffice,
|
|
352
|
+
Analytics, Retention), six services, five aggregates with their value
|
|
353
|
+
objects and three domain events on one RabbitMQ exchange, one queue per
|
|
354
|
+
subscriber, the MySQL tables from the Doctrine mappings including a JOINED
|
|
355
|
+
`steps` family, and sixteen flows. The one to read is
|
|
356
|
+
`backoffice-frontend-courses-post`: the back office's form dispatches
|
|
357
|
+
Mooc's `CreateCourseCommand` over an in-memory bus, so the flow crosses to
|
|
358
|
+
`mooc.backend` with a `call` step, records `CourseCreated`, saves the
|
|
359
|
+
course, and the subscribers in Mooc, Backoffice and Analytics pick the event
|
|
360
|
+
up from their own queues.
|
|
361
|
+
|
|
362
|
+
The warnings are findings about the project: Analytics and Retention have no
|
|
363
|
+
application under `apps/`, so their code deploys nowhere and Analytics'
|
|
364
|
+
subscriber to every event runs nowhere; `TrimVideoCommandHandler` is named
|
|
365
|
+
like a handler and implements neither bus interface; Retention's four modules
|
|
366
|
+
and Mooc's Notifications are empty scaffolding; the front end routes
|
|
367
|
+
`/api/courses` to a controller that does not exist. How the fragments were
|
|
368
|
+
made is in `vendor/repos/CodelyTV/php-ddd-example/README.md`; the reading
|
|
369
|
+
rules are in `plugins/extract-php-ddd/README.md`.
|
|
370
|
+
|
|
371
|
+
### The same lesson in C#: Modular Monolith with DDD
|
|
372
|
+
|
|
373
|
+
The fifth profile, **Modular Monolith with DDD**, is Kamil Grzybek's
|
|
374
|
+
[modular-monolith-with-ddd](https://github.com/kgrzybek/modular-monolith-with-ddd),
|
|
375
|
+
the reference for the pattern in .NET: five modules laid out as
|
|
376
|
+
`src/Modules/<Module>/{Domain,Application,Infrastructure,IntegrationEvents}`,
|
|
377
|
+
one API host, one SQL Server database with a schema per module, read by
|
|
378
|
+
`extract-csharp-ddd` alone - through Roslyn, the compiler's own parser, over
|
|
379
|
+
the tree as one compilation and without restoring a single package. What
|
|
380
|
+
comes out, pinned in `vendor/repos/kgrzybek/modular-monolith-with-ddd`: five
|
|
381
|
+
contexts with one service each, nineteen aggregates and five model groups,
|
|
382
|
+
sixty domain events dispatched in process and seven integration events on
|
|
383
|
+
the in-memory bus, sixty-one commands and twenty-nine queries, an internal command queue
|
|
384
|
+
per module, the forty-three tables and seventeen views of the database
|
|
385
|
+
project with the root tables mapped to their aggregates through the EF
|
|
386
|
+
configurations, fifty-one HTTP operations across four documents, the
|
|
387
|
+
project's seventeen decision records, and a hundred and two flows. The one
|
|
388
|
+
to read is `register-new-user`: the User Access controller hands
|
|
389
|
+
`RegisterNewUserCommand` to the Registrations module's facade in process,
|
|
390
|
+
the flow crosses with a `call` step, `NewUserRegisteredDomainEvent` is
|
|
391
|
+
raised and saved, and from there the outbox republishes it to the bus as an
|
|
392
|
+
integration event that Meetings, Administration and Payments each hear and
|
|
393
|
+
turn into a job on their own internal queue.
|
|
394
|
+
|
|
395
|
+
The warnings are findings about the project: `MemberCreatedIntegrationEvent`
|
|
396
|
+
is declared and published nowhere while User Access subscribes to it;
|
|
397
|
+
Payments subscribes to `MeetingGroupProposalAcceptedIntegrationEvent` and
|
|
398
|
+
has no handler; the `app` schema belongs to no module; the view is created
|
|
399
|
+
as `v_Countriess` and the query reads `v_Countries`; and two modules keep a
|
|
400
|
+
`Domain/Users` directory with a typed id and nothing to hold it. How the
|
|
401
|
+
fragments were made is in
|
|
402
|
+
`vendor/repos/kgrzybek/modular-monolith-with-ddd/README.md`; the reading
|
|
403
|
+
rules are in `plugins/extract-csharp-ddd/README.md`.
|
|
404
|
+
|
|
237
405
|
## Develop Portolan itself
|
|
238
406
|
|
|
239
407
|
```bash
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package catalog
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// An enum round-trips whole: the aggregate's list, the values with their
|
|
9
|
+
// docs, the deprecation marks - and the proto side's numbers. The fixture is
|
|
10
|
+
// literal rather than read from a catalog so that the test says exactly what
|
|
11
|
+
// the mirror is expected to keep.
|
|
12
|
+
func TestEnumRoundTrip(t *testing.T) {
|
|
13
|
+
raw := `{"id":"a","slug":"a","name":"A","readme":"","root":"A","entities":[],"valueObjects":[],"operations":[],"events":[],
|
|
14
|
+
"enums":[{"id":"shop.oms.order.status","slug":"status","name":"Status","doc":"Where an order is.","deprecated":true,
|
|
15
|
+
"values":[{"name":"placed","doc":"just in"},{"name":"legacy","doc":"","deprecated":true}]}]}`
|
|
16
|
+
|
|
17
|
+
var agg Aggregate
|
|
18
|
+
if err := json.Unmarshal([]byte(raw), &agg); err != nil {
|
|
19
|
+
t.Fatal(err)
|
|
20
|
+
}
|
|
21
|
+
if len(agg.Enums) != 1 || agg.Enums[0].ID != "shop.oms.order.status" || !agg.Enums[0].Deprecated {
|
|
22
|
+
t.Fatalf("enum not read: %+v", agg.Enums)
|
|
23
|
+
}
|
|
24
|
+
if got := agg.Enums[0].Values; len(got) != 2 || got[0].Name != "placed" || !got[1].Deprecated {
|
|
25
|
+
t.Fatalf("values not read: %+v", got)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
out, err := json.Marshal(agg)
|
|
29
|
+
if err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
var before, after any
|
|
33
|
+
_ = json.Unmarshal([]byte(raw), &before)
|
|
34
|
+
_ = json.Unmarshal(out, &after)
|
|
35
|
+
if diffs := compare("aggregate", normalize(before), normalize(after)); len(diffs) != 0 {
|
|
36
|
+
t.Fatalf("round trip lost something: %v", diffs)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var svc RpcService
|
|
40
|
+
if err := json.Unmarshal([]byte(`{"id":"shop.v1.OrderService","methods":[],"source":"","enums":[{"name":"Status","values":[{"name":"STATUS_UNSPECIFIED","number":0},{"name":"PLACED","number":1,"doc":"in"}]}]}`), &svc); err != nil {
|
|
41
|
+
t.Fatal(err)
|
|
42
|
+
}
|
|
43
|
+
if len(svc.Enums) != 1 || svc.Enums[0].Values[1].Number != 1 || svc.Enums[0].Values[0].Number != 0 {
|
|
44
|
+
t.Fatalf("proto enum not read: %+v", svc.Enums)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package catalog
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"reflect"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestRelationEvidenceSurvivesEverySupportedCarrier(t *testing.T) {
|
|
10
|
+
evidence := []RelationEvidence{{Kind: "binding", Rule: "provider-signature", Source: "di.go:14", Symbol: "Provide", Candidates: []string{"one", "two"}}}
|
|
11
|
+
before := struct {
|
|
12
|
+
Step Step
|
|
13
|
+
Call RpcCall
|
|
14
|
+
Table Table
|
|
15
|
+
Persists Persists
|
|
16
|
+
}{
|
|
17
|
+
Step: Step{Evidence: evidence}, Call: RpcCall{Evidence: evidence},
|
|
18
|
+
Table: Table{Evidence: evidence}, Persists: Persists{Evidence: evidence},
|
|
19
|
+
}
|
|
20
|
+
raw, err := json.Marshal(before)
|
|
21
|
+
if err != nil {
|
|
22
|
+
t.Fatal(err)
|
|
23
|
+
}
|
|
24
|
+
after := before
|
|
25
|
+
after.Step.Evidence = nil
|
|
26
|
+
after.Call.Evidence = nil
|
|
27
|
+
after.Table.Evidence = nil
|
|
28
|
+
after.Persists.Evidence = nil
|
|
29
|
+
if err := json.Unmarshal(raw, &after); err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
if !reflect.DeepEqual(before, after) {
|
|
33
|
+
t.Fatalf("evidence changed: %s", raw)
|
|
34
|
+
}
|
|
35
|
+
}
|