> В UserService может использоваться логика ProjectService'a. Для этого я передаю в userService.New(projectService ProjectService)
По хорошему в UserService не должно быть логики ProjectService.
Логику лучше не разносить по разным местам, такой код очень трудно потом поддерживать.
Представьте, что через год придёт новый человек и внесёт правки в ProjectService, он может не знать/забыть внести нужные правки в UserService.
Если без этого совсем никак, тогда лучше сделать 3й сервис, который в конструкторе примет сервисы Users и Project и будет вызывать нужные методы из каждого из них.
В идеале нужно перестроиться и проектировать пакеты так, чтобы таких проблем не возникало.
Но когда вы перестроитесь - результаты работы будут намного качественнее.
Мне после Perl, Python понадобилось не мало времени чтобы перестроиться :))
Но сейчас могу с уверенностью сказать оно того точно стоило!
Подход как у вас с ServiceManager тоже используется, даже видел библиотеки готовые, но уже не помню как назывались. Я попробовал их, у меня не прижились эти библиотеки )
Сейчас я использую такой подход:
users/user.go
тут описываю структуру Users, простая валидация полей, методы у структуры типа GetFullName и т.д.
users/repo.go
тут описываю интерфейсы:
QueryRepo для извлечения данных
CommandRepo для внесения изменения в данные пользователей
репозитории используются только в рамках пакета users, другие пакеты используют интерфейсы сервисов
users/querysvc.go
тут описываю интерфейс QuerySvc (только для извлечения данных связанных с пользователей),
он уже использует интерфейс QueryRepo
этот пакет будут импортировать другие пакеты
users/commandsvc.go
тут описываю интерфейс CommandSvc (только для внесения изменений в данные пользователей), он тоже использует интерфейсы QueryRepo, CommandRepo
этот пакет будут импортировать другие пакеты
users/signupsvc.go
это сервис регистрации пользователей, тут описываю интерфейс SignupSvc
users/repos/mysql/query.go
тут реализация интерфейса QueryRepo на MySQL
этот пакет будет импортироваться только в пакете `app` (см. ниже)
users/repos/mysql/command.go
тут реализация интерфейса CommandRepo на MySQL
этот пакет будет импортироваться только в пакете `app` (см. ниже)
users/services/querysvc.go
тут реализация интерфейса QuerySvc
users/services/commandsvc.go
тут реализация интерфейса CommandSvc
users/services/signupsvc.go
тут реализация интерфейса SignupSvc
core/
тут все константы, которые могут быть использованы в разных пакетах приложения (например коды ошибок)
этот пакет могут включать любые пакеты, но он ничего не импортирует в рамках проекта
app/
тут иницализация всех репозиториев, сервисов, конфигов и т.д.
здесь же происходит связывание всех пакетов.
Т.е. что-то вроде
usersQuerySvc := usersservices.NewQuerySvc(usersRepo, ...)
usersSignupSvc := usersservices.NewSignupSvc(usersQuerySvc, usersCommandSvc)
// например сервис для админов (с проверкой полномочий), методы которого уже можно использовать в endpoint'ax
usersAdminSvc := usersservices.NewAdminSvc(usersQuerySvc, usersCommandSvc)
projectSvc := projectservices.New(usersQuerySvc)
Мне такой подход очень нравится тем, что нет скрытых зависимостей, всё видно как на ладони, кто и что использует.
А если возникают сложности с применением такой схемы - для меня это верный признак того, что нужно спроектировать иначе, раз сложности уже возникли, значит в будущем их будет еще больше ))
Ну и все пакеты как параметры принимают везде только интерфейсы.
Хотя... с точки зрения Dependency Inversion, правильно бы было описывать под каждый пакет свой интерфейс, а не так, как я :), но я делаю интерфейсы очень маленькими, тогда получается очень гибко и, как мне кажется, после этого еще внутри других пакетов описывать интерфейсы уже избыточно в моём случае, работы больше, а толку не так много, по этому пока экономлю на этом время :)
cmd/serve.go - импортирует app и запускает веб сервер например
Если нужны будут еще какие то подробности - пишите, буду рад помочь