You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Move the cross-domain shared trees out of the repo root into a single
platform/ umbrella:
- core/{errs,metrics,consumer} -> platform/{errs,metrics,consumer}
- core/httpclient -> platform/http (package renamed to http,
net/http aliased as nethttp; callers that
also import net/http use the phttp alias)
- entity/* -> platform/base/* (root doc package renamed
entity -> base; change, mergestrategy,
messagequeue preserved as subpackages)
- extension/* -> platform/extension/*
Rewrites all import paths and Bazel labels, updates Makefile schema/admin
paths, and refreshes docs (CLAUDE.md, READMEs, RFCs) to the platform/
layout. Domain-scoped trees (submitqueue/, stovepipe/, runway/) keep their
own core/entity/extension.
Co-authored-by: Cursor <cursoragent@cursor.com>
│ └── testutil/ # Test utilities (ComposeStack, MySQL helpers)
63
+
└── doc/ # Documentation
65
64
```
66
65
67
-
The repo hosts shared building blocks at the top level — cross-domain infrastructure in `core/`, shared entities in `entity/`, shared extensions in `extension/` — followed by one folder per **domain** (`submitqueue/`, `stovepipe/`). Each domain owns the same internal layout (`gateway/`, `orchestrator/`, `entity/`, `extension/`, `core/`); a domain's own `core/` (e.g. `submitqueue/core/`) holds infra shared only between that domain's services.
66
+
The `platform/` tree holds code reused across domains (infrastructure, shared entities, shared extension contracts). Each **domain** (`submitqueue/`, `stovepipe/`, …) keeps the same internal layout (`gateway/`, `orchestrator/`, `entity/`, `extension/`, `core/`); a domain's own `core/` (e.g. `submitqueue/core/`) holds infra shared only between that domain's services.
67
+
68
+
### Platform notes
69
+
70
+
- Import path `github.com/uber/submitqueue/platform/http` uses Go package name `http` and aliases the standard library as `nethttp` inside the package. Source files that also import `net/http` should import the platform package with a distinct alias (for example `phttp "github.com/uber/submitqueue/platform/http"`) and call `phttp.NewClient`, `phttp.BaseURLTransport`, etc.
71
+
-`platform/base` is the shared entity root; subpackages (`change`, `messagequeue`, …) hold concrete types. The root `base` package is documentation-only.
68
72
69
73
### Services
70
74
@@ -101,7 +105,7 @@ Controllers receive `consumer.Delivery` (subset interface without Ack/Nack) to e
101
105
102
106
### Entities
103
107
104
-
Domain objects in `entity/`, organized by domain. Guidelines:
108
+
Domain objects live under each domain's `entity/` tree, or under `platform/base/` when shared across domains. Guidelines:
105
109
1. Pure and framework-agnostic — no external dependencies
106
110
2. Value types, not references
107
111
3. `int64` milliseconds for timestamps (`CreatedAt int64`) and durations (`TimeoutMs int64`)
@@ -112,17 +116,17 @@ Domain objects in `entity/`, organized by domain. Guidelines:
112
116
### Extensions
113
117
114
118
Vendor-agnostic, pluggable interfaces with implementations in subdirectories:
115
-
1. Define interfaces at `extension/{ext}/`
116
-
2. Implementations at `extension/{ext}/{impl}/`
117
-
3. Factory interface for dependency injection and lifecycle management
119
+
1. **Shared across domains** — define interfaces at `platform/extension/{ext}/`, implementations at `platform/extension/{ext}/{impl}/`.
120
+
2. **Domain-specific** — define at `{domain}/extension/{ext}/`, implementations at `{domain}/extension/{ext}/{impl}/`.
121
+
3. Factory interface for dependency injection and lifecycle management (constructed in wiring, not inside `platform/extension` packages).
118
122
119
123
**Extensions hold contracts and implementations only — not factories or routing.**
120
124
121
-
An `extension/{ext}` package contains the behavioral interface, its `Config`, the `Factory` *interface*, and impl constructors `New(...)` that return the interface. It must **not** contain `Factory` *implementations* (`NewFactory()` constructors or factory structs) or any queue-selection logic.
125
+
A `{domain}/extension/{ext}` or `platform/extension/{ext}` package contains the behavioral interface, its `Config`, the `Factory` *interface*, and impl constructors `New(...)` that return the interface. It must **not** contain `Factory` *implementations* (`NewFactory()` constructors or factory structs) or any queue-selection logic.
122
126
123
127
Why: an impl package (e.g. `scorer/heuristic`) can't know the queue topology or the other impls, so a "which impl for which queue" decision doesn't belong there. Per-queue routing — and the small adapters that wrap a `New(...)` impl in the `Factory` interface — live in the wiring layer (e.g. `example/{domain}/{service}/server/main.go`), the one place that knows the full queue set. That's where you route on `Config.QueueName`.
124
128
125
-
Rule of thumb: if you're about to add a `NewFactory()` or a `map[queue]impl` under `extension/`, it belongs in the wiring layer instead.
129
+
Rule of thumb: if you're about to add a `NewFactory()` or a `map[queue]impl` under `{domain}/extension/` or `platform/extension/`, it belongs in the wiring layer instead.
126
130
127
131
**Design interfaces for the technology *space*, not the implementation in front of you.** The interface is a contract every backend will have to satisfy — SQL, key-value (DynamoDB, Bigtable), document, message queue, search, RPC, in-memory, mocks. If the contract assumes a capability that some plausible backend can't provide cheaply, you've baked the current impl's strengths into the API.
128
132
@@ -142,18 +146,18 @@ When in doubt, ask: *"If the next implementation were DynamoDB / Kafka / Bigtabl
142
146
143
147
### Import Paths
144
148
145
-
Paths follow the directory layout: shared code is top-level, domain code nests under the domain folder (`submitqueue/`, `stovepipe/`).
149
+
Paths follow the directory layout: shared packages live under `platform/` at the repo root; domain code nests under `submitqueue/`, `stovepipe/`, and other domain folders.
@@ -225,11 +229,11 @@ make clean # Clean Bazel cache
225
229
2. Wire up in `example/{domain}/{service}/server/main.go`
226
230
227
231
**Add new extension:**
228
-
1. Create the extension under `{domain}/extension/{ext}/{impl}/` (domain-specific, e.g. `submitqueue/extension/...`) or top-level `extension/{ext}/{impl}/` (shared across domains) with factory and interfaces
232
+
1. Create the extension under `{domain}/extension/{ext}/{impl}/` (domain-specific, e.g. `submitqueue/extension/...`) or `platform/extension/{ext}/{impl}/` (shared across domains) with factory and interfaces
229
233
2. Add `BUILD.bazel`, tests, and README.md
230
234
231
235
**Add new entity:**
232
-
1. Create `{domain}/entity/{entity}.go` (domain-specific) or top-level `entity/{name}/{entity}.go` (shared) with test file and `BUILD.bazel`
236
+
1. Create `{domain}/entity/{entity}.go` (domain-specific) or add packages under `platform/base/` (shared) with test file and `BUILD.bazel`
233
237
234
238
**Add gomock for an extension interface:**
235
239
@@ -252,7 +256,7 @@ To create a mock package for a new extension (e.g., `submitqueue/extension/newex
252
256
3. Run `make mocks` to generate mock files into the new directory.
253
257
4. Run `make gazelle` to create the `BUILD.bazel` file automatically.
254
258
255
-
For inline mocks (mock in the same package, e.g., `extension/messagequeue/mysql/mock_stores.go`):
259
+
For inline mocks (mock in the same package, e.g., `platform/extension/messagequeue/mysql/mock_stores.go`):
256
260
257
261
1. Add a `//go:generate` directive with `-package=mypkg` and `-destination=mock_file.go`.
258
262
2. Run `make mocks` and `make gazelle`.
@@ -305,9 +309,9 @@ CI runs on every PR and enforces all checks via a `required-checks` gate. **Befo
305
309
3.**Value types over pointers** — prefer value types for structs, configs, and return values. Use `(T, bool)` to signal absence instead of `*T`. Pointers only when mutation or shared ownership is needed.
306
310
4.**Errors for failures, not control flow** — reserve `error` returns for unexpected or infrastructure failures. Use result types (structs, bools) for expected outcomes like `(Result, error)` or `(T, bool)`. Avoid sentinel errors that represent non-failure states.
307
311
308
-
### Error Classification (`core/errs`)
312
+
### Error Classification (`platform/errs`)
309
313
310
-
Errors are classified by origin (user vs infra) and retryability. The framework lives in `core/errs/`. See [core/errs/README.md](core/errs/README.md) for full details.
314
+
Errors are classified by origin (user vs infra) and retryability. The framework lives in `platform/errs/`. See [platform/errs/README.md](platform/errs/README.md) for full details.
311
315
312
316
**Key rules:**
313
317
1.**Non-retryable by default** — a plain `fmt.Errorf(...)` is non-retryable. Wrap with `errs.NewRetryableError(...)` to opt in to retry.
Copy file name to clipboardExpand all lines: README.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,10 @@ SubmitQueue is a high-performance speculative merge queue that keeps your trunk
8
8
9
9
Designed for large monorepos and fast-moving teams where concurrent changes can introduce subtle conflicts and destabilize builds.
10
10
11
+
## Repository layout
12
+
13
+
Cross-domain Go code (errors, metrics, consumer framework, HTTP helpers, shared entities, shared extension contracts) lives under [`platform/`](platform/README.md). Each product domain has its own tree (`submitqueue/`, `stovepipe/`, …) with `gateway/`, `orchestrator/`, `entity/`, `extension/`, and domain-local `core/`. See [CLAUDE.md](CLAUDE.md) for conventions and import paths.
14
+
11
15
## Quick Start
12
16
13
17
Requires Docker and Docker Compose. See [Development Setup](doc/howto/DEVELOPMENT.md) for full prerequisites.
0 commit comments