Where should interfaces live?

3,380 views
Skip to first unread message

Vorn Mom

unread,
Nov 21, 2016, 10:30:21 AM11/21/16
to golang-nuts
Sorry if it was asked before, but where should interfaces live?
  • In the package that contains an implementation of it.
  • In its own package.
  • or in the package that needs it.
The standard library is not consistent.  For example:
  • io.Writer is defined in a package that also has an implementation, io.PipeWriter.  
  • Encoding is all interfaces, defering implementation to sub-packages.
  • database/sql returns a DB struct on Open() instead of an interface.
Because Go interfaces are decoupled from implementation, I think it should be defined where it's needed.  So I like having my own interface for DB that may only define a subset of what behaviors it provides.

However, doesn't defining an interface that depends on other packages decrease cohesion and leaks the abstraction?  For example,

type interface DbQuery {
  QueryRow(query string, args ...interface{}) *sql.Row
}

This interface depends on sql.Row.  This decreases cohesion because the interface is now defined across packages?

-Vorn


Dave Cheney

unread,
Nov 21, 2016, 2:47:31 PM11/21/16
to golang-nuts
My vote is for "in the package that needs them"

Axel Wagner

unread,
Nov 21, 2016, 3:10:06 PM11/21/16
to golang-nuts
My vote would be "it depends" :) You mentioned several different instances of tradeoffs:

*  I find io.Writer a bit special, because of it's pervasiveness; let's talk about image.Image, which is probably closer to what you're asking. It makes sense for the image package to bundle the Image interface with various implementations of it, the implementations likely share a bunch of code and you want the interface to be clear and centrally available, so that other packages can put it in their signatures and things work. Note, though, that even though the types also implement the draw.Image interface, that's in a different package.
* Encoding makes sense in it's own package; the encoders won't share a lot of code so it makes sense to put them into separate packages (also encouraging smaller binaries; don't need to link in encoders that won't be used). At the same time, you want the interfaces to, again, be centrally defined, so that other packages can use them in their own type definitions (which, AFAIR, is how that package came to be in the first place). Thus, it makes sense to put the interfaces in a separate package at the place where it is in the hierarchy.
* database/sql doesn't have a real need for defining the interface. While it's definitely sensible to have an interface abstracting over a database for testing purposes or some abstractions in limited cases, in general, people will just use the concrete struct implementation and that's fine. Thus, in that case it makes sense to not have an interface at all and leave that up to packages wanting to use it. (full disclosure though: You could apply similar reasoning to log.Logger, which still makes me a bit sad for not being an interface…).

I think it makes sense, to make this tradeoff for the specific cases. If you expect a lot of reimplementations of that interface and people wanting to operate on the interface in general, it makes sense to provide it only once. If your provided implementations don't share much code and are worthy of own-packaged-nes, do the encoding thing.

There is this technical argument favoring writing the interface yourself though (where it makes sense), which is that two interfaces from different packages are different types, even if they share all methods, which will matter, if they in turn are to be used in interfaces or other type definitions (And before some zelot points me to it again: Yes, I know about #8082, yes, I agree it would be a good idea, but I'm also not optimistic that it'll happen, as it stands).

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Egon

unread,
Nov 22, 2016, 4:14:36 AM11/22/16
to golang-nuts
On Monday, 21 November 2016 17:30:21 UTC+2, Vorn Mom wrote:
Sorry if it was asked before, but where should interfaces live?
  • In the package that contains an implementation of it.
  • In its own package.
  • or in the package that needs it.
The quick answer here is "yes to all", but most commonly it should be in "the package that needs it".

The interface can be seen as an evolution of indirection:

#1 - no interface

type memorydb struct { people []*Person }
func (db *memorydb) PersonByName(name string) (*Person, error) { ... }

type Server struct {
    db *memorydb
}

#2 - private interface, introduced to allow multiple db or to separate code more

type memorydb struct { ... }
...
type sqllitedb struct { ... }
...

type peopledb interface { PersonByName(name string) (*Person, error) }

type Server struct {
    db peopledb
}

#3 - public interface - introduced to allow sub-packages access/see the same interface, this usually happens together with moving the memorydb/sqllitedb into a separate package. It can also be used to break dependency cycles.

...
type PeopleDB interface { PersonByName(name string) (*Person, error) }

type Server struct {
    db PeopleDB
}

#4 - common interface - introduced to allow wide compatibility between things; you only end-up here when you are implementing a widely used package, e.g. "database/sql/driver.Driver", "io.Reader", "io.Writer", "context.Context".

Try to stop at the "smallest number" that solves your problem. Each step adds extra indirection and more artifacts, so making code harder to understand. Of course, you should keep in mind not to make each file/struct/package/interface too large, which also makes code harder to read. It's a balancing act . Most web applications will end up at #3... frameworks/libraries end up at #3 or #4. Anything else can vary from #1 - #4.

+ Egon

Vorn Mom

unread,
Nov 23, 2016, 9:02:01 AM11/23/16
to golang-nuts
Great responses. Thanks!  I think what it comes down to is there's two main definition/use cases of an interface:
  • The inheritance-based one that you find in Java and C# where the interface provides an contract into various implementations.   The design is typically API-driven and you would approach the problem by defining the interface first.  It's usually defined in its own package.
  • The other is when you're writing code that depends on an external dependency and you want to hide it behind an interface.  This was not practical in Java / C# because of the inheritance-based interface, and showcases the power of Go interfaces.  It's usually defined in the package that needs it.
Both use cases are valid depending on what you're building and both are possible in Go.
Reply all
Reply to author
Forward
0 new messages