LAYERED ARCHITECTURE
Naming Conventions
Entity-Specific Services
{Entity}PersistenceService = CRUD operations on LOCAL database only
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
Example: KnowledgeOrchestrator
Location: Service/Orchestrator/
Base: extends BaseOrchestrator
Rule: ALWAYS final class.
Rule: Uses NotionSyncService for Notion sync after persist.
Rule: Uses MapperRegistry with MapperNames constants,
NOT direct mapper injection.
NotionSyncService = Entity sync to/from Notion (single service)
Example: NotionSyncService::syncKnowledgeToNotion()
Location: Service/Notion/NotionSyncService.php
Rule: Called from persistence services and NotionSyncController
{Entity}ResolutionService = Entity resolution from an opaque identifier
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.
{Entity}QueryService = Domain-level read queries + Notion repository gateway
Example: KnowledgeQueryService
Location: Service/{Domain}/
Base: extends BaseQueryService<TEntity, TRepository>
Rule: ALWAYS final class.
Rule: READ-ONLY queries only — no mutations.Cross-Entity Services
NotionSyncService = Sync service for all entity types (local ↔ Notion)
Location: Service/Notion/NotionSyncService.php
NotionRepositoryManager = Repository facade (smart repository selection)
Location: Service/Notion/
Pattern: Facade PatternRepository Pattern
{Entity}Repository = SQLite 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.Service Responsibilities
Orchestrators
| Service | Responsibilities |
|---|---|
| KnowledgeOrchestrator | Create/update/delete knowledge, coordinate Notion sync. AI summaries deferred to digest pipeline |
| CategoryOrchestrator | Manage categories, digest settings, coordinate Notion sync |
| DigestOrchestrator | 7-step digest generation pipeline (category → date range → query → AI highlights → format → create entity → persist + sync) |
Query Services
| Service | Domain Queries | Notion Access |
|---|---|---|
| KnowledgeQueryService | searchByTitle, searchByCategory, getRecentKnowledge, date range queries | via getNotionRepository() |
| CategoryQueryService | findByNotionId, findById, findAll, findByName | via getNotionRepository() |
| DigestQueryService | findKnowledgeForDigest, parseDateRange, getStatistics | No |
| SubscriptionQueryService | findActiveSubscriptions, findSubscribers, isSubscribed | No |
Resolution Services
| Service | Responsibilities |
|---|---|
| KnowledgeResolutionService | Resolve Knowledge IDs (Notion/Local) |
| CategoryResolutionService | Resolve Category identifiers (ID, NotionID, Name) |
Persistence Services
| Service | Responsibilities |
|---|---|
| KnowledgePersistenceService | CRUD operations for Knowledge entity in local database |
| CategoryPersistenceService | CRUD operations for Category entity in local database |
| DigestPersistenceService | CRUD operations for Digest entity in local database |
| DigestDeliveryPersistenceService | CRUD operations for DigestDelivery entity |
Sync Services
| Service | Responsibilities |
|---|---|
| NotionSyncService | Per-entity sync (Knowledge, Category, Digest, Subscription) and bulk pull/push |
| NotionMarkdownContentService | Manage Notion page body content (blocks): append, replace, retrieve |
Delivery & Transport Services
| Service | Responsibilities |
|---|---|
| SlackDigestDeliveryService | Format digests as Slack mrkdwn and send via DM |
| SlackMessageTransport | HTTP transport for Slack API (auth, timeouts, error handling) |
| DigestDistributionService | Resolve recipients, coordinate delivery, log status |
AI Services
| Service | Responsibilities |
|---|---|
| AiProviderInterface | Abstract interface for all AI providers |
| OpenAIService | OpenAI/OpenRouter API integration (dual support) |
| KnowledgeHighlightGenerator | Generate 2-3 sentence Dutch summaries using hardcoded prompt |
Repository Manager
| Service | Responsibilities |
|---|---|
| NotionRepositoryManager | Smart repository selection based on ID type (UUID → Local, Notion ID → Notion) |
Repositories
| Repository | Type | Responsibilities |
|---|---|---|
| KnowledgeRepository | Local | SQLite access for Knowledge |
| NotionKnowledgeRepository | Notion | Notion API access for Knowledge |
| CategoryRepository | Local | SQLite access for Category |
| NotionCategoryRepository | Notion | Notion API access for Category |
| DigestRepository | Local | SQLite access for Digest |
| NotionDigestRepository | Notion | Notion API access for Digest |
| SubscriptionRepository | Local | SQLite access for Subscription |
| DigestDeliveryRepository | Local | SQLite access for DigestDelivery |
Design Patterns
Result Pattern
ResultInterface: Common interface for all Result objects
- Methods:
isSuccess(),isFailure(),getMessage() - Enables polymorphic result handling
- Implementations:
ContentExtractionResult,DigestDistributionResult,DeliveryResult
Data Transfer Object (DTO) Pattern
- Request DTOs:
CreateKnowledgeRequest,GenerateDigestRequestwith Symfony validation - Response DTOs:
CategoryResponse,KnowledgeResponse,DigestResponse - Internal DTOs:
SyncResultDto,TransportPayloadDto,TransportResultDto
Resolution Service Pattern
BaseResolutionService: Abstract base providing common resolution logic:
- Normalization, caching, batching, error handling
- Concrete:
KnowledgeResolutionService,CategoryResolutionService
Value Object Pattern
ContentExtractionResult: Extraction results with content, type, metadataBlockCollection: Collection of Notion blocks
Factory Pattern
NotionClientFactory: Creates Notion client instances (production vs test)
Builder Pattern
NotionPageBuilder: Builds Notion pages with properties and relationsDigestEntityBuilder: Builds Digest entities from data arrays
Repository Pattern
- Local: SQLite access via Doctrine ORM (
KnowledgeRepository,CategoryRepository,DigestRepository) - Remote: Notion API access (
NotionKnowledgeRepository,NotionCategoryRepository,NotionDigestRepository)
Service Layer Pattern
- Persistence Services: CRUD operations
- Query Services: Complex queries
- Orchestrators: Multi-service workflows
- Domain Services: Business logic
Orchestrator Pipeline Pattern
- Resolve: Use
ResolutionServiceto convert identifiers to entities - Map: Use
MapperRegistryto map request DTOs to entities - Persist: Use
PersistenceServiceto save to local database - Sync: (Optional, best-effort) Delegate to
NotionSync{Entity}
Template Method Pattern
BasePersistenceService: Common CRUD logic, templatecreate(),update(),delete()
Strategy Pattern
ContentBlockConverterInterface: Content format conversion
Facade Pattern
NotionRepositoryManager: Simplifies repository selection
Base Classes & Interfaces
Inheritance Hierarchy
BaseService (logging: logInfo, logWarning, logError)
├── BasePersistenceService<TEntity, TRepository>
│ ├── final KnowledgePersistenceService
│ ├── final CategoryPersistenceService
│ └── DigestPersistenceService
├── BaseResolutionService
│ ├── final CategoryResolutionService
│ └── final KnowledgeResolutionService
BaseOrchestrator (own $logger, executeWithLogging)
├── final KnowledgeOrchestrator
├── final CategoryOrchestrator
└── DigestOrchestrator
BaseQueryService<TEntity, TRepository> (own $logger, read-only)
├── final CategoryQueryService
├── final KnowledgeQueryService
├── DigestQueryService
└── SubscriptionQueryService
BaseMapper<TEntity, TDto>
├── CategoryMapper
├── KnowledgeMapper
└── DigestMapperComplete Layer Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ INPUT LAYER: EXTERNAL SOURCES │
│ │
│ Slack Events Slack Shortcuts Slack App Home │
│ • message_action • Add to YapHub • Add Knowledge button │
│ • view_submission • Quick Add • Category list │
│ │
│ 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 (6): │
│ • KnowledgeController • CategoryController • DigestController │
│ • NotionSyncController • SubscriptionController • CategorySettingsController │
│ │
│ COMMANDS: │
│ • GenerateDigestCommand • SyncAllCommand │
│ • SyncKnowledgeFromNotionCommand • SyncCategoriesFromNotionCommand │
│ • TestAiCommand │
│ │
│ Responsibilities: │
│ • HTTP request/response handling │
│ • Input validation and DTO mapping │
│ • Delegates to Orchestrators │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 2: ORCHESTRATION │
│ (High-Level Business Workflows) │
│ │
│ ORCHESTRATORS (3): │
│ • KnowledgeOrchestrator - Knowledge lifecycle + Notion sync │
│ • CategoryOrchestrator - Category lifecycle + digest settings │
│ • DigestOrchestrator - 7-step digest generation pipeline │
│ │
│ Responsibilities: │
│ • Coordinate multi-service workflows │
│ • Business rule enforcement │
│ • Delegates to Domain Services, Sync Services, and Persistence Services │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 3: SYNC SERVICES │
│ (Bidirectional Sync: Local ↔ Notion) │
│ │
│ NotionSyncService NotionMarkdownContentService │
│ │
│ Responsibilities: │
│ • Per-entity sync methods (Knowledge, Category, Digest, Subscription) │
│ • Bulk pull/push via console commands and NotionSyncController │
│ • Markdown embedding via NotionMarkdownContentService │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 4: DOMAIN SERVICES │
│ (Specialized Business Logic) │
│ │
│ DIGEST DOMAIN: │
│ DigestQueryService KnowledgeHighlightGenerator DigestContentFormatter │
│ DateRangeParser DigestDistributionService DigestDeliveryLogService │
│ DigestCreationService DigestSchedulerService │
│ │
│ KNOWLEDGE DOMAIN: │
│ KnowledgeQueryService KnowledgeResolutionService │
│ ContentExtractionService HtmlContentExtractor │
│ │
│ CATEGORY DOMAIN: │
│ CategoryResolutionService CategoryQueryService │
│ │
│ SUBSCRIPTION DOMAIN: │
│ SubscriptionQueryService SubscriptionService │
│ │
│ AI SERVICES: │
│ AiProviderInterface OpenAIService KnowledgeHighlightGenerator │
│ │
│ SLACK SERVICES: │
│ SlackDigestDeliveryService SlackMessageTransport │
│ │
│ NOTION CONTENT: │
│ NotionMarkdownContentService MarkdownBlockConverter │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 5: PERSISTENCE SERVICES │
│ (CRUD Operations) │
│ │
│ KnowledgePersistenceService CategoryPersistenceService │
│ DigestPersistenceService DigestDeliveryPersistenceService │
│ │
│ Responsibilities: │
│ • Create, Read, Update, Delete operations │
│ • Uses Repository Manager for data access │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 6: REPOSITORY MANAGER │
│ (Smart Repository Selection) │
│ │
│ NotionRepositoryManager (Facade Pattern) │
│ │
│ • Numeric ID → Local repository (SQLite) │
│ • UUID/Notion ID → Notion repository (Notion API) │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 7: DATA ACCESS │
│ (Repositories) │
│ │
│ LOCAL REPOSITORIES NOTION REPOSITORIES │
│ KnowledgeRepository NotionKnowledgeRepository │
│ CategoryRepository NotionCategoryRepository │
│ DigestRepository NotionDigestRepository │
│ SubscriptionRepository │
│ DigestDeliveryRepository │
│ │
│ UTILITY SERVICES: │
│ NotionPageBuilder NotionPropertyExtractor NotionClientFactory │
└─────────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 8: DATA STORAGE │
│ │
│ SQLite Database Notion API │
│ • Knowledge items • Notion pages and databases │
│ • Categories • Blocks and properties │
│ • Digests • Relations and rollups │
│ • Subscriptions │
│ • Digest deliveries │
│ │
│ Doctrine ORM Notion HTTP API │
│ • Entity mapping • Rate limiting (3 req/s) │
│ • Query builder • Pagination │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 9: CROSS-CUTTING CONCERNS │
│ │
│ LOGGING: Monolog with structured context DTOs │
│ SECURITY: Bearer token auth (ROLE_ADMIN), CORS │
│ VALIDATION: Symfony Validator, custom constraints │
│ EVENTS: Symfony EventDispatcher, Gedmo Timestampable │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 10: AI PROVIDER │
│ │
│ AiProviderInterface │
│ → OpenAIService (OpenAI / OpenRouter auto-detection) │
│ → Symfony HttpClient → AI API │
│ │
│ Configuration: Temperature 0.3, Max 150 tokens, Dutch language │
│ Fallback: Content truncation on failure │
└─────────────────────────────────────────────────────────────────────────────┘Directory Structure
See project_structure.md for the complete file tree.