Я пишу небольшой RPC-сервис, который позволяет удаленное CRUD базы данных, используя nameko и sqlalchemy. Для некоторых методов / свойств / обработчиков событий мои модели требуют извлечения некоторых данных с использованием зависимости. То, как я хотел бы, чтобы это работало, всякий раз, когда я вызываю один из этих методов впервые в течение жизни экземпляра модели, данные выбираются и кэшируются в модели. После этого методы, которым нужны внешние данные, будут просто использовать кэшированную версию.
У меня возникли проблемы при разработке этого. Передача зависимости в качестве аргумента функции работает только с методами модели. Свойства не позволяют передавать аргументы, и я не контролирую, как вызываются обработчики событий в SQL Alchemy, поэтому я не могу вставить туда какую-либо зависимость. Единственный способ найти эту работу - связать зависимость с экземпляром модели на ранней стадии его жизненного цикла, но я чувствую, что это идет вразрез с паттерном DI.
model.py
from uuid import uuid4
import sqlalchemy as sa
from sqlalchemy import event
from sqlalchemy.dialects import postgresql
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = Base.metadata
class Foo(Base):
__tablename__ = 'foo'
id = sa.Column(postgresql.UUID(as_uuid=True), primary_key=True, default=uuid4)
remote_id = sa.Column(sa.Integer, nullable=False, unique=True)
bar = sa.Column(sa.String, nullable=True)
def __init__(self, *args, **kwargs):
super(Foo, self).__init__(*args, **kwargs)
self._remote_data = None
@property
def remote_service(self):
# somehow return dependency
pass
@property
def remote_data(self):
if self._remote_data is None:
self._remote_data = self.remote_service.get(id=self.remote_id)
return self._remote_data
@property
def baz(self):
return self.bar + self.remote_data.baz
def do_before_insert(mapper, connection, foo):
# do something depending on value in foo.remote_data
pass
event.listen(Foo, 'before_insert', do_before_insert)
service.py
from nameko.extensions import DependencyProvider
from nameko.rpc import rpc
from nameko_sqlalchemy import Database
from .model import Base, Foo
class RemoteDataService(object):
def get(self, remote_id):
pass
class RemoteDataServiceProvider(DependencyProvider):
def get_dependency(self, worker_ctx):
return RemoteDataService()
class FooRPC:
name = "foo_rpc"
db = Database(Base)
@rpc
def get_foo(self, foo_id):
with self.db.get_session() as session:
foo = session.query(Foo).get(foo_id)
return foo
@rpc
def create_foo(self, remote_id, bar=None):
with self.db.get_session() as session:
foo = Foo(remote_id=remote_id, bar=bar)
session.add(foo)
session.commit()
return foo
Одно из моих решений заключается в привязке экземпляра RemoteDataService
, возвращенного поставщиком зависимостей, к пользовательскому сеансу базы данных во время создания сеанса, а затем свойство зависимости для модели выглядит примерно так:
from sqlalchemy.orm import object_session
...
@property
def remote_service(self):
return object_session(self).get_remote_service()
Это решает мои проблемы, но, похоже, не соответствует DI. Также это работает только для моделей, которые уже связаны с сеансом, но в моем конкретном случае я могу жить с этим. При этом я найду лучшее решение, если оно есть.
Что я пытаюсь сделать по своей сути неправильно в области DI / nameko / sqla? Должны ли модели никогда не иметь дело напрямую с зависимостями? В любом случае, как согласовать использование обработчиков событий SQLalchemy (типичный случай, когда вы почти не можете контролировать вызов функции), DI и необходимость использовать зависимость в указанном обработчике?