Ну, я постараюсь ответить, полностью объяснив, как я вижу этот дизайн.Извините заранее, если это слишком много, и не в точку ..
- Entity / Domain
- Ядро приложения, будет включать в себя структуру объекта, не будет импортировать ЛЮБОЙпакет внешнего слоя, но может быть импортирован любым пакетом (почти)
- Приложение / вариант использования
- «Служба».Будет отвечать главным образом за логику приложения, не будет знать о транспорте (http), будет «общаться» с БД через интерфейс.Здесь вы можете проверить домен, например, если ресурс не найден или текст слишком короткий.Все, что связано с бизнес-логикой.
- transport
- Будет обрабатывать запрос http, декодировать запрос, получать службу, выполняющую его задачи, и кодировать ответ.Здесь вы можете вернуть 401, если в запросе отсутствует обязательный параметр, или пользователь не авторизован, или что-то ...
- инфраструктура
- подключение к БД
- Может быть, какой-нибудь http-движок, маршрутизатор и прочее.
- Абсолютно независим от приложений, не импортируйте никакой внутренний пакет, даже Pseron
Например, допустим, мы хотим сделать что-то столь же простое, как вставить person в базу данных.
package person будет включать в себя только структуру struct
package person
type Person struct{
name string
}
func New(name string) Person {
return Person{
name: name,
{
}
О базе данных, скажем, выиспользуйте sql, я рекомендую сделать пакет с именем sql
для обработки репо.(если вы используете postgress, используйте пакет postgress ...).
personRepo получит dbConnection, который будет инициализирован в main и реализует DBAndler.только соединение будет «общаться» с БД напрямую, главная цель хранилища - быть шлюзом к БД и говорить в терминах приложения.(соединение не зависит от приложения)
package sql
type DBAndler interface{
exec(string, ...interface{}) (int64, error)
}
type personRepo struct{
dbHandler DBHandler
}
func NewPersonRepo(dbHandler DBHandler) &personRepo {
return &personRepo{
dbHandler: dbHandler,
}
}
func (p *personRepo) InsertPerson(p person.Person) (int64, error) {
return p.dbHandler.Exec("command to insert person", p)
}
Служба получит этот репозиторий как зависимость (как интерфейс) в initailzer и будет взаимодействовать с ним для выполнения бизнес-логики
package service
type PersonRepo interface{
InsertPerson(person.Person) error
}
type service struct {
repo PersonRepo
}
func New(repo PersonRepo) *service {
return &service{
repo: repo
}
}
func (s *service) AddPerson(name string) (int64, error) {
person := person.New(name)
return s.repo.InsertPerson(person)
}
Ваш обработчик транспорта будет инициализирован со службой как зависимость, и он обработает запрос http.
package http
type Service interface{
AddPerson(name string) (int64, error)
}
type handler struct{
service Service
}
func NewHandler(s Service) *handler {
return &handler{
service: s,
}
}
func (h *handler) HandleHTTP(w http.ResponseWriter, r *http.Request) {
// read request
// decode name
id, err := h.service.AddPerson(name)
// write response
// ...
}
И в main.go вы все свяжете:
- Инициализировать соединение БД
- Инициализировать personRepo с этим соединением
- Инициализировать службу с репо
- Инициализировать транспорт с пакетом услуги
main
func main() {
pool := makePool()
conn := pool.GetConnection()
// repo
personRepo := sql.NewPersonRepo(conn)
// service
personService := service.New(personRepo)
// handler
personHandler := http.NewPersonHandler(personService)
// Do the rest of the stuff, init the http engine/router by passing this handler.
}
Обратите внимание, что каждая структура пакета была инициализирована с interface
, но вернула struct
, а также интерфейсы были объявлены в пакете, который их использовал, а не в пакете, который их реализовал.
Это облегчает модульное тестирование этих пакетов.например, если вы хотите протестировать сервис, вам не нужно беспокоиться о запросе http, просто используйте некоторую «фиктивную» структуру, которая реализует интерфейс, от которого зависит сервис (PersonRepo), и вы готовы идти ..
Что ж, я надеюсь, что это немного вам помогло, поначалу это может показаться странным, но со временем вы увидите, как это выглядит как большой кусок кода, но это помогает, когда вам нужно добавить функциональностьили переключение драйвера БД и тому подобное .. Я рекомендую вам прочитать о дизайне, управляемом доменом в go, а также шестиугольную арку.
edit:
Кроме того, таким образом вы проходите соединение с сервисом, сервис не не импортирует и не использует глобальный пул БД.Честно говоря, я не знаю, почему это так часто встречается, я полагаю, что у него есть свои преимущества, и оно лучше для какого-то приложения, но в целом я думаю, что позволить вашей службе зависеть от какого-то интерфейса, даже не зная, что происходит, гораздолучшая практика.