Skip to main content

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 BookmarkService singleton, 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 BookmarkService to maintain shared state (cache and search index) across different route modules.
  • The SearchIndex implements a simple inverted index in-memory, which is rebuilt from the BookmarkRepository on startup.
  • A custom LRUCache is used internally by the service layer to optimize bookmark retrieval by ID.
  • The BookmarkRepository provides 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 a metadata field for extensibility.
  • Tag: A label that can be applied to bookmarks for organization. Each tag has a name and a TagColor. The system tracks usage_count to 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_rule that 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 Archived or Trashed states can be returned to the Active state using restore_bookmark.
  • Cross-Transitions: The system allows moving bookmarks directly between Archived and Trashed states.

Collection Lifecycle

Collections are categorized by their CollectionType and their pinning status.

  • Types: Collections are either Manual (user-added bookmarks) or Smart (filter-based auto-population). This type is immutable after creation.
  • Pinning: Both manual and smart collections can inhabit Pinned or Unpinned states, toggled via the pin() and unpin() 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.