LAYERED ARCHITECTURE
Naming Conventions
Entity-Specific Services
{Entity}PersistenceService = CRUD operations on LOCAL database only
(Create, Read, Update, Delete + persistence)
Example: KnowledgePersistenceService
Location: Service/Knowledge/
Base: extends BasePersistenceService<TEntity, TRepository>
Rule: ALWAYS final class.
Rule: Receives injected repository, NOT EntityManager.
{Entity}Orchestrator = Business logic orchestration + workflows
(Orchestrates Persistence + Notion sync)
Example: KnowledgeOrchestrator
Location: Service/Knowledge/
Base: extends BaseOrchestrator
Rule: ALWAYS final class.
Rule: Uses concrete NotionSync{Entity} type-hints, NOT
NotionSyncInterface (avoids DI ambiguity).
Rule: Uses MapperRegistry with MapperNames constants,
NOT direct mapper injection.
NotionSync{Entity} = Entity-specific sync implementation
(Bidirectional sync: Local ↔ Notion)
Example: NotionSyncKnowledge
Location: Service/Notion/Sync/
Base: extends AbstractNotionSync implements NotionSyncInterface
{Entity}ResolutionService = Entity resolution from an opaque identifier
(Normalizes IDs, checks local DB via QueryService, name lookup)
Example: CategoryResolutionService
Location: Service/{Domain}/
Base: extends BaseResolutionService
Pattern: Strategy Pattern (Strategies: ID, NotionID, Name)
Rule: ALWAYS final class.
Rule: READ-ONLY. Must NOT use PersistenceService.
Rule: ALWAYS delegated to by orchestrators when mapping external
identifiers (from API requests, Slack events) to entities.
{Entity}QueryService = Domain-level read queries + Notion repository gateway
(Search, filter, list, date range, findAll, getNotionRepository())
Example: KnowledgeQueryService
Location: Service/{Domain}/
Base: extends BaseQueryService<TEntity, TRepository>
Rule: ALWAYS final class.
Rule: READ-ONLY queries only — no mutations.
Rule: Used by ResolutionServices as their data source.
Rule: Used by Orchestrators for domain searches and Notion sync
(via getNotionRepository()). NOT for entity-by-ID lookup
(that belongs to ResolutionService).Cross-Entity Services
NotionSyncService = Top-level sync orchestrator for all entity syncs
(transitional) (Currently named NotionSyncService; will be renamed to
NotionSyncOrchestrator to match origin/dev)
(Delegates to NotionSync{Entity} concrete services)
Location: Service/Notion/
Pattern: Delegates to concrete NotionSyncCategory,
NotionSyncKnowledge via injected dependencies.
Note: POC scope covers Knowledge + Category only.
NotionRepositoryManager = Repository facade (smart repository selection)
(Provides getKnowledgeRepository(), getCategoryRepository(),
getLocalKnowledgeRepository(), etc.)
Location: Service/Notion/
Pattern: Facade PatternRepository Pattern
{Entity}Repository = PostgreSQL access via Doctrine
Example: KnowledgeRepository
Location: Repository/
Base: extends ServiceEntityRepository implements BaseRepositoryInterface
Notion{Entity}Repository = Notion API access via NotionClient
Example: NotionKnowledgeRepository
Location: Service/Notion/
Rule: Data access only — no logging (logging belongs in sync layer).Service Responsibilities
Orchestrators
| Service | Responsibilities |
|---|---|
| KnowledgeOrchestrator | Create/update/delete knowledge, coordinate sync with Notion. AI summaries are deferred to async via separate endpoint to comply with Slack 3s timeout |
| CategoryOrchestrator | Manage categories, coordinate sync with Notion |
| PromptTemplateOrchestrator | Manage prompt templates, coordinate sync with Notion |
| NotionSyncOrchestrator | Coordinate all entity syncs using strategy pattern |
| TargetGroupSubscriptionOrchestrator | Manage target group subscriptions, coordinate Slack home refresh |
| CategorySubscriptionOrchestrator | Manage category subscriptions and user preferences |
| AiSummaryOrchestrator | AI summary generation lifecycle and coordination |
| DigestOrchestrator | Main digest orchestrator for generation (distribution delegated to DigestDistributionService) |
| DigestSummaryOrchestrator | Ensure AI summaries exist for all knowledge items before digest generation (generates missing summaries on-demand) |
| UserDeliveryPreferenceOrchestrator | Manage user delivery channel preferences |
Query Services
| Service | Domain Queries | Notion Access |
|---|---|---|
| KnowledgeQueryService | advancedSearch, searchByTitle, searchByCategory, getRecentKnowledge | ✅ via getNotionRepository() |
| CategoryQueryService | findByNotionId, findById, findAll, findByName | ✅ via getNotionRepository() |
| AiSummaryQueryService | findAllByKnowledge, findLatestByKnowledgeAndTargetGroup | ❌ |
| DigestQueryService | findKnowledgeForDigest, parseDateRange, getStatistics | ❌ |
| TargetGroupQueryService | findByName, findById, getActiveGroups | ❌ |
| CategorySubscriptionQueryService | findActiveSubscriptions, findSubscribers, isSubscribed, findByUserAndCategory | ❌ |
| TargetGroupSubscriptionQueryService | findActiveSubscriptions, findSubscribers, isSubscribed, findByUserAndTargetGroup | ❌ |
| PromptTemplateQueryService | findByTargetGroupAndCategory | ✅ via getNotionRepository() |
Resolution Services
| Service | Responsibilities |
|---|---|
| KnowledgeResolutionService | Resolve Knowledge IDs (Notion/Local) |
| CategoryResolutionService | Resolve Category identifiers (ID, NotionID, Name) |
| TargetGroupResolutionService | Resolve Target Group identifiers (ID, Name) |
Persistence Services
| Service | Responsibilities |
|---|---|
| KnowledgePersistenceService | CRUD operations for Knowledge entity in local database |
| CategoryPersistenceService | CRUD operations for Category entity in local database |
| SummaryPersistenceService | CRUD operations for AiSummary entity in local database |
| DigestPersistenceService | CRUD operations for Digest entity in local database |
| TargetGroupSubscriptionPersistenceService | Mutation operations (create, update) for TargetGroupSubscription entity |
| PromptTemplatePersistenceService | CRUD operations for PromptTemplate entity in local database |
| CategorySubscriptionPersistenceService | Mutation operations (create, update) for CategorySubscription entity |
Sync Services
| Service | Responsibilities |
|---|---|
| NotionSyncKnowledge | Bidirectional sync for Knowledge (Local ↔ Notion) |
| NotionSyncCategory | Bidirectional sync for Category (Local ↔ Notion) |
| NotionAiSummaryService | One-way sync: Fetch AI summaries from Notion (read-only) |
| NotionContentService | Manage Notion page body content (blocks): append, replace, retrieve |
| DeliveryChannelFactory | Create delivery channel instances based on user preference |
| SlackDeliveryChannel | Generic delivery via Slack direct messages (supports all deliverables) |
| EmailDeliveryChannel | Generic delivery via email (supports digest, knowledge) |
| NotionDeliveryChannel | Generic delivery via Notion pages (supports digest, knowledge) |
| DistributionService | Unified distribution service for any deliverable type |
| UserDeliveryPreferenceService | Manage user delivery preferences, get effective channel |
Delivery & Transport Services
Transport Layer (Low-Level I/O)
Purpose: Pure message transport to external APIs (Slack, Email, Notion)
| Service | Responsibilities |
|---|---|
| MessageTransportInterface | Contract for transport implementations (pure I/O) |
| SlackMessageTransport | HTTP transport for Slack API (auth, timeouts, error handling) |
| TransportPayloadDto | Immutable payload structure for transport |
| TransportResultDto | Immutable result from transport operations |
Formatting Layer (Content Transformation)
Purpose: Transform DeliverableInterface into channel-specific formats
| Service | Responsibilities | |---------|-----------------|| | SlackBlockFormatterInterface | Contract for Slack Block Kit formatting | | SlackBlockFormatter | Format any deliverable as Slack Block Kit JSON (supports Digest, Knowledge, AiSummary) |
Channel Layer (Strategy Pattern)
Purpose: Implement channel-specific delivery logic using transport + formatter
| Service | Responsibilities | |---------|-----------------|| | DeliveryChannelInterface | Strategy contract for all delivery channels | | AbstractDeliveryChannel | Template Method base class (shared validation, logging, success/failure handling) | | SlackDeliveryChannel | Slack channel implementation (extends AbstractDeliveryChannel) | | EmailDeliveryChannel | Email channel stub (extends AbstractDeliveryChannel) | | NotionDeliveryChannel | Notion channel stub (extends AbstractDeliveryChannel) |
Distribution Layer (Orchestration)
Purpose: Coordinate channel selection and delivery to multiple users
| Service | Responsibilities | |---------|-----------------|| | DistributionService | Main distribution orchestrator for any DeliverableInterface | | DeliveryChannelFactory | Strategy Factory - creates appropriate channel based on user preference |
AI Services
| Service | Responsibilities |
|---|---|
| AiProviderInterface | Abstract interface for all AI providers |
| SymfonyAiProvider | Main AI provider using Symfony AI Platform, agent selection, cost tracking |
| CustomGatewayPlatform | Custom Symfony AI Platform implementation, bypasses model validation |
| AuthOverrideHttpClient | HTTP client decorator for auth/URL overrides |
| PromptBuilderService | Build prompts from templates with variable substitution |
| AiSummaryQueryService | Query service for AI summaries with filtering and search |
Repository Manager
| Service | Responsibilities |
|---|---|
| NotionRepositoryManager | Smart repository selection based on ID type (UUID → Local, Notion ID → Notion) |
Repositories
| Repository | Type | Responsibilities |
|---|---|---|
| KnowledgeRepository | Local | PostgreSQL access for Knowledge |
| NotionKnowledgeRepository | Notion | Notion API access for Knowledge |
| CategoryRepository | Local | PostgreSQL access for Category |
| NotionCategoryRepository | Notion | Notion API access for Category |
| DigestRepository | Local | PostgreSQL access for Digest |
| NotionDigestRepository | Notion | Notion API access for Digest |
| PromptTemplateRepository | Local | PostgreSQL access for PromptTemplate |
| NotionPromptTemplateRepository | Notion | Notion API access for PromptTemplate |
| AiSummaryRepository | Local | PostgreSQL access for AiSummary |
| NotionAiSummaryService | Notion | One-way sync: Fetch AI summaries from Notion (read-only) |
| TargetGroupSubscriptionRepository | Local | PostgreSQL access for TargetGroupSubscription |
| UserDeliveryPreferenceRepository | Local | PostgreSQL access for UserDeliveryPreference |
Generic Delivery Layer
The generic delivery system enables ANY content to be delivered through ANY channel. It uses a 4-layer architecture: Transport → Formatter → Channel → Distribution.
Architecture
DeliverableInterface (Contract)
↑
┌────────────────────┼────────────────────┐
│ │ │
┌───────────────┐ ┌─────────────┐ ┌────────────────────┐
│ Digest Entity │ │ Knowledge │ │ AiSummaryDeliverable│
│ │ │ Entity │ │ DTO │
└───────────────┘ └─────────────┘ └────────────────────┘
│ │ │
└────────────────────┴────────────────────┘
▼
┌─────────────────────────┐
│ DistributionService │
│ (orchestrates delivery)│
└──────────────┬──────────┘
▼
┌─────────────────────────┐
│ DeliveryChannelFactory │
│ (creates channels) │
└──────────────┬──────────┘
▼
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐
│CHANNEL LAYER │ │ CHANNEL LAYER │ │ CHANNEL LAYER │
│ │ │ │ │ │
│SlackDelivery │ │ EmailDelivery │ │ NotionDelivery │
│Channel │ │ Channel │ │ Channel │
│(AbstractImpl) │ │ (AbstractImpl) │ │ (AbstractImpl) │
└────────┬────────┘ └────────┬─────────┘ └────────┬───────┘
▼ ▼ ▼
┌──────────────────────────────────────────────────────┐
│ FORMATTER LAYER (Content Transformation) │
├──────────────────────────────────────────────────────┤
│ SlackBlockFormatter → Slack Block Kit JSON │
│ EmailFormatter → HTML/Plain Text (future) │
│ NotionFormatter → Notion Block structure (future) │
└───────────────┬──────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ TRANSPORT LAYER (Pure I/O to External APIs) │
├──────────────────────────────────────────────────────┤
│ MessageTransportInterface (contract) │
│ ├─ SlackMessageTransport (HTTP to Slack Bot API) │
│ ├─ EmailTransport (SMTP) [future] │
│ └─ NotionTransport (HTTP to Notion API) [future] │
└───────────────┬──────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ EXTERNAL APIs (Slack, Email, Notion) │
└──────────────────────────────────────────────────────┘Design Patterns
Result Pattern
ResultInterface: Common interface for all Result objects
- Methods:
isSuccess(),isFailure(),getMessage() - Implemented by 17 Result classes across the codebase
- Enables polymorphic result handling
Implementations:
BaseResult<T>: Generic template class (extended by 2 classes)- Rich Result Objects (8 classes): Domain-specific with static factories
DigestDistributionResult,ContentExtractionResult,DeliveryResultCategorySubscriptionResult,TargetGroupSubscriptionResult- Factory methods:
success(),failure() - Domain methods:
getSuccessRate(),getPreview(), etc.
- Simple Result Objects (7 classes): Data containers
BlockAppendResult,BlockReplaceResult,SyncResultDtoDigestGenerationResultDto,NotionDatabaseQueryResultDto
Data Transfer Object (DTO) Pattern
Categories:
- Request DTOs:
CreateCategoryRequest,UpdateKnowledgeRequest,SearchKnowledgeRequest,GenerateDigestRequest- Include Symfony validation constraints
- Factory methods:
fromArray()for construction
- Response DTOs:
CategoryResponse,KnowledgeResponse,SyncResultResponse,DigestResponse- Serialized to JSON for API responses
- Internal DTOs:
SyncResultDto,PromptVariablesDto,SearchResultItemDTO- Used for service-to-service communication
- Log Context DTOs:
OperationContextDto,DomainContextDto,QueryContextDto
Structured Logging Context DTO Pattern
Rule: Every base service class has exactly one corresponding context DTO type. Using the wrong DTO type in a layer is a static analysis violation.
| Base Class | Layer | Context DTO | Used In |
|---|---|---|---|
BaseOrchestrator | L2 – Orchestration | OperationContextDto | logOperationStart, logOperationSuccess, logOperationFailure, executeWithLogging |
BaseResolutionService | L4 – Domain | DomainContextDto | logInfo on resolve/unresolved |
BasePersistenceService | L5 – Persistence | DomainContextDto | buildCreateContext, buildUpdateContext, buildDeleteContext |
BaseQueryService | L4 – Domain | QueryContextDto | logQuery, logQueryResult |
Builders: Each DTO has a companion builder via AbstractContextBuilder:
// Layer 2 — Orchestrators
OperationContextDto::builder()
->withTitle($request->title)
->build();
// Layer 4/5 — Persistence & Resolution
DomainContextDto::builder()
->withId($entity->getId())
->withTitle($entity->getTitle())
->build();
// Layer 4 — Query Services
QueryContextDto::builder()
->withNotionId($notionId)
->build();BaseService::logInfo() accepts the abstract LogContextDto parent, so all three types flow through it — but the concrete method signatures on each base class (logOperationStart(?OperationContextDto), buildCreateContext(): DomainContextDto, logQuery(?QueryContextDto)) enforce the correct type at the point of use.
Resolution Service Pattern
BaseResolutionService: Abstract base providing common resolution logic:
- Normalization: Notion ID hyphenation and format validation
- Caching: In-memory caching for repeated lookups
- Batching: Methods for resolving multiple identifiers at once
- Error Handling: Standardized logging and exception catching
- Concrete Implementations:
KnowledgeResolutionService: Resolves by local ID or Notion IDCategoryResolutionService: Resolves by ID, Notion ID, or NameTargetGroupResolutionService: Resolves by ID or Name
Value Object Pattern
Implementations:
ContentExtractionResult: Extraction results with content, type, metadataUrlMetadata: URL metadata (title, description, favicon)TargetGroup: Target group configuration (name, description)BlockCollection: Collection of Notion blocks- Properties:
blocks(list of BlockInterface) - Methods:
all(),count(),isEmpty() - Used by:
NotionContentService::getPageContent()
- Properties:
Factory Pattern
Implementations:
NotionClientFactory: Creates Notion client instances- Production: Real HTTP client with API credentials
- Test: Mock HTTP client for testing
- Handles environment detection and configuration
DeliveryChannelFactory: Creates delivery channel instances- Selects appropriate channel based on user preference
- Returns:
SlackDeliveryChannel,EmailDeliveryChannel, orNotionDeliveryChannel - Handles fallback to default channel (Slack)
Builder Pattern
Implementations:
NotionPageBuilder: Builds Notion pages with properties and relations- Methods:
addTitle(),addRichText(),addRelation(),addDate(),build() - Stateless, reusable across multiple page constructions
- Methods:
DigestEntityBuilder: Builds Digest entities from data arrays- Method:
buildFromData()with all required parameters
- Method:
DigestContextBuilder: Builds context for AI generation- Method:
buildContext()with knowledge items and metadata
- Method:
PromptBuilderService: Builds AI prompts with variable substitution- Methods:
buildPrompt(),substituteVariables()
- Methods:
- Log Context Builders (
OperationContextDtoBuilder,DomainContextDtoBuilder,QueryContextDtoBuilder):- All extend
AbstractContextBuilder— fluent->set(key, value)->build() - Each produces its layer-specific DTO type (see: Structured Logging Context DTO Pattern)
- All extend
Repository Pattern
Implementations:
- Local repositories: PostgreSQL access via Doctrine ORM
- Extend
ServiceEntityRepositoryand implementBaseRepositoryInterface CategoryRepository,KnowledgeRepository,DigestRepository- Methods:
find(),findAll(),findOneBy(),save(),flush(), custom queries
- Extend
- Remote repositories: Notion API access via NotionClient
NotionKnowledgeRepository,NotionCategoryRepository,NotionDigestRepository- Methods:
create(),update(),listAll(), custom queries - Rule: No logging — data access only (logging belongs in sync service layer)
- Interface:
BaseRepositoryInterface(generic interface for local repositories)
Service Layer Pattern
Organization by Responsibility:
- Persistence Services: CRUD operations (
CategoryPersistenceService,DigestPersistenceService) - Query Services: Complex queries (
DigestQueryService,AiSummaryQueryService,KnowledgeQueryService) - Orchestrators: Multi-service workflows (
CategoryOrchestrator,KnowledgeOrchestrator,DigestOrchestrator) - Domain Services: Business logic (
DigestGenerationService,DigestContentFormatter,DigestDistributionService)
Base Service: BaseService provides common functionality (logging, entity manager, flush).
QueryServiceInterface Pattern
Interface Methods:
- Core query operations for read-only data access
- Implemented by domain-specific query services
- Separates query concerns from persistence operations
Implementations:
AiSummaryQueryService: AI summary queriesDigestQueryService: Digest queries with date rangesKnowledgeQueryService: Knowledge search and filtering (with Notion repository access)
BaseQueryService Pattern
Features:
- Generic:
BaseQueryService<TEntity, TRepository> - Implements
QueryServiceInterfacecontract - Manages own
$loggerproperty (does NOT extendBaseService) - Receives
$repositoryand$loggervia constructor - Provides
logQuery(),logAndReturnSingleResult(),logAndReturnCollectionResult()helpers - Enforces read-only operations (no flush/persist)
Usage:
- Extended by all query service implementations (must be
final) - Ensures consistent query service structure
- Centralizes common query patterns
Mapper Pattern
Implementations:
BaseMapper<TEntity, TDto>: Abstract template withtoDto(),toEntity(), collection mappingCategoryMapper: Category ↔ CategoryResponseKnowledgeMapper: Knowledge ↔ KnowledgeResponseDigestMapper: Digest ↔ DigestResponseSummaryMapper: Summary ↔ SummaryResponseDtoNotionResponseMapper: Notion API responses ↔ internal DTOs
Command Pattern
Implementations (10 commands):
GenerateDigestCommand: Generate digests for categoriesSyncKnowledgeFromNotionCommand: Sync knowledge from NotionSyncCategoriesFromNotionCommand: Sync categories from NotionSyncAllCommand: Sync all entitiesTestAiCommand: Test AI provider integration
Responsibilities: Transaction management, error handling, service coordination, business rule enforcement.
Orchestrator Pipeline Pattern
Orchestrators follow a standard "Pipeline" pattern for write operations:
- Resolve: Use
{Entity}ResolutionServiceto convert identifiers to entities. - Map: Use
MapperRegistryto get the{Entity}Mapperand map request DTOs to entities. - Persist: Use
{Entity}PersistenceServiceto save the business item to the local database. - Sync: (Optional, best-effort with try/catch) Delegate to concrete
NotionSync{Entity}for remote synchronization.
Rule: Orchestrators MUST use final class. Rule: Orchestrators MUST use concrete NotionSync{Entity} type-hints (e.g., NotionSyncKnowledge), NOT NotionSyncInterface — avoids Symfony DI ambiguity when multiple implementations exist. Rule: Orchestrators should NOT inject direct mappers; use MapperRegistry with MapperNames constants. Rule: Orchestrators should NOT inject redundant services (avoid injecting PersistenceService if only Resolution is needed). Rule: Controllers should NOT contain logging, filtering, or try/catch logic — delegate everything to orchestrators.
Adapter Pattern
Implementations:
OpenAiPlatformAdapter: Adapts Symfony AI Platform interface to OpenAI API- Implements
PlatformInterface - Delegates to underlying OpenAI platform
- Adds custom API key formatting
- Implements
Template Method Pattern
Implementations:
AbstractNotionSync: Common sync logic (validation, structured logging via DomainContextDto, error handling)- Abstract methods:
performSync(),getEntityNameTitleCase(),buildSyncContext(), log message getters - Template method:
syncToNotion()orchestrates the flow
- Abstract methods:
BasePersistenceService<TEntity, TRepository>: Common CRUD logic- Receives injected
$repository(BaseRepositoryInterface) — NOT EntityManager - Template methods:
create(),update(),delete()with structured logging - Concrete services MUST be
final
- Receives injected
BaseMapper<TEntity, TDto>: Common mapping logic- Abstract methods:
toDto(),toEntity() - Template methods:
toDtoCollection(),toEntityCollection()
- Abstract methods:
BaseService: Common service functionality- Methods:
flush(),logInfo(),logWarning(),logError()
- Methods:
BaseException: Common exception handling- Constructor with message and code
Strategy Pattern (Interface-based)
Implementations:
NotionSyncInterface: Sync strategy interface- Implementations:
NotionSyncKnowledge,NotionSyncCategory(POC),NotionSyncDigest(MVP) - Used by:
NotionSyncService(transitional) to delegate entity-specific sync - Important: Orchestrators MUST inject concrete types, NOT NotionSyncInterface (avoids Symfony DI ambiguity with multiple implementations)
- Implementations:
ContentBlockConverterInterface: Content format conversion strategy- Implementations:
MarkdownBlockConverter(supports markdown → Notion blocks) - Used by:
NotionContentServiceto convert content formats to Notion blocks - Extensible: Add new converters for HTML, JSON, plain text formats
- Implementations:
DeliveryChannelInterface: Generic delivery channel strategy- Implementations:
SlackDeliveryChannel,EmailDeliveryChannel,NotionDeliveryChannel - Used by:
DistributionServiceto deliver anyDeliverableInterface - Extensible: Add new channels (SMS, webhook, etc.) by implementing interface
- Methods:
deliver(),getChannelName(),isAvailable(),supports(),getSupportedContentTypes()
- Implementations:
ResolutionStrategyInterface: Entity resolution strategy interface- Base class:
BaseResolutionService(provides concrete multi-strategy algorithm: DB ID → Notion ID → raw Notion ID viaNotionResolvableQueryServiceInterface, batch resolution, Notion ID normalization) - Implementations (only need to provide
getEntityName()and pass their query service):CategoryResolutionServiceKnowledgeResolutionServiceTargetGroupResolutionService
- Pattern: Template Method and concrete multi-strategy resolution
- Location:
Service/Resolution/(base),Service/Category/,Service/Knowledge/,Service/Subscription/
- Base class:
PromptBuilderServiceInterface: Prompt building strategyAiSummaryOrchestratorInterface: AI summary generation strategyKnowledgeRepositoryInterface: Repository selection strategy
Facade Pattern
Implementations:
NotionRepositoryManager: Simplifies repository selection (local vs remote)- Methods:
getKnowledgeRepository(),getCategoryRepository() - Decides local vs remote based on ID type (UUID vs Notion ID)
- Methods:
HealthCheckService: Unified health check interface- Aggregates:
DatabaseHealthCheck,NotionHealthCheck
- Aggregates:
Decorator Pattern
Implementations:
AuthOverrideHttpClient: Decorates Symfony HttpClient- Adds: Authorization header injection, base URL override
- Wraps: Original HttpClient interface
Dependency Injection
All services use constructor injection for dependencies, configured via Symfony's service container.
Base Classes & Interfaces
Inheritance Hierarchy
BaseService (logging: logInfo, logWarning, logError)
├── BasePersistenceService<TEntity, TRepository> (CRUD + repository pattern)
│ ├── final CategoryPersistenceService
│ ├── final KnowledgePersistenceService
│ ├── DigestPersistenceService (MVP)
│ └── ...
├── BaseResolutionService (ID normalization, caching, strategy resolve)
│ ├── final CategoryResolutionService
│ ├── final KnowledgeResolutionService
│ └── TargetGroupResolutionService (MVP)
└── AbstractNotionSync implements NotionSyncInterface
├── NotionSyncCategory
└── NotionSyncKnowledge
BaseOrchestrator implements OrchestratorInterface (own $logger, executeWithLogging)
├── final CategoryOrchestrator
├── final KnowledgeOrchestrator
├── DigestOrchestrator (MVP)
└── ...
BaseQueryService<TEntity, TRepository> implements QueryServiceInterface (own $logger, read-only)
├── final CategoryQueryService
├── final KnowledgeQueryService
├── DigestQueryService (MVP)
└── ...
BaseMapper<TEntity, TDto> implements MapperInterface (toDto, toEntity, collections)
├── CategoryMapper
├── KnowledgeMapper
└── ...Note:
BaseQueryServiceandBaseOrchestratoreach manage their own$loggerproperty. They do NOT extendBaseService. OnlyBasePersistenceService,BaseResolutionService, andAbstractNotionSyncextendBaseService.
BaseOrchestrator
Purpose: Common orchestration patterns for all orchestrators Location: /backend/src/Service/BaseOrchestrator.phpInterface: OrchestratorInterface
Features:
- Manages own
$logger(does NOT extendBaseService) - Structured logging with context (
logOperationStart,logOperationSuccess,logOperationFailure) - Error handling utilities
- Transaction coordination helpers (
executeWithLogging) - Result aggregation patterns
Extended by (all final):
final KnowledgeOrchestratorfinal CategoryOrchestratorDigestOrchestrator(MVP)DigestSummaryOrchestrator(MVP)AiSummaryOrchestrator(MVP)PromptTemplateOrchestrator(MVP)CategorySubscriptionOrchestrator(MVP)TargetGroupSubscriptionOrchestrator(MVP)UserDeliveryPreferenceOrchestrator(MVP)
BasePersistenceService
Purpose: Common CRUD operations using repository pattern Location: /backend/src/Service/BasePersistenceService.phpGeneric: BasePersistenceService<TEntity, TRepository>
Features:
- Extends
BaseServicefor logging - Receives
BaseRepositoryInterfacevia constructor — NOTEntityManagerInterface - Template methods:
create(),update(),delete()with structured logging - Entity lifecycle management via injected repository
Extended by (all final):
final CategoryPersistenceServicefinal KnowledgePersistenceServiceSummaryPersistenceService(MVP)DigestPersistenceService(MVP)
BaseDomainService
Purpose: Common domain service functionality Location: /backend/src/Service/BaseDomainService.phpInterface: DomainServiceInterface
Features:
- Extends
BaseServicefor logging and entity manager - Domain-specific business logic patterns
- Validation and error handling
Extended by:
DigestGenerationServiceDigestContentFormatterDigestDistributionServiceKnowledgeValidationServiceMetadataExtractionService
BaseResolutionService
Purpose: Common resolution strategy patterns Location: /backend/src/Service/Resolution/BaseResolutionService.php
Features:
- Extends
BaseServicefor logging - Provides concrete multi-strategy resolution algorithm (
resolveFromSourcechecking ID then Notion ID) - Receives
NotionResolvableQueryServiceInterfacevia constructor - Caching for performance
- Batch resolution support
- Notion ID normalization
Extended by (all final):
final KnowledgeResolutionServicefinal CategoryResolutionServiceTargetGroupResolutionService(MVP)
BaseMapper
Purpose: Common entity-DTO mapping patterns Location: /backend/src/Mapper/BaseMapper.phpInterface: MapperInterface
Features:
- Generic template class
BaseMapper<TEntity, TDto> - Abstract methods:
toDto(),toEntity() - Template methods:
toDtoCollection(),toEntityCollection() - Null-safe mapping utilities
Extended by:
CategoryMapperKnowledgeMapperDigestMapper(MVP)SummaryMapper(MVP)NotionResponseMapper(MVP)
BaseQueryService
Purpose: Common query service patterns Location: /backend/src/Service/BaseQueryService.phpInterface: QueryServiceInterfaceGeneric: BaseQueryService<TEntity, TRepository>
Features:
- Manages own
$logger(does NOT extendBaseService) - Receives
$repository(BaseRepositoryInterface) and$loggervia constructor - Read-only operations (no flush/persist)
- Provides
logQuery(),logAndReturnSingleResult(),logAndReturnCollectionResult() - Pagination and filtering patterns
Extended by (all final):
final CategoryQueryServicefinal KnowledgeQueryServiceDigestQueryService(MVP)AiSummaryQueryService(MVP)CategorySubscriptionQueryService(MVP)TargetGroupSubscriptionQueryService(MVP)
Enhanced Naming Conventions
Service Naming Patterns
Orchestrators
Pattern: {Entity}OrchestratorPurpose: High-level business workflows, multi-service coordination
Examples:
KnowledgeOrchestrator- Knowledge lifecycle managementDigestOrchestrator- Digest generation and distributionAiSummaryOrchestrator- AI summary generation lifecycle
Responsibilities:
- Coordinate multiple services
- Transaction management
- Business rule enforcement
- Error handling and rollback
Domain Services
Pattern: {Entity}{Purpose}ServicePurpose: Specialized business logic for specific domains
Examples:
DigestGenerationService- Generate digest dataDigestContentFormatter- Format digest outputKnowledgeValidationService- Validate knowledge data
Responsibilities:
- Focused business logic
- Data transformation
- Complex calculations
- Domain-specific rules
Resolution Services
Pattern: {Entity}ResolutionServicePurpose: Entity resolution and conflict handling
Examples:
KnowledgeResolutionService- Resolve knowledge conflictsCategoryResolutionService- Resolve category conflictsTargetGroupResolutionService- Resolve target group conflicts
Responsibilities:
- Entity resolution logic
- Conflict detection and resolution
- Merge strategies
- Deduplication
Persistence Services
Pattern: {Entity}PersistenceServicePurpose: CRUD operations on local database
Examples:
KnowledgePersistenceService- Knowledge CRUDCategoryPersistenceService- Category CRUDDigestPersistenceService- Digest CRUD
Responsibilities:
- Create, Read, Update, Delete
- Entity lifecycle management
- Basic validation
- Database transactions
Query Services
Pattern: {Entity}QueryService or {Entity}SearchServicePurpose: Complex read-only queries
Examples:
DigestQueryService- Digest queries with date rangesAiSummaryQueryService- AI summary queriesKnowledgeQueryService- Knowledge search, filtering, and Notion repository access
Responsibilities:
- Complex queries
- Filtering and pagination
- Aggregations and statistics
- Read-only operations
Sync Services
Pattern: NotionSync{Entity} or {Entity}SyncServicePurpose: Bidirectional sync between local and Notion
Examples:
NotionSyncKnowledge- Knowledge sync (Local ↔ Notion)NotionSyncCategory- Category sync (Local ↔ Notion)NotionAiSummaryService- AI summary sync (Notion → Local, one-way)
Responsibilities:
- Entity-specific sync logic
- Data transformation
- Conflict resolution
- Batch processing
Complete Layer Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ INPUT LAYER: EXTERNAL SOURCES │
│ │
│ Slack Events Slack Commands Slack App Home │
│ • message_action • /knowledge • Add Knowledge button │
│ • message (URL) • /digest • View items │
│ • view_submission │
│ │
│ HTTP API Requests CLI Commands Cron Jobs │
│ • POST /api/knowledge • app:generate-digest • Scheduled digests │
│ • POST /api/digests • app:sync-notion • Periodic syncs │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 1: PRESENTATION │
│ (Controllers, Commands, API) │
│ │
│ CONTROLLERS (14): │
│ • KnowledgeController • CategoryController • DigestController │
│ • NotionSyncController • NotionDigestController • LocalKnowledgeController │
│ • Api/SummaryController • Api/TargetGroupController │
│ • Api/PromptTemplateController • Health/HealthController │
│ • Api/TargetGroupSubscriptionController • Api/UserDeliveryPreferenceController │
│ • SubscriptionController • SlackWebhookController │
│ │
│ COMMANDS (9): │
│ • GenerateDigestCommand • SyncAllCommand │
│ • SyncKnowledgeFromNotionCommand • SyncCategoriesFromNotionCommand │
│ • SyncAiSummariesFromNotionCommand • SyncTargetGroupsFromNotionCommand │
│ • SyncDigestsToNotionCommand • SyncPromptTemplatesFromNotionCommand │
│ • TestAiCommand │
│ │
│ Responsibilities: │
│ • HTTP request/response handling │
│ • Input validation and DTO mapping │
│ • CLI command execution │
│ • Delegates to Orchestrators │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 2: ORCHESTRATION │
│ (High-Level Business Workflows) │
│ │
│ ORCHESTRATORS (10): │
│ • KnowledgeOrchestrator - Knowledge lifecycle + Notion sync │
│ • CategoryOrchestrator - Category lifecycle + target groups │
│ • PromptTemplateOrchestrator - Template versioning + sync │
│ • NotionSyncOrchestrator - Coordinate entity syncs (Strategy) │
│ • DigestOrchestrator - Main digest orchestrator (generation only) │
│ • AiSummaryOrchestrator - AI summary generation lifecycle │
│ • CategorySubscriptionOrchestrator - Manage category subscriptions │
│ • TargetGroupSubscriptionOrchestrator - Manage target group subscriptions │
│ • UserDeliveryPreferenceOrchestrator - Manage delivery preferences │
│ │
│ Responsibilities: │
│ • Coordinate multi-service workflows │
│ • Transaction management and rollback │
│ • Business rule enforcement │
│ • Delegates to Domain Services, Sync Services, and Persistence Services │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 3: SYNC SERVICES │
│ (Bidirectional Sync: Local ↔ Notion) │
│ │
│ NotionSyncKnowledge NotionSyncCategory NotionSyncDigest │
│ NotionAiSummaryService DigestNotionSyncService SyncQueryService │
│ NotionSyncSubscription │
│ │
│ Responsibilities: │
│ • Entity-specific sync logic (extends AbstractNotionSync) │
│ • Data transformation (Local ↔ Notion) │
│ • Conflict resolution and merge strategies │
│ • Batch processing with pagination │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 4: DOMAIN SERVICES │
│ (Specialized Business Logic) │
│ │
│ DIGEST DOMAIN (18): │
│ DigestQueryService DigestGenerationService DigestContentFormatter │
│ DigestEntityBuilder DigestContextBuilder DigestAiSummaryGenerator │
│ DigestRelationLinker DateRangeParser NotionRelationExtractor │
│ TargetGroupResolver DigestDistributionService DigestRecipientResolver │
│ DigestDeliveryLogService DigestDeliveryPersistenceService │
│ DigestDeliveryQueryService DigestPersistenceService DigestOrchestrator │
│ DigestSummaryOrchestrator │
│ │
│ KNOWLEDGE DOMAIN (5): │
│ KnowledgeQueryService KnowledgeValidationService │
│ KnowledgeResolutionService MetadataExtractionService │
│ TargetGroupResolutionService │
│ │
│ CATEGORY DOMAIN (2): │
│ CategoryResolutionService CategoryQueryService │
│ │
│ SUBSCRIPTION DOMAIN (7): │
│ CategorySubscriptionOrchestrator CategorySubscriptionPersistenceService │
│ CategorySubscriptionQueryService TargetGroupSubscriptionOrchestrator │
│ TargetGroupSubscriptionPersistenceService │
│ TargetGroupSubscriptionQueryService UserDeliveryPreferenceService │
│ │
│ DELIVERY DOMAIN (9): │
│ DeliveryChannelInterface (Strategy Pattern) │
│ • AbstractDeliveryChannel - Template Method base class │
│ • SlackDeliveryChannel - Deliver via Slack DM (extends Abstract) │
│ • EmailDeliveryChannel - Deliver via email stub (extends Abstract) │
│ • NotionDeliveryChannel - Deliver via Notion page stub (extends Abstract) │
│ DeliveryChannelFactory - Channel selection based on user preference │
│ UserDeliveryPreferenceOrchestrator UserDeliveryPreferencePersistenceService│
│ UserDeliveryPreferenceQueryService DistributionService │
│ │
│ SLACK NOTIFICATION DOMAIN (4): │
│ HomeRefreshStrategyInterface (Strategy Pattern) │
│ • ImmediateRefreshStrategy - User-initiated actions │
│ • SelectiveRefreshStrategy - Affected users only │
│ • BatchRefreshStrategy - Bulk operations with queuing │
│ │
│ CONTENT EXTRACTION (5): │
│ ContentExtractionService HtmlContentExtractor PdfContentExtractor │
│ NotionContentExtractor PlainTextToNotionConverter │
│ │
│ CONTENT MANAGEMENT (4): │
│ NotionContentService MarkdownBlockConverter │
│ ContentBlockConverterInterface BlockCollection │
│ │
│ AI SERVICES (7): │
│ AiProviderInterface SymfonyAiProvider CustomGatewayPlatform │
│ AuthOverrideHttpClient OpenAiPlatformAdapter AiContentCleanerService │
│ PromptBuilderService SimpleResultConverter AiSummaryQueryService │
│ │
│ TARGET GROUP (4): │
│ TargetGroupResolutionService TargetGroupManagerService │
│ TargetGroupQueryService TargetGroupQueryServiceInterface │
│ │
│ Responsibilities: │
│ • Focused business logic for specific domains │
│ • Data transformation and formatting │
│ • Complex queries and filtering │
│ • Entity building and validation │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 5: PERSISTENCE SERVICES │
│ (CRUD Operations) │
│ │
│ KnowledgePersistenceService CategoryPersistenceService │
│ DigestPersistenceService SummaryPersistenceService │
│ PromptTemplatePersistenceService CategorySubscriptionPersistenceService │
│ TargetGroupSubscriptionPersistenceService │
│ │
│ Responsibilities: │
│ • Create, Read, Update, Delete operations │
│ • Entity lifecycle management │
│ • Basic validation and error handling │
│ • Uses Repository Manager for data access │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 6: REPOSITORY MANAGER │
│ (Smart Repository Selection) │
│ │
│ NotionRepositoryManager (Facade Pattern) │
│ │
│ Responsibilities: │
│ • Determines local vs remote repository based on ID type │
│ • Numeric ID → Local repository (PostgreSQL) │
│ • UUID/Notion ID → Notion repository (Notion API) │
│ • Provides unified interface for data access │
│ • Simplifies repository selection for upper layers │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 7: DATA ACCESS │
│ (Repositories) │
│ │
│ LOCAL REPOSITORIES NOTION REPOSITORIES │
│ (implement BaseRepositoryInterface) │
│ KnowledgeRepository NotionKnowledgeRepository │
│ CategoryRepository NotionCategoryRepository │
│ DigestRepository (MVP) NotionDigestRepository (MVP) │
│ PromptTemplateRepository (MVP) NotionPromptTemplateRepository (MVP) │
│ AiSummaryRepository (MVP) NotionAiSummaryService (MVP) │
│ SummaryRepository (MVP) NotionTargetGroupRepository (MVP) │
│ TargetGroupRepository (MVP) NotionDigestOrchestrator (MVP) │
│ CategorySubscriptionRepository (MVP) NotionSubscriptionRepository (MVP) │
│ TargetGroupSubscriptionRepository (MVP) │
│ UserDeliveryPreferenceRepository (MVP) │
│ DigestDeliveryRepository (MVP) │
│ ResourceRepository (MVP) │
│ ResourceContentRepository (MVP) │
│ TagRepository (MVP) │
│ │
│ UTILITY SERVICES: │
│ NotionPageBuilder NotionPropertyExtractor NotionDatabaseService │
│ NotionClientFactory EmojiConverter │
│ │
│ Responsibilities: │
│ • Database queries (local) or API calls (Notion) │
│ • Entity mapping and hydration │
│ • Query optimization and pagination │
│ • Data transformation between storage and domain models │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 8: DATA STORAGE │
│ │
│ PostgreSQL Database Notion API │
│ • Knowledge entities • Notion pages and databases │
│ • Categories • Blocks and properties │
│ • Digests • Relations and rollups │
│ • AI Summaries • Rich text and files │
│ • Prompt Templates │
│ • Target Groups │
│ • Resources and Tags │
│ • Category Subscriptions │
│ • Target Group Subscriptions │
│ • User Delivery Preferences │
│ • Digest Deliveries │
│ │
│ Doctrine ORM Notion SDK │
│ • Entity mapping • API client │
│ • Query builder • Pagination │
│ • Migrations • Rate limiting │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 9: CROSS-CUTTING CONCERNS │
│ │
│ LOGGING & MONITORING: │
│ • Monolog (PSR-3 Logger) │
│ • Structured logging with context │
│ • Log levels: debug, info, warning, error │
│ │
│ HEALTH CHECKS: │
│ • HealthCheckService (orchestrator) │
│ • DatabaseHealthCheck (PostgreSQL connectivity) │
│ • NotionHealthCheck (Notion API connectivity) │
│ • SlackHealthCheck (Slack app connectivity) │
│ • MetricsService (system metrics collection) │
│ │
│ EXTERNAL INTEGRATIONS: │
│ • SlackNotificationInterface (abstract notification interface) │
│ ├─ SlackHomeRefreshService (home tab refresh operations) │
│ ├─ SlackDigestDeliveryService (digest delivery via DM) │
│ └─ SlackApiClient (HTTP communication with Slack app) │
│ • Uses Facade Pattern for simplified external communication │
│ • Uses Strategy Pattern for different notification types │
│ • Retry logic with exponential backoff │
│ • Batch processing for bulk operations │
│ │
│ VALIDATION: │
│ • Symfony Validator (constraint-based) │
│ • Custom validators: ValidTargetGroup, ValidUrl │
│ • Domain validation in services │
│ │
│ ERROR HANDLING: │
│ • BaseException (custom exception hierarchy) │
│ • Specific exceptions: KnowledgeNotFoundException, DigestNotFoundException │
│ • ValidationException for constraint violations │
│ • Result pattern for expected failures │
│ │
│ SECURITY: │
│ • Symfony Security component │
│ • API key authentication │
│ • CORS configuration │
│ • Internal API secret for service-to-service auth │
│ │
│ CACHING: │
│ • Symfony Cache component │
│ • Repository-level caching │
│ • Active user list caching (5 minutes) │
│ • Subscription data caching (1 minute) │
│ │
│ EVENTS: │
│ • Symfony EventDispatcher │
│ • Gedmo Timestampable (automatic timestamps) │
│ │
│ • Applied across all layers via Symfony DI and AOP │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 10: AI PROVIDER STACK │
│ (AI Service Integration Layer) │
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ APPLICATION SERVICES │ │
│ │ AiSummaryOrchestrator, DigestSummaryOrchestrator, PromptBuilderService│ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ PROVIDER INTERFACE │ │
│ │ AiProviderInterface │ │
│ │ • generateCompletion(prompt, options): AiResponse │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ SYMFONY AI PROVIDER │ │
│ │ SymfonyAiProvider │ │
│ │ • Agent selection (creative/balanced/precise) │ │
│ │ • Cost calculation and metadata tracking │ │
│ │ • Mock mode support │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ CUSTOM GATEWAY PLATFORM │ │
│ │ CustomGatewayPlatform (implements PlatformInterface) │ │
│ │ • Bypasses model catalog validation │ │
│ │ • Supports custom model names (claude-sonnet-4.5) │ │
│ │ • Extracts messages from Symfony AI objects │ │
│ │ • Builds OpenAI-compatible payloads │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ AUTH OVERRIDE HTTP CLIENT │ │
│ │ AuthOverrideHttpClient (decorates HttpClient) │ │
│ │ • Overrides auth_bearer with actual API key │ │
│ │ • Rewrites URLs: /v1/responses → /v1/chat/completions │ │
│ │ • Transforms payload: indexed messages → messages array │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ HTTP CLIENT │ │
│ │ Symfony HttpClient │ │
│ │ • HTTP requests, connection pooling, retries, timeouts │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ AI GATEWAY / API │ │
│ │ • OpenAI API (api.openai.com) │ │
│ │ • Gateway (api.yappa.ai) │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ OUTPUT LAYER: EXTERNAL DESTINATIONS │
│ │
│ Notion Pages Email (SMTP) Slack Messages │
│ • Digest pages • Digest emails • Digest notifications │
│ • Knowledge sync • Summary reports • Knowledge updates │
│ • Category sync │
│ │
│ API Responses CLI Output Webhooks │
│ • JSON responses • Console output • External integrations │
│ • Status codes • Progress logs │
└─────────────────────────────────────────────────────────────────────────────┘Directory Structure
NOTE
The detailed directory structure for the entire project is now maintained in [project_structure.md](file:///home/ubuntu/yappa-knowledge-hub/docs/project/project_structure.md). Please refer to that file for the most up-to-date tree view of the codebase.