Wire service.Fat with one function per operation - #59
Merged
Conversation
`service.Fat` no longer holds capabilities. It carries the logger and one private func field per operation, and an exported wiring function per operation (`service.GetUser`) sets that field from the narrow interfaces it declares. The public methods delegate and panic when unwired, so `*Fat` keeps satisfying interfaces like `http.userGetter`. `service.NewFat` now takes only global configuration, `service.Setup` does the wiring and is called from `main`, and `servicetest.NewFat` constructs without wiring so each test wires what it exercises. An internal `TestSetup` asserts `service.Setup` leaves no operation unwired. Drops the unused `bucket` and `sender` fields, which leaves `cmd/app/main.go` with no reason to build an S3 bucket.
Neither has an operation to wire it yet, but both stay in the composition path instead of being dropped: `service.Setup` takes them as parameters, so the first operation that needs one finds it already flowing from `main`. `service.Fat` still holds no capabilities. Restores the `s3.NewBucket` setup in `cmd/app/main.go`.
`service.NewFat` sets a `trace.Tracer` from `otel.Tracer("app/service")`, matching how
`http.AddUserToContext` names its own. Like the logger, it is there for the first operation that
needs it rather than being reached for per operation.
`service.GetUser` guards its func field before setting it, so wiring happens exactly once. Together with the delegate's not-wired panic, the two bracket the lifecycle: an operation is callable only after its one wiring, and against the capabilities that wiring chose. The rule was a sentence in the `service.Fat` doc before; the doc now states the panic instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
service.Fatwas a flat struct: every method could reach every capability, and two of the threefields (
bucket,sender) were reached by nothing at all. This gives each operation a wiringfunction that hands it exactly the capabilities it uses, mirroring how handlers are registered in
http.Fatkeeps only ambient state (logger, tracer) and one private func field per operation.Capabilities leave the struct.
service.GetUser(f, db)takes*Fatfirst, thennarrow private interfaces naming exactly the methods the operation calls, and sets the func field.
An operation cannot reach a capability it was not given, because
Fatholds none.function when unwired — so
*Fatkeeps satisfying consumer-side interfaces likehttp.userGetter.documented: an operation is callable only after its one wiring, against the capabilities that
wiring chose.
service.NewFattakes global configuration only (a nilLogdiscards);service.Setupiswiring-only and called from
main.servicetest.NewFat(t)constructs without wiring, with at.Log-backed logger, so each test wires what it exercises.TestSetupreflects over the func fields, so an operation that gets a wiring function but never aline in
Setupfails the test instead of panicking in production.Four things are plumbed and waiting for their first operation, which is deliberate — this is a
template, and a project that starts here should find them already flowing:
bucketandsenderare parameters ofservice.Setup, passed frommain. That keeps them out ofFat, where every operation could reach them, while leaving them one wiring function away. Thedoc on
Setupsays as much, so the unused parameters do not read as an oversight.logandtracerare ambient state onFatitself, the tracer fromotel.Tracer("app/service")to match
otel.Tracer("app/http")inhttp.AddUserToContext. Every operation gets both for free;the one that exists does not happen to use either yet.
cmd/app/main.gois unchanged apart from the three wiring lines.One thing worth a look:
servicetest.NewFatlosing its variadic options is a small API break foranything already built on the template — tests now build their own database and pass it to the
wiring function.