Recently I saw an article come up about using the Go Factory pattern. The article was encouraging people to use a pattern that may have a place in languages such as Java and C#, but have no place within Go.
Unfortunately, I have run into various Go code that uses a factory pattern. The code is normally brittle because of the misuse of the Go interface types.
I believe this pattern shows up because people tend to bring the patterns that they used in other languages to new languages. When I began writing Go code, it looked more like Python code, the language I had recently been using.
I don’t write in Java and I wrote my last C# code 20+ years ago. So I am not familiar with the nature of interfaces in those languages. But my understanding from hearsay is that there is tight coupling between those interfaces and the static types that implement them.
Go has loose coupling with interfaces. You do not need to declare what types implement what, they implement them simply by having the same method signatures as the interface. They can have methods that the interface does not, but they have to have the minimum set to implement the interface.
An Example of A Go Factory Pattern
Let’s look at the factory code example that I saw in the article I referenced above. I am not going to link the original article, as this article isn’t about shaming anyone. It is about helping to encourage better Go programming that is easier to understand, use, and extend.
type Database interface {
Query(query string) string
}
type MySQL struct {}
func (m MySQL) Query(query string) string {
// implementation for MySQL
return "MySQL result"
}
type PostgreSQL struct {}
func (p PostgreSQL) Query(query string) string {
// implementation for PostgreSQL
return "PostgreSQL result"
}
type DatabaseFactory func() Database
func NewMySQL() Database {
return MySQL{}
}
func NewPostgreSQL() Database {
return PostgreSQL{}
}
type Service struct {
dbFactory DatabaseFactory
}
func (s *Service) GetData(query string) string {
db := s.dbFactory()
return db.Query(query)
}
func main() {
service := Service{dbFactory: NewMySQL}
fmt.Println(service.GetData("SELECT * FROM table")) // Outputs: MySQL result
service.dbFactory = NewPostgreSQL
fmt.Println(service.GetData("SELECT * FROM table")) // Outputs: PostgreSQL result
}
You can run the code here.
This code provides two constructors: NewMySql() and NewPostgreSQL() that return an interface type called Database.
There is another type, DatabaseFactory, that is a function that returns a Database.
The Service type has a DatabaseFactory that creates a database that is used by the method GetData().
To start with, the DatabaseFactory is a pretty useless here.
Simply ditching the DatabaseFactory for Database will yield you the same results:
…
type Service struct {
db Database // <- Changed From DatabaseFactory to Database type
}
…
func main() {
service := Service{db: MySQL{}}
fmt.Println(service.GetData("SELECT * FROM table")) // Outputs: MySQL result
service.db = Postgres{}
fmt.Println(service.GetData("SELECT * FROM table")) // Outputs: PostgreSQL result
}
You can see the runnable example here.
Even with that change, the code is brittle
With the removal of DatabaseFactory, we still have brittle code. Let’s add a method to the Database interface that is not needed by Service and see what happens:
type Database interface {
Query(query string) string
Create(query string) error // <- New method
}
Run it here.
./prog.go:25:9: cannot use MySQL{} (value of type MySQL) as Database value in return statement: MySQL does not implement Database (missing method Create)
./prog.go:29:9: cannot use PostgreSQL{} (value of type PostgreSQL) as Database value in return statement: PostgreSQL does not implement Database (missing method Create)
./prog.go:41:25: cannot use MySQL{} (value of type MySQL) as Database value in struct literal: MySQL does not implement Database (missing method Create)
./prog.go:44:15: cannot use PostgreSQL{} (value of type PostgreSQL) as Database value in assignment: PostgreSQL does not implement Database (missing method Create)
Now, both PostgreSQL and MySQL no longer work. But Service never required Create and we have broken it for no reason.
In a real life scenario, we probably have these implementations or wrappers in other packages and not in the same package. This makes this pattern even more brittle because we don’t immediately see that we broke something.
If we wrote test versions of Database so that our tests are hermetic, those would also break.
This is because Go interfaces are for CONSUMERS and not CREATORS.
Let’s stop returning interfaces
Instead let’s return concrete types and consume interfaces.
type Database interface {
Query(query string) string
}
type MySQL struct{}
func (m MySQL) Query(query string) string {
// implementation for MySQL
return "MySQL result"
}
func (m MySQL) Create(query string) error { // <- added Create here, didn't break anything
return nil
}
type PostgreSQL struct{}
func (p PostgreSQL) Query(query string) string {
// implementation for PostgreSQL
return "PostgreSQL result"
}
func NewMySQL() MySQL { // <- Return the concrete type
return MySQL{}
}
func NewPostgreSQL() PostgreSQL { // <- Return the concrete type
return PostgreSQL{}
}
type Service struct {
db Database // We don't need Create, so we don't change the interface
}
func (s *Service) GetData(query string) string {
return s.db.Query(query)
}
func main() {
service := Service{db: NewMySQL()}
fmt.Println(service.GetData("SELECT * FROM table")) // Outputs: MySQL result
service.db = NewPostgreSQL()
fmt.Println(service.GetData("SELECT * FROM table")) // Outputs: PostgreSQL result
}
Now, when we need to add something like Create() to one of our database implementations for use in another package, we don’t need to expose it in Database interface. Database should be local to our package to define what our types need to consume.
In fact, if we changed Database to database (private type), outside packages could still implement database and this package could still consume them, but without as much danger that changes to database would affect other packages.
The long of this is, database should define what we will consume, not a return type. By doing so, we can add functionality to our implementors without affecting this package or other packages designed like it.
A few interface pointers
Interfaces are great in Go, but can also give headaches if not used thoughtfully.
Here are some general rules to follow:
- Consume interfaces, do not create them with constructors
- Keep your consumed interfaces defined locally to prevent breaking changes
- Do not expose public interfaces unless they will NEVER change
– The io library is an example of interfaces that do not change
– Rarely is this a good idea and rarely do they not change - If you need to expose interfaces that could change, allow only internal types to implement them
– This can be accomplished by including a private() method on the interface
– A more sophisticated method for this: https://medium.com/@johnsiilver/writing-an-interface-that-only-sub-packages-can-implement-fe36e7511449
Note: A factory-like structure that is safe to use
There is a type of Go struct that I’ve seen used and I use myself occasionally that has some characteristics of a factory pattern but without all the dangerous problems.
Normally, this is when I’m wrapping some type of autogenerated REST client to create a Go client for a service. As many of you have seen, Cloud clients can be quite large and ugly.
This use case allows for Go doc pages that are concise and easy to follow for users.
The pattern looks like the following pseudo code:
import (
"../subClientA"
"../subClientB"
)
type Client struct {
conn *restClient
...
}
func New(conn *restClient) (*Client, error) {
...
}
func (c *Client) SubClientA() *subClientA.Client {
return subClientA.New(c.conn)
}
func (c *Client) SubClientB() *subClientB.Client {
return subClientB.New(c.conn)
}
I have used this pattern when I want to divide a large client with hundreds of methods into digestible chunks. The various sub-clients are concrete types that simply bundle like methods together. This allows sharing of a single connection among the client and sub-clients and allows you to create the sub-clients individually if you want by going directly to the sub-packages.
This pattern lacks the brittle nature of the factory pattern, has a purpose that makes sense in the context of Go and is easily testable.
Note: Never say never
Last but not least, while Factories are *mostly* an antipattern in Go, that doesn’t mean there isn’t ever a use case.
It just means that it shouldn’t be the first tool to grab in your toolbox, but a specialized tool that your rarely need because Go has better options in most situations, where other languages are built on this pattern.