Я использую такой подход:
1) Хендлеры занимаются лишь обработкой входных параметров от клиента, например, извлечение параметров, вывод ошибок пользователю;
2) Бизнес-логика вынесена в отдельный пакет или класс;
3) Запросы во внешние хранилища тоже в отдельном пакете;
4) Передача соединений бд или хранилищ только через контекст с помощью миддлвари.
Пример структуры проекта:
| + api
| - - router.go
| + models
| - - modelA.go
| - - modelB.go
| + app
| - - taskA.go
| - - taskB.go
| + database
| - - requests.go
| main.go
main.go:
package main
import "api"
func main(){
...
r := api.NewRouter(...)
http.ListenAndServe(...)
}
api/router.go:
package api
import (
"app"
"models"
)
func NewRouter(...) ...{
// Инициализация роутера, регистрация обработчиков и т.д.
...
return r
}
func handleEndpointA(w http.ResponseWriter, r *http.Request){
// Извлечение, валидация параметров.
// Извлечение соединений БД и друхих хранилищ из контекста.
// Обработка ошибок.
...
result, err := app.DoTaskA(ctx, db, paramA, paramB)
...
render(w, result)
}
func handleEndpointB(w http.ResponseWriter, r *http.Request){
...
result, err := app.DoTaskB(ctx, db, paramC)
...
render(w, result)
}
app/taskA.go:
package app
import(
"database"
"models"
)
func DoTaskA(ctx, db, paramA, paramB) (result, err){
// Какие-то манипуляции с параметрами.
...
// Запрос в бд.
modelA, err := database.RequestA(ctx, db, param)
...
return result, err
}
database/requests.go:
package database
import (
"models"
)
func RequestA(ctx, db, param) (result, err) {
// Не забываем поставить таймаут на запрос к БД.
ctx, cancel = context.WithTimeout(ctx, defaultRequestTimeout)
defer cancel()
...
result, err := db.DoRequest(ctx, ...)
...
return result, err
}