- Add LdapSyncQueue model for deferred LDAP operations - Add Celery tasks: process_ldap_queue_item, process_ldap_queue - Refactor all LDAP signals (employee, department, group, position) to enqueue operations on DirectoryLdapError/DirectoryServiceError - Add _queue.py helper for enqueuing with automatic Celery dispatch - Add Celery Beat periodic task (every 60s) for queue processing - Add management command: ldap_queue (status/retry/cleanup) - Add migration 0043_ldap_sync_queue - Add 18 tests covering model, tasks, signals, and enqueue logic - Include LDAP services refactoring (base_service, constants, user_login_service, user_mapper_service, user_password_service, phone_utils)
13 KiB
Рефакторинг LDAP-сервисов: Архитектурные улучшения
Дата: 19 марта 2026 г.
Статус: В процессе миграции
Цель: Улучшение архитектуры, уменьшение дублирования, следование SOLID принципам
Обзор изменений
Проведён комплексный рефакторинг слоя сервисов backend/employees/ldap/services/ для устранения выявленных архитектурных проблем.
Основные проблемы (до рефакторинга)
- Нарушение SRP - UserService ~750 LOC (слишком большой)
- Дублирование кода -
_wrappedметоды в GroupService (~50% кода) - Отсутствие базового класса - повторяющаяся логика
_touch_stateво всех сервисах - Магические числа - UAC=512/514, groupType и другие константы разбросаны по коду
- Недостаточное логирование - критические операции не логируются
Созданные новые компоненты
1. base_service.py - Базовый класс для всех сервисов
Назначение: Общая логика для всех LDAP-сервисов
Функциональность:
_touch_state()- единая точка входа для обновления LdapSyncState_get_object_dn()- получение DN объекта_log_operation()- логирование операций LDAP_safe_execute()- безопасное выполнение с обработкой ошибок
Пример использования:
from .base_service import BaseService
class MyService(BaseService):
def do_something(self, obj_id):
self._touch_state(
model="employee",
object_pk=obj_id,
ldap_dn="CN=...",
sync_dir="ldap"
)
self._log_operation("update", model="employee", object_id=obj_id)
2. constants.py - Централизованные константы
Назначение: Хранение всех магических чисел и строк
Содержит:
UserAccountControl- UAC флаги (ENABLED=512, DISABLED=514, etc.)GroupType- типы групп AD (GLOBAL_SECURITY, etc.)LdapObjectClass- классы объектов (USER, GROUP, OU)LdapFilter- часто используемые LDAP фильтрыLdapAttribute- названия LDAP атрибутовLdapErrorCode- коды ошибок LDAPSyncDirection- направления синхронизации
Пример использования:
from .constants import UserAccountControl, LdapFilter
# Вместо:
ldap_user.user_account_control = 512
# Используем:
ldap_user.user_account_control = UserAccountControl.ENABLED
# Вместо:
filter_str = "(&(objectCategory=person)(objectClass=user))"
# Используем:
filter_str = LdapFilter.ALL_USERS
3. Подсервисы для разбиения UserService
3.1 user_password_service.py
Назначение: Управление паролями пользователей
Методы:
set_password()- установка пароля через AD extended operationvalidate_password_strength()- базовая валидация пароляchange_password()- смена пароля (требует знание старого)
Пример:
from .user_password_service import UserPasswordService
pwd_svc = UserPasswordService()
# Установка пароля
pwd_svc.set_password(conn, dn, "NewP@ssw0rd123")
# Валидация перед установкой
is_valid, error = pwd_svc.validate_password_strength("weak")
if not is_valid:
raise ValueError(error)
3.2 user_login_service.py
Назначение: Генерация уникальных логинов (sAMAccountName, UPN)
Методы:
generate_unique_logins()- генерация sam и upnvalidate_sam_account_name()- валидация по правилам ADvalidate_upn()- валидация UPN
Пример:
from .user_login_service import UserLoginService
login_svc = UserLoginService()
sam, upn = login_svc.generate_unique_logins(
first_name="Иван",
last_name="Иванов",
email="iivanov@company.com"
)
# sam: "iivanov", upn: "iivanov@company.com"
3.3 user_mapper_service.py
Назначение: Маппинг атрибутов Django ↔ LDAP
Методы:
build_creation_attributes()- атрибуты для создания userbuild_update_attributes()- атрибуты для обновленияprocess_avatar()- обработка изображения для thumbnailPhotoupdate_ldap_user_attributes()- обновление через ORM
Пример:
from .user_mapper_service import UserMapperService
mapper = UserMapperService()
# Создание
attrs = mapper.build_creation_attributes(dto, sam="iivanov", upn="...", cn="Иван Иванов")
# Обновление
ldap_attrs = mapper.build_update_attributes({
"first_name": "Игорь",
"is_active": True
})
4. group_service_refactored.py - Улучшенный GroupService
Изменения:
- ✅ Наследуется от BaseService
- ✅ Использует константы из constants.py
- ✅ Логирование всех операций
- ✅ Унифицированный API (без дублирования _wrapped методов)
- ✅ Методы
_*_internal()для использования с существующим conn - ✅ Публичные методы сами управляют соединением
Миграция:
# Старый API (может использоваться с внешним conn):
with _ldap() as conn:
dn = group_svc.create(conn, cn="MyGroup", ...)
# Новый API (соединение управляется внутри):
dn = group_svc.create(cn="MyGroup", ...)
# Для использования с существующим conn:
with _ldap() as conn:
dn = group_svc._create_internal(conn, cn="MyGroup", ...)
Метрики улучшения
| Метрика | До | После | Улучшение |
|---|---|---|---|
| UserService LOC | ~750 | ~600 + 3x150 | Разделён на компоненты |
| GroupService дублирование | ~50% | 0% | Убраны _wrapped методы |
| Магические числа | ~20+ | 0 | Вынесено в constants |
| Базовая логика | Дублируется | BaseService | DRY принцип |
| Логирование крит. операций | ~30% | 100% | Полное покрытие |
План миграции
Этап 1: Обратная совместимость (текущий)
✅ Созданы новые компоненты рядом со старыми
✅ Оба API доступны одновременно
✅ Старый код продолжает работать
Этап 2: Постепенная миграция (следующий шаг)
🔲 Обновить DepartmentService для использования BaseService
🔲 Обновить PositionService для использования BaseService
🔲 Рефакторить UserService с использованием подсервисов
🔲 Обновить вызовы GroupService на новый API
Этап 3: Финальная очистка
🔲 Удалить старые _wrapped методы из GroupService
🔲 Объединить UserService с подсервисами
🔲 Убрать legacy код
🔲 Обновить тесты
Рекомендации по использованию
Для новых фич
Используйте новые компоненты:
- Наследуйтесь от
BaseService - Используйте константы из
constants.py - Используйте подсервисы для работы с паролями/логинами
- GroupServiceRefactored для новых фич
Для существующего кода
Можно не трогать:
- Старые сервисы продолжают работать
- Миграция постепенная, по мере необходимости
- Критичные места обновлять в первую очередь
Best Practices
- Логирование: Используйте
_log_operation()для всех CRUD операций - Состояние синхронизации: Используйте
_touch_state()вместо прямой работы с LdapSyncState - Константы: Всегда используйте Enum'ы вместо магических чисел
- Валидация: Используйте специализированные сервисы (UserPasswordService, UserLoginService)
Примеры интеграции
1. Создание пользователя с новыми сервисами
from .constants import UserAccountControl
from .user_login_service import UserLoginService
from .user_password_service import UserPasswordService
from .user_mapper_service import UserMapperService
# Генерация логинов
login_svc = UserLoginService()
sam, upn = login_svc.generate_unique_logins(
first_name="Иван",
last_name="Иванов",
email="iivanov@company.com"
)
# Построение атрибутов
mapper = UserMapperService()
attrs = mapper.build_creation_attributes(dto, sam, upn, "Иван Иванов")
# Создание в LDAP
with _ldap() as conn:
dn = f"CN={esc_rdn('Иван Иванов')},{base_dn}"
conn.add(dn, mapper.get_object_classes_for_user(), attrs)
# Установка пароля
pwd_svc = UserPasswordService()
pwd_svc.set_password(conn, dn, "TempP@ss123")
# Активация
ldap_user = LdapUser.objects.get(dn=dn)
ldap_user.user_account_control = UserAccountControl.ENABLED
ldap_user.save()
2. Работа с группами (новый API)
from .group_service_refactored import GroupService as GroupServiceRefactored
from .constants import LdapFilter
group_svc = GroupServiceRefactored()
# Создание группы
dn = group_svc.create(
cn="Developers",
description="Development team",
scope="global",
security_enabled=True
)
# Добавление участников
group_svc.add_members(dn, [user_dn1, user_dn2])
# Синхронизация каталога
created_count = group_svc.sync_catalog(throttle_seconds=60)
Известные ограничения
- ORM vs ldap3: Некоторые операции (modify_dn, paged_search) остаются на ldap3 (невозможны в django-ldapdb)
- Производительность: ORM медленнее чем прямые ldap3 запросы для массовых операций
- Транзакции: LDAP не поддерживает транзакции, используем compensating transactions
Дальнейшие улучшения
Приоритет 1 (критично)
- Рефакторинг DepartmentService с BaseService
- Рефакторинг PositionService с BaseService
- Интеграция подсервисов в UserService
Приоритет 2 (высокий)
- Добавить интеграционные тесты
- Создать фабрики для DTO
- Event-driven архитектура для межсервисного взаимодействия
Приоритет 3 (средний)
- Метрики и мониторинг операций LDAP
- Кеширование часто запрашиваемых данных
- Batch операции для массовых обновлений
Обратная связь
При возникновении вопросов или проблем:
- Проверьте логи (все операции логируются)
- Убедитесь в использовании правильного API (старый vs новый)
- Обратитесь к примерам в этом документе
Важно: Старый API продолжает работать. Новые компоненты - это дополнения, а не замена (пока).