interface all the things

21
Interface All The Things 12 May 2015 Jonathan Gomez Engineer, Zumata

Upload: jonathan-gomez

Post on 10-Aug-2015

44 views

Category:

Engineering


1 download

TRANSCRIPT

Interface All The Things12 May 2015

Jonathan GomezEngineer, Zumata

Overview

What are interfaces?

Why they are awesome?

Tips / Gotchas

Illustrate via examples

Intro to Interfaces

type Gopher interface { Nod() Wink()}

Could be read as, a Gopher is something that has these behaviours / actions:

Ability to nod

Ability to wink

Intro to Interfaces

Lets implement Nod() and Wink():

type SingGopher struct{}

func (g *SingGopher) Nod() { fmt.Println("can nod")}

func (g *SingGopher) Wink() { fmt.Println("wink also can")}

By having these methods, SingGopher implicitly satisfies the Gopher interface.

i.e. don't need to explicitly state that SingGopher implements the interface.

Intro to Interfaces

With the interface satisfied, we can assign our SingGopher to the Gopher type.

func play(gopher Gopher) { gopher.Nod() gopher.Wink()}

func main() { play(&SingGopher{})} Run

Intro to Interfaces

What if a method was missing?

Think in terms of method sets: set of interface ⊆ set of concrete type

func main() { play(&SingGopher{})} Run

Why use interfaces?

Why use interfaces? Abstract and avoid repetition

e.g. Gopher processing code (business logic)

func pipeline(gophers ...Gopher) { for _, gopher := range gophers { play(gopher) }}

As new gophers are added, no change is required.

Note: Can define functions and methods that take interfaces, but can't definemethods on the interface (e.g. gopher.Play())

func main() { pipeline(&SingGopher{}, &YodaGopher{})} Run

Why use interfaces? Combine simple units to achieve complexity

Example of standard library's io.Reader interface:

type Reader interface { Read(p []byte) (n int, err error)}

Functions/methods that take io.Reader arguments are indifferent to where datais coming from. e.g. os.File, bytes.Buffer, net.Conn, http.Request.Body

// jsonfunc NewDecoder(r io.Reader) *Decoder

// gzipfunc NewReader(r io.Reader) (*Reader, error)

Chain for complexity:

response, _ := client.Do(request)raw, _ := gzip.NewReader(response.Body)json.NewDecoder(raw).Decode(&data)

Why use interfaces? Sharing patterns

Dropbox has a interesting caching library @ github.com/dropbox/godropbox(https://godoc.org/github.com/dropbox/godropbox/caching)

Patterns of caching / managing storage

func NewCacheOnStorage(cache, storage Storage) Storagefunc NewRateLimitedStorage(storage Storage, maxConcurrency int) Storage

Developers gain access via implementing Storage on their own:

type Storage interface { Get(...) (interface{}, error) Set(...) error Delete(...) error ...}

Tap into / contribute to the growing set of community resources

Why use interfaces? Testing

If our functions have interface args, we can test them with mock implementations

i.e. use a FakeGopher to test our code that depends on Gopher

type FakeGopher struct { Nodded bool Winked bool}

func (g *FakeGopher) Nod() { g.Nodded = true}

func (g *FakeGopher) Wink() { g.Winked = true}

func TestPlay() { g := &FakeGopher{} g.ShowState() play(g) g.ShowState()}

Run

Why use interfaces? Flexibility to change your lib/infra decisions

In one of our apps, we use a 3rd party logging library, logrus.

Avoid our codebase being coupled to it, by having an interface for logging.

type Logger interface { Info(args ...interface{}) Warn(args ...interface{}) Error(args ...interface{}) Infof(args ...interface{}) Warnf(args ...interface{}) Errorf(args ...interface{})}

e.g. Publisher w/ Publish() (to queue) & Mailer & Mail() (3rd party service)

Seize opportunities to decrease coupling / increase options.

Better to do earlier than later.

Why use interfaces? Flexibility for package consumers

type Storage interface { Get(...) (Kites, error) Add(...) error Update(...) error Delete(...) error Upsert(...) error}

Interesting microservices project github.com/koding/kite (https://github.com/koding/kite)

Storage interface with Postgres and Etcd implementations

Useful here to give the consumer options

Maybe unnecessary in your own app

Why use interfaces? Very natural fit for some problems

When standardisation is essential to design

The method set creates the standard

Project to create and manage Docker hosts across providersgithub.com/docker/machine (https://github.com/docker/machine)

type Driver interface { Create() error GetIP() (string, error) Start() error Stop() error ...}

Implementations for Amazon, Google, Digital Ocean, Azure, OpenStack, Rackspace+

Tips: Check out the standard library

100+ interfaces

errorStringerHandlerResponseWritersort.InterfaceReader, Writer, ReadWriter,Marshaler, UmarshalerTB (tests, benchmarks)

Convention is to name the one method interface Method-er

Encouragement to keep interfaces small, but it isn't a hard rule

Smaller = less work to satisfy

Where your code can be compatible with standard library / community interfaces,will be more easily useable by others

e.g. middleware that takes a Handler and returns a Handler

Tips: Use embedding to keep interfaces small

Compose larger interfaces out of smaller ones

type Reader interface { Read(p []byte) (n int, err error)}

type Writer interface { Write(p []byte) (n int, err error)}

type ReadWriter interface { Reader Writer}

When method invoked, the receiver is the inner type, not outer

Will reduce code complexity, encourage simpler implementations

Tips: Type assert for accessing different functionality

Assume some gophers are also coders which can Code()

type Coder interface { Code()}

Runtime type assert to see whether the Gopher is a Coder & call Code()

func writeCode(gopher Gopher) {

// if our Gopher satisfies Coder, then type assert if coder, ok := gopher.(Coder); ok { coder.Code() }}

Note: the coder can't call Nod() or Wink().

Considered an idiom in Go to convert type to access different methods.

Tips: Interfaces and nil

What is an interface? Implemented as a type and a value

If type is set, and value is a nil pointer, the interface is not nil

When implementing custom errors - return nil instead of interfaces with nil values.

func main() {

var singGopher *SingGopher var gopher Gopher var gopher2 Gopher = singGopher

fmt.Println("values:") fmt.Println("singGopher: ", singGopher, singGopher == nil) fmt.Println("gopher: ", gopher, gopher == nil) fmt.Println("gopher2: ", gopher2, gopher2 == nil)

fmt.Println("types: ") fmt.Printf("singGopher: %#v\n", singGopher) fmt.Printf("gopher: %#v\n", gopher) fmt.Printf("gopher2: %#v\n", gopher2)

} Run

Within reason...

Thank you

Jonathan GomezEngineer, [email protected] (mailto:[email protected])

@jonog (http://twitter.com/jonog)