Transports — Which transports are supported?

Go kit ships with support for HTTP, gRPC, Thrift, and net/rpc. It’s straightforward to add support for new transports; just file an issue if you need something that isn’t already offered.

Service Discovery — Which service discovery systems are supported?

Go kit ships with support for Consul, etcd, ZooKeeper, and DNS SRV records.

Service Discovery — Do I even need to use package sd?

It depends on your infrastructure.

Some platforms, like Kubernetes, take care of registering services instances and making them discoverable automatically, via platform-specific concepts. So, if you run on Kubernetes, you probably don’t need to use package sd.

But if you’re putting together your own infrastructure or platform with open-source components, then your services will likely need to register themselves with the service registry. Or if you have reached a scale where internal load balancers become a bottleneck, you may need to have your services subscribe to the system of record directly, and maintain their own connection pools. (This is the client-side discovery pattern.) In these situations, package sd will be useful.

Observability — Which monitoring systems are supported?

Go kit ships with support for modern monitoring systems like Prometheus and InfluxDB, as well as more traditional systems like statsd, Graphite, and expvar, and hosted systems like Datadog via DogStatsD and Circonus.

Observability — Which monitoring system should I use?

Prometheus.

Logging — Why is package log so different?

Experience has taught us that a good logging package should be based on a minimal interface and should enforce so-called structured logging. Based on these invariants, Go kit’s package log has evolved through many design iterations, extensive benchmarking, and plenty of real-world use to arrive at its current state.

With a well-defined core contract, ancillary concerns like levels, colorized output, and synchronization can be easily bolted on using the familiar decorator pattern. It may feel a little unfamiliar at first, but we believe package log strikes the ideal balance between usability, maintainability, and performance.

For more details on the evolution of package log, see issues and PRs 63, 76, 131, 157, and 252. For more on logging philosophy, see The Hunt for a Logger Interface, Let’s talk about logging, and Logging v. instrumentation.

Logging — How should I aggregate my logs?

Collecting, shipping, and aggregating logs is the responsibility of the platform, not individual services. So, just make sure you’re writing logs to stdout/stderr, and let another component handle the rest.

Panics — How should my service handle panics?

Panics indicate programmer error and signal corrputed program state. They shouldn’t be treated as errors, or ersatz exceptions. In general, you shouldn’t explicitly recover from panics: you should allow them to crash your program or handler goroutine, and allow your service to return a broken response to the calling client. Your observability stack should alert you to these problems as they occur, and you should fix them as soon as possible.

With that said, if you have the need to handle exceptions, the best strategy is probably to wrap the concrete transport with a transport-specific middleware that performs a recover. For example, with HTTP:

var h http.Handler
h = httptransport.NewServer(...)
h = newRecoveringMiddleware(h, ...)
// use h normally

Persistence — How should I work with databases and data stores?

Accessing databases is typically part of the core business logic. Therefore, it probably makes sense to include an e.g. *sql.DB pointer in the concrete implementation of your service.

type MyService struct {
    db     *sql.DB
    value  string
    logger log.Logger
}

func NewService(db *sql.DB, value string, logger log.Logger) *MyService {
    return &MyService{
        db:     db,
        value:  value,
        logger: logger,
    }
}

Even better: consider defining an interface to model your persistence operations. The interface will deal in business domain objects, and have an implementation that wraps the database handle. For example, consider a simple persistence layer for user profiles.

type Store interface {
    Insert(Profile) error
    Select(id string) (Profile, error)
    Delete(id string) error
}

type databaseStore struct{ db *sql.DB }

func (s *databaseStore) Insert(p Profile) error            { /* ... */ }
func (s *databaseStore) Select(id string) (Profile, error) { /* ... */ }
func (s *databaseStore) Delete(id string) error            { /* ... */ }

In this case, you’d include a Store, rather than a *sql.DB, in your concrete implementation.

最后编辑: Simon  文档更新时间: 2021-02-04 15:44   作者:Simon