Hi,
I'm interested in building complex Python app based on Neo4j.
As I'm also influenced by software architecture, TDD and testability, I recently found an amazing online book "Architecture Patterns with Python"
Among the architectural patterns presented, the repository pattern struck me as essential in order to write my Python app with testability in mind
Repository Pattern
The intent of the pattern is to add an abstraction between the core parts of our application and the database to increase flexibility and testability, through the dependency inversion principle.
Example from the book with SQLAlchemy
Benefits:
- decouples the Domain Model from Infrastructure concerns
- dependency inversion: the ORM should import our model, not the other way around
- switching between Neo4j drivers is now easy and doesn't involve a refactoring of our model
- building a fake repository for unit test is now a trivial task
Repository Pattern with py2neo
This is my attempt at building this pattern for my application with py2neo
:
First I defined my model:
Even though it should be clear from infrastructure definitions, I still had to implement the idea of a graph database here, by specifying 2 classes GraphNode
and GraphRelationship
.
model.py
Property = # TODO
class GraphNode:
pass
class GraphRelationship:
pass
# represents our users
class User(GraphNode):
name = Property()
Then I created an abstract repository interface, with a single match
method for now:
repo.py
from model import GraphNode, GraphRelationship
from abc import ABC, abstractmethod
class AbstractGraphRepository(ABC):
@abstractmethod
def match(object: Union[GraphNode, GraphRelationship]):
raise NotImplementedError()
And implemented a concrete class based on py2neo
py2neo_repo.py
from repo import AbstractGraphRepository
from py2neo import Graph
class Py2NeoRepository(AbstractGraphRepository):
def __init__(url: str):
self._graph = Graph(url)
def match(object: Union[GraphNode, GraphRelationship]):
# how to map my domain model to py2neo OGM ?
I'm stuck at this step above , figuring out what's the best way to define a mapping between my domain model, and py2neo's OGM.
SQLAlchemy
If we take a look how they implemented this pattern in book, based on SQLAlchemy, we can see the following definitions:
orm.py
from sqlalchemy.orm import mapper, relationship
import model # dependency-inversion: the ORM depends on the model
metadata = MetaData()
order_lines = Table( #(2)
"order_lines",
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("sku", String(255)),
Column("qty", Integer, nullable=False),
Column("orderid", String(255)),
)
...
def start_mappers():
# mapping is defined between the OrderLine model object and the order_lines table
lines_mapper = mapper(model.OrderLine, order_lines)
And this allows them to implement a concrete SQLAlchemyRepository
really easily:
sqlalchemy_repo.py
from model import Batch
class SqlAlchemyRepository(AbstractRepository):
def __init__(self, session):
self.session = session
def add(self, batch: model.Batch):
self.session.add(batch) # the mapping and translation from the Domain Model to the ORM is done transparently, thanks to the mapping !
def get(self, reference):
return self.session.query(model.Batch).filter_by(reference=reference).one()
def list(self):
return self.session.query(model.Batch).all()
how to implement the same pattern with py2neo
or neomodel
as concrete repositories ?
how to translate from a domain model to the OGM while keeping the overall code simple and efficient by design ?
Thank you in advance !
pinging @technige and and maybe Robin Edwards from neomodel