diff --git a/.golangci.yml b/.golangci.yml index e6f8be83b..307f22b7c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,6 +21,9 @@ linters: - revive - errorlint + # License header check: + - goheader + linters-settings: govet: # These govet checks are disabled by default, but they're useful. @@ -30,6 +33,34 @@ linters-settings: - sortslice - unusedwrite + goheader: + values: + const: + COMPANY: 'Uber Technologies, Inc.' + regexp: + YEAR_RANGE: '\d{4}(-\d{4})?' + template: |- + Copyright (c) {{ YEAR_RANGE }} {{ COMPANY }} + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + issues: # Print all issues reported by all linters. max-issues-per-linter: 0 @@ -62,9 +93,3 @@ issues: - linters: [revive] path: '_test\.go$' text: 'should not use dot imports' - - # Ignore logger.Sync() errcheck failures in example_test.go - # since those are intended to be uncomplicated examples. - - linters: [errcheck] - path: example_test.go - text: 'Error return value of `logger.Sync` is not checked' diff --git a/Makefile b/Makefile index aef997620..bbda9cc12 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ build: go build ./... .PHONY: lint -lint: golangci-lint tidy-lint license-lint fx-lint docs-lint +lint: golangci-lint tidy-lint fx-lint docs-lint .PHONY: test test: @@ -58,10 +58,6 @@ tidy-lint: go mod tidy && \ git diff --exit-code -- go.mod go.sum) &&) true -.PHONY: license-lint -license-lint: - ./checklicense.sh - .PHONY: fx-lint fx-lint: $(FXLINT) @$(FXLINT) ./... @@ -76,4 +72,3 @@ $(MDOX): tools/go.mod $(FXLINT): tools/cmd/fxlint/main.go cd tools && go install go.uber.org/fx/tools/cmd/fxlint - diff --git a/checklicense.sh b/checklicense.sh deleted file mode 100755 index 28057d2fb..000000000 --- a/checklicense.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -e - -ERROR_COUNT=0 -while read -r file -do - case "$(head -1 "${file}")" in - *"Copyright (c) "*" Uber Technologies, Inc.") - # everything's cool - ;; - *) - echo "$file is missing license header." - (( ERROR_COUNT++ )) - ;; - esac -done < <(git ls-files "*\.go" | grep -v /testdata/) - -exit $ERROR_COUNT diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index c7d942413..430a0efb3 100755 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -101,6 +101,7 @@ module.exports = { { title: 'Concepts', children: [ + 'container.md', 'lifecycle.md', 'modules.md', ], diff --git a/docs/container.md b/docs/container.md new file mode 100644 index 000000000..affabd686 --- /dev/null +++ b/docs/container.md @@ -0,0 +1,156 @@ +# Container + +Container is the abstraction responsible for holding all constructors and values. +It’s the primary means by which an application interacts with Fx. +You teach the container about the needs of your application, +how to perform certain operations, +and then you let it handle actually running your application. + +Fx does not provide direct access to the container. +Instead, you specify operations to perform on the container +by providing `fx.Option`s to the `fx.New` constructor. + +```go +package fx + +type App + func New(opts ...Option) *App + func (app *App) Run() + +type Option + func Provide(constructors ...interface{}) Option + func Invoke(funcs ...interface{}) Option +``` + +Check the [API Reference](https://pkg.go.dev/go.uber.org/fx#Option) +for a complete list of options and their behaviors. + +## Providing values + +You must provide values to the container before you can use them. +Fx provides two ways to provide values to the container: + +- `fx.Provide` for values that have a constructor. + + ```go + fx.Provide( + func(cfg *Config) *Logger { /* ... */ }, + ) + ``` + + This says that Fx should use this function to construct a `*Logger`, + and that a `*Config` is required to build one. + +- `fx.Supply` for pre-built non-interface values. + + ```go + fx.Provide( + fx.Supply(&Config{ + Name: "my-app", + }), + ) + ``` + + This says that Fx should use the provided `*Config` as-is. + + **Important**: `fx.Supply` is only for non-interface values. + See *When to use fx.Supply* for more details. + +Values provided to the container are available to all other constructors. +In the example above, the `*Config` would become available to the `*Logger` constructor, +and the `*Logger` to any other constructors that need it. + +### When to use fx.Supply + +Usually, `fx.Provide` is the right choice because more often than not, +constructing an object requires its dependencies. +`fx.Supply` is a convenience function for the rare cases where that isn't true: +standalone values that don't depend on anything else. + +```go +fx.Provide(func() *Config { return &Config{Name: "my-app"} }) +// is the same as +fx.Supply(&Config{Name: "my-app"}) +``` + +However, even then, `fx.Supply` comes with a caveat: +it can only be used for non-interface values. + +
+ Why can't I use fx.Supply for interface values? + +This is a technical limitation imposed by the fact that `fx.Supply` has to rely +on runtime reflection to determine the type of the value. + +Passing an interface value to `fx.Supply` is a lossy operation: +it loses the original interface type, only giving us `interface{}`, +at which point reflection will only reveal the concrete type of the value. + +For example, consider: + +```go +var svc RepositoryService = &repoService{ ... } +``` + +If you were to pass `svc` to `fx.Supply`, +the container would only know that it's a `*repoService`, +and it will not know that you intend to use it as a `RepositoryService`. + +
+ +## Using values + +Providing values to the container only makes them available to the application. +It doesn't do anything with them yet. +Constructors passed to `fx.Provide` are not called until they are needed. + +For example, the following won't do anything: + +```go +fx.New( + fx.Provide(newHTTPServer), // provides an *http.Server +).Run() +``` + +You next have to tell the container what is needed, and what to do with it. +Fx provides [`fx.Invoke`](https://pkg.go.dev/go.uber.org/fx#Invoke) for this purpose. + +In the example above, we'll want an invocation that starts the server: + +```go +fx.New( + fx.Provide(newHTTPServer), + fx.Invoke(startHTTPServer), +).Run() +``` + +### When to use fx.Invoke + +`fx.Invoke` is typically used for root-level invocations, +like starting a server or running a main loop. +It's also useful for invoking functions that have side effects. + +Examples of cases where you might use `fx.Invoke`: + +- Starting a background worker +- Configuring a global logger + +As an example, consider an application organized into many distinct abstractions. + +```mermaid +flowchart LR + CacheWarmer --> Redis + Server[http.Server] --> UserHandler & PostHandler + UserHandler --> Redis[redis.Client] & Client[http.Client] + PostHandler --> sqlDB[sql.DB] + + subgraph Roots + CacheWarmer + Server + end +``` + +`CacheWarmer` and `http.Server` are the roots of the application. +We'll need `fx.Invoke` for the side effects of starting the server +and the cache warmer loop. +Everything else will be handled by the container automatically. diff --git a/signal.go b/signal.go index 1dbfd840e..8a86599ca 100644 --- a/signal.go +++ b/signal.go @@ -12,7 +12,7 @@ // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPSignalE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN diff --git a/signal_test.go b/signal_test.go index 527213244..8e8030ae7 100644 --- a/signal_test.go +++ b/signal_test.go @@ -12,7 +12,7 @@ // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPSignalE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN