BaseRepository
Base class for repositories -- the persistence abstraction for aggregates.
Repositories provide a collection-oriented interface (add, get, all)
to load and persist aggregates, hiding database details behind a clean domain API.
See Repositories guide for practical usage and Repositories concept for design rationale.
Bases: Element, OptionsMixin
This is the baseclass for concrete Repository implementations.
The three methods in this baseclass to add, get or all entities are sufficient in most cases
to handle application requirements. They have built-in support for handling child relationships and
honor Unit of Work constructs. While they can be overridden, it is generally suggested to call the
parent method first before writing custom code.
Repositories are strictly meant to be used in conjunction with Aggregate elements. It is always prudent to deal with persistence at the transaction boundary, which is at an Aggregate's level.
Design note: no delete/remove method.
Repositories intentionally do not support hard deletion. Domain state changes — cancellation,
deactivation, archival — should be modeled as explicit state transitions via commands and events,
not as record erasure. Hard deletion is available at the infrastructure level (_dao.delete())
for projection rebuilds, test teardown, and compliance requirements (e.g. GDPR right to erasure).
Source code in src/protean/core/repository.py
60 61 62 | |
query
property
query: QuerySet
Return a QuerySet for fluent filtering on the aggregate's data store.
Use this inside custom repository methods instead of self._dao.query::
@domain.repository(part_of=Person)
class PersonRepository:
def adults(self):
return self.query.filter(age__gte=18).all().items
find_by
find_by(**kwargs: Any) -> Any
Find a single aggregate matching the given criteria.
Raises ObjectNotFoundError if no match is found.
Raises TooManyObjectsError if multiple matches are found.
Example::
@domain.repository(part_of=Person)
class PersonRepository:
def find_by_email(self, email: str) -> Person:
return self.find_by(email=email)
Source code in src/protean/core/repository.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
find
find(criteria: Q) -> ResultSet
Find all aggregates matching a Q criteria expression.
Returns a :class:~protean.core.queryset.ResultSet containing
the matching aggregates. Accepts composable Q objects, making
it easy to build reusable, domain-named query functions::
from protean.utils.query import Q
def overdue_orders() -> Q:
return Q(status="pending", due_date__lt=datetime.now())
results = repo.find(overdue_orders())
results = repo.find(overdue_orders() & Q(total__gte=5000))
Source code in src/protean/core/repository.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
exists
exists(criteria: Q) -> bool
Check if any aggregate matches the given Q criteria.
Returns True when at least one aggregate satisfies the
criteria, False otherwise. Unlike find, this method
does not load aggregate objects -- it only checks for existence::
if repo.exists(Q(email="john@example.com")):
raise ValueError("Email already taken")
Source code in src/protean/core/repository.py
168 169 170 171 172 173 174 175 176 177 178 | |
add
add(item: Any) -> Any
This method helps persist or update aggregates or projections into the persistence store.
Returns the persisted item.
Protean adopts a collection-oriented design pattern to handle persistence. What this means is that the Repository interface does not hint in any way that there is an underlying persistence mechanism, avoiding any notion of saving or persisting data in the design layer. The task of syncing the data back into the persistence store is handled automatically.
To be specific, a Repository mimics a set collection. Whatever the implementation, the repository
will not allow instances of the same object to be added twice. Also, when retrieving objects from
a Repository and modifying them, you don't need to "re-save" them to the Repository.
If there is a Unit of Work in progress, then the changes are performed on the UoW's active session. They are committed whenever the entire UoW is committed. If there is no transaction in progress, changes are committed immediately to the persistence store. This mechanism is part of the DAO's design, and is automatically used wherever one tries to persist data.
Source code in src/protean/core/repository.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | |
get
get(identifier) -> Any
This is a utility method to fetch data from the persistence store by its key identifier. All child objects, including enclosed entities, are returned as part of this call.
Returns the fetched object.
All other data filtering capabilities can be implemented by using the underlying DAO's
BaseDAO.filter method.
Filter methods are typically implemented as domain-contextual queries, like find_adults(),
find_residents_of_area(zipcode), etc. It is also possible to make use of more complicated,
domain-friendly design patterns like the Specification pattern.
Source code in src/protean/core/repository.py
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 | |