diff --git a/architecture.mmd b/architecture.mmd new file mode 100644 index 0000000..0f838dc --- /dev/null +++ b/architecture.mmd @@ -0,0 +1,132 @@ +--- +title: Movies Diary — Hexagonal Architecture +--- +graph TB + subgraph Binaries["Binaries (Composition Root)"] + WEB["presentation
Axum web server
Routes, Handlers, Mappers"] + WORKER["worker
Event consumer
Concurrent dispatch, graceful shutdown"] + TUI["tui
Terminal UI"] + end + + subgraph Application["Application Layer"] + direction TB + CTX["AppContext
Repositories + Services"] + subgraph UseCases["Use Cases"] + UC_AUTH["auth
login, register"] + UC_DIARY["diary
log_review, get_diary,
get_activity_feed, export"] + UC_MOVIES["movies
get_movies, get_movie_profile,
enrich_movie, sync_poster,
reindex_search"] + UC_IMPORT["import
create_session, apply_mapping,
execute, profiles"] + UC_USERS["users
get_users, get_profile,
update_profile"] + UC_WATCHLIST["watchlist
add, remove, get"] + UC_WRAPUP["wrapup
generate, compute,
list, delete"] + UC_INTEGRATIONS["integrations
webhooks, watch_queue,
confirm, dismiss"] + UC_SEARCH["search
execute"] + UC_PERSON["person
get, get_credits"] + end + subgraph EventHandlers["Event Handlers"] + EH_ENRICH["EnrichmentHandler"] + EH_DISCOVER["MovieDiscoveryIndexer"] + EH_CLEANUP["SearchCleanupHandler"] + EH_REINDEX["SearchReindexHandler"] + EH_WRAPUP["WrapUpEventHandler"] + end + subgraph Jobs["Periodic Jobs"] + JOB_IMPORT["ImportSessionCleanup"] + JOB_WATCH["WatchEventCleanup"] + JOB_STALE["EnrichmentStaleness"] + JOB_WRAPGEN["WrapUpAutoGenerate"] + end + WORKER_SVC["WorkerService
Semaphore(8), JoinSet,
shutdown signal
"] + end + + subgraph Domain["Domain Layer (0 dependencies)"] + direction TB + subgraph Models["Models"] + M_MOVIE["Movie, MovieSummary,
MovieProfile"] + M_REVIEW["Review, DiaryEntry,
FeedEntry"] + M_USER["User, UserSummary"] + M_PERSON["Person, PersonId,
PersonCredits"] + M_WATCHLIST["WatchlistEntry,
WatchEvent"] + M_WRAPUP["WrapUpReport,
MovieRef, PersonStat"] + M_SEARCH["SearchQuery,
SearchResults"] + end + subgraph Ports["Port Traits (Interfaces)"] + P_REPOS["MovieRepository
ReviewRepository
DiaryRepository
UserRepository
WatchlistRepository
WatchEventRepository
WebhookTokenRepository
ImportSessionRepository
MovieProfileRepository
WrapUpRepository"] + P_SERVICES["AuthService
MetadataClient
PosterFetcherClient
ObjectStorage
EventPublisher
EventConsumer
PasswordHasher
DiaryExporter
DocumentParser"] + P_SEARCH["SearchPort
SearchCommand
PersonQuery
PersonCommand"] + P_FEDERATION["SocialQueryPort
LocalApContentQuery
RemoteWatchlistRepository"] + end + EVENTS["DomainEvent enum
ReviewLogged, MovieDiscovered,
SearchReindexRequested, ...
"] + VO["Value Objects
MovieId, UserId, Rating,
Email, Username, ...
"] + end + + subgraph ApiTypes["api-types (0 domain deps)"] + DTO["DTOs
MovieDto, ReviewDto,
FeedEntryDto, UserSummaryDto,
CastMemberDto, ...
"] + end + + subgraph Adapters["Adapters (implement Port Traits)"] + direction TB + subgraph Storage["Storage"] + A_SQLITE["sqlite
SQLite repos"] + A_PG["postgres
PostgreSQL repos"] + A_SQLITE_SEARCH["sqlite-search
FTS5"] + A_PG_SEARCH["postgres-search
tsvector/GIN"] + A_OBJ["object-storage
S3 / filesystem"] + end + subgraph Messaging["Messaging"] + A_NATS["nats
JetStream / Core"] + A_PG_QUEUE["postgres-event-queue
Polling, dead-letter"] + A_PAYLOAD["event-payload
Serde (de)serialization"] + end + subgraph External["External Services"] + A_METADATA["metadata
TMDB search/details"] + A_TMDB["tmdb-enrichment
Credits, keywords, cast"] + A_POSTER["poster-fetcher
TMDB image download"] + A_AUTH["auth
JWT tokens"] + end + subgraph Federation["Federation (feature-gated)"] + A_AP["activitypub
k_ap library"] + A_SQLITE_FED["sqlite-federation"] + A_PG_FED["postgres-federation"] + end + subgraph Media["Media Processing"] + A_IMG["image-converter
AVIF/WebP"] + A_POSTER_SYNC["poster-sync"] + A_WRAPUP_RENDER["wrapup-renderer
ffmpeg video, slides"] + end + subgraph Presentation["Presentation Helpers"] + A_TEMPLATE["template-askama
HTML templates"] + A_RSS["rss
Feed generation"] + A_EXPORT["export
CSV/JSON diary"] + A_IMPORT["importer
CSV/JSON/XLSX parser"] + 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 + + 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 Application app + class Adapters adapter + class Binaries binary + class ApiTypes api