Skip to content

Customizing constructors

Noah Dietz edited this page Apr 7, 2020 · 2 revisions

Introduction

A client may need to be instantiated with API-specific configuration or have some API-specific behavior.

To support customizing client construction, client libraries support the implementation of an internal client constructor hook. This hook enables integration of API-specific configuration or behaviors directly into the client library constructor.

Example

Given the following protobuf service:

service LibraryService {
    ...
}

The LibraryClient has the following service-specific clientHook:

var newLibraryClientHook clientHook

A clientHook implementation that detects the presence of an emulator host name in the environment might be as follows:

package library

import (
        "context"
        "os"

        "google.golang.org/api/option"
        "google.golang.org/grpc"
)

func init() {
        newLibraryClientHook = func(ctx context.Context, p clientHookParams) ([]option.ClientOption, error) {
                if emulator := os.Getenv("LIBRARY_EMULATOR_HOST"); emulator != "" {
                        return []option.ClientOption{
                            option.WithEndpoint(emulator),
                            option.WithGRPCDialOption(grpc.WithInsecure())
                        }, nil
                }

                return nil, nil
        }
}

Interface

Each client library package has the same client hook type defined.

import (
    "context"
    "google.golang.org/api/option"
)

type clientHookParams struct{}
type clientHook func(context.Context, clientHookParams) ([]option.ClientOption, error)

A clientHook receives a context.Context and an instance of clientHookParams, and returns a option.ClientOption slice.

The clientHookParams type provides flexibility to the client hook interface. As new use cases are discovered, more data can be made available to clientHook implementations through the clientHookParams.

Note: both types are part of the generated client content and cannot be manually edited.

Each service client has a service-specific, package-level variable of type clientHook.

var newFooClientHook clientHook

Client owners can implement a clientHook and assign it to one or many service-specific clientHook variables as needed.

How it works

At runtime during client construction, if a client hook variable is assigned an implementation, it is invoked with the same Context that was provided to the constructor, and a clientHookParams struct. Client hook invocation occurs prior to connection dialing.

Options are applied in the following order:

  1. Default options.
  2. clientHook options.
  3. User-supplied options.

So, user-supplied options have precedence over clientHook options, which have precedence over default options.

Best practices

  • The Context given to the clientHook must be used as is. It must not be copied and mutated. This is to preserve user management of Context.

  • A clientHook must not explicitly use panic.

  • A clientHook must not emit log messages.

  • All clientHook implementation code must be unexported.

  • A clientHook must not add new dependencies.

  • Every clientHook implementation must have tests.

  • The clientHook implementation should be in a file named hook.go.