Repository: moov-io/customers
Pull request #182 (2020-09-23 12:01:13)

Templater reorg files
With this PR we are moving all files from cmd/server into separate directories under pkg/.
I'm trying to keep changes small.

This is the first step (or part of it) of migrating customers into new project structure:

1. new file structure: move/split files into pkg/model
1. rework repositories
1. rework models
1. rework services
1. rework controllers
2. rework Config
3. rework Router (a little bit)
4. rework DB Migrations: split into files, keep only one set of them (not separate versions for mysql and slite)
5. rework service (how it starts, etc.)

We need to merge this PR as soon as possible to avoid new changes into "old" structure...




Patch:
diff --git a/cmd/server/main.go b/cmd/server/main.go
index dce4a0e..ca14102 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -19,18 +19,19 @@ import (
 	"github.com/moov-io/base/admin"
 	moovhttp "github.com/moov-io/base/http"
 	"github.com/moov-io/base/http/bind"
-	"github.com/moov-io/customers"
-	"github.com/moov-io/customers/cmd/server/accounts"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-	"github.com/moov-io/customers/cmd/server/accounts/validator/microdeposits"
-	"github.com/moov-io/customers/cmd/server/accounts/validator/mx"
-	"github.com/moov-io/customers/cmd/server/accounts/validator/plaid"
-	"github.com/moov-io/customers/cmd/server/fed"
-	"github.com/moov-io/customers/cmd/server/paygate"
+	mainPkg "github.com/moov-io/customers"
 	"github.com/moov-io/customers/internal/database"
 	"github.com/moov-io/customers/internal/util"
+	"github.com/moov-io/customers/pkg/accounts"
 	"github.com/moov-io/customers/pkg/configuration"
+	"github.com/moov-io/customers/pkg/customers"
+	"github.com/moov-io/customers/pkg/fed"
+	"github.com/moov-io/customers/pkg/paygate"
 	"github.com/moov-io/customers/pkg/secrets"
+	"github.com/moov-io/customers/pkg/validator"
+	"github.com/moov-io/customers/pkg/validator/microdeposits"
+	"github.com/moov-io/customers/pkg/validator/mx"
+	"github.com/moov-io/customers/pkg/validator/plaid"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -57,7 +58,7 @@ func main() {
 	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
 	logger = log.With(logger, "caller", log.DefaultCaller)
 
-	logger.Log("startup", fmt.Sprintf("Starting moov-io/customers server version %s", customers.Version))
+	logger.Log("startup", fmt.Sprintf("Starting moov-io/customers server version %s", mainPkg.Version))
 
 	// Channel for errors
 	errs := make(chan error)
@@ -86,15 +87,15 @@ func main() {
 	}()
 
 	accountsRepo := accounts.NewRepo(logger, db)
-	customerRepo := &sqlCustomerRepository{db, logger}
-	customerSSNRepo := &sqlCustomerSSNRepository{db, logger}
-	disclaimerRepo := &sqlDisclaimerRepository{db, logger}
-	documentRepo := &sqlDocumentRepository{db, logger}
+	customerRepo := customers.NewCustomerRepo(logger, db)
+	customerSSNRepo := customers.NewCustomerSSNRepository(logger, db)
+	disclaimerRepo := customers.NewDisclaimerRepo(logger, db)
+	documentRepo := customers.NewDocumentRepo(logger, db)
 	validationsRepo := validator.NewRepo(db)
 
 	// Start Admin server (with Prometheus metrics)
 	adminServer := admin.NewServer(*adminAddr)
-	adminServer.AddVersionHandler(customers.Version) // Setup 'GET /version'
+	adminServer.AddVersionHandler(mainPkg.Version) // Setup 'GET /version'
 	go func() {
 		logger.Log("admin", fmt.Sprintf("listening on %s", adminServer.BindAddr()))
 		if err := adminServer.Listen(); err != nil {
@@ -119,19 +120,16 @@ func main() {
 	// Create our Watchman client
 	debugWatchmanCalls := util.Or(os.Getenv("WATCHMAN_DEBUG_CALLS"), "false")
 	watchmanEndpoint := util.Or(os.Getenv("WATCHMAN_ENDPOINT"), os.Getenv("OFAC_ENDPOINT"))
-	watchmanClient := newWatchmanClient(logger, watchmanEndpoint, util.Yes(debugWatchmanCalls))
+	watchmanClient := customers.NewWatchmanClient(logger, watchmanEndpoint, util.Yes(debugWatchmanCalls))
 	if watchmanClient == nil {
 		panic("No Watchman client created, see WATCHMAN_ENDPOINT")
 	}
 	adminServer.AddLivenessCheck("watchman", watchmanClient.Ping)
-	ofac := &ofacSearcher{
-		repo:           customerRepo,
-		watchmanClient: watchmanClient,
-	}
+	ofac := customers.NewOFACSearcher(customerRepo, watchmanClient)
 
 	// Register our admin routes
-	addApprovalRoutes(logger, adminServer, customerRepo, customerSSNRepo, ofac)
-	addDisclaimerAdminRoutes(logger, adminServer, disclaimerRepo, documentRepo)
+	customers.AddApprovalRoutes(logger, adminServer, customerRepo, customerSSNRepo, ofac)
+	customers.AddDisclaimerAdminRoutes(logger, adminServer, disclaimerRepo, documentRepo)
 
 	// Setup Customer SSN storage wrapper
 	ctx := context.Background()
@@ -140,10 +138,8 @@ func main() {
 		panic(err)
 	}
 	stringKeeper := secrets.NewStringKeeper(keeper, 10*time.Second)
-	customerSSNStorage := &ssnStorage{
-		keeper: stringKeeper,
-		repo:   customerSSNRepo,
-	}
+
+	customerSSNStorage := customers.NewSSNStorage(stringKeeper, customerSSNRepo)
 
 	// read transit keeper
 	transitKeeper, err := secrets.OpenLocal(os.Getenv("TRANSIT_LOCAL_BASE64_KEY"))
@@ -170,10 +166,10 @@ func main() {
 	moovhttp.AddCORSHandler(router)
 	addPingRoute(router)
 	accounts.RegisterRoutes(logger, router, accountsRepo, validationsRepo, fedClient, stringKeeper, transitStringKeeper, validationStrategies, &accountOfacSeacher)
-	addCustomerRoutes(logger, router, customerRepo, customerSSNStorage, ofac)
-	addDisclaimerRoutes(logger, router, disclaimerRepo)
-	addDocumentRoutes(logger, router, documentRepo, getBucket(bucketName, cloudProvider, signer))
-	addOFACRoutes(logger, router, customerRepo, ofac)
+	customers.AddCustomerRoutes(logger, router, customerRepo, customerSSNStorage, ofac)
+	customers.AddDisclaimerRoutes(logger, router, disclaimerRepo)
+	customers.AddDocumentRoutes(logger, router, documentRepo, customers.GetBucket(bucketName, cloudProvider, signer))
+	customers.AddOFACRoutes(logger, router, customerRepo, ofac)
 
 	// Add Configuration routes
 	configRepo := configuration.NewRepository(db)
@@ -182,7 +178,7 @@ func main() {
 	// Optionally serve /files/ as our fileblob routes
 	// Note: FILEBLOB_BASE_URL needs to match something that's routed to /files/...
 	if cloudProvider == "file" {
-		addFileblobRoutes(logger, router, signer, getBucket(bucketName, cloudProvider, signer))
+		customers.AddFileblobRoutes(logger, router, signer, customers.GetBucket(bucketName, cloudProvider, signer))
 	}
 
 	// Start business HTTP server
@@ -249,7 +245,7 @@ func setupStorageBucket(logger log.Logger, bucketName, cloudProvider string) *fi
 			secret = "secret"
 			logger.Log("main", "WARNING!!!! USING INSECURE DEFAULT FILE STORAGE, set FILEBLOB_HMAC_SECRET for ANY production usage")
 		}
-		signer, err := fileblobSigner(baseURL, secret)
+		signer, err := customers.FileblobSigner(baseURL, secret)
 		if err != nil {
 			panic(fmt.Sprintf("fileBucket: %v", err))
 		}
diff --git a/cmd/server/accounts/accounts_ofac.go b/pkg/accounts/accounts_ofac.go
similarity index 100%
rename from cmd/server/accounts/accounts_ofac.go
rename to pkg/accounts/accounts_ofac.go
diff --git a/cmd/server/accounts/accounts_ofac_test.go b/pkg/accounts/accounts_ofac_test.go
similarity index 100%
rename from cmd/server/accounts/accounts_ofac_test.go
rename to pkg/accounts/accounts_ofac_test.go
diff --git a/cmd/server/accounts/micro_deposits.go b/pkg/accounts/micro_deposits.go
similarity index 97%
rename from cmd/server/accounts/micro_deposits.go
rename to pkg/accounts/micro_deposits.go
index 81bcf22..7b88dcf 100644
--- a/cmd/server/accounts/micro_deposits.go
+++ b/pkg/accounts/micro_deposits.go
@@ -10,8 +10,8 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/moov-io/customers/cmd/server/paygate"
 	customerClient "github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/paygate"
 	"github.com/moov-io/paygate/pkg/client"
 )
 
diff --git a/cmd/server/accounts/mock_repository.go b/pkg/accounts/mock_repository.go
similarity index 100%
rename from cmd/server/accounts/mock_repository.go
rename to pkg/accounts/mock_repository.go
diff --git a/cmd/server/accounts/repository.go b/pkg/accounts/repository.go
similarity index 100%
rename from cmd/server/accounts/repository.go
rename to pkg/accounts/repository.go
diff --git a/cmd/server/accounts/repository_test.go b/pkg/accounts/repository_test.go
similarity index 100%
rename from cmd/server/accounts/repository_test.go
rename to pkg/accounts/repository_test.go
diff --git a/cmd/server/accounts/router.go b/pkg/accounts/router.go
similarity index 98%
rename from cmd/server/accounts/router.go
rename to pkg/accounts/router.go
index 67773d0..6858cb1 100644
--- a/cmd/server/accounts/router.go
+++ b/pkg/accounts/router.go
@@ -13,15 +13,13 @@ import (
 
 	"github.com/moov-io/ach"
 	moovhttp "github.com/moov-io/base/http"
-
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-
-	"github.com/moov-io/customers/cmd/server/fed"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/fed"
+	"github.com/moov-io/customers/pkg/route"
 	"github.com/moov-io/customers/pkg/secrets"
 	"github.com/moov-io/customers/pkg/secrets/hash"
 	"github.com/moov-io/customers/pkg/secrets/mask"
+	"github.com/moov-io/customers/pkg/validator"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
diff --git a/cmd/server/accounts/validate.go b/pkg/accounts/validate.go
similarity index 98%
rename from cmd/server/accounts/validate.go
rename to pkg/accounts/validate.go
index d8e4d15..6d3608f 100644
--- a/cmd/server/accounts/validate.go
+++ b/pkg/accounts/validate.go
@@ -12,10 +12,10 @@ import (
 
 	"github.com/gorilla/mux"
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 	"github.com/moov-io/customers/pkg/secrets"
+	"github.com/moov-io/customers/pkg/validator"
 
 	"github.com/go-kit/kit/log"
 )
diff --git a/pkg/configuration/router.go b/pkg/configuration/router.go
index 3eab402..6139b04 100644
--- a/pkg/configuration/router.go
+++ b/pkg/configuration/router.go
@@ -9,8 +9,8 @@ import (
 	"net/http"
 
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
diff --git a/cmd/server/approval.go b/pkg/customers/approval.go
similarity index 91%
rename from cmd/server/approval.go
rename to pkg/customers/approval.go
index 218bfef..037e240 100644
--- a/cmd/server/approval.go
+++ b/pkg/customers/approval.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"encoding/json"
@@ -15,8 +15,8 @@ import (
 
 	"github.com/moov-io/base/admin"
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -36,10 +36,10 @@ var (
 	errNoAddressId = errors.New("no Address ID found")
 )
 
-// addApprovalRoutes contains "back office" admin endpoints used to validate (or reject) a Customer
+// AddApprovalRoutes contains "back office" admin endpoints used to validate (or reject) a Customer
 // TODO(adam): We need to hide these behind an admin level auth, but we'll write them for now.
 // What about a header like x-admin-id ??
-func addApprovalRoutes(logger log.Logger, svc *admin.Server, repo customerRepository, customerSSNRepo customerSSNRepository, ofac *ofacSearcher) {
+func AddApprovalRoutes(logger log.Logger, svc *admin.Server, repo CustomerRepository, customerSSNRepo CustomerSSNRepository, ofac *OFACSearcher) {
 	svc.AddHandler("/customers/{customerID}/status", updateCustomerStatus(logger, repo, customerSSNRepo, ofac))
 	svc.AddHandler("/customers/{customerID}/addresses/{addressId}", updateCustomerAddress(logger, repo))
 }
@@ -58,7 +58,7 @@ func containsValidPrimaryAddress(addrs []client.CustomerAddress) bool {
 	return false
 }
 
-func updateCustomerStatus(logger log.Logger, repo customerRepository, customerSSNRepo customerSSNRepository, ofac *ofacSearcher) http.HandlerFunc {
+func updateCustomerStatus(logger log.Logger, repo CustomerRepository, customerSSNRepo CustomerSSNRepository, ofac *OFACSearcher) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
@@ -125,7 +125,7 @@ func (req *updateCustomerAddressRequest) validate() error {
 	}
 }
 
-func updateCustomerAddress(logger log.Logger, repo customerRepository) http.HandlerFunc {
+func updateCustomerAddress(logger log.Logger, repo CustomerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
diff --git a/cmd/server/approval_ofac.go b/pkg/customers/approval_ofac.go
similarity index 84%
rename from cmd/server/approval_ofac.go
rename to pkg/customers/approval_ofac.go
index dc6ce7c..738e3d9 100644
--- a/cmd/server/approval_ofac.go
+++ b/pkg/customers/approval_ofac.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -13,8 +13,8 @@ import (
 	"time"
 
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 	watchman "github.com/moov-io/watchman/client"
 
 	"github.com/go-kit/kit/log"
@@ -29,14 +29,21 @@ type ofacSearchResult struct {
 	CreatedAt time.Time `json:"createdAt"`
 }
 
-type ofacSearcher struct {
-	repo           customerRepository
+type OFACSearcher struct {
+	repo           CustomerRepository
 	watchmanClient WatchmanClient
 }
 
+func NewOFACSearcher(repo CustomerRepository, client WatchmanClient) *OFACSearcher {
+	return &OFACSearcher{
+		repo:           repo,
+		watchmanClient: client,
+	}
+}
+
 // storeCustomerOFACSearch performs OFAC searches against the Customer's name and nickname if populated.
 // The higher matching search result is stored in s.customerRepository for use later (in approvals)
-func (s *ofacSearcher) storeCustomerOFACSearch(cust *client.Customer, requestID string) error {
+func (s *OFACSearcher) storeCustomerOFACSearch(cust *client.Customer, requestID string) error {
 	ctx, cancelFn := context.WithTimeout(context.TODO(), 10*time.Second)
 	defer cancelFn()
 
@@ -46,13 +53,13 @@ func (s *ofacSearcher) storeCustomerOFACSearch(cust *client.Customer, requestID
 
 	sdn, err := s.watchmanClient.Search(ctx, formatCustomerName(cust), requestID)
 	if err != nil {
-		return fmt.Errorf("ofacSearcher.storeCustomerOFACSearch: name search for customer=%s: %v", cust.CustomerID, err)
+		return fmt.Errorf("OFACSearcher.storeCustomerOFACSearch: name search for customer=%s: %v", cust.CustomerID, err)
 	}
 	var nickSDN *watchman.OfacSdn
 	if cust.NickName != "" {
 		nickSDN, err = s.watchmanClient.Search(ctx, cust.NickName, requestID)
 		if err != nil {
-			return fmt.Errorf("ofacSearcher.storeCustomerOFACSearch: nickname search for customer=%s: %v", cust.CustomerID, err)
+			return fmt.Errorf("OFACSearcher.storeCustomerOFACSearch: nickname search for customer=%s: %v", cust.CustomerID, err)
 		}
 	}
 	// Save the higher matching SDN (from name search or nick name)
@@ -75,17 +82,17 @@ func (s *ofacSearcher) storeCustomerOFACSearch(cust *client.Customer, requestID
 		})
 	}
 	if err != nil {
-		return fmt.Errorf("ofacSearcher.storeCustomerOFACSearch: saveCustomerOFACSearch customer=%s: %v", cust.CustomerID, err)
+		return fmt.Errorf("OFACSearcher.storeCustomerOFACSearch: saveCustomerOFACSearch customer=%s: %v", cust.CustomerID, err)
 	}
 	return nil
 }
 
-func addOFACRoutes(logger log.Logger, r *mux.Router, repo customerRepository, ofac *ofacSearcher) {
+func AddOFACRoutes(logger log.Logger, r *mux.Router, repo CustomerRepository, ofac *OFACSearcher) {
 	r.Methods("GET").Path("/customers/{customerID}/ofac").HandlerFunc(getLatestCustomerOFACSearch(logger, repo))
 	r.Methods("PUT").Path("/customers/{customerID}/refresh/ofac").HandlerFunc(refreshOFACSearch(logger, repo, ofac))
 }
 
-func getLatestCustomerOFACSearch(logger log.Logger, repo customerRepository) http.HandlerFunc {
+func getLatestCustomerOFACSearch(logger log.Logger, repo CustomerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -106,7 +113,7 @@ func getLatestCustomerOFACSearch(logger log.Logger, repo customerRepository) htt
 	}
 }
 
-func refreshOFACSearch(logger log.Logger, repo customerRepository, ofac *ofacSearcher) http.HandlerFunc {
+func refreshOFACSearch(logger log.Logger, repo CustomerRepository, ofac *OFACSearcher) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
diff --git a/cmd/server/bucket.go b/pkg/customers/bucket.go
similarity index 89%
rename from cmd/server/bucket.go
rename to pkg/customers/bucket.go
index 04d6b2b..a4fa6e4 100644
--- a/cmd/server/bucket.go
+++ b/pkg/customers/bucket.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -24,7 +24,7 @@ import (
 
 type bucketFunc func() (*blob.Bucket, error)
 
-func getBucket(bucketName, cloudProvider string, fileblobSigner *fileblob.URLSignerHMAC) bucketFunc {
+func GetBucket(bucketName, cloudProvider string, FileblobSigner *fileblob.URLSignerHMAC) bucketFunc {
 	return func() (*blob.Bucket, error) {
 		ctx, cancelFn := context.WithTimeout(context.TODO(), 10*time.Second)
 		defer cancelFn()
@@ -32,18 +32,18 @@ func getBucket(bucketName, cloudProvider string, fileblobSigner *fileblob.URLSig
 		if bucketName == "" || cloudProvider == "" {
 			return nil, fmt.Errorf("storage: missing BUCKET_NAME=%s and/or CLOUD_PROVIDER=%s", bucketName, cloudProvider)
 		}
-		return openBucket(ctx, bucketName, cloudProvider, fileblobSigner)
+		return openBucket(ctx, bucketName, cloudProvider, FileblobSigner)
 	}
 }
 
 // openBucket returns a Go Cloud Development Kit (Go CDK) Bucket object which can be used to read and write arbitrary
 // blobs from a cloud provider blob store. Checkout https://gocloud.dev/ref/blob/ for more details
-func openBucket(ctx context.Context, bucketName, cloudProvider string, fileblobSigner *fileblob.URLSignerHMAC) (*blob.Bucket, error) {
+func openBucket(ctx context.Context, bucketName, cloudProvider string, FileblobSigner *fileblob.URLSignerHMAC) (*blob.Bucket, error) {
 	switch strings.ToLower(cloudProvider) {
 	case "aws":
 		return awsBucket(ctx, bucketName)
 	case "file":
-		return fileBucket(ctx, bucketName, fileblobSigner)
+		return fileBucket(ctx, bucketName, FileblobSigner)
 	case "gcp":
 		return gcpBucket(ctx, bucketName)
 	default:
@@ -60,7 +60,7 @@ func awsBucket(ctx context.Context, bucketName string) (*blob.Bucket, error) {
 	return s3blob.OpenBucket(ctx, s, bucketName, nil)
 }
 
-func fileblobSigner(baseURL, secret string) (*fileblob.URLSignerHMAC, error) {
+func FileblobSigner(baseURL, secret string) (*fileblob.URLSignerHMAC, error) {
 	if u, err := url.Parse(baseURL); err != nil {
 		return nil, fmt.Errorf("invalid base URL %s error=%s", baseURL, err)
 	} else {
diff --git a/cmd/server/customer_search.go b/pkg/customers/customer_search.go
similarity index 96%
rename from cmd/server/customer_search.go
rename to pkg/customers/customer_search.go
index 9b7ba58..7514406 100644
--- a/cmd/server/customer_search.go
+++ b/pkg/customers/customer_search.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"encoding/json"
@@ -12,11 +12,11 @@ import (
 
 	"github.com/go-kit/kit/log"
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 )
 
-func searchCustomers(logger log.Logger, repo customerRepository) http.HandlerFunc {
+func searchCustomers(logger log.Logger, repo CustomerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
diff --git a/cmd/server/customer_ssn.go b/pkg/customers/customer_ssn.go
similarity index 87%
rename from cmd/server/customer_ssn.go
rename to pkg/customers/customer_ssn.go
index 5e7bb49..4e193ef 100644
--- a/cmd/server/customer_ssn.go
+++ b/pkg/customers/customer_ssn.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"database/sql"
@@ -28,7 +28,14 @@ func (s *SSN) String() string {
 
 type ssnStorage struct {
 	keeper *secrets.StringKeeper
-	repo   customerSSNRepository
+	repo   CustomerSSNRepository
+}
+
+func NewSSNStorage(keeper *secrets.StringKeeper, repo CustomerSSNRepository) *ssnStorage {
+	return &ssnStorage{
+		keeper: keeper,
+		repo:   repo,
+	}
 }
 
 func (s *ssnStorage) encryptRaw(customerID, raw string) (*SSN, error) {
@@ -60,11 +67,18 @@ func maskSSN(s string) string {
 	}
 }
 
-type customerSSNRepository interface {
+type CustomerSSNRepository interface {
 	saveCustomerSSN(*SSN) error
 	getCustomerSSN(customerID string) (*SSN, error)
 }
 
+func NewCustomerSSNRepository(logger log.Logger, db *sql.DB) CustomerSSNRepository {
+	return &sqlCustomerSSNRepository{
+		db:     db,
+		logger: logger,
+	}
+}
+
 type sqlCustomerSSNRepository struct {
 	db     *sql.DB
 	logger log.Logger
diff --git a/cmd/server/customers.go b/pkg/customers/customers.go
similarity index 96%
rename from cmd/server/customers.go
rename to pkg/customers/customers.go
index 8200309..d098226 100644
--- a/cmd/server/customers.go
+++ b/pkg/customers/customers.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"database/sql"
@@ -16,17 +16,16 @@ import (
 
 	"github.com/moov-io/base"
 	moovhttp "github.com/moov-io/base/http"
-
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/internal/usstates"
 	"github.com/moov-io/customers/pkg/client"
 	"github.com/moov-io/customers/pkg/model"
+	"github.com/moov-io/customers/pkg/route"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
 )
 
-func addCustomerRoutes(logger log.Logger, r *mux.Router, repo customerRepository, customerSSNStorage *ssnStorage, ofac *ofacSearcher) {
+func AddCustomerRoutes(logger log.Logger, r *mux.Router, repo CustomerRepository, customerSSNStorage *ssnStorage, ofac *OFACSearcher) {
 	r.Methods("GET").Path("/customers").HandlerFunc(searchCustomers(logger, repo))
 	r.Methods("GET").Path("/customers/{customerID}").HandlerFunc(getCustomer(logger, repo))
 	r.Methods("DELETE").Path("/customers/{customerID}").HandlerFunc(deleteCustomer(logger, repo))
@@ -52,7 +51,7 @@ func formatCustomerName(c *client.Customer) string {
 	return strings.TrimSpace(out)
 }
 
-func getCustomer(logger log.Logger, repo customerRepository) http.HandlerFunc {
+func getCustomer(logger log.Logger, repo CustomerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
@@ -65,7 +64,7 @@ func getCustomer(logger log.Logger, repo customerRepository) http.HandlerFunc {
 	}
 }
 
-func deleteCustomer(logger log.Logger, repo customerRepository) func(http.ResponseWriter, *http.Request) {
+func deleteCustomer(logger log.Logger, repo CustomerRepository) func(http.ResponseWriter, *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
@@ -84,7 +83,7 @@ func deleteCustomer(logger log.Logger, repo customerRepository) func(http.Respon
 	}
 }
 
-func respondWithCustomer(logger log.Logger, w http.ResponseWriter, customerID string, requestID string, repo customerRepository) {
+func respondWithCustomer(logger log.Logger, w http.ResponseWriter, customerID string, requestID string, repo CustomerRepository) {
 	cust, err := repo.getCustomer(customerID)
 	if err != nil {
 		logger.Log("customers", fmt.Sprintf("getCustomer: lookup: %v", err), "requestID", requestID)
@@ -220,7 +219,7 @@ func (req customerRequest) asCustomer(storage *ssnStorage) (*client.Customer, *S
 	return customer, nil, nil
 }
 
-func createCustomer(logger log.Logger, repo customerRepository, customerSSNStorage *ssnStorage, ofac *ofacSearcher) http.HandlerFunc {
+func createCustomer(logger log.Logger, repo CustomerRepository, customerSSNStorage *ssnStorage, ofac *OFACSearcher) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -288,7 +287,7 @@ type replaceMetadataRequest struct {
 	Metadata map[string]string `json:"metadata"`
 }
 
-func replaceCustomerMetadata(logger log.Logger, repo customerRepository) http.HandlerFunc {
+func replaceCustomerMetadata(logger log.Logger, repo CustomerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var req replaceMetadataRequest
 		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -312,7 +311,7 @@ func replaceCustomerMetadata(logger log.Logger, repo customerRepository) http.Ha
 	}
 }
 
-func addCustomerAddress(logger log.Logger, repo customerRepository) http.HandlerFunc {
+func addCustomerAddress(logger log.Logger, repo CustomerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		customerID, requestID := route.GetCustomerID(w, r), moovhttp.GetRequestID(r)
 		if customerID == "" {
@@ -336,7 +335,7 @@ func addCustomerAddress(logger log.Logger, repo customerRepository) http.Handler
 	}
 }
 
-type customerRepository interface {
+type CustomerRepository interface {
 	getCustomer(customerID string) (*client.Customer, error)
 	createCustomer(c *client.Customer, namespace string) error
 	updateCustomerStatus(customerID string, status client.CustomerStatus, comment string) error
@@ -354,6 +353,13 @@ type customerRepository interface {
 	saveCustomerOFACSearch(customerID string, result ofacSearchResult) error
 }
 
+func NewCustomerRepo(logger log.Logger, db *sql.DB) CustomerRepository {
+	return &sqlCustomerRepository{
+		db:     db,
+		logger: logger,
+	}
+}
+
 type sqlCustomerRepository struct {
 	db     *sql.DB
 	logger log.Logger
diff --git a/cmd/server/disclaimers.go b/pkg/customers/disclaimers.go
similarity index 88%
rename from cmd/server/disclaimers.go
rename to pkg/customers/disclaimers.go
index aa12bfa..6698367 100644
--- a/cmd/server/disclaimers.go
+++ b/pkg/customers/disclaimers.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"database/sql"
@@ -15,8 +15,8 @@ import (
 	"github.com/moov-io/base"
 	"github.com/moov-io/base/admin"
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -26,13 +26,13 @@ var (
 	errNoDisclaimerID = errors.New("no Disclaimer ID found")
 )
 
-func addDisclaimerRoutes(logger log.Logger, r *mux.Router, repo disclaimerRepository) {
+func AddDisclaimerRoutes(logger log.Logger, r *mux.Router, repo DisclaimerRepository) {
 	r.Methods("GET").Path("/customers/{customerID}/disclaimers").HandlerFunc(getCustomerDisclaimers(logger, repo))
 	r.Methods("POST").Path("/customers/{customerID}/disclaimers/{disclaimerID}").HandlerFunc(acceptDisclaimer(logger, repo))
 }
 
-func addDisclaimerAdminRoutes(logger log.Logger, svc *admin.Server, disclaimRepo disclaimerRepository, docRepo documentRepository) {
-	svc.AddHandler("/customers/{customerID}/disclaimers", createDisclaimer(logger, disclaimRepo, docRepo))
+func AddDisclaimerAdminRoutes(logger log.Logger, svc *admin.Server, disclaimerRepo DisclaimerRepository, docRepo DocumentRepository) {
+	svc.AddHandler("/customers/{customerID}/disclaimers", createDisclaimer(logger, disclaimerRepo, docRepo))
 }
 
 func getDisclaimerID(w http.ResponseWriter, r *http.Request) string {
@@ -44,7 +44,7 @@ func getDisclaimerID(w http.ResponseWriter, r *http.Request) string {
 	return v
 }
 
-func getCustomerDisclaimers(logger log.Logger, repo disclaimerRepository) http.HandlerFunc {
+func getCustomerDisclaimers(logger log.Logger, repo DisclaimerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -65,7 +65,7 @@ func getCustomerDisclaimers(logger log.Logger, repo disclaimerRepository) http.H
 	}
 }
 
-func acceptDisclaimer(logger log.Logger, repo disclaimerRepository) http.HandlerFunc {
+func acceptDisclaimer(logger log.Logger, repo DisclaimerRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -96,7 +96,7 @@ type createDisclaimerRequest struct {
 	Text       string `json:"text"`
 }
 
-func createDisclaimer(logger log.Logger, disclaimRepo disclaimerRepository, docRepo documentRepository) http.HandlerFunc {
+func createDisclaimer(logger log.Logger, disclaimerRepo DisclaimerRepository, docRepo DocumentRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -127,7 +127,7 @@ func createDisclaimer(logger log.Logger, disclaimRepo disclaimerRepository, docR
 			return
 		}
 
-		disclaimer, err := disclaimRepo.insertDisclaimer(req.Text, req.DocumentID)
+		disclaimer, err := disclaimerRepo.insertDisclaimer(req.Text, req.DocumentID)
 		if err != nil {
 			moovhttp.Problem(w, err)
 			return
@@ -138,7 +138,7 @@ func createDisclaimer(logger log.Logger, disclaimRepo disclaimerRepository, docR
 	}
 }
 
-func documentExistsForCustomer(customerID string, req createDisclaimerRequest, docRepo documentRepository) error {
+func documentExistsForCustomer(customerID string, req createDisclaimerRequest, docRepo DocumentRepository) error {
 	if req.DocumentID != "" {
 		docs, err := docRepo.getCustomerDocuments(customerID)
 		if err != nil {
@@ -154,7 +154,7 @@ func documentExistsForCustomer(customerID string, req createDisclaimerRequest, d
 	return nil
 }
 
-type disclaimerRepository interface {
+type DisclaimerRepository interface {
 	getCustomerDisclaimer(customerID, disclaimerID string) (*client.Disclaimer, error)
 	getCustomerDisclaimers(customerID string) ([]*client.Disclaimer, error)
 	acceptDisclaimer(customerID, disclaimerID string) error
@@ -166,6 +166,13 @@ type sqlDisclaimerRepository struct {
 	logger log.Logger
 }
 
+func NewDisclaimerRepo(logger log.Logger, db *sql.DB) DisclaimerRepository {
+	return &sqlDisclaimerRepository{
+		db:     db,
+		logger: logger,
+	}
+}
+
 func (r *sqlDisclaimerRepository) close() error {
 	return r.db.Close()
 }
diff --git a/cmd/server/documents.go b/pkg/customers/documents.go
similarity index 93%
rename from cmd/server/documents.go
rename to pkg/customers/documents.go
index 20548b7..c9a0e60 100644
--- a/cmd/server/documents.go
+++ b/pkg/customers/documents.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"bytes"
@@ -18,8 +18,8 @@ import (
 
 	"github.com/moov-io/base"
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/route"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -32,7 +32,7 @@ var (
 	maxDocumentSize int64 = 20 * 1024 * 1024 // 20MB
 )
 
-func addDocumentRoutes(logger log.Logger, r *mux.Router, repo documentRepository, bucketFactory bucketFunc) {
+func AddDocumentRoutes(logger log.Logger, r *mux.Router, repo DocumentRepository, bucketFactory bucketFunc) {
 	r.Methods("GET").Path("/customers/{customerID}/documents").HandlerFunc(getCustomerDocuments(logger, repo))
 	r.Methods("POST").Path("/customers/{customerID}/documents").HandlerFunc(uploadCustomerDocument(logger, repo, bucketFactory))
 	r.Methods("GET").Path("/customers/{customerID}/documents/{documentId}").HandlerFunc(retrieveRawDocument(logger, repo, bucketFactory))
@@ -47,7 +47,7 @@ func getDocumentID(w http.ResponseWriter, r *http.Request) string {
 	return v
 }
 
-func getCustomerDocuments(logger log.Logger, repo documentRepository) http.HandlerFunc {
+func getCustomerDocuments(logger log.Logger, repo DocumentRepository) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
@@ -80,7 +80,7 @@ func readDocumentType(v string) (string, error) {
 	return "", fmt.Errorf("unknown Document type: %s", orig)
 }
 
-func uploadCustomerDocument(logger log.Logger, repo documentRepository, bucketFactory bucketFunc) http.HandlerFunc {
+func uploadCustomerDocument(logger log.Logger, repo DocumentRepository, bucketFactory bucketFunc) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 		// TODO(adam): should we store x-namespace along with the Document?
@@ -160,7 +160,7 @@ func uploadCustomerDocument(logger log.Logger, repo documentRepository, bucketFa
 	}
 }
 
-func retrieveRawDocument(logger log.Logger, repo documentRepository, bucketFactory bucketFunc) http.HandlerFunc {
+func retrieveRawDocument(logger log.Logger, repo DocumentRepository, bucketFactory bucketFunc) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
@@ -202,7 +202,7 @@ func makeDocumentKey(customerID, documentId string) string {
 	return fmt.Sprintf("customer-%s-document-%s", customerID, documentId)
 }
 
-type documentRepository interface {
+type DocumentRepository interface {
 	getCustomerDocuments(customerID string) ([]*client.Document, error)
 	writeCustomerDocument(customerID string, doc *client.Document) error
 }
@@ -212,6 +212,12 @@ type sqlDocumentRepository struct {
 	logger log.Logger
 }
 
+func NewDocumentRepo(logger log.Logger, db *sql.DB) DocumentRepository {
+	return &sqlDocumentRepository{
+		db:     db,
+		logger: logger,
+	}
+}
 func (r *sqlDocumentRepository) getCustomerDocuments(customerID string) ([]*client.Document, error) {
 	query := `select document_id, type, content_type, uploaded_at from documents where customer_id = ?`
 	stmt, err := r.db.Prepare(query)
diff --git a/cmd/server/files.go b/pkg/customers/files.go
similarity index 93%
rename from cmd/server/files.go
rename to pkg/customers/files.go
index 443176d..3fca817 100644
--- a/cmd/server/files.go
+++ b/pkg/customers/files.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -14,14 +14,14 @@ import (
 	"time"
 
 	moovhttp "github.com/moov-io/base/http"
-	"github.com/moov-io/customers/cmd/server/route"
+	"github.com/moov-io/customers/pkg/route"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
 	"gocloud.dev/blob/fileblob"
 )
 
-func addFileblobRoutes(logger log.Logger, r *mux.Router, signer *fileblob.URLSignerHMAC, bucketFactory bucketFunc) {
+func AddFileblobRoutes(logger log.Logger, r *mux.Router, signer *fileblob.URLSignerHMAC, bucketFactory bucketFunc) {
 	if v := os.Getenv("FILEBLOB_BASE_URL"); v != "" {
 		u, err := url.Parse(v)
 		if u != nil && err == nil {
diff --git a/cmd/server/watchman.go b/pkg/customers/watchman.go
similarity index 97%
rename from cmd/server/watchman.go
rename to pkg/customers/watchman.go
index 60882c9..f18f28d 100644
--- a/cmd/server/watchman.go
+++ b/pkg/customers/watchman.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -157,12 +157,12 @@ func (c *moovWatchmanClient) Search(ctx context.Context, name string, requestID
 	return nil, nil // Nothing found
 }
 
-// newWatchmanClient returns an WatchmanClient instance and will default to using the Watchman address in
+// NewWatchmanClient returns an WatchmanClient instance and will default to using the Watchman address in
 // moov's standard Kubernetes setup.
 //
 // endpoint is a DNS record responsible for routing us to an Watchman instance.
 // Example: http://watchman.apps.svc.cluster.local:8080
-func newWatchmanClient(logger log.Logger, endpoint string, debug bool) WatchmanClient {
+func NewWatchmanClient(logger log.Logger, endpoint string, debug bool) WatchmanClient {
 	conf := watchman.NewConfiguration()
 	conf.BasePath = "http://localhost" + bind.HTTP("watchman")
 	conf.Debug = debug
diff --git a/cmd/server/fed/cache.go b/pkg/fed/cache.go
similarity index 100%
rename from cmd/server/fed/cache.go
rename to pkg/fed/cache.go
diff --git a/cmd/server/fed/cache_test.go b/pkg/fed/cache_test.go
similarity index 100%
rename from cmd/server/fed/cache_test.go
rename to pkg/fed/cache_test.go
diff --git a/cmd/server/fed/client.go b/pkg/fed/client.go
similarity index 100%
rename from cmd/server/fed/client.go
rename to pkg/fed/client.go
diff --git a/cmd/server/fed/client_test.go b/pkg/fed/client_test.go
similarity index 100%
rename from cmd/server/fed/client_test.go
rename to pkg/fed/client_test.go
diff --git a/cmd/server/fed/fed.go b/pkg/fed/fed.go
similarity index 100%
rename from cmd/server/fed/fed.go
rename to pkg/fed/fed.go
diff --git a/cmd/server/fed/mock_client.go b/pkg/fed/mock_client.go
similarity index 100%
rename from cmd/server/fed/mock_client.go
rename to pkg/fed/mock_client.go
diff --git a/cmd/server/paygate/client.go b/pkg/paygate/client.go
similarity index 100%
rename from cmd/server/paygate/client.go
rename to pkg/paygate/client.go
diff --git a/cmd/server/paygate/client_test.go b/pkg/paygate/client_test.go
similarity index 100%
rename from cmd/server/paygate/client_test.go
rename to pkg/paygate/client_test.go
diff --git a/cmd/server/paygate/mock_client.go b/pkg/paygate/mock_client.go
similarity index 100%
rename from cmd/server/paygate/mock_client.go
rename to pkg/paygate/mock_client.go
diff --git a/cmd/server/route/customers.go b/pkg/route/customers.go
similarity index 100%
rename from cmd/server/route/customers.go
rename to pkg/route/customers.go
diff --git a/cmd/server/route/customers_test.go b/pkg/route/customers_test.go
similarity index 100%
rename from cmd/server/route/customers_test.go
rename to pkg/route/customers_test.go
diff --git a/cmd/server/route/http.go b/pkg/route/http.go
similarity index 100%
rename from cmd/server/route/http.go
rename to pkg/route/http.go
diff --git a/cmd/server/route/http_test.go b/pkg/route/http_test.go
similarity index 100%
rename from cmd/server/route/http_test.go
rename to pkg/route/http_test.go
diff --git a/cmd/server/route/namespace.go b/pkg/route/namespace.go
similarity index 100%
rename from cmd/server/route/namespace.go
rename to pkg/route/namespace.go
diff --git a/cmd/server/route/namespace_test.go b/pkg/route/namespace_test.go
similarity index 100%
rename from cmd/server/route/namespace_test.go
rename to pkg/route/namespace_test.go
diff --git a/cmd/server/accounts/validator/microdeposits/strategy.go b/pkg/validator/microdeposits/strategy.go
similarity index 96%
rename from cmd/server/accounts/validator/microdeposits/strategy.go
rename to pkg/validator/microdeposits/strategy.go
index ca43b67..43c48f7 100644
--- a/cmd/server/accounts/validator/microdeposits/strategy.go
+++ b/pkg/validator/microdeposits/strategy.go
@@ -11,9 +11,9 @@ import (
 	"strings"
 
 	"github.com/mitchellh/mapstructure"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-	"github.com/moov-io/customers/cmd/server/paygate"
 	customersclient "github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/paygate"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/moov-io/paygate/pkg/client"
 )
 
diff --git a/cmd/server/accounts/validator/mock_repository.go b/pkg/validator/mock_repository.go
similarity index 100%
rename from cmd/server/accounts/validator/mock_repository.go
rename to pkg/validator/mock_repository.go
diff --git a/cmd/server/accounts/validator/mx/strategy.go b/pkg/validator/mx/strategy.go
similarity index 97%
rename from cmd/server/accounts/validator/mx/strategy.go
rename to pkg/validator/mx/strategy.go
index e34f7bd..a5b2310 100644
--- a/cmd/server/accounts/validator/mx/strategy.go
+++ b/pkg/validator/mx/strategy.go
@@ -9,8 +9,8 @@ import (
 	"fmt"
 
 	"github.com/mitchellh/mapstructure"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/mxenabled/atrium-go"
 )
 
diff --git a/cmd/server/accounts/validator/plaid/strategy.go b/pkg/validator/plaid/strategy.go
similarity index 97%
rename from cmd/server/accounts/validator/plaid/strategy.go
rename to pkg/validator/plaid/strategy.go
index be54c44..488c949 100644
--- a/cmd/server/accounts/validator/plaid/strategy.go
+++ b/pkg/validator/plaid/strategy.go
@@ -9,8 +9,8 @@ import (
 	"net/http"
 
 	"github.com/mitchellh/mapstructure"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/plaid/plaid-go/plaid"
 )
 


Test patch:
diff --git a/cmd/server/main_test.go b/cmd/server/main_test.go
index 1d2753b..c2214aa 100644
--- a/cmd/server/main_test.go
+++ b/cmd/server/main_test.go
@@ -10,7 +10,7 @@ import (
 
 	"github.com/go-kit/kit/log"
 	"github.com/moov-io/base/admin"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/stretchr/testify/require"
 )
 
diff --git a/cmd/server/accounts/micro_deposits_test.go b/pkg/accounts/micro_deposits_test.go
similarity index 98%
rename from cmd/server/accounts/micro_deposits_test.go
rename to pkg/accounts/micro_deposits_test.go
index 8caa10b..7f4cac5 100644
--- a/cmd/server/accounts/micro_deposits_test.go
+++ b/pkg/accounts/micro_deposits_test.go
@@ -10,7 +10,7 @@ import (
 	"testing"
 
 	"github.com/moov-io/base"
-	"github.com/moov-io/customers/cmd/server/paygate"
+	"github.com/moov-io/customers/pkg/paygate"
 	"github.com/moov-io/paygate/pkg/client"
 )
 
diff --git a/cmd/server/accounts/router_test.go b/pkg/accounts/router_test.go
similarity index 98%
rename from cmd/server/accounts/router_test.go
rename to pkg/accounts/router_test.go
index 0296d5b..6ce6bda 100644
--- a/cmd/server/accounts/router_test.go
+++ b/pkg/accounts/router_test.go
@@ -23,13 +23,13 @@ import (
 	"github.com/moov-io/base"
 	"github.com/stretchr/testify/require"
 
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-	"github.com/moov-io/customers/cmd/server/accounts/validator/testvalidator"
-
-	"github.com/moov-io/customers/cmd/server/fed"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/fed"
 	"github.com/moov-io/customers/pkg/secrets"
 
+	"github.com/moov-io/customers/pkg/validator"
+	"github.com/moov-io/customers/pkg/validator/testvalidator"
+
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
 )
diff --git a/cmd/server/accounts/validate_test.go b/pkg/accounts/validate_test.go
similarity index 98%
rename from cmd/server/accounts/validate_test.go
rename to pkg/accounts/validate_test.go
index bdd1922..243050b 100644
--- a/cmd/server/accounts/validate_test.go
+++ b/pkg/accounts/validate_test.go
@@ -14,12 +14,12 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/moov-io/base"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-	"github.com/moov-io/customers/cmd/server/accounts/validator/microdeposits"
-	"github.com/moov-io/customers/cmd/server/accounts/validator/testvalidator"
-	"github.com/moov-io/customers/cmd/server/paygate"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/paygate"
 	"github.com/moov-io/customers/pkg/secrets"
+	"github.com/moov-io/customers/pkg/validator"
+	"github.com/moov-io/customers/pkg/validator/microdeposits"
+	"github.com/moov-io/customers/pkg/validator/testvalidator"
 	payclient "github.com/moov-io/paygate/pkg/client"
 	"github.com/stretchr/testify/require"
 
diff --git a/cmd/server/approval_ofac_test.go b/pkg/customers/approval_ofac_test.go
similarity index 92%
rename from cmd/server/approval_ofac_test.go
rename to pkg/customers/approval_ofac_test.go
index b2e1906..c810394 100644
--- a/cmd/server/approval_ofac_test.go
+++ b/pkg/customers/approval_ofac_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"encoding/json"
@@ -19,14 +19,14 @@ import (
 	watchman "github.com/moov-io/watchman/client"
 )
 
-func createTestOFACSearcher(repo customerRepository, watchmanClient WatchmanClient) *ofacSearcher {
+func createTestOFACSearcher(repo CustomerRepository, watchmanClient WatchmanClient) *OFACSearcher {
 	if repo == nil {
 		repo = &testCustomerRepository{}
 	}
 	if watchmanClient == nil {
 		watchmanClient = &testWatchmanClient{}
 	}
-	return &ofacSearcher{repo: repo, watchmanClient: watchmanClient}
+	return &OFACSearcher{repo: repo, watchmanClient: watchmanClient}
 }
 
 func TestOFACSearcher__nil(t *testing.T) {
@@ -91,7 +91,7 @@ func TestOFACApproval__getLatest(t *testing.T) {
 		},
 	}
 
-	addOFACRoutes(logger, router, repo, nil)
+	AddOFACRoutes(logger, router, repo, nil)
 
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", fmt.Sprintf("/customers/%s/ofac", customerID), nil)
@@ -130,12 +130,12 @@ func TestOFACApproval__refresh(t *testing.T) {
 		},
 	}
 	testWatchmanClient := &testWatchmanClient{}
-	ofac := &ofacSearcher{
+	ofac := &OFACSearcher{
 		repo:           repo,
 		watchmanClient: testWatchmanClient,
 	}
 
-	addOFACRoutes(logger, router, repo, ofac)
+	AddOFACRoutes(logger, router, repo, ofac)
 
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("PUT", fmt.Sprintf("/customers/%s/refresh/ofac", customerID), nil)
@@ -180,12 +180,12 @@ func TestOFACApproval__refreshErr(t *testing.T) {
 		},
 	}
 	testWatchmanClient := &testWatchmanClient{err: errors.New("bad error")}
-	ofac := &ofacSearcher{
+	ofac := &OFACSearcher{
 		repo:           repo,
 		watchmanClient: testWatchmanClient,
 	}
 
-	addOFACRoutes(logger, router, repo, ofac)
+	AddOFACRoutes(logger, router, repo, ofac)
 
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("PUT", fmt.Sprintf("/customers/%s/refresh/ofac", customerID), nil)
diff --git a/cmd/server/approval_test.go b/pkg/customers/approval_test.go
similarity index 97%
rename from cmd/server/approval_test.go
rename to pkg/customers/approval_test.go
index 7a152f5..1821bd3 100644
--- a/cmd/server/approval_test.go
+++ b/pkg/customers/approval_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"bytes"
@@ -33,7 +33,7 @@ func TestCustomers__updateCustomerStatus(t *testing.T) {
 
 	svc := admin.NewServer(":10001")
 	defer svc.Shutdown()
-	addApprovalRoutes(log.NewNopLogger(), svc, repo, ssnRepo, searcher)
+	AddApprovalRoutes(log.NewNopLogger(), svc, repo, ssnRepo, searcher)
 	go svc.Listen()
 
 	body := strings.NewReader(`{"status": "ReceiveOnly", "comment": "test comment"}`)
@@ -147,7 +147,7 @@ func TestCustomers__updateCustomerAddress(t *testing.T) {
 
 	svc := admin.NewServer(":10002")
 	defer svc.Shutdown()
-	addApprovalRoutes(log.NewNopLogger(), svc, repo, ssnRepo, searcher)
+	AddApprovalRoutes(log.NewNopLogger(), svc, repo, ssnRepo, searcher)
 	go svc.Listen()
 
 	body := strings.NewReader(`{"type": "primary", "validated": true}`)
diff --git a/cmd/server/bucket_test.go b/pkg/customers/bucket_test.go
similarity index 84%
rename from cmd/server/bucket_test.go
rename to pkg/customers/bucket_test.go
index 85b5084..990b2ab 100644
--- a/cmd/server/bucket_test.go
+++ b/pkg/customers/bucket_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -16,7 +16,7 @@ import (
 
 var (
 	testBucket bucketFunc = func() (*blob.Bucket, error) {
-		signer, err := fileblobSigner("http://localhost:8087", "secret")
+		signer, err := FileblobSigner("http://localhost:8087", "secret")
 		if err != nil {
 			panic(fmt.Sprintf("testBucket: %v", err))
 		}
@@ -41,15 +41,15 @@ func TestBucket__openBucket(t *testing.T) {
 	}
 }
 
-func TestBucket__getBucket(t *testing.T) {
+func TestBucket__GetBucket(t *testing.T) {
 	dir, _ := ioutil.TempDir("", "customers-getBucket")
 
-	signer, err := fileblobSigner("http://localhost:8087", "secret")
+	signer, err := FileblobSigner("http://localhost:8087", "secret")
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	bucket, err := getBucket(dir, "file", signer)()
+	bucket, err := GetBucket(dir, "file", signer)()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -58,7 +58,7 @@ func TestBucket__getBucket(t *testing.T) {
 	}
 
 	// error case
-	if _, err := getBucket("", "", nil)(); err == nil {
+	if _, err := GetBucket("", "", nil)(); err == nil {
 		t.Fatal("expected error")
 	}
 }
diff --git a/cmd/server/customer_search_scope_test.go b/pkg/customers/customer_search_scope_test.go
similarity index 95%
rename from cmd/server/customer_search_scope_test.go
rename to pkg/customers/customer_search_scope_test.go
index 31310c6..063efbf 100644
--- a/cmd/server/customer_search_scope_test.go
+++ b/pkg/customers/customer_search_scope_test.go
@@ -1,4 +1,4 @@
-package main
+package customers
 
 import (
 	"encoding/json"
@@ -30,7 +30,7 @@ func Setup(t *testing.T) Scope {
 
 func (scope *Scope) GetCustomers(query string) ([]*client.Customer, error) {
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, scope.customerRepo, nil, nil)
+	AddCustomerRoutes(log.NewNopLogger(), router, scope.customerRepo, nil, nil)
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", "/customers"+query, nil)
 	req.Header.Set("X-Namespace", "namespace")
diff --git a/cmd/server/customer_search_test.go b/pkg/customers/customer_search_test.go
similarity index 99%
rename from cmd/server/customer_search_test.go
rename to pkg/customers/customer_search_test.go
index 76ff1d1..b07256e 100644
--- a/cmd/server/customer_search_test.go
+++ b/pkg/customers/customer_search_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"database/sql"
@@ -22,7 +22,7 @@ func TestCustomersSearchRouter(t *testing.T) {
 	defer repo.close()
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, nil, nil)
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, nil, nil)
 
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", "/customers?query=jane+doe", nil)
diff --git a/cmd/server/customer_ssn_test.go b/pkg/customers/customer_ssn_test.go
similarity index 99%
rename from cmd/server/customer_ssn_test.go
rename to pkg/customers/customer_ssn_test.go
index 90912fb..141cf1f 100644
--- a/cmd/server/customer_ssn_test.go
+++ b/pkg/customers/customer_ssn_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"encoding/base64"
diff --git a/cmd/server/customers_test.go b/pkg/customers/customers_test.go
similarity index 96%
rename from cmd/server/customers_test.go
rename to pkg/customers/customers_test.go
index 5a1e755..d7a595f 100644
--- a/cmd/server/customers_test.go
+++ b/pkg/customers/customers_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"bytes"
@@ -146,7 +146,7 @@ func TestCustomers__GetCustomer(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -172,7 +172,7 @@ func TestCustomers__GetCustomerEmpty(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -203,7 +203,7 @@ func TestCustomers__DeleteCustomer(t *testing.T) {
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("DELETE", fmt.Sprintf("/customers/%s", customer.CustomerID), nil)
 
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -246,7 +246,7 @@ func TestCustomers__GetCustomersError(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -336,7 +336,7 @@ func TestCustomers__createCustomer(t *testing.T) {
 	customerSSNStorage := testCustomerSSNStorage(t)
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, customerSSNStorage, createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, customerSSNStorage, createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -554,7 +554,7 @@ func TestCustomers__replaceCustomerMetadata(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -579,7 +579,7 @@ func TestCustomers__replaceCustomerMetadata(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router2 := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router2, repo2, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router2, repo2, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -615,7 +615,7 @@ func TestCustomers__replaceCustomerMetadataInvalid(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -648,7 +648,7 @@ func TestCustomers__replaceCustomerMetadataError(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -725,7 +725,7 @@ func TestCustomers__addCustomerAddress(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, testCustomerSSNStorage(t), createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -880,7 +880,7 @@ func TestCustomers__minimumFields(t *testing.T) {
 	customerSSNStorage := testCustomerSSNStorage(t)
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, repo, customerSSNStorage, createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, repo, customerSSNStorage, createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -901,7 +901,7 @@ func TestCustomers__BadReq(t *testing.T) {
 	customerSSNStorage := testCustomerSSNStorage(t)
 
 	router := mux.NewRouter()
-	addCustomerRoutes(log.NewNopLogger(), router, nil, customerSSNStorage, createTestOFACSearcher(nil, nil))
+	AddCustomerRoutes(log.NewNopLogger(), router, nil, customerSSNStorage, createTestOFACSearcher(nil, nil))
 	router.ServeHTTP(w, req)
 	w.Flush()
 
diff --git a/cmd/server/disclaimers_test.go b/pkg/customers/disclaimers_test.go
similarity index 93%
rename from cmd/server/disclaimers_test.go
rename to pkg/customers/disclaimers_test.go
index d9b4bec..d81ca71 100644
--- a/cmd/server/disclaimers_test.go
+++ b/pkg/customers/disclaimers_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"bytes"
@@ -116,7 +116,7 @@ func TestDisclaimers__getCustomerDisclaimers(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addDisclaimerRoutes(log.NewNopLogger(), router, repo)
+	AddDisclaimerRoutes(log.NewNopLogger(), router, repo)
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -160,7 +160,7 @@ func TestDisclaimers__acceptDisclaimer(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addDisclaimerRoutes(log.NewNopLogger(), router, repo)
+	AddDisclaimerRoutes(log.NewNopLogger(), router, repo)
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -231,7 +231,7 @@ func TestDisclaimersRepository(t *testing.T) {
 }
 
 func TestDisclaimersAdmin__create(t *testing.T) {
-	disclaimRepo := &testDisclaimerRepository{}
+	disclaimerRepo := &testDisclaimerRepository{}
 
 	documentID := base.ID()
 	docRepo := &testDocumentRepository{
@@ -244,7 +244,7 @@ func TestDisclaimersAdmin__create(t *testing.T) {
 
 	svc := admin.NewServer(":0")
 	defer svc.Shutdown()
-	addDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimRepo, docRepo)
+	AddDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimerRepo, docRepo)
 	go svc.Listen()
 
 	body := strings.NewReader(fmt.Sprintf(`{"text": "terms and conditions", "documentId": "%s"}`, documentID))
@@ -276,12 +276,12 @@ func TestDisclaimersAdmin__create(t *testing.T) {
 }
 
 func TestDisclaimersAdmin__createErr(t *testing.T) {
-	disclaimRepo := &testDisclaimerRepository{}
+	disclaimerRepo := &testDisclaimerRepository{}
 	docRepo := &testDocumentRepository{}
 
 	svc := admin.NewServer(":0")
 	defer svc.Shutdown()
-	addDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimRepo, docRepo)
+	AddDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimerRepo, docRepo)
 	go svc.Listen()
 
 	body := strings.NewReader(`{"text": "", "documentId": ""}`)
@@ -304,12 +304,12 @@ func TestDisclaimersAdmin__createErr(t *testing.T) {
 }
 
 func TestDisclaimersAdmin__createMethodErr(t *testing.T) {
-	disclaimRepo := &testDisclaimerRepository{}
+	disclaimerRepo := &testDisclaimerRepository{}
 	docRepo := &testDocumentRepository{}
 
 	svc := admin.NewServer(":0")
 	defer svc.Shutdown()
-	addDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimRepo, docRepo)
+	AddDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimerRepo, docRepo)
 	go svc.Listen()
 
 	req, err := http.NewRequest("PUT", "http://"+svc.BindAddr()+"/customers/adam/disclaimers", nil)
@@ -331,12 +331,12 @@ func TestDisclaimersAdmin__createMethodErr(t *testing.T) {
 }
 
 func TestDisclaimersAdmin__createJSONErr(t *testing.T) {
-	disclaimRepo := &testDisclaimerRepository{}
+	disclaimerRepo := &testDisclaimerRepository{}
 	docRepo := &testDocumentRepository{}
 
 	svc := admin.NewServer(":0")
 	defer svc.Shutdown()
-	addDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimRepo, docRepo)
+	AddDisclaimerAdminRoutes(log.NewNopLogger(), svc, disclaimerRepo, docRepo)
 	go svc.Listen()
 
 	body := strings.NewReader(`not-valid-json`)
diff --git a/cmd/server/documents_test.go b/pkg/customers/documents_test.go
similarity index 96%
rename from cmd/server/documents_test.go
rename to pkg/customers/documents_test.go
index 14e42b0..2af1ab5 100644
--- a/cmd/server/documents_test.go
+++ b/pkg/customers/documents_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"bytes"
@@ -65,7 +65,7 @@ func TestDocuments__getCustomerDocuments(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
+	AddDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -148,7 +148,7 @@ func TestDocumentsUploadAndRetrieval(t *testing.T) {
 	req.Header.Set("x-request-id", "test")
 
 	router := mux.NewRouter()
-	addDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
+	AddDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
 	router.ServeHTTP(w, req)
 	w.Flush()
 
@@ -195,7 +195,7 @@ func TestDocuments__uploadCustomerDocument(t *testing.T) {
 	req.URL = u // replace query params with invalid values
 
 	router := mux.NewRouter()
-	addDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
+	AddDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
 	router.ServeHTTP(w, req)
 	w.Flush()
 
diff --git a/cmd/server/files_test.go b/pkg/customers/files_test.go
similarity index 93%
rename from cmd/server/files_test.go
rename to pkg/customers/files_test.go
index d881f0e..4936c99 100644
--- a/cmd/server/files_test.go
+++ b/pkg/customers/files_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -63,7 +63,7 @@ func TestFiles__proxyLocalFile(t *testing.T) {
 		t.Fatalf("u=%s err=%v", u, err)
 	}
 	u.Scheme, u.Host = "", "" // blank out 'http://' and 'localhost' in below assumption
-	signer, err := fileblobSigner("http://localhost", "secret")
+	signer, err := FileblobSigner("http://localhost", "secret")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -72,7 +72,7 @@ func TestFiles__proxyLocalFile(t *testing.T) {
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", "/files"+u.String(), nil)
 	router := mux.NewRouter()
-	addFileblobRoutes(log.NewNopLogger(), router, signer, func() (*blob.Bucket, error) { return bucket, nil })
+	AddFileblobRoutes(log.NewNopLogger(), router, signer, func() (*blob.Bucket, error) { return bucket, nil })
 	router.ServeHTTP(w, req)
 	w.Flush()
 
diff --git a/cmd/server/watchman_test.go b/pkg/customers/watchman_test.go
similarity index 98%
rename from cmd/server/watchman_test.go
rename to pkg/customers/watchman_test.go
index c1ac91c..cd581e3 100644
--- a/cmd/server/watchman_test.go
+++ b/pkg/customers/watchman_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an Apache License
 // license that can be found in the LICENSE file.
 
-package main
+package customers
 
 import (
 	"context"
@@ -73,7 +73,7 @@ func spawnWatchman(t *testing.T) *watchmanDeployment {
 		t.Fatal(err)
 	}
 
-	client := newWatchmanClient(log.NewNopLogger(), fmt.Sprintf("http://localhost:%s", resource.GetPort("8080/tcp")), false)
+	client := NewWatchmanClient(log.NewNopLogger(), fmt.Sprintf("http://localhost:%s", resource.GetPort("8080/tcp")), false)
 	err = pool.Retry(func() error {
 		return client.Ping()
 	})
@@ -85,7 +85,7 @@ func spawnWatchman(t *testing.T) *watchmanDeployment {
 
 func TestWatchman__client(t *testing.T) {
 	endpoint := ""
-	if client := newWatchmanClient(log.NewNopLogger(), endpoint, false); client == nil {
+	if client := NewWatchmanClient(log.NewNopLogger(), endpoint, false); client == nil {
 		t.Fatal("expected non-nil client")
 	}
 
diff --git a/cmd/server/accounts/validator/microdeposits/strategy_test.go b/pkg/validator/microdeposits/strategy_test.go
similarity index 95%
rename from cmd/server/accounts/validator/microdeposits/strategy_test.go
rename to pkg/validator/microdeposits/strategy_test.go
index f383d13..054cbb0 100644
--- a/cmd/server/accounts/validator/microdeposits/strategy_test.go
+++ b/pkg/validator/microdeposits/strategy_test.go
@@ -8,9 +8,9 @@ import (
 	"testing"
 
 	"github.com/moov-io/base"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
-	"github.com/moov-io/customers/cmd/server/paygate"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/paygate"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/stretchr/testify/require"
 
 	payclient "github.com/moov-io/paygate/pkg/client"
diff --git a/cmd/server/accounts/validator/mx/strategy_test.go b/pkg/validator/mx/strategy_test.go
similarity index 97%
rename from cmd/server/accounts/validator/mx/strategy_test.go
rename to pkg/validator/mx/strategy_test.go
index 66ab5ad..7acf1cc 100644
--- a/cmd/server/accounts/validator/mx/strategy_test.go
+++ b/pkg/validator/mx/strategy_test.go
@@ -11,8 +11,8 @@ import (
 
 	"github.com/antihax/optional"
 	"github.com/moov-io/base"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/mxenabled/atrium-go"
 	"github.com/stretchr/testify/require"
 )
diff --git a/cmd/server/accounts/validator/plaid/strategy_test.go b/pkg/validator/plaid/strategy_test.go
similarity index 97%
rename from cmd/server/accounts/validator/plaid/strategy_test.go
rename to pkg/validator/plaid/strategy_test.go
index 4e927df..8f367e1 100644
--- a/cmd/server/accounts/validator/plaid/strategy_test.go
+++ b/pkg/validator/plaid/strategy_test.go
@@ -11,8 +11,8 @@ import (
 	"testing"
 
 	"github.com/moov-io/base"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/plaid/plaid-go/plaid"
 	"github.com/stretchr/testify/require"
 )
diff --git a/cmd/server/accounts/validator/repository.go b/pkg/validator/repository.go
similarity index 100%
rename from cmd/server/accounts/validator/repository.go
rename to pkg/validator/repository.go
diff --git a/cmd/server/accounts/validator/repository_test.go b/pkg/validator/repository_test.go
similarity index 100%
rename from cmd/server/accounts/validator/repository_test.go
rename to pkg/validator/repository_test.go
diff --git a/cmd/server/accounts/validator/strategy.go b/pkg/validator/strategy.go
similarity index 100%
rename from cmd/server/accounts/validator/strategy.go
rename to pkg/validator/strategy.go
diff --git a/cmd/server/accounts/validator/testvalidator/strategy.go b/pkg/validator/testvalidator/strategy.go
similarity index 95%
rename from cmd/server/accounts/validator/testvalidator/strategy.go
rename to pkg/validator/testvalidator/strategy.go
index bba1f28..c7bb468 100644
--- a/cmd/server/accounts/validator/testvalidator/strategy.go
+++ b/pkg/validator/testvalidator/strategy.go
@@ -9,8 +9,8 @@ import (
 	"fmt"
 
 	"github.com/mitchellh/mapstructure"
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/validator"
 )
 
 type testStrategy struct{}
diff --git a/cmd/server/accounts/validator/testvalidator/strategy_test.go b/pkg/validator/testvalidator/strategy_test.go
similarity index 94%
rename from cmd/server/accounts/validator/testvalidator/strategy_test.go
rename to pkg/validator/testvalidator/strategy_test.go
index a45252c..3fe2fa2 100644
--- a/cmd/server/accounts/validator/testvalidator/strategy_test.go
+++ b/pkg/validator/testvalidator/strategy_test.go
@@ -7,8 +7,8 @@ package testvalidator
 import (
 	"testing"
 
-	"github.com/moov-io/customers/cmd/server/accounts/validator"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/validator"
 	"github.com/stretchr/testify/require"
 )