import
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UserService — интерфейс сервиса пользователей для хендлера.
|
||||
type UserService interface {
|
||||
GetProfile(token string) string
|
||||
}
|
||||
|
||||
// AuthService — интерфейс сервиса авторизации для хендлера.
|
||||
type AuthService interface {
|
||||
// методы, которые хендлер использует из сервиса авторизации
|
||||
}
|
||||
|
||||
// NotificationService — интерфейс сервиса уведомлений для хендлера.
|
||||
type NotificationService interface {
|
||||
// методы, которые хендлер использует из сервиса уведомлений
|
||||
}
|
||||
|
||||
// Handler — интерфейс HTTP-обработчика.
|
||||
// Содержит только хендлеры и роутинг.
|
||||
// Ничего не знает про http.Server, порт или lifecycle —
|
||||
// это ответственность app-слоя.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewHandler.
|
||||
type Handler interface {
|
||||
Routes() http.Handler
|
||||
}
|
||||
|
||||
// handler — обработчик HTTP-запросов.
|
||||
type handler struct {
|
||||
userService UserService
|
||||
authService AuthService
|
||||
notificationService NotificationService
|
||||
}
|
||||
|
||||
// NewHandler создаёт обработчик HTTP-запросов.
|
||||
func NewHandler(
|
||||
userService UserService,
|
||||
authService AuthService,
|
||||
notificationService NotificationService,
|
||||
) Handler {
|
||||
return &handler{
|
||||
userService: userService,
|
||||
authService: authService,
|
||||
notificationService: notificationService,
|
||||
}
|
||||
}
|
||||
|
||||
// Routes возвращает маршрутизатор со всеми зарегистрированными хендлерами.
|
||||
// App-слой использует этот http.Handler при создании http.Server.
|
||||
func (h *handler) Routes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", h.healthHandler)
|
||||
mux.HandleFunc("/users/me", h.getUserProfile)
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func (h *handler) healthHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if _, err := fmt.Fprintln(w, "ok"); err != nil {
|
||||
slog.Error("ошибка записи ответа", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getUserProfile — хендлер профиля пользователя.
|
||||
// Вызывает userService.GetProfile(), который внутри дёрнет authService.ValidateToken().
|
||||
// Если authService == nil (как в antipattern-broken) — тут будет nil pointer dereference.
|
||||
func (h *handler) getUserProfile(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
profile := h.userService.GetProfile(token)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if _, err := fmt.Fprintln(w, profile); err != nil {
|
||||
slog.Error("ошибка записи ответа", "err", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/olezhek28/di-demo/internal/config"
|
||||
)
|
||||
|
||||
// App — структура приложения.
|
||||
// Содержит DI-контейнер и HTTP-сервер.
|
||||
// API-слой отвечает за хендлеры и роутинг, а http.Server живёт здесь —
|
||||
// это инфраструктура, а не бизнес-логика.
|
||||
type App struct {
|
||||
diContainer *diContainer
|
||||
httpServer *http.Server
|
||||
}
|
||||
|
||||
// New создаёт приложение и инициализирует все зависимости через DI-контейнер.
|
||||
// Вся цепочка зависимостей разрешается здесь: от базы данных до HTTP-хендлеров.
|
||||
// Если какая-то зависимость не создалась — процесс завершится внутри контейнера.
|
||||
func New() *App {
|
||||
a := &App{
|
||||
diContainer: newDIContainer(),
|
||||
}
|
||||
|
||||
a.initDeps()
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// initDeps последовательно вызывает функции инициализации.
|
||||
// Если нужно добавить новый шаг (миграции, метрики и т.д.) —
|
||||
// просто добавить функцию в слайс inits.
|
||||
func (a *App) initDeps() {
|
||||
inits := []func(){
|
||||
a.initHTTPServer,
|
||||
}
|
||||
|
||||
for _, fn := range inits {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
// initHTTPServer создаёт HTTP-сервер.
|
||||
// API-слой (Handler) предоставляет только роутинг — Routes().
|
||||
// А http.Server с адресом и конфигом создаётся здесь, в app-слое.
|
||||
func (a *App) initHTTPServer() {
|
||||
a.httpServer = &http.Server{
|
||||
Addr: config.AppConfig().HTTPAddr,
|
||||
Handler: a.diContainer.Handler().Routes(),
|
||||
}
|
||||
}
|
||||
|
||||
// Run запускает HTTP-сервер.
|
||||
//
|
||||
// Сейчас просто запускаем и всё. Перехват сигналов, graceful shutdown,
|
||||
// закрытие ресурсов в правильном порядке — тема отдельного видео.
|
||||
func (a *App) Run() error {
|
||||
slog.Info("сервер запущен", "addr", config.AppConfig().HTTPAddr)
|
||||
|
||||
return a.httpServer.ListenAndServe()
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/olezhek28/di-demo/internal/api"
|
||||
"github.com/olezhek28/di-demo/internal/cache"
|
||||
"github.com/olezhek28/di-demo/internal/config"
|
||||
"github.com/olezhek28/di-demo/internal/database"
|
||||
"github.com/olezhek28/di-demo/internal/events"
|
||||
"github.com/olezhek28/di-demo/internal/repository"
|
||||
"github.com/olezhek28/di-demo/internal/service"
|
||||
)
|
||||
|
||||
// diContainer — контейнер зависимостей с ленивой инициализацией.
|
||||
//
|
||||
// Принцип работы:
|
||||
// Все поля начинаются как nil. При первом вызове геттера (например, DB())
|
||||
// зависимость создаётся и сохраняется в поле. При повторном вызове —
|
||||
// возвращается уже созданный экземпляр. Это гарантирует, что каждая
|
||||
// зависимость существует в единственном экземпляре (singleton в рамках приложения).
|
||||
//
|
||||
// Зачем это нужно:
|
||||
// В отличие от плоского main.go, где порядок строк определяет порядок инициализации,
|
||||
// здесь порядок определяется самим графом зависимостей. Каждый геттер вызывает
|
||||
// геттеры своих зависимостей — и они рекурсивно создаются автоматически.
|
||||
// Не нужно думать «что создать первым» — контейнер разберётся сам.
|
||||
//
|
||||
// Как добавить новую зависимость:
|
||||
// 1. Добавить поле в структуру
|
||||
// 2. Написать геттер с проверкой на nil
|
||||
// 3. Вызвать геттер из нужных мест — всё
|
||||
// Никакой перестановки строк в main.go.
|
||||
//
|
||||
// Почему поля — интерфейсы:
|
||||
// Каждый пакет экспортирует интерфейс (database.DB, cache.Cache и т.д.),
|
||||
// а конкретная реализация скрыта (неэкспортируемая структура).
|
||||
// Это гарантирует, что объект можно создать ТОЛЬКО через конструктор (New).
|
||||
// Нельзя написать &database.db{} — структура не видна за пределами пакета.
|
||||
// Контейнер зависит от абстракций, а не от конкретных типов.
|
||||
//
|
||||
// Почему геттеры не возвращают ошибку:
|
||||
// Если зависимость не создалась (например, база не подключилась) —
|
||||
// приложение всё равно не может работать. Нет смысла протаскивать ошибку
|
||||
// через 5 уровней вызовов — проще сразу завершить процесс.
|
||||
// Это упрощает код: геттеры становятся однострочными, без if err != nil.
|
||||
type diContainer struct {
|
||||
// Инфраструктура — базовые ресурсы, от которых зависит всё остальное.
|
||||
db database.DB
|
||||
cache cache.Cache
|
||||
eventBus events.EventBus
|
||||
|
||||
// Репозитории — слой доступа к данным. Зависят от инфраструктуры.
|
||||
userRepo repository.UserRepo
|
||||
sessionRepo repository.SessionRepo
|
||||
notificationRepo repository.NotificationRepo
|
||||
|
||||
// Сервисы — бизнес-логика. Зависят от репозиториев и друг от друга.
|
||||
authService service.AuthService
|
||||
userService service.UserService
|
||||
notificationService service.NotificationService
|
||||
|
||||
// API — хендлеры и роутинг. Зависит от сервисов.
|
||||
handler api.Handler
|
||||
}
|
||||
|
||||
// newDIContainer создаёт новый пустой контейнер.
|
||||
// Все поля nil — зависимости будут создаваться лениво при первом обращении.
|
||||
func newDIContainer() *diContainer {
|
||||
return &diContainer{}
|
||||
}
|
||||
|
||||
// DB возвращает подключение к базе данных.
|
||||
// Создаётся один раз при первом вызове. При повторном — возвращается тот же экземпляр.
|
||||
// Если подключение не удалось — завершаем процесс: без базы приложение бессмысленно.
|
||||
func (d *diContainer) DB() database.DB {
|
||||
if d.db == nil {
|
||||
db, err := database.New(config.AppConfig().DSN)
|
||||
if err != nil {
|
||||
slog.Error("не удалось подключиться к БД", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
d.db = db
|
||||
}
|
||||
|
||||
return d.db
|
||||
}
|
||||
|
||||
// EventBus возвращает шину событий.
|
||||
// Чистый pub/sub брокер без зависимостей — все сервисы зависят от него, а не наоборот.
|
||||
// В антипаттерне EventBus зависел от NotificationService (тупик с циклом).
|
||||
// Здесь — EventBus не зависит ни от кого, а сервисы подключаются к нему сами.
|
||||
func (d *diContainer) EventBus() events.EventBus {
|
||||
if d.eventBus == nil {
|
||||
d.eventBus = events.NewEventBus()
|
||||
}
|
||||
|
||||
return d.eventBus
|
||||
}
|
||||
|
||||
// Cache возвращает подключение к кэшу.
|
||||
// Создаётся один раз при первом вызове.
|
||||
func (d *diContainer) Cache() cache.Cache {
|
||||
if d.cache == nil {
|
||||
d.cache = cache.New(config.AppConfig().RedisAddr)
|
||||
}
|
||||
|
||||
return d.cache
|
||||
}
|
||||
|
||||
// UserRepo возвращает репозиторий пользователей.
|
||||
// Зависит от DB — если база ещё не создана, d.DB() создаст её автоматически.
|
||||
func (d *diContainer) UserRepo() repository.UserRepo {
|
||||
if d.userRepo == nil {
|
||||
d.userRepo = repository.NewUserRepo(d.DB())
|
||||
}
|
||||
|
||||
return d.userRepo
|
||||
}
|
||||
|
||||
// SessionRepo возвращает репозиторий сессий.
|
||||
// Зависит от DB и Cache — оба подтянутся автоматически при первом вызове.
|
||||
func (d *diContainer) SessionRepo() repository.SessionRepo {
|
||||
if d.sessionRepo == nil {
|
||||
d.sessionRepo = repository.NewSessionRepo(d.DB(), d.Cache())
|
||||
}
|
||||
|
||||
return d.sessionRepo
|
||||
}
|
||||
|
||||
// NotificationRepo возвращает репозиторий уведомлений.
|
||||
// Зависит от DB.
|
||||
func (d *diContainer) NotificationRepo() repository.NotificationRepo {
|
||||
if d.notificationRepo == nil {
|
||||
d.notificationRepo = repository.NewNotificationRepo(d.DB())
|
||||
}
|
||||
|
||||
return d.notificationRepo
|
||||
}
|
||||
|
||||
// AuthService возвращает сервис авторизации.
|
||||
// Зависит от UserRepo, SessionRepo, Cache и EventBus.
|
||||
// Все зависимости подтягиваются рекурсивно — если UserRepo ещё не создан,
|
||||
// он создастся, а для этого создастся DB, и так далее по цепочке.
|
||||
// EventBus добавился одной строкой — никакой перестановки, никаких тупиков.
|
||||
func (d *diContainer) AuthService() service.AuthService {
|
||||
if d.authService == nil {
|
||||
d.authService = service.NewAuthService(d.UserRepo(), d.SessionRepo(), d.Cache(), d.EventBus())
|
||||
}
|
||||
|
||||
return d.authService
|
||||
}
|
||||
|
||||
// UserService возвращает сервис пользователей.
|
||||
// Зависит от UserRepo и AuthService — перекрёстная зависимость между сервисами.
|
||||
// В плоском main.go это создавало проблему с порядком строк.
|
||||
// Здесь — просто вызываем d.AuthService(), и он рекурсивно создаст всё что нужно.
|
||||
func (d *diContainer) UserService() service.UserService {
|
||||
if d.userService == nil {
|
||||
d.userService = service.NewUserService(d.UserRepo(), d.AuthService(), d.EventBus())
|
||||
}
|
||||
|
||||
return d.userService
|
||||
}
|
||||
|
||||
// NotificationService возвращает сервис уведомлений.
|
||||
// Зависит от NotificationRepo и UserService.
|
||||
// UserService → AuthService → UserRepo → DB — вся цепочка разрешится автоматически.
|
||||
func (d *diContainer) NotificationService() service.NotificationService {
|
||||
if d.notificationService == nil {
|
||||
d.notificationService = service.NewNotificationService(d.NotificationRepo(), d.UserService(), d.EventBus())
|
||||
}
|
||||
|
||||
return d.notificationService
|
||||
}
|
||||
|
||||
// Handler возвращает HTTP-хендлер.
|
||||
// Вершина графа зависимостей — зависит от всех трёх сервисов.
|
||||
// Один вызов d.Handler() рекурсивно создаст ВСЕ зависимости приложения.
|
||||
// Хендлер отвечает только за роутинг и обработку запросов.
|
||||
// http.Server с адресом создаётся в app.go — это инфраструктура, а не API.
|
||||
func (d *diContainer) Handler() api.Handler {
|
||||
if d.handler == nil {
|
||||
d.handler = api.NewHandler(
|
||||
d.UserService(),
|
||||
d.AuthService(),
|
||||
d.NotificationService(),
|
||||
)
|
||||
}
|
||||
|
||||
return d.handler
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
package cache
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// Cache — интерфейс подключения к кэшу.
|
||||
// Структура неэкспортируемая — создать объект можно только через New.
|
||||
type Cache interface {
|
||||
Get(key string) (string, error)
|
||||
Set(key, value string) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// redisCache — обёртка над подключением к кэшу.
|
||||
type redisCache struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
// New создаёт подключение к кэшу.
|
||||
func New(addr string) Cache {
|
||||
slog.Info("подключились к кэшу", "addr", addr)
|
||||
return &redisCache{addr: addr}
|
||||
}
|
||||
|
||||
// Get получает значение по ключу.
|
||||
func (c *redisCache) Get(key string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Set устанавливает значение по ключу.
|
||||
func (c *redisCache) Set(key, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close закрывает подключение к кэшу.
|
||||
func (c *redisCache) Close() error {
|
||||
slog.Info("подключение к кэшу закрыто")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package config
|
||||
|
||||
// Config хранит конфигурацию приложения.
|
||||
type Config struct {
|
||||
DSN string
|
||||
RedisAddr string
|
||||
HTTPAddr string
|
||||
}
|
||||
|
||||
// appConfig — глобальный экземпляр конфигурации.
|
||||
var appConfig = &Config{
|
||||
DSN: "postgres://localhost:5432/demo",
|
||||
RedisAddr: "localhost:6379",
|
||||
HTTPAddr: ":8080",
|
||||
}
|
||||
|
||||
// AppConfig возвращает конфигурацию приложения.
|
||||
func AppConfig() *Config {
|
||||
return appConfig
|
||||
}
|
||||
|
||||
// New создаёт конфиг с дефолтными значениями.
|
||||
func New() *Config {
|
||||
return appConfig
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// DB — интерфейс подключения к базе данных.
|
||||
// Потребители зависят от этого интерфейса, а не от конкретной реализации.
|
||||
// Структура db неэкспортируемая — создать объект можно только через New.
|
||||
type DB interface {
|
||||
Query(query string) error
|
||||
QueryRow(query string) error
|
||||
Exec(query string) error
|
||||
BeginTx() error
|
||||
BulkInsert(query string, args ...any) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// db — обёртка над подключением к базе данных.
|
||||
type db struct {
|
||||
dsn string
|
||||
}
|
||||
|
||||
// New создаёт подключение к базе данных.
|
||||
func New(dsn string) (DB, error) {
|
||||
if dsn == "" {
|
||||
return nil, fmt.Errorf("dsn пустой")
|
||||
}
|
||||
|
||||
slog.Info("подключились к базе данных", "dsn", dsn)
|
||||
return &db{dsn: dsn}, nil
|
||||
}
|
||||
|
||||
// Query выполняет запрос на чтение.
|
||||
func (d *db) Query(query string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryRow выполняет запрос, возвращающий одну строку.
|
||||
func (d *db) QueryRow(query string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec выполняет запрос на запись.
|
||||
func (d *db) Exec(query string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeginTx начинает транзакцию.
|
||||
func (d *db) BeginTx() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BulkInsert выполняет массовую вставку.
|
||||
func (d *db) BulkInsert(query string, args ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close закрывает подключение к базе данных.
|
||||
func (d *db) Close() error {
|
||||
slog.Info("подключение к базе данных закрыто")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package events
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// EventBus — интерфейс шины событий приложения.
|
||||
// Чистый pub/sub брокер без внешних зависимостей.
|
||||
// Сервисы сами публикуют события и подписываются на них.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewEventBus.
|
||||
type EventBus interface {
|
||||
Publish(event string)
|
||||
Subscribe(event string, handler func())
|
||||
}
|
||||
|
||||
// eventBus — реализация шины событий.
|
||||
type eventBus struct{}
|
||||
|
||||
// NewEventBus создаёт шину событий.
|
||||
func NewEventBus() EventBus {
|
||||
slog.Info("шина событий создана")
|
||||
|
||||
return &eventBus{}
|
||||
}
|
||||
|
||||
// Publish публикует событие.
|
||||
func (b *eventBus) Publish(event string) {
|
||||
slog.Info("событие опубликовано", "event", event)
|
||||
}
|
||||
|
||||
// Subscribe подписывается на событие.
|
||||
func (b *eventBus) Subscribe(event string, handler func()) {
|
||||
slog.Info("подписка на событие", "event", event)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package repository
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// NotificationDB — интерфейс базы данных, который нужен NotificationRepo.
|
||||
// Включает BulkInsert — для массовой вставки уведомлений.
|
||||
type NotificationDB interface {
|
||||
Query(query string) error
|
||||
Exec(query string) error
|
||||
BulkInsert(query string, args ...any) error
|
||||
}
|
||||
|
||||
// NotificationRepo — интерфейс репозитория уведомлений.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewNotificationRepo.
|
||||
type NotificationRepo interface{}
|
||||
|
||||
// notificationRepo — репозиторий уведомлений.
|
||||
type notificationRepo struct {
|
||||
db NotificationDB
|
||||
}
|
||||
|
||||
// NewNotificationRepo создаёт репозиторий уведомлений.
|
||||
func NewNotificationRepo(db NotificationDB) NotificationRepo {
|
||||
slog.Info("репозиторий уведомлений создан")
|
||||
return ¬ificationRepo{db: db}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package repository
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// SessionDB — интерфейс базы данных, который нужен SessionRepo.
|
||||
// Включает BeginTx — для атомарного создания/удаления сессий.
|
||||
type SessionDB interface {
|
||||
Query(query string) error
|
||||
Exec(query string) error
|
||||
BeginTx() error
|
||||
}
|
||||
|
||||
// SessionCache — интерфейс кэша, который нужен SessionRepo.
|
||||
type SessionCache interface {
|
||||
Get(key string) (string, error)
|
||||
Set(key, value string) error
|
||||
}
|
||||
|
||||
// SessionRepo — интерфейс репозитория сессий.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewSessionRepo.
|
||||
type SessionRepo interface{}
|
||||
|
||||
// sessionRepo — репозиторий сессий.
|
||||
type sessionRepo struct {
|
||||
db SessionDB
|
||||
cache SessionCache
|
||||
}
|
||||
|
||||
// NewSessionRepo создаёт репозиторий сессий.
|
||||
func NewSessionRepo(db SessionDB, cache SessionCache) SessionRepo {
|
||||
slog.Info("репозиторий сессий создан")
|
||||
return &sessionRepo{db: db, cache: cache}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package repository
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// UserDB — интерфейс базы данных, который нужен UserRepo.
|
||||
// Включает QueryRow — для поиска одного пользователя по ID/email.
|
||||
type UserDB interface {
|
||||
Query(query string) error
|
||||
QueryRow(query string) error
|
||||
Exec(query string) error
|
||||
}
|
||||
|
||||
// UserRepo — интерфейс репозитория пользователей.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewUserRepo.
|
||||
type UserRepo interface{}
|
||||
|
||||
// userRepo — репозиторий пользователей.
|
||||
type userRepo struct {
|
||||
db UserDB
|
||||
}
|
||||
|
||||
// NewUserRepo создаёт репозиторий пользователей.
|
||||
func NewUserRepo(db UserDB) UserRepo {
|
||||
slog.Info("репозиторий пользователей создан")
|
||||
return &userRepo{db: db}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package service
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// AuthUserRepository — интерфейс репозитория пользователей для AuthService.
|
||||
type AuthUserRepository interface {
|
||||
// методы, которые AuthService использует из репозитория пользователей
|
||||
}
|
||||
|
||||
// AuthSessionRepository — интерфейс репозитория сессий для AuthService.
|
||||
type AuthSessionRepository interface {
|
||||
// методы, которые AuthService использует из репозитория сессий
|
||||
}
|
||||
|
||||
// AuthCache — интерфейс кэша для AuthService (хранит токены).
|
||||
type AuthCache interface {
|
||||
Get(key string) (string, error)
|
||||
Set(key, value string) error
|
||||
}
|
||||
|
||||
// AuthEventPublisher — интерфейс для публикации событий авторизации.
|
||||
type AuthEventPublisher interface {
|
||||
Publish(event string)
|
||||
}
|
||||
|
||||
// AuthService — интерфейс сервиса авторизации.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewAuthService.
|
||||
type AuthService interface {
|
||||
ValidateToken(token string) bool
|
||||
}
|
||||
|
||||
// authService — сервис авторизации.
|
||||
type authService struct {
|
||||
userRepo AuthUserRepository
|
||||
sessionRepo AuthSessionRepository
|
||||
cache AuthCache
|
||||
events AuthEventPublisher
|
||||
}
|
||||
|
||||
// NewAuthService создаёт сервис авторизации.
|
||||
func NewAuthService(
|
||||
userRepo AuthUserRepository,
|
||||
sessionRepo AuthSessionRepository,
|
||||
cache AuthCache,
|
||||
events AuthEventPublisher,
|
||||
) AuthService {
|
||||
slog.Info("сервис авторизации создан")
|
||||
return &authService{
|
||||
userRepo: userRepo,
|
||||
sessionRepo: sessionRepo,
|
||||
cache: cache,
|
||||
events: events,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateToken проверяет токен авторизации.
|
||||
// Проверяет наличие токена в кэше — именно тут взорвётся nil,
|
||||
// если кэш не был правильно инициализирован (как в antipattern-broken).
|
||||
func (s *authService) ValidateToken(token string) bool {
|
||||
slog.Info("проверяем токен", "token", token)
|
||||
|
||||
// Проверяем токен в кэше. Если cache == nil — паника.
|
||||
_, err := s.cache.Get(token)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package service
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// NotificationRepository — интерфейс репозитория уведомлений для NotificationService.
|
||||
type NotificationRepository interface {
|
||||
// методы, которые NotificationService использует из репозитория уведомлений
|
||||
}
|
||||
|
||||
// NotificationUserService — интерфейс сервиса пользователей для NotificationService.
|
||||
type NotificationUserService interface {
|
||||
// методы, которые NotificationService использует из сервиса пользователей
|
||||
}
|
||||
|
||||
// NotificationEventSubscriber — интерфейс для подписки на события.
|
||||
type NotificationEventSubscriber interface {
|
||||
Subscribe(event string, handler func())
|
||||
}
|
||||
|
||||
// NotificationService — интерфейс сервиса уведомлений.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewNotificationService.
|
||||
type NotificationService interface{}
|
||||
|
||||
// notificationService — сервис уведомлений.
|
||||
type notificationService struct {
|
||||
notificationRepo NotificationRepository
|
||||
userService NotificationUserService
|
||||
events NotificationEventSubscriber
|
||||
}
|
||||
|
||||
// NewNotificationService создаёт сервис уведомлений.
|
||||
func NewNotificationService(
|
||||
notificationRepo NotificationRepository,
|
||||
userService NotificationUserService,
|
||||
events NotificationEventSubscriber,
|
||||
) NotificationService {
|
||||
slog.Info("сервис уведомлений создан")
|
||||
return ¬ificationService{
|
||||
notificationRepo: notificationRepo,
|
||||
userService: userService,
|
||||
events: events,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package service
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// UserRepository — интерфейс репозитория пользователей для UserService.
|
||||
type UserRepository interface {
|
||||
// методы, которые UserService использует из репозитория пользователей
|
||||
}
|
||||
|
||||
// UserAuthService — интерфейс сервиса авторизации для UserService.
|
||||
type UserAuthService interface {
|
||||
ValidateToken(token string) bool
|
||||
}
|
||||
|
||||
// UserEventPublisher — интерфейс для публикации событий пользователей.
|
||||
type UserEventPublisher interface {
|
||||
Publish(event string)
|
||||
}
|
||||
|
||||
// UserService — интерфейс сервиса пользователей.
|
||||
// Структура неэкспортируемая — создать объект можно только через NewUserService.
|
||||
type UserService interface {
|
||||
GetProfile(token string) string
|
||||
}
|
||||
|
||||
// userService — сервис пользователей.
|
||||
type userService struct {
|
||||
userRepo UserRepository
|
||||
authService UserAuthService
|
||||
events UserEventPublisher
|
||||
}
|
||||
|
||||
// NewUserService создаёт сервис пользователей.
|
||||
func NewUserService(userRepo UserRepository, authService UserAuthService, events UserEventPublisher) UserService {
|
||||
slog.Info("сервис пользователей создан")
|
||||
return &userService{
|
||||
userRepo: userRepo,
|
||||
authService: authService,
|
||||
events: events,
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfile возвращает профиль текущего пользователя.
|
||||
// Сначала проверяет токен через authService — и именно тут взорвётся nil,
|
||||
// если authService не был правильно инициализирован.
|
||||
func (s *userService) GetProfile(token string) string {
|
||||
if !s.authService.ValidateToken(token) {
|
||||
return "unauthorized"
|
||||
}
|
||||
|
||||
return "user profile data"
|
||||
}
|
||||
Reference in New Issue
Block a user