Architecture Overview
This section contains architecture diagrams and documentation for etchblok-test-api.
Available Diagrams
Bookmark Management System Context
The Bookmark Management System (internally named "pagemark-api") is a RESTful web service built with Flask. It provides endpoints for managing bookmarks, tags, and collections. The system follows a layered architecture where routes handle HTTP requests and delegate business logic to the BookmarkService.
The service layer orchestrates interactions between the Overview of the Repository Pattern, an internal Service Architecture and Caching Strategy, and a SearchIndex. While the current implementation uses in-memory storage for the repository and search index, the codebase includes configuration and connection pooling stubs for a PostgreSQL database and is designed to integrate with external search services like Elasticsearch or Typesense. Users interact with the system through a set of public API endpoints for CRUD operations and full-text search.
Key Architectural Findings:
- The system is a Flask-based REST API providing CRUD operations for bookmarks, tags, and collections.
- It implements a layered architecture with clear separation between routing, business logic (services), and data access (repositories).
- A custom LRU cache is integrated into the service layer to optimize bookmark retrieval performance.
- Full-text search is provided by an inverted index that maps tokens from titles and descriptions to bookmark IDs.
- The database layer includes a connection pool implementation targeting a PostgreSQL instance (port 5432), although the current repository implementation is in-memory.
- Internal health and diagnostic endpoints (/health, /ready, /info) are provided for monitoring and load balancer integration.
Backend Service Component Architecture
This diagram illustrates the internal layered architecture of the Pagemark bookmark management API.
The system follows a standard layered pattern:
- The API Layer consists of Flask Blueprints that handle HTTP requests and responses. These routes delegate all business logic to the service layer.
- The Service Layer is centered around the
BookmarkServicesingleton, which acts as an orchestrator. It coordinates between the SearchIndex for full-text search, the LRUCache for performance, and the BookmarkRepository for data persistence. - The Data Access Layer provides an abstraction over the storage mechanism. In this implementation, it uses an in-memory repository to manage the lifecycle of Domain Models (Bookmarks, Tags, and Collections).
- The Infrastructure component handles application configuration, providing environment-specific settings to the Flask application factory.
Key interactions include the BookmarkService invalidating the cache on updates, the SearchIndex performing incremental indexing via the repository, and the consistent use of domain models across all layers for data transfer.
Key Architectural Findings:
- The application uses a Singleton pattern for the
BookmarkServiceto maintain shared state (cache and search index) across different route modules. - The
SearchIndeximplements a simple inverted index in-memory, which is rebuilt from theBookmarkRepositoryon startup. - A custom
LRUCacheis used internally by the service layer to optimize bookmark retrieval by ID. - The
BookmarkRepositoryprovides a clean abstraction for CRUD operations, currently backed by in-memory dictionaries but designed to be swappable for a real database. - Domain models (Bookmark, Tag, Collection) are shared across all layers, from the repository up to the API routes for serialization.
Bookmark Domain Entity Relationship Diagram
The data model for the Etchblok API is centered around three core domain entities: Bookmark, Tag, and Collection. These entities are implemented as Python dataclasses and managed by an in-memory repository.
Core Entities
- Bookmark: Represents a saved URL. It includes metadata like title and description, and tracks its lifecycle via a
BookmarkStatus(Active, Archived, or Trashed). It also supports arbitrary key-value pairs in ametadatafield for extensibility. - Tag: A label that can be applied to bookmarks for organization. Each tag has a name and a
TagColor. The system tracksusage_countto monitor how many bookmarks are associated with each tag. - Collection: A grouping mechanism for bookmarks. Collections can be Manual (where users explicitly add bookmarks) or Smart (where bookmarks are automatically included based on a
filter_rulethat matches text in the title or description).
Relationships
- Many-to-Many (Bookmark <-> Tag): A bookmark can have multiple tags, and a single tag can be applied to many bookmarks. This is managed via a list of tag IDs within the Bookmark entity.
- Many-to-Many (Collection <-> Bookmark): A collection contains multiple bookmarks, and a bookmark can belong to multiple collections. For manual collections, this is stored as a list of bookmark IDs. Smart collections resolve this relationship dynamically at runtime using their filter rules.
The architecture uses a clean separation between the domain models (dataclasses) and the persistence logic (repository), allowing for easy transition from the current in-memory storage to a persistent database in the future.
Key Architectural Findings:
- Entities are implemented as Python dataclasses with UUID-based identifiers.
- Relationships are managed through lists of IDs (e.g., Bookmark.tags, Collection.bookmark_ids), representing many-to-many associations.
- The Bookmark entity includes a status enum (ACTIVE, ARCHIVED, TRASHED) to manage its lifecycle.
- Collections support two modes: Manual (static list of IDs) and Smart (dynamic filtering based on rules).
- Tags include a usage counter that is incremented/decremented as bookmarks are tagged or untagged.
Bookmark and Collection Lifecycle State Machine
The state architecture diagram illustrates the lifecycle of the two primary entities in the system: Bookmark and Collection.
Bookmark Lifecycle
Bookmarks follow a visibility-based state machine defined by the BookmarkStatus enum.
- Active: The initial state upon creation via
create_bookmark. - Archived: A state for bookmarks that are no longer active but preserved. Transitions occur via
archive_bookmark. - Trashed: A soft-deleted state. Bookmarks are moved here via
delete_bookmark. - Restoration: Bookmarks in either the
ArchivedorTrashedstates can be returned to theActivestate usingrestore_bookmark. - Cross-Transitions: The system allows moving bookmarks directly between
ArchivedandTrashedstates.
Collection Lifecycle
Collections are categorized by their CollectionType and their pinning status.
- Types: Collections are either
Manual(user-added bookmarks) orSmart(filter-based auto-population). This type is immutable after creation. - Pinning: Both manual and smart collections can inhabit
PinnedorUnpinnedstates, toggled via thepin()andunpin()methods. This affects their display priority in the UI.
The diagram captures these transitions as implemented in the Bookmark and Collection models and orchestrated by the BookmarkService.
Key Architectural Findings:
- Bookmarks use a 'BookmarkStatus' enum with ACTIVE, ARCHIVED, and TRASHED states.
- The 'delete_bookmark' service method performs a soft delete by transitioning the bookmark to the TRASHED state.
- The 'restore_bookmark' method can transition a bookmark from either ARCHIVED or TRASHED back to ACTIVE.
- Collections have a fixed 'CollectionType' (MANUAL or SMART) set at creation.
- Collections track a 'is_pinned' boolean state, modified by 'pin()' and 'unpin()' methods.
- The 'BookmarkService' acts as the state transition coordinator, ensuring repository updates and cache invalidation.