Architecture
BookBoss follows hexagonal (ports & adapters) architecture. Dependencies point inward toward the core domain. The core crate never depends on any outer crate.
Crate Layout
crates/
├── api/ # Adapter: gRPC interface, calls into core ports
├── core/ # Domain layer: business logic, models, port traits
├── database/ # Adapter: persistence (SeaORM — Postgres, MySQL, MariaDB, SQLite)
├── formats/ # Adapter: e-book file formats (EPUB, OPF, KEPUB conversion)
├── frontend/ # Adapter: Dioxus web UI, OPDS catalog server, Kobo sync protocol
├── import/ # Adapter: library scanner + import job handler
├── metadata/ # Adapter: external metadata providers (Hardcover, OpenLibrary, GoogleBooks)
├── storage/ # Adapter: local filesystem library store
├── utils/ # Shared utilities (hashing, string similarity, token generation)
├── bookboss/ # Entry point: wires adapters to ports, loads configuration
└── integration-tests/
Core Crate
The core crate uses domain-based modules. Each domain groups its model, repository trait (port), and service:
crates/core/src/
├── lib.rs # CoreServices composition root, create_services()
├── error.rs # Error, ErrorKind, RepositoryError
├── types.rs # Shared newtypes (Email, Capability, etc.)
├── repository.rs # Repository, Transaction traits; RepositoryService; transaction macros
├── test_support.rs # Mock implementations (behind "test-support" feature)
├── auth/ # Session auth: Session, AuthService, SessionRepository
├── book/ # Books, authors, series, publishers, genres, tags, files
├── conversion/ # ConversionService port trait (EPUB enrichment, KEPUB conversion)
├── device/ # Device sync: Device, DeviceBook, DeviceSyncLog
├── event/ # EventService: broadcast channel for real-time UI updates (SSE)
├── filter/ # BookFilter, FilterCondition, operators (for smart shelves)
├── import/ # Acquisition pipeline: ImportJob, ImportJobService
├── jobs/ # Job queue: Job, JobRepository, JobWorker, JobService, JobHandler
├── library/ # LibraryService (delete_book, library_stats), LibraryRepository
├── opds/ # OpdsService port (OPDS password management)
├── pipeline/ # Port traits: MetadataExtractor, MetadataProvider; PipelineService
├── reading/ # Per-user reading state: UserBookMetadata, ReadStatus
├── shelf/ # Shelves (manual + smart): Shelf, ShelfFilter, ShelfService
├── storage/ # FileStoreService port trait + BookSidecar struct
└── user/ # Users and settings: User, UserService, UserSettingService
Each domain module typically contains:
mod.rs— re-exportsmodel.rs(ormodel/) — domain types (Foo,NewFoo,FooId,FooToken)repository.rs(orrepository/) —FooRepositorytrait (port)service.rs—FooServicetrait +FooServiceImpl
Metadata Providers
The metadata crate implements the MetadataProvider port from core. Providers are queried in parallel with title+author similarity scoring to select the best match:
- Hardcover — primary provider (GraphQL API); returns metadata, cover, ratings, genres
- Open Library — ISBN-based and title search fallback; returns metadata and cover
- Google Books — additional fallback; returns metadata and cover
Each provider implements MetadataProvider::enrich(extracted) -> Option<ProviderBook>.
Import Pipeline
The import subsystem (crates/import/) owns the library scanner:
- LibraryScanner — polls
BOOKBOSS__IMPORT__BOOKDROP_PATHon a timer (or via manual trigger), hashes new files, and enqueuesImportJobrecords
The import worker runs via the core job system (CoreSubsystem/JobWorker):
- Processes
ImportJobrecords through thePipelineService: extract metadata → enrich from providers → create book record → write sidecar → queue for review
Job System
The core job system provides a generic background task framework:
- JobService — manages job enqueueing, handler registration, and dispatch
- JobWorker — polls for pending jobs and dispatches to handlers via
JobService - JobHandler — trait implemented by each handler (import pipeline, EPUB enrichment, KEPUB conversion)
Jobs are persisted in the database with status tracking (Pending, Processing, Completed, Failed). Recovery runs on startup to retry stalled jobs.
Event System
The EventService broadcasts real-time events via a tokio broadcast channel:
IncomingChanged— fired when imports reach NeedsReview, or are approved/rejectedJobsChanged— fired when background jobs are queued, completed, or failed
The frontend exposes these as Server-Sent Events (SSE) at GET /api/v1/events for live UI updates.
Subsystem Pattern
Each crate that owns background work exposes an XxxSubsystem struct and create_xxx_subsystem() factory. Subsystems are composed in bookboss/main.rs via tokio-graceful-shutdown:
#![allow(unused)]
fn main() {
Toplevel::new()
.start(SubsystemBuilder::new("api", api_subsystem.run()))
.start(SubsystemBuilder::new("import", import_subsystem.run()))
.start(SubsystemBuilder::new("core", core_subsystem.run()))
...
}
Current subsystems: ApiSubsystem (gRPC), CoreSubsystem (job worker), ImportSubsystem (library scanner).
Adding a New Domain
- Create a directory under
crates/core/src/(e.g.order/) - Add
mod.rs,model.rs,repository.rs,service.rs - Re-export from
mod.rs - Register the module in
lib.rs - Wire the new service into
CoreServices
Import Conventions
Use flat re-exports from domain modules:
#![allow(unused)]
fn main() {
use crate::user::{User, UserService, UserId}; // not user::model::User
use crate::session::{Session, NewSession};
use crate::repository::{Repository, Transaction};
use crate::types::{Email, Age};
}
Cross-domain references are allowed (e.g. use crate::user::UserId in a shelf model for foreign keys). Keep references one-directional where possible.