Skip to content

Commit 406f63b

Browse files
committed
Tech stack: Go: add Interfaces section
1 parent 7b77880 commit 406f63b

File tree

1 file changed

+97
-0
lines changed
  • docs/2_development/2_tech-stack

1 file changed

+97
-0
lines changed

docs/2_development/2_tech-stack/go.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,103 @@ jobs:
325325
Make sure to pin the linter version (`version: v1.45`) since the same linters can behave differently from a version to another.
326326
:::
327327

328+
## Interfaces
329+
330+
Go interfaces are useful for two main reasons:
331+
332+
1. Abstract implementation details
333+
2. Test mocks generation (see the Mocking section)
334+
335+
### Defining an interface
336+
337+
There are three rules to define interfaces and enforce good code quality, as described in the subsections below:
338+
339+
#### Accept interfaces, return concrete types
340+
341+
This is by far the most important and fruitful rule.
342+
When returning something, always return a pointer to your struct.
343+
344+
For example:
345+
346+
```go
347+
package database
348+
349+
type Database struct {}
350+
351+
func New() *Database {
352+
return &Database{}
353+
}
354+
355+
func (d *Database) Get(key string) (value string) {
356+
// ...
357+
}
358+
359+
func (d *Database) Set(key string, value string) {
360+
// ...
361+
}
362+
```
363+
364+
For callers, they should define **their own interface in their own package** (and **NOT import them** from another package).
365+
Continuing our example:
366+
367+
```go
368+
package service
369+
370+
type Database interface {
371+
Get(key string) (value string)
372+
Set(key string, value string)
373+
}
374+
375+
type Service struct {
376+
database Database
377+
}
378+
379+
func New(database Database) *Service {
380+
return &Service{database: database}
381+
}
382+
```
383+
384+
An additional element to note is that you should inject the actual implementation down your call stack.
385+
Implementation initialisation should ideally occur at the top-most layer of your call stack, such as the `main.main` function.
386+
387+
#### Narrow interfaces and composition
388+
389+
All your interfaces should be as **narrow** as possible. This means two things:
390+
391+
1. Interfaces should only have methods that must be used in your package. For example if you need only a single method and the concrete implementation has 2 methods, your interface should only contain that single method. As a side effect, this also produces smaller generated mock test file.
392+
2. Interfaces should have one or two methods. If you need a larger interface, compose it using smaller interfaces. For example:
393+
394+
```go
395+
type Database interface {
396+
Getter
397+
Setter
398+
}
399+
400+
type Getter interface {
401+
Get(key string) (value string)
402+
}
403+
404+
type Setter interface {
405+
Set(key string, value string)
406+
}
407+
```
408+
409+
This is also useful such that you can use the narrow interfaces (such as `Getter`) in unexported package-local functions.
410+
411+
#### Exported interfaces with exported methods only
412+
413+
All your interfaces and their methods should be **EXPORTED**. This forces you to either:
414+
415+
* dependency inject the concrete implementation, which would force you to export the interface
416+
* test the actual implementation if it's defined in the same package (you shouldn't interface & mock it), which would force you to remove the interface definition.
417+
* split out the implementation in its own subpackage and then define your exported interface with exported methods.
418+
419+
A side note is that `mockgen` is rather terrible at generating mocks from interfaces with unexported methods, so that's something you can avoid by having exported methods.
420+
421+
### Additional notes on interfaces
422+
423+
* The exception to importing interfaces is the standard library. Feel free to use for example `io.Reader`.
424+
328425
## Panic
329426

330427
In Go, `panic` should only be used when a **programming error** has been encountered.

0 commit comments

Comments
 (0)