Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Hands-On Dependency Injection in Go
Hands-On Dependency Injection in Go

Hands-On Dependency Injection in Go: Develop clean Go code that is easier to read, maintain, and test

eBook
$24.99 $35.99
Paperback
$43.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Table of content icon View table of contents Preview book icon Preview Book

Hands-On Dependency Injection in Go

Never Stop Aiming for Better

Do you want code that is easier to maintain? How about easier to test? Easier to extend? Dependency Injection (DI) might be just the tool you need.

In this chapter, we will define DI, perhaps in a somewhat atypical way, and explore the code smells that could indicate you need DI. We will also talk briefly about Go and how I would like you to approach the ideas presented in this book.

Are you ready to join me on a journey to better Go code?

We will cover the following topics:

  • Why does DI matter?
  • What is DI?
  • When should I apply DI?
  • How can I improve as a Go programmer?

Technical requirements

Why does DI matter?

As professionals, we should never stop learning. Learning is the one true way to ensure we stay in demand and continue delivering value to our customers. Doctors, lawyers, and scientists are all highly respected professionals and all focus on continuously learning. Why should programmers be different?

In this book, we will take a journey that will start with some code that gets the job done and, by selectively applying various DI methods available in Go, together, we will transform it into something a hell of a lot easier to maintain, test, and extend.

Not everything in this book is traditional or perhaps even idiomatic, but I would ask you to try it before you deny it. If you like it, fantastic. If not, at least you learned what you don't want to do.

So, how do I define DI?

DI is coding in such a way that those resources (that is, functions or structs) that we depend on are abstractions. Because these dependencies are abstract, changes to them do not necessitate changes to our code. The fancy word for this is decoupling.

The use of the word abstraction here may be a little misleading. I do not mean an abstract class like you find in Java; Go does not have that. Go does, however, have interfaces and function literals (also known as closures).

Consider the following example of an interface and the SavePerson() function that uses it:

// Saver persists the supplied bytes
type Saver interface {
Save(data []byte) error
}

// SavePerson will validate and persist the supplied person
func SavePerson(person *Person, saver Saver) error {
// validate the inputs
err := person.validate()
if err != nil {
return err
}

// encode person to bytes
bytes, err := person.encode()
if err != nil {
return err
}

// save the person and return the result
return saver.Save(bytes)
}

// Person data object
type Person struct {
Name string
Phone string
}

// validate the person object
func (p *Person) validate() error {
if p.Name == "" {
return errors.New("name missing")
}

if p.Phone == "" {
return errors.New("phone missing")
}

return nil
}

// convert the person into bytes
func (p *Person) encode() ([]byte, error) {
return json.Marshal(p)
}

In the preceding example, what does Saver do? It saves some bytes somewhere. How does it do this? We don't know and, while working on the SavePerson function, we don't care.

Let's look at another example that uses a function literal:

// LoadPerson will load the requested person by ID.
// Errors include: invalid ID, missing person and failure to load
// or decode.
func LoadPerson(ID int, decodePerson func(data []byte) *Person) (*Person, error) {
// validate the input
if ID <= 0 {
return nil, fmt.Errorf("invalid ID '%d' supplied", ID)
}

// load from storage
bytes, err := loadPerson(ID)
if err != nil {
return nil, err
}

// decode bytes and return
return decodePerson(bytes), nil
}

What does decodePerson do? It converts the bytes into a person. How? We don't need to know to right now.

This is the first advantage of DI that I would highlight to you:

DI reduces the knowledge required when working on a piece of code, by expressing dependencies in an abstract or generic manner

Now, let's say that the preceding code came from a system that stored data in a Network File Share (NFS). How would we write unit tests for that? Having access to an NFS at all times would be a pain. Any such tests would also fail more often than they should due to entirely unrelated issues, such as network connectivity.

On the other hand, by relying on an abstraction, we could swap out the code that saves to the NFS with fake code. This way, we are only testing our code in isolation from the NFS, as shown in the following code:

func TestSavePerson_happyPath(t *testing.T) {
// input
in := &Person{
Name: "Sophia",
Phone: "0123456789",
}

// mock the NFS
mockNFS := &mockSaver{}
mockNFS.On("Save", mock.Anything).Return(nil).Once()

// Call Save
resultErr := SavePerson(in, mockNFS)

// validate result
assert.NoError(t, resultErr)
assert.True(t, mockNFS.AssertExpectations(t))
}

Don't worry if the preceding code looks unfamiliar; we will examine all of the parts in depth later in this book.

Which brings us to the second advantage of DI:

DI enables us to test our code in isolation of our dependencies

Considering the earlier example, how could we test our error-handling code? We could shut down the NFS through some external script every time we run the tests, but this would likely be slow and would definitely annoy anyone else that depended on it.

On the other hand, we could quickly make a fake Saver that always failed, as shown in the following code:

func TestSavePerson_nfsAlwaysFails(t *testing.T) {
// input
in := &Person{
Name: "Sophia",
Phone: "0123456789",
}

// mock the NFS
mockNFS := &mockSaver{}
mockNFS.On("Save", mock.Anything).Return(errors.New("save failed")).Once()

// Call Save
resultErr := SavePerson(in, mockNFS)

// validate result
assert.Error(t, resultErr)
assert.True(t, mockNFS.AssertExpectations(t))
}

The above test is fast, predictable, and reliable. Everything we could want from tests!

This gives us the third advantage of DI:

DI enables us to quickly and reliably test situations that are otherwise difficult or impossible

Let's not forget about the traditional sales pitch for DI. Tomorrow, if we decided to save to a NoSQL database instead of our NFS, how would our SavePerson code have to change?  Not one bit. We would only need to write a new Saver implementation, giving us the fourth advantage of DI:

DI reduces the impact of extensions or changes

At the end of the day, DI is a tool—a handy tool, but no magic bullet. It's a tool that can make code easier to understand, test, extend, and reuse—a tool that can also help reduce the likelihood of circular dependency issues that commonly plague new Go developers.

Code smells that indicate you might need DI

The saying to a man with only a hammer, every problem looks like a nail is old and yet is never truer than in programming. As professionals, we should be continually striving to acquire more tools to be better equipped for whatever our job throws at us. DI, while a highly useful tool, is useful only for particular nails. In our case, these nails are code smells. Code smells are indications in the code of a potentially deeper problem.

There are many different types of code smell; in this section, we will examine only those that can be alleviated by DI. In later chapters, we will reference these smells as we attempt to remove them from our code.

Code smells generally fall into four different categories:

  • Code bloat
  • Resistance to change
  • Wasted effort
  • Tight coupling

Code bloat

Code bloat smells are cases where unwieldy slabs of code have been added to structs or functions so that they have become hard to understand, maintain, and test. Frequently found in older code, they are often the result of a gradual degradation and lack of maintenance rather than intentional choices.

They can be found with a visual scan of the source code or by employing a cyclomatic complexity checker (a software metric that indicates the complexity of a piece of code) such as gocyclo (https://github.com/fzipp/gocyclo).

These smells include the following:

  • Long methods: While the code is run on computers, it is written for humans. Any method of more than about 30 lines should be split into smaller chunks. While it makes no difference to the computer, it makes it easier for us humans to understand.
  • Long structs: Similar to long methods, the longer a struct, the harder it is to understand and therefore maintain. Long structs typically also indicate the struct is doing too much. Splitting one struct into several smaller ones is also a great way to increase the reusability potential of the code.
  • Long parameter lists: Long parameter lists also indicate that the method is likely doing more than it should. When adding new features, it is tempting to add a new parameter to an existing function to account for the new use case. This is a slippery slope. This new parameter is either optional/unnecessary for the existing use cases or is an indication of a significant increase in complexity in the method.
  • Long conditional blocks: Switch statements are amazing. The problem is they are very easy to abuse and tend to multiply like proverbial rabbits. Perhaps the most significant problem, however, is their effect on the readability of the code. Long conditional blocks take up a lot of space and interrupt the readability of the function. Consider the following code:
func AppendValue(buffer []byte, in interface{}) []byte{
var value []byte

// convert input to []byte
switch concrete := in.(type) {
case []byte:
value = concrete

case string:
value = []byte(concrete)

case int64:
value = []byte(strconv.FormatInt(concrete, 10))

case bool:
value = []byte(strconv.FormatBool(concrete))

case float64:
value = []byte(strconv.FormatFloat(concrete, 'e', 3, 64))
}

buffer = append(buffer, value...)
return buffer
}

By taking interface{} as input, anywhere we wish to use it, we are almost forced to have a switch like this one. We would be better off changing from interface{} to an interface and then adding the necessary operations to the interface. This approach is better illustrated by the json.Marshaller and driver.Valuer interfaces in the standard library.

Applying DI to these smells will typically reduce the complexity of individual pieces of code by breaking them into smaller, separate pieces, which in turn makes them easier to understand, maintain, and test.

Resistance to change

These are cases where it is difficult and/or slow to add new features. Similarly, tests are often harder to write, especially tests for failure conditions. Similar to code bloat, these smells can be the result of a gradual degradation and lack of maintenance, but they can also be caused by a lack of up-front planning or poor API design.

They can be found by examining the pull request log or commit history and, in particular, determining if new features require many small changes in different parts of the code. 
If your team tracks feature velocity and you notice it is declining, this is also a likely cause.

These smells include the following:

  • Shotgun surgery: This is when small changes made to one struct necessitate changes in other structs. These changes imply that the organisation or abstraction used was incorrect. Typically, all of these changes should be in one class.
    In the following example, you can see how adding an email field to the person data would result in changing all three structs (Presenter, Validator, and Saver):
// Renderer will render a person to the supplied writer
type Renderer struct{}

func (r Renderer) render(name, phone string, output io.Writer) {
// output the person
}

// Validator will validate the supplied person has all the
// required fields
type Validator struct{}

func (v Validator) validate(name, phone string) error {
// validate the person
return nil
}

// Saver will save the supplied person to the DB
type Saver struct{}

func (s *Saver) Save(db *sql.DB, name, phone string) {
// save the person to db
}
  • Leaking implementation details: One of the more popular idioms in the Go community is accept interfaces, return structs. It's a catchy turn of phrase, but its simplicity masks its cleverness. When a function accepts a struct, it ties the user to a particular implementation—a strict relationship that makes future changes or additional usage difficult. By extension, if that implementation detail were to change, the API changes and forces changes on its users.

Applying DI to these smells is typically a good investment in the future. While not fixing them is not fatal, the code will progressively degrade until you are dealing with the proverbial big ball of mud. You know the type—a package that no-one understands, no-one trusts, and only the brave or stupid are willing to make changes to. DI enables you to decouple from the implementation choices, thereby making it easier to refactor, test, and maintain small chunks of code in isolation.

Wasted effort

These smells are cases where the cost to maintain the code is higher than it needs to be. They are typically caused by laziness or lack of experience. It's always easier to copy/paste code than to carefully refactor it. The problem is, coding like this is like eating unhealthy snacks. It feels great in the moment, but the long-term consequences suck.

They can be found by taking a critical look at the source code and asking yourself do I really need this code? Or, can I make this easier to understand?

Using tools such as dupl (https://github.com/mibk/dupl) or PMD (https://pmd.github.io/) will also help you identify areas of the code to investigate.

These smells include the following:

  • Excessive duplicated code: Firstly, please, please do not become a zealot about this one. While in most cases, duplicated code is a bad thing, sometimes copying code can result in a system that is easier to maintain and can evolve. We will deal with a common source of this smell in Chapter 8, Dependency Injection by Config.
  • Excessive comments: Leaving a note for those that come after you, even it is only you 6 months from now, is a friendly and professional thing to do. But when that note becomes an essay, then it's time to refactor:
// Excessive comments
func outputOrderedPeopleA(in []*Person) {
// This code orders people by name.
// In cases where the name is the same, it will order by
// phone number.
// The sort algorithm used is a bubble sort
// WARNING: this sort will change the items of the input array
for _, p := range in {
// ... sort code removed ...
}

outputPeople(in)
}

// Comments replaced with descriptive names
func outputOrderedPeopleB(in []*Person) {
sortPeople(in)
outputPeople(in)
}
  • Overly complicated code: The harder code is for other people to understand, the worse it is. Typically, this is the result of someone trying to be too fancy or not putting enough effort into structure or naming. Taking a more selfish view, if you are the only one who understands a piece of code, you are the only one that can work on it. Meaning, you are doomed to maintain it forever. What does the following code do:
for a := float64(0); a < 360; a++ {
ra := math.Pi * 2 * a / 360
x := r*math.Sin(ra) + v
y := r*math.Cos(ra) + v
i.Set(int(x), int(y), c)
}
  • DRY/WET code: The Don't Repeat Yourself (DRY) principle is aimed at reducing duplicated efforts by grouping responsibilities together and providing clean abstractions. By contrast, in WET code, sometimes called Waste Everyone's Time code, you will find the same responsibility in many places. This smell often appears in formatting or conversion code. This sort of code should exist at the system boundaries, that is, converting user input or formatting output.

While many of these smells can be fixed without DI, DI provides an easier way to lift and shift the duplication into an abstraction that can then be used to reduce the duplication and improve the readability and maintainability of the code.

Tight coupling

For people, tight coupling might be a good thing. For Go code, it's really not. Coupling is a measure of how objects relate to or depend on each other. When the tight coupling is present, this interdependence forces the objects or packages to evolve together, adding complexity and maintenance costs.

Coupling-related smells are perhaps the most insidious and obstinate but by far the most rewarding when dealt with. They are often the result of a lack of object-oriented design or insufficient use of interfaces.

Sadly, I don't have a handy tool to help you find these smells but I am confident that, by the end of this book, you will have no trouble spotting and dealing with them.

Frequently, I find it useful to implement a feature in a tightly coupled form first and then work backward to decouple and thoroughly unit test my code before submitting it. For me, it is especially helpful in cases where the correct abstractions are not obvious.

These smells include the following:

  • Dependence on God objects: These are large objects that know too much or do too much. While this is a general code smell and something that should be avoided like the plague, the problem from a DI perspective is that too much of the code is dependent on this one object. When they exist and we are not careful, it won't be long before Go will be refusing to compile due to a circular dependency. Interestingly, Go considers dependencies and imports not at an object level but at a package level. So we have to avoid God packages as well.  We will address a very common God object problem in Chapter 8, Dependency Injection by Config.
  • Circular dependencies: These are where package A depends on package B, and package B depends on package A. This is an easy mistake to make and sometimes a hard one to get rid of.

In the following example, while the config is arguably a God object and therefore a code smell, I am hard pressed to find a better way to import the config from a single JSON file. Instead, I would argue that the problem to be solved is the use of the config package by orders package. A typical config God object follows:

package config

import ...

// Config defines the JSON format of the config file
type Config struct {
// Address is the host and port to bind to.
// Default 0.0.0.0:8080
Address string

// DefaultCurrency is the default currency of the system
DefaultCurrency payment.Currency
}

// Load will load the JSON config from the file supplied
func Load(filename string) (*Config, error) {
// TODO: load currency from file
return nil, errors.New("not implemented yet")
}

In the attempted usage of the config package, you can see that the Currency type belongs to the Package package and so including it in config, as shown in the preceding example, causes a circular dependency:

package payment

import ...

// Currency is custom type for currency
type Currency string

// Processor processes payments
type Processor struct {
Config *config.Config
}

// Pay makes a payment in the default currency
func (p *Processor) Pay(amount float64) error {
// TODO: implement me
return errors.New("not implemented yet")
}
  • Object orgy: These occur when an object has too much knowledge of and/or access to the internals of another or, to put it another way, insufficient encapsulation between objects. Because the objects are joined at the hip, they will frequently have to evolve together, increasing the cost of understanding the code and maintaining it. Consider the following code:
type PageLoader struct {
}

func (o *PageLoader) LoadPage(url string) ([]byte, error) {
b := newFetcher()

// check cache
payload, err := b.cache.Get(url)
if err == nil {
// found in cache
return payload, nil
}

// call upstream
resp, err := b.httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()

// extract data from HTTP response
payload, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

// save to cache asynchronously
go func(key string, value []byte) {
b.cache.Set(key, value)
}(url, payload)

// return
return payload, nil
}

type Fetcher struct {
httpClient http.Client
cache *Cache
}

In this example, PageLoader repeatably calls the member variable of the Fetcher. So much so that, if the implementation of Fetcher changed, it's highly likely that PageLoader would be affected. In this case, these two objects should be merged together as PageLoader has no extra functionality.

  • Yo-yo problem: The standard definition of this smell is when the inheritance graph is so long and complicated that the programmer has to keep flipping through the code to understand it. Given that Go doesn't have inheritance, you would think we would be safe from this problem. However, it is possible if you try hard enough, with excessive composition. To address this issue, it's better to keep relationships as shallow and abstract as possible. In this way, we can concentrate on a much smaller scope when making changes and compose many small objects into a larger system.
  • Feature envy: When a function makes extensive use of another object, it is envious of it. Typically, an indication that the function should be moved away from the object it is envious of. DI may not be the solution to this, but this smell does indicate high coupling and, therefore, is an indicator to consider applying DI techniques:
func doSearchWithEnvy(request searchRequest) ([]searchResults, error) {
// validate request
if request.query == "" {
return nil, errors.New("search term is missing")
}
if request.start.IsZero() || request.start.After(time.Now()) {
return nil, errors.New("start time is missing or invalid")
}
if request.end.IsZero() || request.end.Before(request.start) {
return nil, errors.New("end time is missing or invalid")
}

return performSearch(request)
}

func doSearchWithoutEnvy(request searchRequest) ([]searchResults, error) {
err := request.validate()
if err != nil {
return nil, err
}

return performSearch(request)
}

As your code becomes less coupled, you will find the individual parts (packages, interfaces, and structs) will become more focused. This is referred to as having high cohesion. Both low coupling and high cohesion are desirable as they make the code easier to understand and work with.

Healthy skepticism

As we journey through this book, you will look at some fantastic coding techniques and some not so great. I would ask you to spend some time pondering which is which. Continuous learning should be tempered with a healthy dose of skepticism. For each technique, I will lay out the pros and cons, but I would ask you to dig deeper. Ask yourself the following:

  • What is this technique trying to achieve?
  • What would my code look like after I apply this technique?
  • Do I really need it?
  • Are there any downsides to using this method?

Even when your inner skeptic dismisses the technique, you've at least learned to identify something you don't like and don't want to use, and learning is always a win.

A quick word about idiomatic Go

Personally, I try to avoid using the term idiomatic Go but a Go book is arguably not complete without addressing it in some form. I avoid it because I have seen it too often used as a stick to beat people. Essentially, this is not idiomatic, therefore it's wrong and, by extension, I am idiomatic and therefore better than you. I believe that programming is a craft and, while a craft should have some form of consistency in its application, it should, as with all crafts, be flexible. After all, innovation is often found by bending or breaking the rules. 
So what does idiomatic Go mean to me?

I'll define it as loosely as I can:

  • Format your code with gofmt: Truly one less thing for us programmers to argue about. It's the official style, supported with official tools. Let's find something more substantive to argue about.
  • Read, apply, and regularly revisit the ideas in Effective Go (https://golang.org/doc/effective_go.html) and Code Review Comments (https://github.com/golang/go/wiki/CodeReviewComments): There is a huge amount of wisdom in these pages, so much so that it's perhaps impossible to glean it all from just one reading.
  • Aggressively apply the Unix philosophy: It state that we should design code that does a single thing, but to does it well and works well together well with other code.

While these three things are the minimum for me, there are a couple of other ideas that resonate:

  • Accepting interfaces and returning structs: While accepting interfaces leads to nicely decoupled code, the returning structs might strike you as a contradiction. I know they did with me at first. While outputting an interface might feel like it's more loosely coupled, it's not. Output can only be one thing—whatever you code it to be. Returning an interface is fine if that's what you need, but forcing yourself to do so just ends up with you writing more code.
  • Reasonable defaults: Since switching to Go, I've found many cases where I want to offer my user the ability to configure the module but such configuration is frequently not used. In other languages, this could lead to multiple constructors or seldom used parameters, but by applying this pattern we end up with a much cleaner API and less code to maintain.

Leave your baggage at the door

If you were to ask me what is the most frequent mistake new Go programmers make?, I would not hesitate to tell you that it's bringing other language patterns into Go. I know this was my biggest early mistake. My first Go service looked like a Java app written in Go. Not only was the result subpar but it was rather painful, particularly while I was trying to achieve things such as inheritance. I've had a similar experience programming Go in a functional style, as you might see in Node.js.

In short, please don't do it. Re-read Effective Go and Go blogs as often as you need to until you find yourself using small interfaces, firing off Go routines without reservation, loving channels, and wondering why you ever needed more than composition to achieve nice polymorphism.

Summary

In this chapter, we started a journey—a journey that will lead to code that is easier to maintain, extend, and test.

We started by defining DI and examining some of the benefits it can bring us. With the help of a few examples, we saw how this might look in Go.

After that, we started identifying code smells to look out for and that could be addressed or alleviated by applying DI.

Finally, we examined what I believe Go code looks like, and I challenged you to be skeptical and apply a critical eye to techniques presented in this book.

Questions

  1. What is DI?
  2. What are the four highlighted advantages of DI?
  3. What sorts of issues does it address?
  4. Why is it important to be skeptical?
  5. What does idiomatic Go mean to you?
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • • Learn to evaluate Code UX and make it better
  • • Explore SOLID principles and understand how they relate to dependency injection
  • • Use Google's wire framework to simplify dependence management

Description

Hands-On Dependency Injection in Go takes you on a journey, teaching you about refactoring existing code to adopt dependency injection (DI) using various methods available in Go. Of the six methods introduced in this book, some are conventional, such as constructor or method injection, and some unconventional, such as just-in-time or config injection. Each method is explained in detail, focusing on their strengths and weaknesses, and is followed with a step-by-step example of how to apply it. With plenty of examples, you will learn how to leverage DI to transform code into something simple and flexible. You will also discover how to generate and leverage the dependency graph to spot and eliminate issues. Throughout the book, you will learn to leverage DI in combination with test stubs and mocks to test otherwise tricky or impossible scenarios. Hands-On Dependency Injection in Go takes a pragmatic approach and focuses heavily on the code, user experience, and how to achieve long-term benefits through incremental changes. By the end of this book, you will have produced clean code that’s easy to test.

Who is this book for?

Hands-On Dependency Injection in Go is for programmers with a few year s experience in any language and a basic understanding of Go. If you wish to produce clean, loosely coupled code that is inherently easier to test, this book is for you.

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Nov 27, 2018
Length: 346 pages
Edition : 1st
Language : English
ISBN-13 : 9781789131758
Category :
Languages :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Product Details

Publication date : Nov 27, 2018
Length: 346 pages
Edition : 1st
Language : English
ISBN-13 : 9781789131758
Category :
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total $ 147.97
Hands-On Dependency Injection in Go
$43.99
Hands-On Software Architecture with Golang
$54.99
Go Machine Learning Projects
$48.99
Total $ 147.97 Stars icon

Table of Contents

14 Chapters
Never Stop Aiming for Better Chevron down icon Chevron up icon
SOLID Design Principles for Go Chevron down icon Chevron up icon
Coding for User Experience Chevron down icon Chevron up icon
Introduction to the ACME Registration Service Chevron down icon Chevron up icon
Dependency Injection with Monkey Patching Chevron down icon Chevron up icon
Dependency Injection with Constructor Injection Chevron down icon Chevron up icon
Dependency Injection with Method Injection Chevron down icon Chevron up icon
Dependency Injection by Config Chevron down icon Chevron up icon
Just-in-Time Dependency Injection Chevron down icon Chevron up icon
Off-the-Shelf Injection Chevron down icon Chevron up icon
Curb Your Enthusiasm Chevron down icon Chevron up icon
Reviewing Our Progress Chevron down icon Chevron up icon
Assessment Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.4
(10 Ratings)
5 star 60%
4 star 30%
3 star 0%
2 star 10%
1 star 0%
Filter icon Filter
Top Reviews

Filter reviews by




Mike C Mar 04, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This is the kind of book I wish I'd had when I was starting out with Go. It is one thing to be familiar with best practices in software development in general, but quite another to be able to apply it with a new programming language. As the author explains, naively bringing patterns from other languages isn't going to work with Go - it will lead to frustration and subpar results.Although the book's main focus is Dependency Injection (DI), many other design principles are covered along the way, and reading this made me realise how fundamental DI is to writing good Go code. This is a very practical book, and it contains a wealth of code and examples that can actually be used day-to-day.One of the things I appreciated most about this book is how the author balances both the advantages and disadvantages of the techniques described. No solution is perfect, and it is important to know the potential problems and pitfalls you might face before implementing a new idea.The benefits claimed by reading this book should appeal to every Go professional: more readable, maintainable and testable code. Those reading your code in future - your colleagues, or your future self - will thank you!
Amazon Verified review Amazon
Amazonas Jan 17, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I have never used Go professionally but that din't stop me from enjoying this book. It goes over so many topics that everyone dealing with software need to know. I especially enjoyed how it tied DI to the SOLID principle and encouraged everyone to think about when DI might not be the right choice. A very balanced account on how DI can make your life easier with lots of examples that are easy to grasp but with enough details that can be used in everyday problems.
Amazon Verified review Amazon
Kindle Customer Jan 23, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Writing code in Golang is easy, but it takes some extra effort to write maintainable and sustainable code. Well, nothing lasts forever and it's more true for code, but surely you don't want to curse yourself for what you did just 4 or 6 months back. Dependency Injection is something which is essential if you want to make your code (unit)testable which will result into maintainable code. For example, if your codebase has 80% unit test coverage, you can make changes in a more confident manner by making less mistakes.Corey has been using Golang for over four years, and I know he is someone who is not afraid of trying new things and making mistakes. As he has real-life experience of working on a system developed using golang from scratch and which grew large (100x maybe) and he faced every possible pain in the journey. But it never stopped him to his endeavor to be a better software craftsman. I am happy to see he could put lot of his hard learned lessons into the book for the greater good of golang developers.This book is very important for working professionals. If you are already working on something where you are using Golang, you could reflect about your work while reading this book. I hope you can take something valuable out of it and apply in your system, maybe some good amount of refactoring - which will result in a more robust and maintainable system, and happiness.As software engineering is both art and science, it's very difficult to prescribe a single solution for a problem. Corey did a good job explaining DI from different angles - but still if you are a practicing golang developer, you may have different opinions about some of the topics/examples. So, I would request the reader not to get discouraged or confused by this, having an open mind helps.I wish all the best for this book and knowing the author, I hope he will continue iterating this book as he continues his journey as a software craftsman.[Disclaimer - I am a colleague of the author and due to my impression, I might be positively biased as a reader].
Amazon Verified review Amazon
regularbuyer Apr 11, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I have been writing golang for a year, but haven't put much serious thoughts in how to write my code better. By reading the first few chapters of this book, I have already gained some useful insights. The book showed useful examples of how to improve the structure of code with dependency injection. I highly recommend this book for anyone interested in improving his craft.
Amazon Verified review Amazon
Mark Angelo Feb 25, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This book is very helpful and has covered many ways on how to work your way and develop good code in Golang. As a developer coming from Java world, it is quite common to make mistakes in writing it the Java way and having conventions that I am already used to. This books has helped me basically get more comfortable writing code the Golang way. Dependency injection definitely needs to be part of the development and knowing how to do it the right way in Golang is really important. I recommend this book for everyone who would like to explore the possibilities of dependency injection in Golang.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.