crcm-protocol-service живёт в экосистеме из 4 сервисов + shared-библиотеки. Прежде чем читать архитектуру — важно держать в голове эту топологию:
┌─── CRED_cred-question-api ─────────────────────────┐
│ Источник вопросов для кредитных комитетов. │
│ Файлы: метаданные + ECM. RabbitMQ events │
│ (не слушаем). │
└─────────────────▲──────────────────────────────────┘
│ Feign (через agenda, не напрямую)
│
┌─── OBESRABUO_agenda ───────────────────────────────┐
│ Повестки, вопросы (обогащённые), голосования. │
│ V2 API с ФИО/ролями. Polling-only, без Kafka │
│ событий. │
│ │
│ ⚠️ По мере работы над protocol 2.0 в agenda │
│ ДОБАВЛЯЮТСЯ эндпоинты под наши нужды │
│ (см. ROADMAP "Agenda extensions"). │
└─────────────────▲──────────────────────────────────┘
│ Feign
│
┌─── crcm-protocol-service (мы пишем) ───────────────┐
│ Protocol, Extract, Signature, генерация DOCX. │
└────────────┬───────────────────────────────────────┘
│ Feign
▼
┌─── OBESRABUO_common ───────────────────────────────┐
│ ⚠️ Это СЕРВИС, не библиотека. │
│ - Events API (ProtocolSigned, ExtractSigned,...) │
│ - Feature Flags API │
│ - Streaming (Kafka 2-way replication cache) │
│ - Email templates, User settings, Employee proxy │
└────────────────────────────────────────────────────┘
+ obesrabuo-common-lib (библиотека-стартеры, 3.4.0-SNAPSHOT):
auth, docx4j, filestorage, redis, http-logging, epa-client.
Ключевые границы:
Полная разведка экосистемы: journal/phase-99-meta/2026-04-18-ecosystem-discovery.md.
crcm-protocol-service/
├── modules/
│ ├── core/ -- общие примитивы, без бизнес-логики
│ ├── document/ -- файлы + генерация документов
│ ├── integration/ -- все внешние вызовы (Feign, Kafka)
│ ├── protocol/ -- протоколы (ядро бизнеса)
│ ├── extract/ -- выписки
│ ├── signature/ -- подписание
│ └── app/ -- склейка, конфигурация, запуск
┌──────────┐
│ app │ -- точка сборки, знает всех
└────┬─────┘
│
┌───────────────┼───────────────┬───────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ integra- │ │ signature │ │ extract │ │ protocol │
│ tion │ │ │ │ │ │ (ядро) │
│(адаптеры) │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
└─────┬─────┘ │ │ ▲
│ │ │ impl ports │
│ │ └───────────────┤
│ │ │
│ impl ports │ impl ports │
└───────────────┴───────────────────────────────┤
│
┌─────────────────────┘
│
▼
┌───────────┐
│ document │
└─────┬─────┘
│
┌─────┴─────┐
│ core │
└───────────┘
Ключевое:
protocol — ядро бизнеса, владелец портов (AgendaProvider, ExtractProvider, ExtractSynthesizer).extract реализует ExtractProvider и ExtractSynthesizer — поэтому extract → protocol (DIP, инверсия: поток данных от extract в protocol, зависимость исходников — от extract к protocol).integration реализует порты бизнес-модулей (AgendaProvider, SignatureStoragePort, EmployeeProvider, etc.) — поэтому зависит от protocol, signature, extract.protocol → document → core; провайдеры (integration, extract, signature) зависят от потребителя (protocol).Фундамент. Зависит от ничего (кроме JDK). Все остальные модули зависят от него.
core/
├── domain/
│ ├── DocumentEntityType.java -- enum типов сущностей
│ ├── CommitteeType.java -- enum + fromCode(String) / code() для agenda's committeeCode
│ └── error/
│ └── DomainException.java -- sealed hierarchy ошибок
├── port/ -- generic utility ports (shared по всем бизнес-модулям)
│ └── FeatureFlagProvider.java -- isEnabled(code), getAll(). Реализуется в integration/common через OpenFeature
├── security/
│ └── SecurityContext.java -- обёртка над SecurityUtils
└── util/
└── DateFormatting.java -- общие утилиты форматирования
Почему FeatureFlagProvider в core, а не в protocol/port:
Фича-флаги — generic utility, не привязаны к бизнес-контексту. Используются потенциально всеми модулями (protocol, extract, signature). Интерфейс тривиальный (2 метода), дублирование его в каждом модуле было бы шумом. Компромисс с ISP оправдан — это уровень utility-абстракции типа Clock или Random.
Что улучшаем:
CommitteeType как enum с поведением вместо разбросанных Set.of("KKKIBB", "KKKIBG") из ReportStrategyFactoryRestException.badRequest() на всёJava 25 фичи в этом модуле:
sealed class для DomainExceptionenum с поведением (методы, поля, lookup map)record для value objectsВся работа с документами в одном месте: генерация, мерж, подписи в docx, шаблоны.
document/
├── model/
│ ├── AgendaReportModel.java -- из docx4j-starter (бизнес-модель)
│ ├── QuestionReportModel.java -- из docx4j-starter
│ └── ApprovalSheetModel.java -- из docx4j-starter
├── template/
│ ├── DocumentTemplateType.java -- enum путей к шаблонам (из docx4j-starter)
│ └── TemplateRegistry.java -- резолвинг шаблонов
├── generation/
│ └── ReportGenerator.java -- использует DocumentEngine из common-lib
├── signing/
│ └── DocumentSigner.java -- из Docx4jSignatureUtils
├── merge/
│ └── DocumentMerger.java -- из DocxMergeUtil
└── resources/
└── templates/ -- 26 docx-шаблонов (из docx4j-starter)
Зависимости: core, obesrabuo-docx4j-starter (движок)
Адаптеры к внешнему миру. Бизнес-логика не знает про Feign вообще.
integration/
├── agenda/ -- OBESRABUO_agenda (V2 API + StructuredTaskScope)
│ ├── AgendaFeignClient.java -- Feign interface
│ ├── AgendaFeignAdapter.java -- implements protocol.port.AgendaProvider
│ ├── AgendaMapper.java -- ACL: AgendaFeignDto → Agenda (package-private)
│ └── dto/ -- внешние Feign DTOs (package-private records)
├── common/ -- OBESRABUO_common (⚠️ сервис, не lib)
│ ├── events/
│ │ ├── CommonEventsFeignClient.java
│ │ ├── ProtocolEventsAdapter.java -- implements protocol.port.ProtocolEventsProvider
│ │ └── dto/ -- EventDto'шки per event type
│ ├── streaming/
│ │ ├── CommonStreamingFeignClient.java
│ │ └── ProtocolStreamingAdapter.java -- implements protocol.port.ProtocolStreamingProvider
│ └── featureflags/
│ ├── CommonFeatureFlagFeignClient.java
│ ├── FeatureFlagAdapter.java -- implements core.port.FeatureFlagProvider через OpenFeature
│ └── OpenFeatureConfig.java -- provider + Caffeine cache (TTL 60s)
├── signature-storage/ -- OBESRABUO Signature Storage
│ ├── SignatureStorageFeignClient.java
│ └── SignatureStorageAdapter.java
├── ecm/ -- через obesrabuo-common-filestorage (библиотека)
│ └── EcmStorageAdapter.java
└── config/
└── FeignConfig.java
Что убрано (vs исходный план):
integration/kafka/AuditEventProducer — события через common/events/ Feign, не прямой Kafka publisher.integration/employee/EmployeeClient — V2 agenda API уже обогащает Question ФИО/должностями/ролями. Отдельный клиент не нужен.integration/decision/DecisionAdapter — прямой обход CRED не делаем. Данные решений идут через agenda.Причины — см. journal/phase-99-meta/2026-04-18-ecosystem-discovery.md.
Паттерн Ports & Adapters + ACL:
// Порт (в модуле-потребителе, protocol/port/)
public interface AgendaProvider {
Agenda getAgenda(UUID id);
AgendaSnapshot loadSnapshot(UUID agendaId); // bulk для Phase 1 генерации protocol
}
// Внешний контракт V2 agenda API (package-private в integration/agenda/dto/)
record AgendaFeignDto(
UUID id,
String committeeCode, // "KKKIBB", "KKKIBG" — строка, не enum
String committeeName,
AgendaStatusState status, // enum agenda — НЕ утекает в ядро
MeetingType formType,
Instant dateCommittee,
List<QuestionFeignDto> questions
) {}
// ACL-маппер (package-private в integration/agenda/)
final class AgendaMapper {
static Agenda toDomain(AgendaFeignDto dto) {
return new Agenda(
dto.id(),
CommitteeType.fromCode(dto.committeeCode()) // String → наш enum
.orElseThrow(() -> new DomainException.Integration(
"Unknown committeeCode: " + dto.committeeCode())),
ProtocolMeetingType.from(dto.formType()), // external enum → наш
dto.dateCommittee(),
dto.questions().stream().map(AgendaMapper::toDomain).toList()
);
}
}
// Адаптер (в integration/agenda/)
@Component
class AgendaFeignAdapter implements AgendaProvider {
private final AgendaFeignClient client;
@Override
public AgendaSnapshot loadSnapshot(UUID agendaId) {
// 3 параллельных Feign через StructuredTaskScope (Java 25)
try (var scope = StructuredTaskScope.open(Joiner.anySuccessfulResultOrThrow())) {
var agenda = scope.fork(() -> client.getAgendaById(agendaId));
var questions = scope.fork(() -> client.getQuestionsByAgenda(agendaId));
var votes = scope.fork(() -> client.getVotesByQuestions(/* extracted from questions */));
scope.join();
return AgendaMapper.toSnapshot(agenda.get(), questions.get(), votes.get());
}
}
}
Три уровня ACL:
AgendaFeignDto) — зеркало внешнего JSON. Package-private.AgendaMapper) — единственная точка превращения external → domain. Knows about CommitteeType.fromCode, ProtocolMeetingType.from.Agenda, Question, Vote) в core/domain/. Это видит ядро. Нет committeeCode: String — есть committeeType: CommitteeType.Зависимости: core, protocol (AgendaProvider, EventsProvider, StreamingProvider), signature (SignatoryDataProvider), extract (если понадобятся), obesrabuo-common-filestorage, obesrabuo-epa-client, OpenFeature SDK.
Ядро бизнеса — протоколы заседаний.
protocol/
├── model/
│ ├── Protocol.java -- entity С ПОВЕДЕНИЕМ
│ ├── ProtocolStatus.java
│ └── ProtocolHistory.java
├── api/
│ ├── ProtocolController.java -- REST
│ ├── request/ -- records
│ └── response/ -- records
├── port/ -- интерфейсы, владелец-потребитель
│ ├── AgendaProvider.java -- loadSnapshot/getQuestion/downloadFile, реализуется в integration (Feign, V2 API)
│ ├── ExtractProvider.java -- чтение выписок, реализуется в extract
│ ├── ExtractSynthesizer.java -- создание недостающих выписок, реализуется в extract
│ ├── ProtocolEventsProvider.java -- notifyProtocolSigned/notifyExtractSigned (events через common)
│ └── ProtocolStreamingProvider.java -- emitProtocolChanged (outbound streaming, Phase 6)
├── service/
│ ├── ProtocolService.java -- тонкий фасад
│ ├── ProtocolGenerator.java -- оркестратор 4 фаз (было protocolByAgendaExtracts)
│ ├── ProtocolGenerationContext.java -- record с данными между фазами
│ ├── ProtocolAssembler.java -- Phase 4: сборка финального документа
│ └── ProtocolHistoryService.java
├── persistence/
│ ├── ProtocolRepository.java
│ ├── ProtocolJpaEntity.java -- JPA entity отдельно от доменной модели
│ └── ProtocolJpaMapper.java -- entity <-> domain
└── report/
├── ProtocolReportStrategy.java -- интерфейс с supports()
└── impl/
├── DefaultProtocolReport.java
├── BankProtocolReport.java
├── SmbProtocolReport.java
├── GoSmbProtocolReport.java
└── GroupProtocolReport.java
Что улучшаем:
ProtocolService (839 строк в Java) -> тонкий фасад + ProtocolGeneratorsupports() вместо if/else в FactoryProtocolService <-> ExtractService разрывается через порты ExtractProvider / ExtractSynthesizer (DIP)ProtocolGenerator ничего не знает про Feign/JPA — только доменные records через портыЗависимости: core, document (реализации портов — в extract и integration, снаружи)
Выписки из протоколов.
extract/
├── model/
│ ├── Extract.java
│ └── ExtractType.java
├── api/
│ ├── ExtractController.java
│ ├── request/
│ └── response/
├── adapter/ -- реализации портов protocol
│ ├── ExtractProviderAdapter.java -- implements protocol.port.ExtractProvider
│ └── ExtractSynthesizerAdapter.java -- implements protocol.port.ExtractSynthesizer
├── service/
│ ├── ExtractService.java
│ ├── ExtractGenerator.java
│ └── ExtractNotificationService.java
├── persistence/
│ ├── ExtractRepository.java
│ ├── ExtractJpaEntity.java
│ └── ExtractJpaMapper.java
└── report/
├── ExtractReportStrategy.java
├── impl/
│ ├── DefaultExtractReport.java
│ ├── CustomExtractReport.java
│ ├── SmbExtractReport.java
│ └── GoSmbExtractReport.java
└── template/
├── ExtractTemplateStrategy.java
└── impl/
├── DefaultExtractTemplate.java
├── BankExtractTemplate.java
├── GroupExtractTemplate.java
├── SmbExtractTemplate.java
└── GoSmbExtractTemplate.java
Зависимости: core, document, protocol (для портов ExtractProvider / ExtractSynthesizer)
DIP: extract — провайдер, protocol — потребитель. Адаптеры в
adapter/реализуют порты, объявленные вprotocol/port/.
Подписание документов ЭП.
signature/
├── model/
│ ├── Signature.java
│ └── SignatureRequest.java
├── api/
│ ├── SignatureController.java
│ └── SignatoryController.java
├── service/
│ ├── SignatureService.java
│ ├── SignatoryService.java
│ └── SignatureRequestCache.java
├── persistence/
│ └── ...
└── strategy/
├── FileSignatureStrategy.java -- интерфейс с getType()
├── FileSignatureStrategyResolver.java -- Map-based registry
└── impl/
├── AbstractFileSignatureStrategy.java -- Template Method
├── ExtractFileSignatureStrategy.java
├── ProtocolFileSignatureStrategy.java
└── QuestionFileSignatureStrategy.java
Зависимости: core, document
Точка сборки. Единственный модуль, который знает про все остальные.
app/
├── Application.java -- @SpringBootApplication
├── config/
│ ├── AsyncConfig.java
│ ├── CacheConfig.java
│ ├── JacksonConfig.java
│ └── SwaggerConfig.java
├── audit/
│ ├── AuditService.java
│ └── AuditConfig.java
├── exception/
│ └── GlobalExceptionHandler.java -- @RestControllerAdvice + pattern matching
└── scheduler/
└── ScheduledTasks.java
Зависимости: все модули + все common-lib стартеры
Snapshot на 2026-04-18. Обновляется по правилам из
CLAUDE.md(Workflow: Definition of Done).
Статусы: ✅ Done · 🟡 Partial · ⏸ Not started.
| Модуль | Статус | Что есть | Что не сделано | Последний апдейт |
|---|---|---|---|---|
core |
✅ Done | CommitteeType, DomainException (sealed), DocumentEntityType, AbstractEntity, JPA entities + repositories |
доменные records (Agenda, Question, Vote) — добавятся вместе с портами |
2026-04-13 |
document |
✅ Done | DocumentTemplateType, шаблоны (26 файлов), ReportGenerator |
— | 2026-04-13 |
app |
🟡 Skeleton | Application, GlobalExceptionHandler, миграции, базовые конфиги |
AsyncConfig (virtual threads), CacheConfig, JacksonConfig, SwaggerConfig, scheduler, audit |
2026-04-16 |
protocol — CRUD |
✅ Done | ProtocolCrudService, ProtocolHistoryService, ProtocolSpecifications, DTO records |
— | 2026-04-17 |
protocol — ports |
⏸ Not started | — | AgendaProvider, ExtractProvider, ExtractSynthesizer, ProtocolEventsProvider, ProtocolStreamingProvider (интерфейсы) |
— |
protocol — generator |
⏸ Not started | — | ProtocolGenerationContext, ProtocolGenerator (4 фазы + StructuredTaskScope), ProtocolAssembler, report strategies по CommitteeType |
— |
core — FeatureFlagProvider |
⏸ Not started | — | generic utility port, реализация через OpenFeature в integration/common | — |
integration/agenda |
⏸ Not started | — | AgendaFeignClient (V2), AgendaFeignAdapter, AgendaMapper (ACL), StructuredTaskScope в loadSnapshot |
— |
integration/common |
⏸ Not started | — | events/ (ProtocolEventsAdapter), streaming/ (ProtocolStreamingAdapter), featureflags/ (OpenFeature + Caffeine cache) |
— |
integration/signature-storage |
⏸ Not started | — | SignatureStorageFeignClient + Adapter | — |
integration/ecm |
⏸ Not started | — | EcmStorageAdapter через obesrabuo-common-filestorage |
— |
extract |
⏸ Not started | — | Бизнес-логика выписок, стратегии отчётов, адаптеры портов protocol (ExtractProviderAdapter, ExtractSynthesizerAdapter) |
— |
signature |
🟡 Skeleton | FileSignatureStrategy + FileSignatureStrategyResolver (интерфейс + registry), DTO records |
AbstractFileSignatureStrategy (Template Method), конкретные стратегии (Extract/Protocol/Question), SignatureService, SignatoryService |
2026-04-16 |
common-lib 3.4.0 |
✅ Applied | DocumentInsertProcessor fix, ECM Resilience4j, версия 3.4.0-SNAPSHOT в репо |
статус каждого фикса — см. COMMON_LIB_REVIEW.md |
2026-04-17 |
| Checkstyle + тесты | ⏸ Not started | — | Google Java Style, unit-тесты, Testcontainers | — |
| Фаза | Модуль | Что делаем | Статус | Файлов |
|---|---|---|---|---|
| 1 | core | Enums, value objects (records), sealed error hierarchy | ✅ | ~8-12 |
| 2 | document | Модели отчётов (records), шаблоны, обёртка над DocumentEngine | ✅ | ~10-12 |
| 3 | protocol — CRUD | Entity, repository, CRUD-сервис, History, Specifications, DTOs | ✅ | ~15-18 |
| 4 | protocol — порты | AgendaProvider, ExtractProvider, ExtractSynthesizer (интерфейсы) + доменные records в core |
⏸ | ~6-8 |
| 5 | protocol — ядро генерации | ProtocolGenerationContext, ProtocolGenerator (4 фазы, StructuredTaskScope), ProtocolAssembler, report strategies |
⏸ | ~12-15 |
| 6 | integration | Feign-клиенты, ACL-мапперы, адаптеры (AgendaFeignAdapter и пр.), Kafka producers |
⏸ | ~20-25 |
| 7 | extract | Бизнес-логика выписок, стратегии отчётов, адаптеры портов protocol | ⏸ | ~22-28 |
| 8 | signature | Дозавершить: Strategy Registry, Template Method, конкретные стратегии, Signature/Signatory services | 🟡 | ~12-15 |
| 9 | app | Дозавершить: AsyncConfig/CacheConfig/JacksonConfig/SwaggerConfig, scheduler, audit | 🟡 | ~10 |
| 10 | — | Checkstyle (Google style), unit-тесты, integration-тесты (Testcontainers) | ⏸ | — |
Почему такой порядок:
AgendaProvider — после этого доступны живые данные повестки.ExtractProvider/ExtractSynthesizer, после чего генерация протокола работает end-to-end.По решению 2026-04-18, agenda полностью не переписываем, но добавляем endpoints под нужды protocol 2.0 по мере реализации. Это отдельная работа в репозитории OBESRABUO_agenda, не в этом проекте.
Процесс:
AgendaProvider) обнаруживаем что текущая V2 не даёт нужного — сразу записываем в ROADMAP.md → "Agenda extensions" конкретное требование.OBESRABUO_agenda — отдельным PR (не в этом репо).AgendaFeignAdapter и обновляем маппинг.journal/phase-06-integration/00-reference-map.md держим мапинг версии agenda API → наш AgendaProvider.Примеры потенциальных расширений (могут понадобиться или нет — уточняется при реализации):
GET /api/v3/agenda/{id}/snapshot — agenda + questions + votes в одном вызове (вместо 3).Не делаем в agenda (принципиально):
CommitteeType enum в agenda — там остаётся committeeCode: String, mapping на нашей стороне.| Паттерн | Где | Java 25 фича |
|---|---|---|
| Strategy Registry | FileSignatureStrategyResolver, ReportStrategies | Map.of(), interface |
| Enum с поведением | CommitteeType вместо magic strings | Enhanced enum |
| Template Method | AbstractFileSignatureStrategy | Abstract class |
| Strategy + supports() | Report strategies (extract + protocol) | Interface + pattern matching |
| Sealed Error Hierarchy | DomainException | sealed class + permits |
| Facade | ProtocolService -> тонкий фасад | Class decomposition |
| Pipeline/Command | protocolByAgendaExtracts -> фазы | Records + functional |
| Value Objects / DDD | Domain model с поведением | Records |
| Ports & Adapters | integration module | Interfaces |
| Repository | JPA entity отдельно от domain model | Separate classes |
| Pattern Matching | GlobalExceptionHandler, strategy selection | switch + pattern matching |