Skip to content

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 Pattern

Repository 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

ServiceResponsibilities
KnowledgeOrchestratorCreate/update/delete knowledge, coordinate Notion sync. AI summaries deferred to digest pipeline
CategoryOrchestratorManage categories, digest settings, coordinate Notion sync
DigestOrchestrator7-step digest generation pipeline (category → date range → query → AI highlights → format → create entity → persist + sync)

Query Services

ServiceDomain QueriesNotion Access
KnowledgeQueryServicesearchByTitle, searchByCategory, getRecentKnowledge, date range queriesvia getNotionRepository()
CategoryQueryServicefindByNotionId, findById, findAll, findByNamevia getNotionRepository()
DigestQueryServicefindKnowledgeForDigest, parseDateRange, getStatisticsNo
SubscriptionQueryServicefindActiveSubscriptions, findSubscribers, isSubscribedNo

Resolution Services

ServiceResponsibilities
KnowledgeResolutionServiceResolve Knowledge IDs (Notion/Local)
CategoryResolutionServiceResolve Category identifiers (ID, NotionID, Name)

Persistence Services

ServiceResponsibilities
KnowledgePersistenceServiceCRUD operations for Knowledge entity in local database
CategoryPersistenceServiceCRUD operations for Category entity in local database
DigestPersistenceServiceCRUD operations for Digest entity in local database
DigestDeliveryPersistenceServiceCRUD operations for DigestDelivery entity

Sync Services

ServiceResponsibilities
NotionSyncServicePer-entity sync (Knowledge, Category, Digest, Subscription) and bulk pull/push
NotionMarkdownContentServiceManage Notion page body content (blocks): append, replace, retrieve

Delivery & Transport Services

ServiceResponsibilities
SlackDigestDeliveryServiceFormat digests as Slack mrkdwn and send via DM
SlackMessageTransportHTTP transport for Slack API (auth, timeouts, error handling)
DigestDistributionServiceResolve recipients, coordinate delivery, log status

AI Services

ServiceResponsibilities
AiProviderInterfaceAbstract interface for all AI providers
OpenAIServiceOpenAI/OpenRouter API integration (dual support)
KnowledgeHighlightGeneratorGenerate 2-3 sentence Dutch summaries using hardcoded prompt

Repository Manager

ServiceResponsibilities
NotionRepositoryManagerSmart repository selection based on ID type (UUID → Local, Notion ID → Notion)

Repositories

RepositoryTypeResponsibilities
KnowledgeRepositoryLocalSQLite access for Knowledge
NotionKnowledgeRepositoryNotionNotion API access for Knowledge
CategoryRepositoryLocalSQLite access for Category
NotionCategoryRepositoryNotionNotion API access for Category
DigestRepositoryLocalSQLite access for Digest
NotionDigestRepositoryNotionNotion API access for Digest
SubscriptionRepositoryLocalSQLite access for Subscription
DigestDeliveryRepositoryLocalSQLite 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, GenerateDigestRequest with 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, metadata
  • BlockCollection: Collection of Notion blocks

Factory Pattern

  • NotionClientFactory: Creates Notion client instances (production vs test)

Builder Pattern

  • NotionPageBuilder: Builds Notion pages with properties and relations
  • DigestEntityBuilder: 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

  1. Resolve: Use ResolutionService to convert identifiers to entities
  2. Map: Use MapperRegistry to map request DTOs to entities
  3. Persist: Use PersistenceService to save to local database
  4. Sync: (Optional, best-effort) Delegate to NotionSync{Entity}

Template Method Pattern

  • BasePersistenceService: Common CRUD logic, template create(), 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
└── DigestMapper

Complete 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.