- Реализована JWT аутентификация с organization-scope - Добавлено хеширование паролей через bcrypt - Созданы репозитории для организаций и пользователей - Реализован AuthService с бизнес-логикой - Добавлен AuthMiddleware для проверки токенов - Созданы handlers для регистрации и входа - Обновлён API сервер для использования аутентификации Готово для этапа 3 - API структура
18 KiB
18 KiB
Детальный план разработки Core Service
🎯 Цель
Реализация Core Service для ERP MVP с упрощённой архитектурой: Go + PostgreSQL + REST API без gRPC, Redis и Document Service.
📋 Общие принципы
- REST API вместо gRPC
- PostgreSQL как единственная БД (без Redis)
- JWT аутентификация с organization-scope
- Структурированное логирование (без Prometheus на MVP)
- Валидация данных на всех уровнях
🚀 Этап 1: Фундамент (Недели 1-2)
Шаг 1.1: Очистка и настройка проекта
- Удалить зависимости:
grpc,redis,prometheus - Обновить
go.mod- оставить только необходимые пакеты - Настроить структуру проекта согласно Go standards
- Добавить
.envдля конфигурации
Файлы для изменения:
go.mod - удалить grpc, redis, prometheus ✅
cmd/main.go - убрать redis, grpc клиенты ✅
internal/config/config.go - упростить конфигурацию ✅
Шаг 1.2: Базовая конфигурация
- Создать
internal/config/config.goс упрощённой структурой - Добавить поддержку
.envфайлов - Настроить логирование через logrus
- Добавить health check endpoint
Структура конфигурации:
type Config struct {
Server ServerConfig
Database DatabaseConfig
JWT JWTConfig
}
type ServerConfig struct {
Port string
Host string
}
type DatabaseConfig struct {
Host string
Port string
User string
Password string
DBName string
SSLMode string
}
type JWTConfig struct {
Secret string
TTL time.Duration
}
Шаг 1.3: Подключение к базе данных
- Создать
internal/database/connection.go - Настроить подключение к PostgreSQL
- Добавить миграции через
golang-migrate - Создать базовые таблицы
Структура БД (упрощённая):
-- organizations
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
settings JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'user',
created_at TIMESTAMP DEFAULT NOW()
);
-- storage_locations
CREATE TABLE storage_locations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
parent_id UUID REFERENCES storage_locations(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
address VARCHAR(100) NOT NULL,
type VARCHAR(50) NOT NULL,
coordinates JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- items
CREATE TABLE items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
-- item_placements
CREATE TABLE item_placements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
item_id UUID REFERENCES items(id) ON DELETE CASCADE,
location_id UUID REFERENCES storage_locations(id) ON DELETE CASCADE,
quantity INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT NOW()
);
Шаг 1.4: Базовые модели
- Создать
internal/models/с основными структурами - Добавить валидацию через
validator - Реализовать JSON теги для API
Основные модели:
// internal/models/organization.go
type Organization struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" validate:"required"`
Type string `json:"type"`
Settings JSON `json:"settings"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// internal/models/user.go
type User struct {
ID uuid.UUID `json:"id" db:"id"`
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
Email string `json:"email" validate:"required,email"`
PasswordHash string `json:"-" db:"password_hash"`
Role string `json:"role"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// internal/models/storage_location.go
type StorageLocation struct {
ID uuid.UUID `json:"id" db:"id"`
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
ParentID *uuid.UUID `json:"parent_id,omitempty" db:"parent_id"`
Name string `json:"name" validate:"required"`
Address string `json:"address" validate:"required"`
Type string `json:"type" validate:"required"`
Coordinates JSON `json:"coordinates"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// internal/models/item.go
type Item struct {
ID uuid.UUID `json:"id" db:"id"`
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
Name string `json:"name" validate:"required"`
Description string `json:"description"`
Category string `json:"category"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// internal/models/item_placement.go
type ItemPlacement struct {
ID uuid.UUID `json:"id" db:"id"`
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
ItemID uuid.UUID `json:"item_id" db:"item_id"`
LocationID uuid.UUID `json:"location_id" db:"location_id"`
Quantity int `json:"quantity" validate:"min=1"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
🔐 Этап 2: Аутентификация (Неделя 2)
Шаг 2.1: JWT аутентификация
- Создать
internal/auth/jwt.go - Реализовать генерацию и валидацию JWT токенов
- Добавить organization-scope в токены
- Создать middleware для проверки аутентификации
JWT структура:
type Claims struct {
UserID uuid.UUID `json:"user_id"`
OrganizationID uuid.UUID `json:"organization_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
Шаг 2.2: Хеширование паролей
- Создать
internal/auth/password.go - Использовать bcrypt для хеширования
- Добавить функции проверки паролей
Шаг 2.3: API endpoints для аутентификации
POST /api/auth/register- регистрация организации и пользователяPOST /api/auth/login- вход в системуPOST /api/auth/refresh- обновление токена (опционально)
Структура запросов:
type RegisterRequest struct {
OrganizationName string `json:"organization_name" validate:"required"`
UserEmail string `json:"user_email" validate:"required,email"`
UserPassword string `json:"user_password" validate:"required,min=8"`
OrganizationType string `json:"organization_type"`
}
type LoginRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
🏗️ Этап 3: API структура (Неделя 3)
Шаг 3.1: Базовые handlers
- Создать
internal/api/handlers/с базовыми структурами - Реализовать middleware для CORS, логирования, аутентификации
- Добавить обработку ошибок
Структура handlers:
internal/api/
├── handlers/
│ ├── auth.go
│ ├── organizations.go
│ ├── locations.go
│ ├── items.go
│ └── operations.go
├── middleware/
│ ├── auth.go
│ ├── cors.go
│ ├── logging.go
│ └── error_handler.go
└── server.go
Шаг 3.2: Repository pattern
- Создать
internal/repository/для работы с БД - Реализовать CRUD операции для всех сущностей
- Добавить organization-scope фильтрацию
Основные репозитории:
// internal/repository/organizations.go
type OrganizationRepository interface {
Create(ctx context.Context, org *models.Organization) error
GetByID(ctx context.Context, id uuid.UUID) (*models.Organization, error)
Update(ctx context.Context, org *models.Organization) error
}
// internal/repository/users.go
type UserRepository interface {
Create(ctx context.Context, user *models.User, password string) error
GetByEmail(ctx context.Context, email string) (*models.User, error)
GetByID(ctx context.Context, id uuid.UUID) (*models.User, error)
}
// internal/repository/locations.go
type LocationRepository interface {
Create(ctx context.Context, location *models.StorageLocation) error
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.StorageLocation, error)
GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error)
Update(ctx context.Context, location *models.StorageLocation) error
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
}
// internal/repository/items.go
type ItemRepository interface {
Create(ctx context.Context, item *models.Item) error
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.Item, error)
GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.Item, error)
Search(ctx context.Context, orgID uuid.UUID, query string) ([]*models.Item, error)
Update(ctx context.Context, item *models.Item) error
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
}
Шаг 3.3: Service layer
- Создать
internal/service/для бизнес-логики - Реализовать валидацию и обработку данных
- Добавить транзакции для сложных операций
📍 Этап 4: Шаблоны помещений (Неделя 4)
Шаг 4.1: Система шаблонов
- Создать
internal/templates/для шаблонов помещений - Реализовать 3 базовых шаблона: Гараж, Мастерская, Склад
- Добавить генерацию адресов мест
Шаблоны:
type Template struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Zones []Zone `json:"zones"`
AddressRules []AddressRule `json:"address_rules"`
}
type Zone struct {
Name string `json:"name"`
Type string `json:"type"` // "cabinet", "floor", "table"
Rows int `json:"rows"`
Columns int `json:"columns"`
Position Position `json:"position"`
}
type AddressRule struct {
ZoneType string `json:"zone_type"`
Prefix string `json:"prefix"`
Format string `json:"format"` // "Ш{zone}-П{row}-Я{col}"
}
Шаг 4.2: API для шаблонов
GET /api/templates- список доступных шаблоновPOST /api/templates/:id/apply- применение шаблона к организации- Генерация мест хранения из шаблона
Шаг 4.3: Адресация мест
- Реализовать систему адресации (Ш1-П2-Я3, З1-У2)
- Автоматическая генерация адресов при создании мест
- Валидация уникальности адресов в рамках организации
🔍 Этап 5: Операции (Неделя 5)
Шаг 5.1: Размещение товаров
POST /api/operations/place-item- размещение товара- Валидация доступности места
- Обновление статуса места
- Логирование операций
Структура запроса:
type PlaceItemRequest struct {
ItemID uuid.UUID `json:"item_id" validate:"required"`
LocationID uuid.UUID `json:"location_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
}
Шаг 5.2: Поиск товаров
GET /api/operations/search- поиск по названию, адресу, категории- Оптимизация запросов через индексы
- Пагинация результатов
Параметры поиска:
type SearchRequest struct {
Query string `form:"q"`
Category string `form:"category"`
Address string `form:"address"`
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=20"`
}
Шаг 5.3: Перемещение товаров
POST /api/operations/move-item- перемещение между местами- Валидация наличия товара в исходном месте
- Атомарность операции через транзакции
🧪 Этап 6: Тестирование и полировка (Неделя 6)
Шаг 6.1: Unit тесты
- Тесты для всех репозиториев
- Тесты для сервисов
- Тесты для handlers
- Покрытие кода > 80%
Шаг 6.2: Интеграционные тесты
- Тесты API endpoints
- Тесты с реальной БД
- Тесты аутентификации
Шаг 6.3: Производительность
- Оптимизация SQL запросов
- Добавление индексов
- Тестирование под нагрузкой
- Мониторинг медленных запросов
Шаг 6.4: Документация API
- Swagger/OpenAPI документация
- Примеры запросов и ответов
- Описание ошибок
📊 Критерии готовности
Функциональные критерии
- Все CRUD операции работают для items, locations
- Аутентификация с organization-scope
- Шаблоны помещений генерируют структуру
- Операции размещения/поиска/перемещения работают
- API отвечает на все запросы
Технические критерии
- Время отклика API ≤ 200мс
- Покрытие тестами > 80%
- Все endpoints документированы
- Логирование работает корректно
- Graceful shutdown реализован
Безопасность
- JWT токены валидируются
- Organization-scope на всех данных
- Валидация входных данных
- SQL injection protection
🚨 Риски и митигация
Высокий риск
- Производительность БД при большом количестве данных
- Митигация: Индексы, пагинация, мониторинг запросов
Средний риск
- JWT токены без refresh механизма
- Митигация: Короткий TTL, возможность перелогина
Низкий риск
- Отсутствие кэширования
- Митигация: Оптимизация SQL, индексы
📁 Структура проекта (финальная)
app/core-service/
├── cmd/
│ └── main.go
├── internal/
│ ├── api/
│ │ ├── handlers/
│ │ ├── middleware/
│ │ └── server.go
│ ├── auth/
│ │ ├── jwt.go
│ │ └── password.go
│ ├── config/
│ │ └── config.go
│ ├── database/
│ │ ├── connection.go
│ │ └── migrations/
│ ├── models/
│ │ ├── organization.go
│ │ ├── user.go
│ │ ├── storage_location.go
│ │ ├── item.go
│ │ └── item_placement.go
│ ├── repository/
│ │ ├── organizations.go
│ │ ├── users.go
│ │ ├── locations.go
│ │ └── items.go
│ ├── service/
│ │ ├── auth_service.go
│ │ ├── location_service.go
│ │ └── item_service.go
│ └── templates/
│ ├── templates.go
│ └── address_rules.go
├── migrations/
├── tests/
├── go.mod
├── go.sum
├── Dockerfile
└── .env.example
🎯 Следующие шаги после Core Service
- Frontend разработка - Angular PWA с QR сканером
- Интеграция - тестирование API с фронтендом
- Пилот - тестирование с реальными пользователями
- Мониторинг - сбор метрик и обратной связи