forked from uber-go/fx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
example_test.go
239 lines (225 loc) · 9.5 KB
/
example_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright (c) 2019 Uber Technologies, Inc.
//
// 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.
package fx_test
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
)
// NewLogger constructs a logger. It's just a regular Go function, without any
// special relationship to Fx.
//
// Since it returns a *log.Logger, Fx will treat NewLogger as the constructor
// function for the standard library's logger. (We'll see how to integrate
// NewLogger into an Fx application in the main function.) Since NewLogger
// doesn't have any parameters, Fx will infer that loggers don't depend on any
// other types - we can create them from thin air.
//
// Fx calls constructors lazily, so NewLogger will only be called only if some
// other function needs a logger. Once instantiated, the logger is cached and
// reused - within the application, it's effectively a singleton.
//
// By default, Fx applications only allow one constructor for each type. See
// the documentation of the In and Out types for ways around this restriction.
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// NewHandler constructs a simple HTTP handler. Since it returns an
// http.Handler, Fx will treat NewHandler as the constructor for the
// http.Handler type.
//
// Like many Go functions, NewHandler also returns an error. If the error is
// non-nil, Go convention tells the caller to assume that NewHandler failed
// and the other returned values aren't safe to use. Fx understands this
// idiom, and assumes that any function whose last return value is an error
// follows this convention.
//
// Unlike NewLogger, NewHandler has formal parameters. Fx will interpret these
// parameters as dependencies: in order to construct an HTTP handler,
// NewHandler needs a logger. If the application has access to a *log.Logger
// constructor (like NewLogger above), it will use that constructor or its
// cached output and supply a logger to NewHandler. If the application doesn't
// know how to construct a logger and needs an HTTP handler, it will fail to
// start.
//
// Functions may also return multiple objects. For example, we could combine
// NewHandler and NewLogger into a single function:
//
// func NewHandlerAndLogger() (*log.Logger, http.Handler, error)
//
// Fx also understands this idiom, and would treat NewHandlerAndLogger as the
// constructor for both the *log.Logger and http.Handler types. Just like
// constructors for a single type, NewHandlerAndLogger would be called at most
// once, and both the handler and the logger would be cached and reused as
// necessary.
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// NewMux constructs an HTTP mux. Like NewHandler, it depends on *log.Logger.
// However, it also depends on the Fx-specific Lifecycle interface.
//
// A Lifecycle is available in every Fx application. It lets objects hook into
// the application's start and stop phases. In a non-Fx application, the main
// function often includes blocks like this:
//
// srv, err := NewServer() // some long-running network server
// if err != nil {
// log.Fatalf("failed to construct server: %v", err)
// }
// // Construct other objects as necessary.
// go srv.Start()
// defer srv.Stop()
//
// In this example, the programmer explicitly constructs a bunch of objects,
// crashing the program if any of the constructors encounter unrecoverable
// errors. Once all the objects are constructed, we start any background
// goroutines and defer cleanup functions.
//
// Fx removes the manual object construction with dependency injection. It
// replaces the inline goroutine spawning and deferred cleanups with the
// Lifecycle type.
//
// Here, NewMux makes an HTTP mux available to other functions. Since
// constructors are called lazily, we know that NewMux won't be called unless
// some other function wants to register a handler. This makes it easy to use
// Fx's Lifecycle to start an HTTP server only if we have handlers registered.
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
// First, we construct the mux and server. We don't want to start the server
// until all handlers are registered.
mux := http.NewServeMux()
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// If NewMux is called, we know that another function is using the mux. In
// that case, we'll use the Lifecycle type to register a Hook that starts
// and stops our HTTP server.
//
// Hooks are executed in dependency order. At startup, NewLogger's hooks run
// before NewMux's. On shutdown, the order is reversed.
//
// Returning an error from OnStart hooks interrupts application startup. Fx
// immediately runs the OnStop portions of any successfully-executed OnStart
// hooks (so that types which started cleanly can also shut down cleanly),
// then exits.
//
// Returning an error from OnStop hooks logs a warning, but Fx continues to
// run the remaining hooks.
lc.Append(fx.Hook{
// To mitigate the impact of deadlocks in application startup and
// shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By
// default, hooks have a total of 15 seconds to complete. Timeouts are
// passed via Go's usual context.Context.
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
// In production, we'd want to separate the Listen and Serve phases for
// better error-handling.
go server.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// Register mounts our HTTP handler on the mux.
//
// Register is a typical top-level application function: it takes a generic
// type like ServeMux, which typically comes from a third-party library, and
// introduces it to a type that contains our application logic. In this case,
// that introduction consists of registering an HTTP handler. Other typical
// examples include registering RPC procedures and starting queue consumers.
//
// Fx calls these functions invocations, and they're treated differently from
// the constructor functions above. Their arguments are still supplied via
// dependency injection and they may still return an error to indicate
// failure, but any other return values are ignored.
//
// Unlike constructors, invocations are called eagerly. See the main function
// below for details.
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func Example() {
app := fx.New(
// Provide all the constructors we need, which teaches Fx how we'd like to
// construct the *log.Logger, http.Handler, and *http.ServeMux types.
// Remember that constructors are called lazily, so this block doesn't do
// much on its own.
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
// Since constructors are called lazily, we need some invocations to
// kick-start our application. In this case, we'll use Register. Since it
// depends on an http.Handler and *http.ServeMux, calling it requires Fx
// to build those types using the constructors above. Since we call
// NewMux, we also register Lifecycle hooks to start and stop an HTTP
// server.
fx.Invoke(Register),
// This is optional. With this, you can control where Fx logs
// its events. In this case, we're using a NopLogger to keep
// our test silent. Normally, you'll want to use an
// fxevent.ZapLogger or an fxevent.ConsoleLogger.
fx.WithLogger(
func() fxevent.Logger {
return fxevent.NopLogger
},
),
)
// In a typical application, we could just use app.Run() here. Since we
// don't want this example to run forever, we'll use the more-explicit Start
// and Stop.
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
log.Fatal(err)
}
// Normally, we'd block here with <-app.Done(). Instead, we'll make an HTTP
// request to demonstrate that our server is running.
if _, err := http.Get("http://localhost:8080/"); err != nil {
log.Fatal(err)
}
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil {
log.Fatal(err)
}
// Output:
// Executing NewLogger.
// Executing NewMux.
// Executing NewHandler.
// Starting HTTP server.
// Got a request.
// Stopping HTTP server.
}