Модуль franchise¶
Статус¶
Создан Spring Modulith-модуль и публичная граница franchise::api. Реализованы агрегаты
Franchisee и LegalEntity.
Назначение¶
Модуль franchise отвечает за договорную структуру франчайзинговой сети: партнёров ГК и
юридические лица, через которые они работают.
Модуль владеет:
- франчайзи;
- юридическими лицами;
- их статусами и реквизитами;
- в будущем — договорами, сотрудниками франчайзи и политиками доступа.
Модуль не отвечает за:
- подключение и обмен данными с iiko;
- импортированные организации iiko;
- жизненный цикл ресторанов;
- каталог и витрину;
- заказы, платежи и чеки.
Границы¶
franchise не зависит от других бизнес-модулей. Другие модули смогут получать необходимые данные
только через named interface franchise::api или реагировать на опубликованные события.
Прямое чтение таблиц модуля из iiko, restaurant, admin и других модулей запрещено.
Структура пакетов¶
ru.panampizza.monolith.franchise
├── FranchiseeEntity
├── FranchiseeRepository
├── FranchiseeService
├── LegalEntityEntity
├── LegalEntityRepository
├── LegalEntityService
└── api
├── Franchisees
└── LegalEntities
Корневой пакет объявлен через @ApplicationModule. Пакет api объявлен через
@NamedInterface("api"). Всё, что в дальнейшем появится вне пакета api, является внутренней
реализацией модуля.
Franchisee¶
Франчайзи — партнёр ГК, внутри которого в дальнейшем создаются юридические лица.
Модель:
id: UUID
name: String(1..200)
status: ACTIVE | DISABLED
createdAt: Instant
updatedAt: Instant
version: Long
Правила:
- новый франчайзи создаётся активным;
- имя обрезается по краям и не может быть пустым или длиннее 200 символов;
- имя не уникально и не используется как идентификатор;
- физическое удаление не поддерживается;
- отключение обратимо;
- имя можно изменить;
- оптимистическая версия защищает конкурентное редактирование;
- при фактической смене статуса публикуется
FranchiseeStatusChanged; - повторная установка текущего статуса является идемпотентной и не публикует событие.
Таблица franchise.franchisee создаётся Flyway-миграцией V3__create_franchisees.sql.
Обычный CRUD реализован через Spring Data JPA.
JPA-сущность использует явный field access: persistent-поля объявлены как var, поскольку JPA
изменяет их при materialization, но setter'ы имеют видимость protected для Hibernate-прокси.
Прикладной код меняет состояние только через методы rename и changeStatus. Неизменяемая
публичная модель отделена от JPA-сущности.
Публичный API¶
Named interface franchise::api предоставляет контракт Franchisees:
interface Franchisees {
fun create(name: String): Franchisee
fun findById(id: UUID): Franchisee?
fun findAll(): List<Franchisee>
fun rename(id: UUID, name: String): Franchisee
fun disable(id: UUID): Franchisee
fun activate(id: UUID): Franchisee
}
Наружу возвращается неизменяемая модель Franchisee; внутренняя JPA-сущность не пересекает
границу модуля. Для обязательных операций отсутствующий ID приводит к
FranchiseeNotFoundException.
События¶
FranchiseeStatusChanged входит в публичный контракт franchise::api:
data class FranchiseeStatusChanged(
val franchiseeId: UUID,
val previousStatus: FranchiseeStatus,
val currentStatus: FranchiseeStatus,
val occurredAt: Instant,
)
Событие публикуется в транзакции изменения статуса. После появления потребителей Spring Modulith будет регистрировать предназначенные им публикации в Event Publication Registry.
Отключение ФЧ не должно перезаписывать собственные статусы его ресторанов. Будущий модуль
restaurant установит операционную блокировку FRANCHISEE_DISABLED; после активации ФЧ блокировка
снимается, а прежний статус ресторана сохраняется. Ресторан со статусом CLOSED не восстанавливается.
Будущий модуль iiko должен пропускать плановый импорт подключений, если их юридическое лицо или
ФЧ операционно неактивны. Проверка выполняется через franchise::api, без чтения таблиц модуля.
LegalEntity¶
Юридическое лицо принадлежит одному ФЧ; принадлежность после создания неизменяема. Один ФЧ может иметь несколько юридических лиц.
id: UUID
franchiseeId: UUID
name: String(1..300)
inn: String(10 или 12 цифр)
kpp: String?(9 цифр)
legalAddress: String(1..500)
status: ACTIVE | DISABLED
createdAt: Instant
updatedAt: Instant
version: Long
Пара (inn, kpp) уникальна во всей системе, включая случай отсутствующего КПП. Модель поддерживает
организации с десятизначным ИНН и ИП с двенадцатизначным ИНН без отдельного discriminator.
Физическое удаление не поддерживается; отключение обратимо.
Собственный статус юридического лица не изменяется при отключении ФЧ. Эффективная активность:
Контракт LegalEntities предоставляет создание, чтение, обновление реквизитов, изменение статуса,
а также одиночную и пакетную проверку эффективной активности. Пакетный метод
findOperationalIds предназначен для планировщика iiko и не создаёт N+1 запросов.
При фактической смене собственного статуса публикуется LegalEntityStatusChanged. Повторная
установка текущего статуса не публикует событие.
Таблица franchise.legal_entity создаётся миграцией V4__create_legal_entities.sql. FK обеспечивает
существование ФЧ, а check constraints дублируют критичную валидацию ИНН, КПП, статуса и обязательных
строк на уровне PostgreSQL.
Зависимости¶
На текущем этапе разрешённых зависимостей на другие модули нет:
Целевые потребители публичного API:
franchiseoffice использует findByFranchisee для списка юридических лиц текущего сотрудника.
Scope определяется его FranchisePrincipal; модуль franchise не зависит от HTTP и IAM.
Проверка модульности¶
Общий ModularityTests вызывает ApplicationModules.verify() и проверяет в том числе модуль
franchise: отсутствие циклов, обращений к внутренним пакетам и неразрешённых зависимостей.
FranchiseeTests использует PostgreSQL Testcontainers и проверяет создание, валидацию имени,
переименование, сортировку, отключение, повторную активацию, обработку отсутствующего ID и
идемпотентную публикацию события изменения статуса.
LegalEntityTests проверяет принадлежность ФЧ, реквизиты, уникальность, обновление, событие статуса
и эффективную активность с учётом состояния ФЧ.
HTTP ownership списка юридических лиц проверяется в модуле franchiseoffice: данные другого ФЧ не
попадают в ответ, а клиент не передаёт franchiseeId.
Следующий этап¶
iiko.connection уже привязан к LegalEntity, а модуль iiko проверяет существование и
операционную активность владельца через franchise::api. Следующий отдельно принимаемый этап —
HTTP ownership-сценарий создания подключения в franchiseoffice.
Полная согласованная модель описана в архитектуре франчайзинга.
Права сотрудников ГК и ФЧ на операции модуля описаны в модели административного доступа.