我正在编写一个小的RPC服务,该服务允许使用nameko和sqlalchemy对数据库进行远程CRUD。对于某些方法/属性/事件处理程序,我的模型需要使用依赖项来获取一些数据。我希望它的工作方式,在模型实例的生存期内,每当我第一次调用这些方法之一时,都会在模型上获取并缓存数据。之后,需要外部数据的方法将仅使用缓存的版本。
我在设计时遇到了麻烦。将依赖项作为函数参数传递仅适用于模型方法。属性不允许传递参数,并且我无法控制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和需要在所述处理程序中使用依赖项?
答案 0 :(得分:2)
有趣的问题。不确定我的答案是您所需要的。但这也许会有用。无论如何,您只能看到一种不同的方法(总比没有好。)
模型和依赖项。
不确定models
是调用某些服务或依赖项的好地方。我认为是存储基于模型结构的微小逻辑的好地方。就是这样。像这样:
@property
def full_price(self):
return self.coefficient * self.price
@property
def full_address(self):
return ' '.join([self.country, self.city, self.street])
事件。 before_insert
,before_update
等
与型号相同。是更新/设置模型某些字段的好地方。像这样:
foo.counter += 1
或foo.updated_time = datetime.utcnow()
。但不是处理某些“远程数据”的好地方。
最好将所有其他逻辑(例如,从任何服务中保存/缓存/检索数据,我的意思是self.remote_service.get (...)
等)存储在另一层上。
我尝试用small example来解释我的意思。
注意!我没有与 nameko 一起工作,也不了解最佳实践等。所以这只是一个愿景。
查看回购中的评论。希望这会有所帮助。