How the Domain Layer Implements the Repository Pattern for SurrealDB Operations
The Open-Notebook domain layer separates business logic from SurrealDB driver specifics by delegating all database operations to repository helpers in open_notebook/database/repository.py while exposing clean CRUD methods through a generic ObjectModel base class.
This architecture keeps the domain layer framework‑agnostic and testable. Instead of embedding SurrealQL queries inside business logic, the repository pattern centralizes connection handling, query construction, and result parsing in dedicated utilities, while domain models in open_notebook/domain/ focus purely on validation and business rules.
Repository Layer: Low‑Level SurrealDB Abstractions
All direct interaction with SurrealDB resides in open_notebook/database/repository.py. This module exposes async helper functions that wrap the official driver, handle RecordID parsing, and manage timestamps.
Query Execution and Record Handling
The repo_query() function executes arbitrary SurrealQL statements and transforms the raw response into plain Python dictionaries. Located at lines 65–73, it parses RecordID values to strings and returns clean data structures that the domain layer can consume without driver dependencies.
from open_notebook.database.repository import repo_query
# Execute a custom SurrealQL statement
results = await repo_query("SELECT * FROM notebook WHERE name = 'Research'")
Atomic CRUD Operations
The repository provides explicit functions for persistence operations, each handling specific SurrealDB commands:
repo_create()(lines 85–99): WrapsINSERTstatements and injectscreated_attimestampsrepo_update()(lines 124–131): HandlesUPDATEoperations with error loggingrepo_upsert()(lines 135–144): ImplementsUPSERTfor idempotent writesrepo_delete()(lines 158–166): ExecutesDELETEcommands with validationrepo_relate()(lines 6–15): Creates graph edges between records using theRELATEstatement
These functions ensure every database interaction includes proper error handling and timestamp management, eliminating repetitive boilerplate from domain models.
Domain Layer: Generic CRUD via ObjectModel
Domain models inherit from ObjectModel in open_notebook/domain/base.py, which implements the repository pattern’s abstraction layer by delegating to the helpers above. Concrete entities only declare a table_name and field definitions to gain full persistence capabilities.
Retrieval and Hydration
The ObjectModel.get(id) method at lines 102–124 constructs a table‑aware SELECT query, validates the record exists, and automatically instantiates the correct subclass via _get_class_by_table_name. For bulk operations, ObjectModel.get_all(order_by) at lines 38–50 builds a SELECT * FROM <table> query with optional ordering validation.
from open_notebook.domain.notebook import Notebook
# Single record retrieval
notebook = await Notebook.get("notebook:abcd1234")
# List all notebooks ordered by creation date
notebooks = await Notebook.get_all(order_by="created_at DESC")
Persistence Lifecycle
The save() method (lines 46–74) orchestrates the persistence workflow:
- Validates the model instance
- Prepares a dictionary of non‑null fields via
_prepare_save_data()(lines 75–84) - Calls
repo_create()for new objects orrepo_update()for existing records - Copies the returned fields back onto the instance to reflect database state
nb = Notebook(name="AI Safety Research", description="Collection of papers")
await nb.save() # Calls repo_create() internally
print(nb.id) # Now populated with RecordID string
Relationship Management
Graph relationships are handled through relate() at lines 117–124, which wraps repo_relate() to create edges between entities. Domain models expose semantic methods like add_to_notebook() that internally call relate() with the appropriate edge type and direction.
Concrete Implementations: Domain‑Specific Models
Concrete entities leverage the inherited CRUD behavior by defining minimal configuration. For example, Notebook in open_notebook/domain/notebook.py (lines 16–18) declares only table_name = "notebook" and gains automatic persistence, retrieval, and deletion capabilities.
class Notebook(ObjectModel):
table_name: str = "notebook"
name: str
description: Optional[str] = None
This declarative approach means adding a new entity requires only field definitions and business logic, with zero boilerplate for database operations.
Advanced Patterns: Complex Queries and Domain Logic
Higher‑level domain methods combine repository calls to implement rich operations. In Notebook.get_sources() (lines 29–42), the method executes a repo_query() with a FETCH clause to retrieve related sources, then hydrates Source objects from the raw rows.
# Fetch notebook with related sources
notebook = await Notebook.get("notebook:xyz789")
sources = await notebook.get_sources(include_full_text=False)
for src in sources:
print(src.title)
Similar patterns appear throughout the domain, such as Source.get_insights() and ChatSession.relate_to_notebook(), demonstrating how the repository pattern enables complex business logic while keeping SurrealQL encapsulated in the infrastructure layer.
Summary
- Repository isolation: All SurrealDB driver code lives in
open_notebook/database/repository.py, providingrepo_query(),repo_create(),repo_update(), andrepo_relate()as the sole database interaction points. - Domain abstraction:
ObjectModelinopen_notebook/domain/base.pyimplements generic CRUD by delegating to repository functions, allowing concrete models to inherit persistence behavior automatically. - Minimal configuration: Domain entities like
Notebookdeclare onlytable_nameand fields, with business logic completely decoupled from SurrealQL syntax. - Relationship support: Graph edges are created via
ObjectModel.relate(), enabling rich domain relationships without exposing the underlyingRELATEstatement complexity. - Testability: The separation allows unit testing domain logic with mocked repository functions, ensuring business rules remain independent of database availability.
Frequently Asked Questions
What is the repository pattern and why does Open-Notebook use it for SurrealDB?
The repository pattern abstracts data access by placing all database operations behind a collection of helper functions. Open‑Notebook uses this pattern to prevent SurrealDB‑specific code (SurrealQL, RecordID handling, connection management) from leaking into business logic. This keeps the domain layer agnostic to the underlying database technology, making it easier to test, maintain, or potentially migrate to different storage backends.
How does ObjectModel handle database record IDs?
ObjectModel automatically manages SurrealDB RecordID objects through the repository layer. When repo_query() executes at lines 65–73, it parses RecordID values to strings before returning them to the domain layer. The get() method at lines 102–124 uses these string IDs to construct table‑aware queries, while save() updates the instance’s id field after creation to reflect the database‑generated identifier.
Can I use the repository functions directly without inheriting from ObjectModel?
Yes. The repository functions in open_notebook/database/repository.py are designed as standalone async utilities that can be imported and used anywhere in the application. While ObjectModel provides convenient CRUD methods for standard entities, direct repository access is appropriate for complex queries, bulk operations, or transactions that don’t map cleanly to single‑model persistence patterns.
How are relationships between entities implemented in the domain layer?
Relationships are created through the relate() method at lines 117–124 of open_notebook/domain/base.py, which delegates to repo_relate() to execute SurrealDB’s RELATE statement. Domain models expose semantic wrapper methods (like Source.add_to_notebook()) that call relate() with the appropriate edge type, keeping the graph structure encapsulated while providing a clean, object‑oriented API for linking entities.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →