This section explains how to use the Wire framework to create a simple Go application that incorporates dependency injection.
Launch your GoLand IDE and initiate a new Go project. Specify your preferred project name and location on your device to ensure proper organization.
Following that, create a main.go
file within the project directory. This file will act as the central hub for your project's codebase.
Proceed to create all the independent modules and components along with their parameter dependencies using the following piece of code:
package main
import "fmt"
type User struct {
name string
}
// NewUser - Creates a new instance of User
func NewUser(name string) User {
return User{name: name}
}
// NewUserName - Returns a string to provide the name of a new user
func NewUserName() string {
return "James"
}
// Get - A method with user as dependency
func (u *User) Get(message string) string {
return fmt.Sprintf("Hello %s - %s", u.name, message)
}
// Run - Depends on user and calls the Get method on User
func Run(user User) {
result := user.Get("It's nice to meet you!")
fmt.Println(result)
}
func main() {
}
As specified in the previous installment:
User
struct represents a user entity with a name
property, with the NewUser
function to create new instances of the User
struct.Get
method is defined on the User
struct and has a message
parameter.Run
function depends on a User
instance and calls the Get
method on that instance. The resulting formatted string is then printed to the console.Now that you have all your components ready, you can proceed to the implementation stage using the Wire framework to inject your component dependencies and run the application.
To begin the process, open your GoLand terminal and execute the following command to install Wire:
go install github.com/google/wire/cmd/wire@latest
For go versions prior to 1.17
go get github.com/google/wire/cmd/wire@latest
can be used.
Proceed to create a new file called wire.go
in the same folder as your main.go
file. This newly created file will serve as the designated location for defining the dependency provision logic:
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func Initialize() User {
wire.Build(NewUser, NewUserName)
return User{}
}
The above code employs the Wire framework to facilitate the resolution of all necessary dependencies within the application. This is achieved with the wire.Build
accessor, which orchestrates the process of dependency injection.
It incorporates a build constraint of // +build wireinject
to ensure the smooth execution of the build process. This constraint ensures that the file is excluded during the build phase since its primary role is to convey crucial information regarding the dependencies required for code generation. By excluding it, you prevent the occurrence of duplicate function names, preserving the integrity of the solution.
Now, launch the GoLand terminal and run the following command to generate the dependency file:
wire
After the successful execution of the above command, the Wire framework generates a file named wire_gen.go
. This file contains the mapping of dependencies, showcasing the relationship between different components and their associated dependencies. The structure and content of the wire_gen.go
file will resemble the following example:
Next, utilize the Initialize()
function from the generated wire_gen.go
file in the main()
function of your main.go
file:
func main() {
user := Initialize()
Run(user)
}
Finally, run the code with the GoLand run configuration by simply clicking the play icon on the left side of the main()
function in your main.go
file:
Alternatively, you can run the main()
function along with the generated code in the GoLand terminal using the following command:
go run main.go wire_gen.go
The appropriate result should be printed in the terminal:
Create a new file called main_test.go
in the same folder as your main.go
file. You can test your new application with the same test cases used in the first part of this series:
package main
import (
"fmt"
"testing"
)
func TestNewUser(t *testing.T) {
name := "James"
expected := User{name: name}
actual := NewUser(name)
if actual != expected {
t.Error("Expected User is not same as actual user")
}
}
func TestUser_Get(t *testing.T) {
name := "James"
user := NewUser(name)
message := "You look great"
expected := fmt.Sprintf("Hello %s - %s", user.name, message)
actual := user.Get(message)
if actual != expected {
t.Error("Expected User is not same as actual user")
}
}
Now, execute the tests either through the built-in GoLand run feature or the terminal. Upon running the tests, the expected outcome should be a successful execution with all tests passing: