Files
movies-diary/architecture.mmd

161 lines
8.0 KiB
Plaintext

---
title: Movies Diary — Hexagonal Architecture
---
graph TB
subgraph Binaries["Binaries (thin: config + bootstrap + run)"]
SERVER["server<br/><i>load config, bootstrap::build_server, axum::serve</i>"]
WORKER["worker<br/><i>bootstrap::build_worker</i><br/>Concurrent dispatch, graceful shutdown"]
TUI["tui<br/><i>Terminal UI (deprecated)</i>"]
end
subgraph Config["config"]
CFG["AppConfig<br/><i>defaults, config.toml / CONFIG_FILE,<br/>legacy env names</i>"]
end
subgraph Application["Application Layer"]
direction TB
DEPS["Deps<br/><i>one narrow deps struct per use case</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"]
UC_SOCIAL["social<br/>follow, unfollow,<br/>accept, reject, block"]
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_POSTER["PosterSyncHandler<br/><i>poster sync</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/>DiaryQuery / StatsRepository<br/>UserRepository / UserProfileFieldsRepository<br/>WatchlistRepository<br/>WatchEventCommand / WatchEventQuery<br/>WebhookTokenRepository<br/>ImportSessionRepository / ImportProfileRepository<br/>MovieProfileRepository<br/>WrapUpRepository / WrapUpStatsQuery<br/>GoalCommand / GoalQuery<br/>UserSettingsRepository / RefreshSessionRepository<br/>MovieDeduplicator"]
P_SERVICES["AuthService<br/>MetadataClient / MovieEnrichmentClient<br/>PersonEnrichmentClient<br/>PosterFetcherClient<br/>ImageFetcher / ObjectStorage<br/>EventPublisher / EventConsumer<br/>PasswordHasher<br/>DiaryExporter / DocumentParser<br/>RssFeedRenderer / MediaServerParser"]
P_SEARCH["SearchPort / SearchCommand<br/>PersonQuery / PersonCommand<br/>FederatedProfileQuery"]
P_FEDERATION["SocialCommand / SocialQuery<br/>FederationAdminQuery<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 Bootstrap["bootstrap"]
BS["build_server, build_worker<br/><i>Composition root: the only crate that names adapters</i>"]
end
subgraph EventPayload["event-payload"]
EP["Serde payloads for domain events"]
end
subgraph Adapters["Adapters (implement Port Traits)"]
direction TB
subgraph Http["HTTP driving adapter"]
A_HTTP["http-axum<br/><i>JSON API, classic HTML (askama),<br/>OpenAPI, federation-gated routes,<br/>one refusal shape</i>"]
end
subgraph Storage["Storage"]
A_SQLITE["sqlite<br/><i>repositories per aggregate, FTS5 search,<br/>social graph, durable event queue, migrations</i>"]
A_OBJ["object-storage<br/><i>S3 / filesystem</i>"]
end
subgraph Messaging["Messaging"]
A_NATS["nats<br/><i>JetStream / Core</i>"]
A_EVT_PUB["event-publisher<br/><i>In-memory channel</i>"]
end
subgraph External["External Services"]
A_METADATA["metadata<br/><i>TMDB / OMDb 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; ActivityPubEventHandler<br/>(reviews, watchlist, goals, UserDeleted, UserAccountMoved)</i>"]
A_SQLITE_FED["sqlite-federation<br/><i>k_ap repositories</i>"]
end
subgraph Media["Media Processing"]
A_IMG["image-converter<br/><i>AVIF/WebP</i>"]
end
subgraph Formats["Formats"]
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>"]
A_MEDIA_SERVER["media-server<br/><i>Jellyfin + Plex webhook parsers</i>"]
end
end
%% Dependency arrows
SERVER -->|"loads"| Config
SERVER -->|"calls"| Bootstrap
WORKER -->|"loads"| Config
WORKER -->|"calls"| Bootstrap
Bootstrap -->|"wires"| Adapters
Bootstrap -->|"builds deps for"| Application
A_HTTP -->|"uses"| Application
A_HTTP -->|"maps to"| ApiTypes
TUI -->|"typed API access"| ApiTypes
Application -->|"depends on"| Domain
UC_WRAPUP -->|"delegates to"| DS_WRAPUP
Adapters -.->|"implements"| Ports
Messaging -.->|"serialises with"| EventPayload
%% Key data flows
A_HTTP ===|"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 Config app
class Bootstrap binary
class ApiTypes api