---
title: Movies Diary — Hexagonal Architecture
---
graph TB
    subgraph Binaries["Binaries (Composition Root)"]
        WEB["presentation<br/><i>Axum web server</i><br/>Routes, Handlers, Mappers"]
        WORKER["worker<br/><i>Event consumer</i><br/>Concurrent dispatch, graceful shutdown"]
        TUI["tui<br/><i>Terminal UI</i>"]
    end

    subgraph Application["Application Layer"]
        direction TB
        CTX["AppContext<br/><i>Repositories + Services</i>"]
        APP_PORTS["ReviewLogger<br/><i>application-layer port</i>"]
        subgraph UseCases["Use Cases"]
            UC_AUTH["auth<br/>login, register"]
            UC_DIARY["diary<br/>log_review, edit_review,<br/>get_diary, get_activity_feed,<br/>export"]
            UC_MOVIES["movies<br/>get_movies, get_movie_profile,<br/>enrich_movie, request_enrichment,<br/>sync_poster, reindex_search,<br/>merge_duplicates"]
            UC_IMPORT["import<br/>create_session, apply_mapping,<br/>execute, profiles"]
            UC_USERS["users<br/>get_users, get_profile,<br/>update_profile, delete_account"]
            UC_WATCHLIST["watchlist<br/>add, remove, get"]
            UC_WRAPUP["wrapup<br/>generate, compute,<br/>list, delete"]
            UC_GOALS["goals<br/>create, update, delete,<br/>get, list"]
            UC_INTEGRATIONS["integrations<br/>webhooks, watch_queue,<br/>confirm, dismiss"]
            UC_SEARCH["search<br/>execute"]
            UC_PERSON["person<br/>get, get_credits"]
        end
        subgraph EventHandlers["Event Handlers"]
            EH_MOVIE["MovieEnrichmentHandler<br/><i>on MovieEnrichmentRequested</i>"]
            EH_PERSON["PersonEnrichmentHandler<br/><i>on PersonEnrichmentRequested</i>"]
            EH_DISCOVER["MovieDiscoveryIndexer"]
            EH_CLEANUP["SearchCleanupHandler"]
            EH_REINDEX["SearchReindexHandler"]
            EH_WRAPUP["WrapUpEventHandler"]
            EH_AP["ActivityPubEventHandler<br/><i>reviews, watchlist, goals,<br/>UserDeleted, UserAccountMoved</i>"]
        end
        subgraph Jobs["Periodic Jobs"]
            JOB_IMPORT["ImportSessionCleanup"]
            JOB_WATCH["WatchEventCleanup"]
            JOB_STALE["EnrichmentStaleness"]
            JOB_WRAPGEN["WrapUpAutoGenerate"]
            JOB_WRAPCLEAN["WrapUpCleanup"]
            JOB_SESSION["RefreshSessionCleanup"]
            JOB_DEDUP["MovieDeduplication<br/><i>merge duplicate movie<br/>records (daily)</i>"]
        end
        WORKER_SVC["WorkerService<br/><i>Semaphore(8), JoinSet,<br/>shutdown signal</i>"]
    end

    subgraph Domain["Domain Layer (0 dependencies)"]
        direction TB
        subgraph Models["Models"]
            M_MOVIE["Movie, MovieSummary,<br/>MovieProfile"]
            M_REVIEW["Review, ReviewEdit,<br/>DiaryEntry, FeedEntry"]
            M_USER["User, UserSummary"]
            M_PERSON["Person, PersonId,<br/>PersonCredits"]
            M_WATCHLIST["WatchlistEntry,<br/>WatchEvent"]
            M_GOAL["Goal, GoalWithProgress,<br/>UserSettings, RemoteGoalEntry"]
            M_WRAPUP["WrapUpReport,<br/>MovieRef, PersonStat"]
            M_SEARCH["SearchQuery,<br/>SearchResults"]
        end
        subgraph Ports["Port Traits (Interfaces)"]
            P_REPOS["MovieCommand / MovieQuery<br/>ReviewRepository<br/>DiaryRepository / StatsRepository<br/>UserRepository<br/>WatchlistRepository<br/>WatchEventCommand / WatchEventQuery<br/>WebhookTokenRepository<br/>ImportSessionRepository<br/>MovieProfileRepository<br/>WrapUpRepository<br/>GoalRepository<br/>UserSettingsRepository<br/>MovieDeduplicator"]
            P_SERVICES["AuthService<br/>MetadataClient<br/>PosterFetcherClient<br/>ImageFetcher<br/>ObjectStorage<br/>EventPublisher<br/>EventConsumer<br/>PasswordHasher<br/>DiaryExporter<br/>DocumentParser<br/>RssFeedRenderer"]
            P_SEARCH["SearchPort<br/>SearchCommand<br/>PersonQuery<br/>PersonCommand"]
            P_FEDERATION["SocialQueryPort<br/>LocalApContentQuery<br/>RemoteWatchlistRepository<br/>RemoteGoalRepository"]
        end
        subgraph DomainServices["Services (pure, no I/O)"]
            DS_WRAPUP["WrapUpAnalyzer<br/><i>build_report, compute_*</i>"]
            DS_REVIEW["ReviewHistoryAnalyzer<br/><i>rating_trend</i>"]
        end
        EVENTS["DomainEvent enum<br/><i>ReviewLogged, MovieDiscovered,<br/>GoalCreated, GoalUpdated,<br/>UserDeleted, UserAccountMoved,<br/>SearchReindexRequested, ...</i>"]
        VO["Value Objects<br/><i>MovieId, UserId, Rating,<br/>WatchMedium, Email, Username,<br/>Password, ...</i>"]
    end

    subgraph ApiTypes["api-types"]
        DTO["DTOs<br/><i>MovieDto, ReviewDto,<br/>FeedEntryDto, UserSummaryDto,<br/>HtmlPageContext, ...</i>"]
    end

    subgraph InfraWiring["infra-wiring"]
        IW["DbPool, EventBusBackend,<br/>AppConfig<br/><i>Shared infra types</i>"]
    end

    subgraph Adapters["Adapters (implement Port Traits)"]
        direction TB
        subgraph Storage["Storage"]
            A_SQLITE["sqlite<br/><i>SQLite repos</i>"]
            A_PG["postgres<br/><i>PostgreSQL repos</i>"]
            A_SQLITE_SEARCH["sqlite-search<br/><i>FTS5</i>"]
            A_PG_SEARCH["postgres-search<br/><i>tsvector/GIN</i>"]
            A_OBJ["object-storage<br/><i>S3 / filesystem</i>"]
        end
        subgraph Messaging["Messaging"]
            A_NATS["nats<br/><i>JetStream / Core</i>"]
            A_PG_QUEUE["postgres-event-queue<br/><i>Polling, dead-letter</i>"]
            A_PAYLOAD["event-payload<br/><i>Serde (de)serialization</i>"]
        end
        subgraph External["External Services"]
            A_METADATA["metadata<br/><i>TMDB search/details</i>"]
            A_TMDB["tmdb-enrichment<br/><i>Credits, keywords, cast</i>"]
            A_POSTER["poster-fetcher<br/><i>TMDB image download</i>"]
            A_AUTH["auth<br/><i>JWT tokens</i>"]
        end
        subgraph Federation["Federation (feature-gated)"]
            A_AP["activitypub<br/><i>k_ap library</i>"]
            A_SQLITE_FED["sqlite-federation"]
            A_PG_FED["postgres-federation"]
        end
        subgraph Media["Media Processing"]
            A_IMG["image-converter<br/><i>AVIF/WebP</i>"]
            A_POSTER_SYNC["poster-sync"]
        end
        subgraph Presentation["Presentation Helpers"]
            A_TEMPLATE["template-askama<br/><i>HTML templates</i>"]
            A_RSS["rss<br/><i>Feed generation</i>"]
            A_EXPORT["export<br/><i>CSV/JSON diary</i>"]
            A_IMPORT["importer<br/><i>CSV/JSON/XLSX parser</i>"]
        end
        subgraph Webhooks["Webhook Parsers"]
            A_JELLYFIN["jellyfin"]
            A_PLEX["plex"]
        end
    end

    %% Dependency arrows
    WEB -->|"uses"| Application
    WEB -->|"maps to"| ApiTypes
    WORKER -->|"uses"| Application

    Application -->|"depends on"| Domain
    UC_WRAPUP -->|"delegates to"| DS_WRAPUP

    Adapters -.->|"implements"| Ports

    %% Key data flows
    WEB ===|"HTTP"| DTO
    WORKER ===|"Events"| EVENTS

    classDef domain fill:#1a1a2e,stroke:#e94560,color:#fff
    classDef app fill:#16213e,stroke:#0f3460,color:#fff
    classDef adapter fill:#0f3460,stroke:#533483,color:#fff
    classDef binary fill:#533483,stroke:#e94560,color:#fff
    classDef api fill:#2a2a4a,stroke:#e94560,color:#fff

    class Domain domain
    class DomainServices domain
    class Application app
    class Adapters adapter
    class Binaries binary
    class ApiTypes api
