File: README.md

package info (click to toggle)
golang-github-google-wire 0.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,744 kB
  • sloc: sh: 83; makefile: 6
file content (419 lines) | stat: -rw-r--r-- 13,095 bytes parent folder | download
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# Wire Tutorial

Let's learn to use Wire by example. The [Wire guide][guide] provides thorough
documentation of the tool's usage. For readers eager to see Wire applied to a
larger server, the [guestbook sample in Go Cloud][guestbook] uses Wire to
initialize its components. Here we are going to build a small greeter program to
understand how to use Wire. The finished product may be found in the same
directory as this README.

[guestbook]: https://github.com/google/go-cloud/tree/master/samples/guestbook
[guide]:     https://github.com/google/wire/blob/master/docs/guide.md

## A First Pass of Building the Greeter Program

Let's create a small program that simulates an event with a greeter greeting
guests with a particular message.

To start, we will create three types: 1) a message for a greeter, 2) a greeter
who conveys that message, and 3) an event that starts with the greeter greeting
guests. In this design, we have three `struct` types:

``` go
type Message string

type Greeter struct {
    // ... TBD
}

type Event struct {
    // ... TBD
}
```

The `Message` type just wraps a string. For now, we will create a simple
initializer that always returns a hard-coded message:

``` go
func NewMessage() Message {
    return Message("Hi there!")
}
```

Our `Greeter` will need reference to the `Message`. So let's create an
initializer for our `Greeter` as well.

``` go
func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}

type Greeter struct {
    Message Message // <- adding a Message field
}
```

In the initializer we assign a `Message` field to `Greeter`. Now, we can use the
`Message` when we create a `Greet` method on `Greeter`:

``` go
func (g Greeter) Greet() Message {
    return g.Message
}
```

Next, we need our `Event` to have a `Greeter`, so we will create an initializer
for it as well.

``` go
func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}

type Event struct {
    Greeter Greeter // <- adding a Greeter field
}
```

Then we add a method to start the `Event`:

``` go
func (e Event) Start() {
    msg := e.Greeter.Greet()
    fmt.Println(msg)
}
```

The `Start` method holds the core of our small application: it tells the
greeter to issue a greeting and then prints that message to the screen.

Now that we have all the components of our application ready, let's see what it
takes to initialize all the components without using Wire. Our main function
would look like this:

``` go
func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}
```

First we create a message, then we create a greeter with that message, and
finally we create an event with that greeter. With all the initialization done,
we're ready to start our event.

We are using the [dependency injection][di] design principle. In practice, that
means we pass in whatever each component needs. This style of design lends
itself to writing easily tested code and makes it easy to swap out one
dependency with another.

[di]: https://stackoverflow.com/questions/130794/what-is-dependency-injection

## Using Wire to Generate Code

One downside to dependency injection is the need for so many initialization
steps. Let's see how we can use Wire to make the process of initializing our
components smoother.

Let's start by changing our `main` function to look like this:

``` go
func main() {
    e := InitializeEvent()

    e.Start()
}
```

Next, in a separate file called `wire.go` we will define `InitializeEvent`.
This is where things get interesting:

``` go
// wire.go

func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}
```

Rather than go through the trouble of initializing each component in turn and
passing it into the next one, we instead have a single call to `wire.Build`
passing in the initializers we want to use. In Wire, initializers are known as
"providers," functions which provide a particular type. We add a zero value for
`Event` as a return value to satisfy the compiler. Note that even if we add
values to `Event`, Wire will ignore them. In fact, the injector's purpose is to
provide information about which providers to use to construct an `Event` and so
we will exclude it from our final binary with a build constraint at the top of
the file:

``` go
//+build wireinject

```

Note, a [build constraint][constraint] requires a blank, trailing line.

In Wire parlance, `InitializeEvent` is an "injector." Now that we have our
injector complete, we are ready to use the `wire` command line tool.

Install the tool with:

``` shell
go install github.com/google/wire/cmd/wire@latest
```

Then in the same directory with the above code, simply run `wire`. Wire will
find the `InitializeEvent` injector and generate a function whose body is
filled out with all the necessary initialization steps. The result will be
written to a file named `wire_gen.go`.

Let's take a look at what Wire did for us:

``` go
// wire_gen.go

func InitializeEvent() Event {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
    return event
}
```

It looks just like what we wrote above! Now this is a simple example with just
three components, so writing the initializer by hand isn't too painful. Imagine
how useful Wire is for components that are much more complex. When working with
Wire, we will commit both `wire.go` and `wire_gen.go` to source control.

[constraint]: https://godoc.org/go/build#hdr-Build_Constraints

## Making Changes with Wire

To show a small part of how Wire handles more complex setups, let's refactor
our initializer for `Event` to return an error and see what happens.

``` go
func NewEvent(g Greeter) (Event, error) {
    if g.Grumpy {
        return Event{}, errors.New("could not create event: event greeter is grumpy")
    }
    return Event{Greeter: g}, nil
}
```

We'll say that sometimes a `Greeter` might be grumpy and so we cannot create
an `Event`. The `NewGreeter` initializer now looks like this:

``` go
func NewGreeter(m Message) Greeter {
    var grumpy bool
    if time.Now().Unix()%2 == 0 {
        grumpy = true
    }
    return Greeter{Message: m, Grumpy: grumpy}
}
```

We have added a `Grumpy` field to `Greeter` struct and if the invocation time
of the initializer is an even number of seconds since the Unix epoch, we will
create a grumpy greeter instead of a friendly one.

The `Greet` method then becomes:

``` go
func (g Greeter) Greet() Message {
    if g.Grumpy {
        return Message("Go away!")
    }
    return g.Message
}
```

Now you see how a grumpy `Greeter` is no good for an `Event`. So `NewEvent` may
fail. Our `main` must now take into account that `InitializeEvent` may in fact
fail:

``` go
func main() {
    e, err := InitializeEvent()
    if err != nil {
        fmt.Printf("failed to create event: %s\n", err)
        os.Exit(2)
    }
    e.Start()
}
```

We also need to update `InitializeEvent` to add an `error` type to the return value:

``` go
// wire.go

func InitializeEvent() (Event, error) {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}, nil
}
```

With the setup complete, we are ready to invoke the `wire` command again. Note,
that after running `wire` once to produce a `wire_gen.go` file, we may also use
`go generate`. Having run the command, our `wire_gen.go` file looks like
this:

``` go
// wire_gen.go

func InitializeEvent() (Event, error) {
    message := NewMessage()
    greeter := NewGreeter(message)
    event, err := NewEvent(greeter)
    if err != nil {
        return Event{}, err
    }
    return event, nil
}
```

Wire has detected that the `NewEvent` provider may fail and has done the right
thing inside the generated code: it checks the error and returns early if one
is present.

## Changing the Injector Signature

As another improvement, let's look at how Wire generates code based on the
signature of the injector. Presently, we have hard-coded the message inside
`NewMessage`. In practice, it's much nicer to allow callers to change that
message however they see fit. So let's change `InitializeEvent` to look like
this:

``` go
func InitializeEvent(phrase string) (Event, error) {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}, nil
}
```

Now `InitializeEvent` allows callers to pass in the `phrase` for a `Greeter` to
use. We also add a `phrase` argument to `NewMessage`:

``` go
func NewMessage(phrase string) Message {
    return Message(phrase)
}
```

After we run `wire` again, we will see that the tool has generated an
initializer which passes the `phrase` value as a `Message` into `Greeter`.
Neat!

``` go
// wire_gen.go

func InitializeEvent(phrase string) (Event, error) {
    message := NewMessage(phrase)
    greeter := NewGreeter(message)
    event, err := NewEvent(greeter)
    if err != nil {
        return Event{}, err
    }
    return event, nil
}
```

Wire inspects the arguments to the injector, sees that we added a string to the
list of arguments (e.g., `phrase`), and likewise sees that among all the
providers, `NewMessage` takes a string, and so it passes `phrase` into
`NewMessage`.

## Catching Mistakes with Helpful Errors

Let's also look at what happens when Wire detects mistakes in our code and see
how Wire's error messages help us correct any problems.

For example, when writing our injector `InitializeEvent`, let's say we forget
to add a provider for `Greeter`. Let's see what happens:

``` go
func InitializeEvent(phrase string) (Event, error) {
    wire.Build(NewEvent, NewMessage) // woops! We forgot to add a provider for Greeter
    return Event{}, nil
}
```

Running `wire`, we see the following:

``` shell
# wrapping the error across lines for readability
$GOPATH/src/github.com/google/wire/_tutorial/wire.go:24:1:
inject InitializeEvent: no provider found for github.com/google/wire/_tutorial.Greeter
(required by provider of github.com/google/wire/_tutorial.Event)
wire: generate failed
```

Wire is telling us some useful information: it cannot find a provider for
`Greeter`. Note that the error message prints out the full path to the
`Greeter` type. It's also telling us the line number and injector name where
the problem occurred: line 24 inside `InitializeEvent`. In addition, the error
message tells us which provider needs a `Greeter`. It's the `Event` type. Once
we pass in a provider of `Greeter`, the problem will be solved.

Alternatively, what happens if we provide one too many providers to `wire.Build`?

``` go
func NewEventNumber() int  {
    return 1
}

func InitializeEvent(phrase string) (Event, error) {
     // woops! NewEventNumber is unused.
    wire.Build(NewEvent, NewGreeter, NewMessage, NewEventNumber)
    return Event{}, nil
}
```

Wire helpfully tells us that we have an unused provider:

``` shell
$GOPATH/src/github.com/google/wire/_tutorial/wire.go:24:1:
inject InitializeEvent: unused provider "NewEventNumber"
wire: generate failed
```

Deleting the unused provider from the call to `wire.Build` resolves the error.

## Conclusion

Let's summarize what we have done here. First, we wrote a number of components
with corresponding initializers, or providers. Next, we created an injector
function, specifying which arguments it receives and which types it returns.
Then, we filled in the injector function with a call to `wire.Build` supplying
all necessary providers. Finally, we ran the `wire` command to generate code
that wires up all the different initializers. When we added an argument to the
injector and an error return value, running `wire` again made all the necessary
updates to our generated code.

The example here is small, but it demonstrates some of the power of Wire, and
how it takes much of the pain out of initializing code using dependency
injection. Furthermore, using Wire produced code that looks much like what we
would otherwise write. There are no bespoke types that commit a user to Wire.
Instead it's just generated code. We may do with it what we will. Finally,
another point worth considering is how easy it is to add new dependencies to
our component initialization. As long as we tell Wire how to provide (i.e.,
initialize) a component, we may add that component anywhere in the dependency
graph and Wire will handle the rest.

In closing, it is worth mentioning that Wire supports a number of additional
features not discussed here. Providers may be grouped in [provider sets][sets].
There is support for [binding interfaces][interfaces], [binding
values][values], as well as support for [cleanup functions][cleanup]. See the
[Advanced Features][advanced] section for more.

[advanced]:   https://github.com/google/wire/blob/master/docs/guide.md#advanced-features
[cleanup]:    https://github.com/google/wire/blob/master/docs/guide.md#cleanup-functions
[interfaces]: https://github.com/google/wire/blob/master/docs/guide.md#binding-interfaces
[sets]:       https://github.com/google/wire/blob/master/docs/guide.md#defining-providers
[values]:     https://github.com/google/wire/blob/master/docs/guide.md#binding-values