У меня вопрос об использовании интерфейсов при разделении бизнес-уровней и других уровней (например, уровня базы данных в моем случае). Я слышал, что интерфейсы желательно определять там же, где они используются.
В моем приложении есть RegistryService
, который инкапсулирует логику регистрации и отмены регистрации рабочих в моем кластере приложений. У меня также есть структура Memberlist
, в которой мои модели хранятся в связанном списке.
В моей реализации RegistryService
я объявляю интерфейс workerProvider
с такими методами, как SaveWorker
, DeleteWorkerByID
и т. д. Эти методы возвращают ошибки. В настоящее время эти ошибки определены в пакете memberlist
. Однако я хочу абстрагировать эти ошибки, чтобы они не были тесно связаны с реализацией memberlist
.
Стоит ли перенести интерфейс workerProvider
в отдельный пакет и определить в нем новые типы ошибок?
package registry
import ( ... )
var (
ErrWorkerAlreadyRegistered = errors.New("worker is already registered")
)
type workersProvider interface {
SaveWorker(ctx context.Context, endpoint string, zone string) (domain.Worker, error)
DeleteWorkerByID(ctx context.Context, id uuid.UUID) (domain.Worker, error)
ListWorkers(ctx context.Context) ([]domain.Worker, error)
FindWorkerByID(ctx context.Context, id uuid.UUID) (domain.Worker, error)
}
type RegistryService struct {
log *slog.Logger
wp workersProvider
}
func NewRegistryService(log *slog.Logger, wp workersProvider) *RegistryService {
return &RegistryService{
log: log,
wp: wp,
}
}
func (reg *RegistryService) RegisterWorker(ctx context.Context, endpoint string, zone string) (domain.Worker, error) {
worker, err := reg.wp.SaveWorker(ctx, endpoint, zone)
if err != nil {
emptyWorker := domain.Worker{}
switch {
case errors.Is(err, memberlist.ErrWorkerAlreadyExists):
return emptyWorker, ErrWorkerAlreadyRegistered
default:
return emptyWorker, lib.WrapError(op, err)
}
}
return worker, nil
}
package memberlist
import (...)
var (
...
ErrWorkerAlreadyExists = errors.New("worker already exists")
...
)
type Memberlist struct { ... }
func New() *Memberlist { ... }
func (mlist *Memberlist) SaveWorker(_ context.Context, endpoint string, zone string) (domain.Worker, error) {
...
return domain.Worker{}, ErrWorkerAlreadyExists
...
}
func (reg *Memberlist) DeleteWorkerByID(_ context.Context, id uuid.UUID) (domain.Worker, error) { ... }
func (reg *Memberlist) ListWorkers(_ context.Context) ([]domain.Worker, error) { ... }
func (reg *Memberlist) FindWorkerByID(_ context.Context, id uuid.UUID) (domain.Worker, error) { ... }
Меня всегда поощряли опробовать свои идеи, и это помогло мне многому научиться, поэтому я предлагаю вам сначала написать тесты, прежде чем вносить изменения. Затем вы можете создать свой пакет и посмотреть, соответствует ли он дизайну и будущим целям, которые вы задумали.
Но также спросите себя, с какими проблемами вы сталкиваетесь, если ошибки связаны со списком участников? Мешает ли это вам реализовать какую-то новую функцию? Является ли нынешний дизайн слишком сложным для работы? Эти вопросы помогут вам принять лучшее решение для себя.
Не усложняйте слишком сильно, лучше всего позволить вашему коду самому решать, где он лучше всего подходит. Во время разработки вы автоматически увидите, где это лучше всего подходит. Но «общий» пакет — хороший выбор, например, apperrors или что-то в этом роде.