example

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package example демонстрирует полную структуру пакета в Clean Architecture.

Этот пакет — эталонный пример для команд, использующих portsmith. Каждый файл отвечает за строго один слой. Зависимости направлены только внутрь:

Handler → ServicePort ← Service → RepositoryPort ← Repository

Файлы в порядке от ядра наружу:

domain.go      — доменные типы (ядро, ноль зависимостей)
errors.go      — доменные ошибки
ports.go       — интерфейсы-порты (обычно генерируется portsmith gen)
service.go     — бизнес-логика
repository.go  — реализация хранилища
handler.go     — HTTP-адаптер
dto.go         — структуры запросов и ответов
mappers.go     — преобразование domain ↔ DTO

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUserNotFound возвращается когда пользователь не найден по ID или email.
	ErrUserNotFound = apperrors.NotFound("user not found")

	// ErrEmailTaken возвращается при попытке создать пользователя
	// с email, который уже занят.
	ErrEmailTaken = apperrors.Conflict("email already taken")

	// ErrCannotDeactivateSelf возвращается когда администратор
	// пытается деактивировать собственный аккаунт.
	ErrCannotDeactivateSelf = apperrors.BadRequest("cannot deactivate your own account")

	// ErrInsufficientPermissions возвращается при попытке выполнить
	// операцию, требующую прав администратора.
	ErrInsufficientPermissions = apperrors.Forbidden("insufficient permissions")
)

Functions

This section is empty.

Types

type CreateParams

type CreateParams struct {
	Email string
	Name  string
	Role  UserRole
}

CreateParams — входные данные для создания пользователя. Используется сервисом: handler преобразует DTO → CreateParams, сервис принимает CreateParams → вызывает репозиторий.

Почему не передавать DTO прямо в сервис? Сервис не должен знать про HTTP-специфику (json-теги, validator-теги). CreateParams — это чистый "язык" бизнес-операции.

type CreateUserRequest

type CreateUserRequest struct {
	Email string   `json:"email" binding:"required,email"`
	Name  string   `json:"name"  binding:"required,min=2,max=100"`
	Role  UserRole `json:"role"  binding:"omitempty,oneof=user admin"`
}

CreateUserRequest — тело POST /users.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler реализует HTTP-обработчики для пользователей.

func NewHandler

func NewHandler(service UserService) *Handler

NewHandler создаёт новый Handler.

func (*Handler) Routes

func (h *Handler) Routes(rg *gin.RouterGroup)

Routes регистрирует маршруты в переданной группе Gin. Вызывается из main.go или при настройке сервера.

Пример:

v1 := srv.Router().Group("/api/v1")
userHandler.Routes(v1)

type ListFilter

type ListFilter struct {
	Role   *UserRole
	Active *bool
}

ListFilter — параметры фильтрации для запросов списка.

type ListUsersResponse

type ListUsersResponse struct {
	Items []*UserResponse `json:"items"`
	Total int64           `json:"total"`
	Page  int             `json:"page"`
	Limit int             `json:"limit"`
}

ListUsersResponse — ответ на GET /users со списком и метаданными пагинации.

type Repository

type Repository struct {
	// contains filtered or unexported fields
}

Repository реализует UserRepository поверх GORM.

func NewRepository

func NewRepository(db *gorm.DB) *Repository

NewRepository создаёт новый Repository.

func (*Repository) Create

func (r *Repository) Create(ctx context.Context, user *User) error

Create сохраняет нового пользователя в базе.

func (*Repository) Delete

func (r *Repository) Delete(ctx context.Context, id uint) error

Delete удаляет пользователя по ID (soft delete если у модели есть DeletedAt).

func (*Repository) FindByEmail

func (r *Repository) FindByEmail(ctx context.Context, email string) (*User, error)

FindByEmail ищет пользователя по email.

func (*Repository) FindByID

func (r *Repository) FindByID(ctx context.Context, id uint) (*User, error)

FindByID возвращает пользователя по первичному ключу. Возвращает ErrUserNotFound если запись не найдена — не gorm.ErrRecordNotFound.

func (*Repository) List

func (r *Repository) List(ctx context.Context, filter ListFilter, page pagination.OffsetPage) ([]*User, int64, error)

List возвращает страницу пользователей с опциональной фильтрацией.

func (*Repository) Update

func (r *Repository) Update(ctx context.Context, user *User) error

Update сохраняет изменения существующего пользователя.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service реализует бизнес-логику управления пользователями.

func NewService

func NewService(repo UserRepository) *Service

NewService создаёт новый Service. Принимает интерфейс — легко тестируется.

func (*Service) Create

func (s *Service) Create(ctx context.Context, params CreateParams) (*User, error)

Create создаёт нового пользователя.

Бизнес-правила:

  • Email должен быть уникальным (проверяем через репозиторий).
  • Если роль не указана — назначаем RoleUser по умолчанию.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, id uint) error

Delete удаляет пользователя по ID.

func (*Service) GetByID

func (s *Service) GetByID(ctx context.Context, id uint) (*User, error)

GetByID возвращает пользователя по первичному ключу.

func (*Service) List

func (s *Service) List(ctx context.Context, filter ListFilter, page pagination.OffsetPage) ([]*User, int64, error)

List возвращает страницу пользователей с фильтрацией и пагинацией.

func (*Service) Update

func (s *Service) Update(ctx context.Context, id uint, params UpdateParams, callerID uint) (*User, error)

Update обновляет поля пользователя.

Бизнес-правила:

  • Обычный пользователь не может менять свою роль.
  • Нельзя деактивировать самого себя (callerID == id && Active = false).

type UpdateParams

type UpdateParams struct {
	Name   *string
	Role   *UserRole
	Active *bool
}

UpdateParams — входные данные для частичного обновления. Указатели означают "поле передано" / "поле не передано" (partial update).

type UpdateUserRequest

type UpdateUserRequest struct {
	Name   *string   `json:"name"   binding:"omitempty,min=2,max=100"`
	Role   *UserRole `json:"role"   binding:"omitempty,oneof=user admin"`
	Active *bool     `json:"active"`
}

UpdateUserRequest — тело PATCH /users/:id. Указатели позволяют отличить "поле не передано" от "передано пустое значение".

type User

type User struct {
	ID        uint     `gorm:"primaryKey"`
	Email     string   `gorm:"uniqueIndex;not null"`
	Name      string   `gorm:"not null"`
	Role      UserRole `gorm:"default:'user'"`
	Active    bool     `gorm:"default:true"`
	CreatedAt time.Time
	UpdatedAt time.Time
}

User — центральная доменная сущность пакета.

Правило: domain.go не импортирует ни database/sql, ни net/http, ни gorm. Это чистые Go-типы. Их можно безопасно передавать между слоями.

GORM-теги (`gorm:"..."`) — компромисс ради AutoMigrate. В строго чистой архитектуре DB-модель была бы отдельным типом в repository.go, а здесь лежали бы только бизнес-поля. Для большинства проектов тегов на домене достаточно — разделяй при необходимости.

type UserRepository

type UserRepository interface {
	Create(ctx context.Context, user *User) error
	FindByID(ctx context.Context, id uint) (*User, error)
	FindByEmail(ctx context.Context, email string) (*User, error)
	Update(ctx context.Context, user *User) error
	Delete(ctx context.Context, id uint) error
	List(ctx context.Context, filter ListFilter, page pagination.OffsetPage) ([]*User, int64, error)
}

UserRepository — порт хранилища, используемый сервисом.

Service зависит от этого интерфейса, а не от конкретного *Repository. Это позволяет тестировать сервис без базы данных — достаточно мок-репозитория.

Compile-time проверка: var _ UserRepository = (*Repository)(nil) Если Repository не реализует интерфейс — ошибка компиляции, а не runtime-паника.

type UserResponse

type UserResponse struct {
	ID     uint     `json:"id"`
	Email  string   `json:"email"`
	Name   string   `json:"name"`
	Role   UserRole `json:"role"`
	Active bool     `json:"active"`
}

UserResponse — представление пользователя в API-ответе. Чувствительные данные (пароли, токены) намеренно исключены.

type UserRole

type UserRole string

UserRole — перечисление ролей. Определяется в домене, не в БД.

const (
	RoleUser  UserRole = "user"
	RoleAdmin UserRole = "admin"
)

type UserService

type UserService interface {
	Create(ctx context.Context, params CreateParams) (*User, error)
	GetByID(ctx context.Context, id uint) (*User, error)
	Update(ctx context.Context, id uint, params UpdateParams, callerID uint) (*User, error)
	Delete(ctx context.Context, id uint) error
	List(ctx context.Context, filter ListFilter, page pagination.OffsetPage) ([]*User, int64, error)
}

UserService — порт бизнес-логики, используемый хендлером.

Handler зависит от этого интерфейса, а не от конкретного *Service. Позволяет тестировать хендлер без сервиса и без базы данных.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL