Repositories¶
Interface for using the Repository Design Pattern.
Instead of using the AbstractStore
(or any of its implementations like
Store
) as a God Class the Repository Design Pattern
allows you to follow the Single Responsibility Rule.
A Repository acts like an in-memory domain object collection, that connects the domain and data mapping layers. Objects can be easily added to and removed from the Repository, due to the mapping code of the Repository which will ensure that the right operations are executed behind the scenes.
Apart from acting as a bridge between the data and the domain layer, the Repository also provides some useful methods for storing, updating, deleting and querying data.
Examples
Lets assume that you are using the default experimentum data store Store
.
With Repository
there is already an implementation which will handle
the mapping between the Repository objects and the SQLAlchemy ORM which is used by
the Store
.
For simplicity we will assume that your database already contains a User and
Address table (for more information on how to specify your domain layer, see:
Migrations
).
Data Layer¶
Each user has a name
, fullname
and a password
field and can have multiple adresses
. Each address has an email
field:
from experimentum.Storage import AbstractRepository
class AddressRepository(AbstractRepository.implementation):
__table__ = 'Address'
def __init__(self, email):
self.email = email
class UserRepository(AbstractRepository.implementation):
__table__ = 'User'
__relationships__ = {
'addresses': [AddressRepository]
}
def __init__(self, name, fullname, password):
self.name = name
self.fullname = fullname
self.password = password
In the __init__
method of the repository you specify the fields of the table.
The __table__
attribute contains the name of the table, while the optional
__relationships__
attribute specifies any relationships of the data.
The key is the attribute under which the data will be accessible, ie:
print(user.addresses) # will print all addresses of a specific user
Actions¶
There are several methods for querying data, like find()
,
get()
, first()
, all()
:
# Find a user by its id
user = UserRepository.find(2) # get user with ID 2
print(user.id, user.name, user.fullname, user.addresses)
# Get many users by a condition (here where name != 'John')
users = UserRepository.get(where=['name', '!=', 'John'])
for user in users:
print(user.id, user.name, user.fullname, user.addresses)
# Get the first user which satisfies a certain condition
user = UserRepository.first(where=['name', 'John'])
print(user.id, user.name, user.fullname, user.addresses)
# Get all users
users = UserRepository.all()
for user in users:
print(user.id, user.name, user.fullname, user.addresses)
The Repository
class also provides several methods for storing, updating, and
deleting data, like create()
, update()
delete()
:
# Create a new user from a data dictionary
user = UserRepository.from_dict({
'name': 'Hello',
'fullname': 'World',
'password': '1234',
# Turns the entries into AddressRepository instances and adds them to the user repo
# so that they get saved with the correct user id
'addresses': [
{'email': 'hello@world.com'},
{'email': 'foo@world.com'},
]
})
user.create() # create the new user
# Update a user
user = UserRepository.find(2)
user.fullname = 'Doe'
user.name = 'John'
user.update()
# Delete a user
user = UserRepository.find(1)
user.delete()
Events¶
A Repository provides several events, allowing you to hook into the following points in a
repositorie’s lifecycle: before_insert()
,
after_insert()
, before_update()
,
after_update()
.
Events allow you to easily execute code each time after/before a specific repository object is saved or updated in the data store.
class AddressRepository(AbstractRepository.implementation):
# ...
def after_insert(self):
print('Gets called each time after an address is saved to the database.')
print(self.id, self.email) # Object has access to the inserted id
Loading¶
In order for the framework to map all the repositories it has to load them via the
RepositoryLoader
class. For the RepositoryLoader
class
to be able to find the repositories, the files have be in your repository folder and
end with Repository.py
. The repository files should contain a repository class with
the same name as the file name.