---
title: B. Clients
---

Unless your service has zero dependencies, it will at some point need to call other services as part of its
request-response flow or trigger-action flow. These will be represented in your code as Clients.

Typically our services will have a `Clients` struct that contains all dependencies, which your server
has a reference to. We attempt to treat **all** clients the same way – this includes database clients,
AWS service clients, and internal Twitch service dependencies.

### Fat Clients

We prefer "fat" clients where possible.
If there is client-specific logic, it should live in the client package itself.
This allows us to implement a simpler interface for callers without exposing any internals of the
underlying dependency.

### Suppressed Errors

If you have client methods that always have a suppressed/logged error,
you should make the method have no return at all and embed the logging within the client.
This reduces the repetitiveness of constant `if err != nil { log.Println(err) }` lines across your service.

```go
// PubsubBroker exposes methods to publish messages to Pubsub-Broker.
type PubsubBroker interface {
    // Publish a message. Swallows and logs errors.
    PublishMessage(context.Context, pubsubapi.Message)
}
```

### Package layout

Package layout should look something like
```text
internal/
    clients/
        clients.go => this defines the Clients struct
        db.go => this defines the DB interface
        users.go => this defines the Users interface
        db/
            db.go => this defines the db.Client struct
        users/
            users.go => this defines the users.Client struct
```

### Standardize Method Signatures

We prefer to keep method signatures standardized across services and across methods within a service.
We follow a pattern very similar to the interface implemented by Twirp servers:

```go
type ThingClient interface {
    DoThing(context.Context, thing.DoThingArgs) (thing.DoThingResult, error)
}
```

This has several notable benefits:
1. Adding or removing arguments or return values does not change the method signature, just adds new fields to the arguments struct.
1. We have the ability to return complex data without returning more than 2 values (an antipattern that should be avoided where possible).
1. There is no ambiguity between different arguments or return values of the same type, because they are all named.
(This solves the "is it GetFriendship(sourceID, targetID string), or GetFriendship(targetID, sourceID string)?" problem.)

One possible downside to be aware of is that adding new *required* fields will no longer break your existing
implementations at compile time. You should use caution when adding fields to check in with all consumers the
same way you would if you changed a method signature.

### Input Validation

Similar to how we validate input at the start of every Twirp handler, we should validate all input to our
client methods. We like to use the [Validator](https://pkg.go.dev/gopkg.in/go-playground/validator.v10) package.
This allows you to specify validation arguments inline in your input struct definition:
```go
type ListThingsArgs struct {
    ThingHolderID string `validate:"required"`
    Limit int `validate:"min=0,max=50"`
}
```
Tip: You can create a helper package like so:
```go
package validate

import (
	validator "github.com/go-playground/validator/v10"
)

var validate *validator.Validate

// Docs:
// * https://github.com/go-playground/validator
// * https://godoc.org/github.com/go-playground/validator

func init() {
	validate = validator.New()
	validate.SetTagName("validate")
}

// Validate is a helper function to run validate.Struct() on an interface using a pre-created validator instance.
func Validate(current interface{}) error {
	return validate.Struct(current)
}
```
This allows you to validate an arbitrary struct in a single line:
```go
import "validate"

err := validate.Struct(x)
```

### Return Structs, Accept Interfaces

We try to follow the guideline of "Return Structs, Accept Interfaces".
What this means in practice is that our client packages are entirely unaware of the interface used to
represent them in `clients.go`. For example, we might have a setup like so:

```go
// internal/clients/db/client.go
package db

type Client struct {
    DB dynamodbiface.DynamoDBAPI
}

func (c *Client) GetItem(ctx context.Context, itemID string) (*Item, error) {
    // ...
}
```
```go
// internal/clients/clients.go
package clients

type Database interface {
    GetItem(context.Context, string) (*db.Item, error)
}

type Clients struct {
    DB Database
}
```
```go
// cmd/main.go
package main

import (
    "internal/clients"
    "internal/clients/db"
)

func main() {
    // ...
    dbClient := &db.Client{
        DB: dynamoClient(),
    }
    clients := &clients.Clients{
        Database: dbClient,
    }

    server := &server.Server{
        Clients: clients,
    }

    // server.ServeHTTP()
}
```