Querying, Pagination, and Search Contracts
Relevant source files
The following files were used as context for generating this wiki page:
- src/forze/application/contracts/querying/builder.py
- src/forze/application/contracts/querying/pagination/cursor_page.py
- src/forze/application/contracts/search/specs.py
- skills/forze-framework-usage/SKILL.md
- skills/forze-documents-search/SKILL.md
- skills/forze-domain-aggregates/SKILL.md
- skills/forze-specs-infrastructure/SKILL.md
The Forze framework employs a structured approach to data retrieval through logical contracts that decouple the application layer from specific database implementations. By defining Specifications (Specs) and Ports, the system allows developers to perform complex filtering, full-text searches, and paginated data access using a unified JSON-based Domain Specific Language (DSL).
These contracts ensure that business logic remains pure, as handlers interact only with abstract interfaces like DocumentQueryPort or SearchQueryPort, while infrastructure details (such as SQL tables or MongoDB collections) are handled by adapters.
Document and Search Specifications
Specifications serve as the logical blueprint for data entities. They define the models used for reading and writing, as well as the unique identity of the resource within the system.
Logical Specs vs. Infrastructure Config
A critical distinction in Forze is that Specs are logical only. They do not contain physical database details like table names or index definitions. Instead, these details are mapped in dependency modules (e.g., PostgresDepsModule) using the name field as a common key.
The diagram shows the inheritance relationship between base specifications and specialized document/search specs.
| Field | Component | Purpose |
|---|---|---|
name | BaseSpec | Logical route identifier used to resolve adapters. |
read | DocumentSpec | The Pydantic model used for returning query results. |
write | DocumentSpec | Mapping of domain, create, and update models. |
fields | SearchSpec | The specific fields included in the search index. |
default_weights | SearchSpec | Ranking importance for different fields (e.g., Title: 0.7, Body: 0.3). |
Sources: [skills/forze-domain-aggregates/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-domain-aggregates/SKILL.md), [skills/forze-documents-search/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-documents-search/SKILL.md), [src/forze/application/contracts/search/specs.py](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/src/forze/application/contracts/search/specs.py)
Query DSL and Filtering
Forze provides a shared JSON DSL for filtering data, which allows application code to remain agnostic of the underlying database query language (SQL, NoSQL, etc.).
DSL Structure
Filters are typically structured using logical operators like $and, $or, and $values.
- Field Shortcuts:
- A simple value (e.g.,
"status": "active") implies an equality$eqoperator. - A list (e.g.,
["a", "b"]) implies an$inoperator. nullimplies$null: true.
- A simple value (e.g.,
Supported Operators
| Category | Operators |
|---|---|
| Comparison | $eq, $neq, $gt, $gte, $lt, $lte |
| Membership | $in, $nin, $null, $empty |
| Set Operations | $superset, $subset, $overlaps, $disjoint |
| Pattern | $like, $ilike, $regex |
Sources: [skills/forze-framework-usage/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-framework-usage/SKILL.md), [skills/forze-documents-search/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-documents-search/SKILL.md)
# Example of the Query DSL in Python
filters = {
"$and": [
{"$values": {"status": {"$in": ["active", "paused"]}}},
{"$values": {"created_at": {"$gte": since_timestamp}}},
]
}
Sources: [skills/forze-documents-search/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-documents-search/SKILL.md)
Pagination Mechanics
Forze supports two primary methods of pagination: Offset-based and Cursor-based. These are integrated into the find_page and search_page methods of the respective ports.
Cursor Pagination
Cursor pagination is preferred for large datasets or real-time streams where offset drift (missing or duplicating items due to concurrent inserts/deletes) must be avoided.
The flow illustrates how the system handles cursor-based requests to ensure stable data navigation.
The CursorPage Structure
The CursorPage data structure encapsulates the result set and the metadata required for subsequent navigation.
| Field | Type | Description |
|---|---|---|
hits | list[T] | The actual data records for the current page. |
count | `int | None` |
next_cursor | `str | None` |
prev_cursor | `str | None` |
Sources: [src/forze/application/contracts/querying/pagination/cursor_page.py](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/src/forze/application/contracts/querying/pagination/cursor_page.py), [skills/forze-framework-usage/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-framework-usage/SKILL.md)
Search Infrastructure
The search system is divided into simple search, hub search, and federated search to handle varying architectural requirements.
Search Types
- Simple Search: Executes a query against a single
SearchSpecindex. - Hub Search: Uses a
HubSearchSpecwhere a primary entity searches through multiple weighted "member legs." - Federated Search: Merges results from multiple independent
SearchSpecdefinitions into a single result set.
This sequence diagram shows the lifecycle of a search request from the handler to the infrastructure adapter.
Port Resolution
Handlers obtain ports via the ExecutionContext. The framework resolves the correct adapter based on the name defined in the Spec and the registration in the DepsRegistry.
# Resolving a search port
search = ctx.search.query(project_search_spec)
page = await search.search_page(
query="roadmap",
filters=...,
limit=20,
offset=0
)
Sources: [skills/forze-framework-usage/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-framework-usage/SKILL.md), [skills/forze-documents-search/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-documents-search/SKILL.md)
Execution and Port Architecture
The relationship between the application layer and the infrastructure is governed by the ExecutionContext.
Port Access
| Port Method | Result | Purpose |
|---|---|---|
ctx.document.query(spec) | DocumentQueryPort | Read operations, filtered listings, and single-object gets. |
ctx.search.query(spec) | SearchQueryPort | Full-text and weighted field searching. |
ctx.search.hub(spec) | SearchQueryPort | Multi-legged weighted search. |
ctx.search.federated(spec) | SearchQueryPort | Unified results from multiple indices. |
Data Flow Logic
- Request: A handler receives a request and identifies the necessary
Spec. - Resolution: The handler calls
ctx.document.query(spec). - Adapter Invocation: The
ExecutionContextlooks up the adapter registered underspec.name. - Transformation: The adapter translates the Forze DSL into database-specific syntax (e.g., SQL
WHEREclauses). - Response: Results are mapped back into the Pydantic
readmodel defined in theSpec.
Sources: [skills/forze-framework-usage/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-framework-usage/SKILL.md), [skills/forze-wiring/SKILL.md](https://github.com/morzecrew/forze/blob/8c2282150eb517e2d337a6c5d671bf8177beaa08/skills/forze-wiring/SKILL.md)
Conclusion
Querying, Pagination, and Search contracts in Forze provide a robust abstraction layer that allows developers to build complex data-driven features without coupling them to specific storage technologies. By utilizing logical Specifications and a powerful JSON DSL, the framework ensures consistency across different adapters (Postgres, MongoDB, Meilisearch) while providing advanced capabilities like cursor pagination and federated search. This architecture is central to maintaining the hexagonal boundaries of the project, allowing for high testability and infrastructure flexibility.