Ответ
Транспортный слой отвечает за взаимодействие приложения с внешним миром (например, прием HTTP-запросов или gRPC-вызовов). Его принято изолировать от бизнес-логики (сервисного слоя).
Расположение в проекте
Чаще всего его размещают в директории internal, чтобы избежать импорта из других проектов. Популярные названия:
internal/transport(общее название)internal/handlerилиinternal/handlersinternal/api
Внутри этих папок создают поддиректории для каждого типа транспорта:
/my-project
└── internal/
├── service/ # Бизнес-логика
└── transport/ # Транспортный слой
├── http/
│ ├── handler.go # Обработчики
│ ├── middleware.go # Middleware
│ └── router.go # Регистрация роутов
└── grpc/
└── server.go
Роль и обязанности транспортного слоя:
- Прием и разбор (parsing) входящих запросов (HTTP, gRPC и т.д.).
- Валидация данных запроса (заголовки, тело, параметры).
- Преобразование (mapping) данных из формата запроса (например, JSON) во внутренние структуры (DTO или модели для сервисного слоя).
- Вызов соответствующего метода сервисного слоя с передачей ему валидированных данных.
- Обработка ошибок, возвращаемых сервисным слоем, и преобразование их в корректные ответы (например, HTTP-статусы 400, 404, 500).
- Сериализация данных ответа (например, в JSON) и отправка клиенту.
Пример HTTP-обработчика:
package http
import "net/http"
// service - интерфейс бизнес-логики, который мы будем вызывать.
type service interface {
GetUser(id int) (*User, error)
}
type Handler struct {
service service
}
func NewHandler(s service) *Handler {
return &Handler{service: s}
}
// GetUser обрабатывает запрос на получение пользователя.
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
// 1. Парсинг и валидация ID из URL
id, err := parseID(r)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
// 2. Вызов бизнес-логики
user, err := h.service.GetUser(id)
if err != nil {
// 3. Обработка ошибок от сервиса
http.Error(w, "User not found", http.StatusNotFound)
return
}
// 4. Сериализация и отправка ответа
respondJSON(w, http.StatusOK, user)
} Ответ 18+ 🔞
А, слушай, вот это тема, про транспортный слой! Ну, это как будто твой дом, а этот слой — это такой консьерж ёбаный, или, там, швейцар в перчатках. Его задача — не пустить в твою квартиру (то есть, в бизнес-логику) всяких пиздюков с улицы, которые в грязных ботинках и с кривыми запросами.
Расположение в проекте
Вот смотри, чтобы этот швейцар не разбежался по всему городу, его обычно сажают в директорию internal. Это типа закрытый клуб, для своих. Называть его можно по-разному, но суть одна:
/my-project
└── internal/
├── service/ # Тут у нас святая святых, бизнес-логика, где деньги считают
└── transport/ # А это — проходная, КПП, ёпта!
├── http/ # Для тех, кто через браузер лезет
│ ├── handler.go # Охранники, которые проверяют пропуска
│ ├── middleware.go # Это как рамки металлоискателя, все проходят
│ └── router.go # Расписание, кто куда идёт
└── grpc/ # Для своих, по закрытому каналу связи
└── server.go
Роль и обязанности этого самого швейцара-транспорта:
- Встретить и обнюхать. Принять запрос — HTTP, gRPC, да хоть голубиной почтой. Разобрать, что этот чувак вообще хочет.
- Проверить документы. Валидация, блядь! А тот ли это JSON? А тот ли заголовок? А не пришёл ли он пьяный? (статус 400 — Bad Request, иди проспись).
- Перевести с птичьего на человеческий. Преобразовать эти кривые данные из запроса во что-то понятное для твоего сервиса. Не тащить же в бизнес-логику сырой JSON, это ж моветон, ёпта!
- Позвать нужного человека. Вызвать соответствующий метод из
serviceи сказать: «Вот, Иван Иваныч, разберитесь с этим гражданином». - Уладить скандалы. Если сервис вернул ошибку («пользователь не найден», «денег нет»), транспорт должен не орать «ой всё!», а культурно сказать клиенту: «Извините, 404» или «Пшел вон, 403».
- Упаковать ответ и выпроводить. Взять результат от сервиса, аккуратно завернуть в JSON (или что там надо), дать статус 200 и вежливо закрыть дверь.
Вот, смотри, как это выглядит в коде, на примере охранника на HTTP-проходной:
package http
import "net/http"
// service — это наш главный по складу, который в internal/service сидит.
type service interface {
GetUser(id int) (*User, error)
}
type Handler struct {
service service // Связь с главным!
}
func NewHandler(s service) *Handler {
return &Handler{service: s}
}
// GetUser — наш конкретный охранник на посту.
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
// 1. Парсинг и валидация. Ну-ка, предъяви ID!
id, err := parseID(r)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest) // Неправильный пропуск, иди нахуй!
return
}
// 2. Звоним главному: «Босс, тут чел с ID таким-то пришёл».
user, err := h.service.GetUser(id)
if err != nil {
// 3. Босс сказал «нет такого». Сообщаем клиенту.
http.Error(w, "User not found", http.StatusNotFound) // Нет тебя в списках, свободен!
return
}
// 4. Всё чики-пуки, упаковываем данные и отдаём.
respondJSON(w, http.StatusOK, user) // Вот твой пропуск, проходи.
}
Вот и вся магия, ебать мои старые костыли. Транспорт — это не умный, он просто посредник. Вся соль должна быть внутри, в сервисах. А этот слой — просто обёрточная бумага, чтобы не тыкать клиентам в лицо сырыми интерфейсами базы данных. Чистота архитектуры, ёпта!