diff --git a/clientutil/backend.go b/clientutil/backend.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd06a19a0371eebe0f8f89a2806d798938b976aa
--- /dev/null
+++ b/clientutil/backend.go
@@ -0,0 +1,115 @@
+package clientutil
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+)
+
+// BackendConfig specifies the configuration to access a service.
+//
+// Services with multiple backends can be replicated or partitioned,
+// depending on a configuration switch, making it a deployment-time
+// decision. Clients are expected to compute their own sharding
+// function (either by database lookup or other methods), and expose a
+// 'shard' parameter on their APIs.
+type BackendConfig struct {
+	URL       string           `yaml:"url"`
+	Sharded   bool             `yaml:"sharded"`
+	TLSConfig *TLSClientConfig `yaml:"tls_config"`
+}
+
+// Backend is a runtime class that provides http Clients for use with
+// a specific service backend. If the service can't be partitioned,
+// pass an empty string to the Client method.
+type Backend interface {
+	// URL for the service for a specific shard.
+	URL(string) string
+
+	// Client that can be used to make a request to the service.
+	Client(string) *http.Client
+}
+
+// NewBackend returns a new Backend with the given config.
+func NewBackend(config *BackendConfig) (Backend, error) {
+	u, err := url.Parse(config.URL)
+	if err != nil {
+		return nil, err
+	}
+
+	var tlsConfig *tls.Config
+	if config.TLSConfig != nil {
+		tlsConfig, err = config.TLSConfig.TLSConfig()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if config.Sharded {
+		return &replicatedClient{newHTTPClient(u, tlsConfig)}, nil
+	}
+	return &shardedClient{
+		baseURL:   u,
+		tlsConfig: tlsConfig,
+		shards:    make(map[string]*http.Client),
+	}, nil
+}
+
+type replicatedClient struct {
+	c *http.Client
+	u *url.URL
+}
+
+func (r *replicatedClient) Client(_ string) *http.Client { return r.c }
+func (r *replicatedClient) URL(_ string) string          { return r.u.String() }
+
+type shardedClient struct {
+	baseURL   *url.URL
+	tlsConfig *tls.Config
+	mx        sync.Mutex
+	urls      map[string]*url.URL
+	shards    map[string]*http.Client
+}
+
+func (s *shardedClient) getShardURL(shard string) *url.URL {
+	if shard == "" {
+		return s.baseURL
+	}
+	u, ok := s.urls[shard]
+	if !ok {
+		var tmp = *s.baseURL
+		tmp.Host = fmt.Sprintf("%s.%s", shard, tmp.Host)
+		u = &tmp
+		s.urls[shard] = u
+	}
+	return u
+}
+
+func (s *shardedClient) URL(shard string) string {
+	s.mx.Lock()
+	defer s.mx.Unlock()
+	return s.getShardURL(shard).String()
+}
+
+func (s *shardedClient) Client(shard string) *http.Client {
+	s.mx.Lock()
+	defer s.mx.Unlock()
+
+	client, ok := s.shards[shard]
+	if !ok {
+		u := s.getShardURL(shard)
+		client = newHTTPClient(u, s.tlsConfig)
+		s.shards[shard] = client
+	}
+	return client
+}
+
+func newHTTPClient(u *url.URL, tlsConfig *tls.Config) *http.Client {
+	return &http.Client{
+		Transport: NewTransport([]string{u.Host}, tlsConfig, nil),
+		Timeout:   30 * time.Second,
+	}
+}