1

我正在编写一个小型 RPC 服务,它允许使用 nameko 和 sqlalchemy 对数据库进行远程 CRUD。对于某些方法/属性/事件处理程序,我的模型需要使用依赖项获取一些数据。我希望它的工作方式,每当我在模型实例的生命周期中第一次调用这些方法之一时,数据就会被获取并缓存在模型上。之后,需要外部数据的方法将只使用缓存版本。

我在设计这个时遇到了一些麻烦。将依赖项作为函数参数传递仅适用于模型方法。属性不允许传递参数,并且我不控制 SQL Alchemy 的事件处理程序的调用方式,因此我不能在那里注入任何依赖项。我发现完成这项工作的唯一方法是在其生命周期的早期将依赖项绑定到模型实例,但我觉得这违反了 DI 模式。

模型.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)

服务.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 以及在所述处理程序中使用依赖项的需要?

4

1 回答 1

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_insertbefore_update

与模型相同。是更新/设置模型某些字段的好地方。就像是:

foo.counter += 1foo.updated_time = datetime.utcnow()。但不是处理一些“远程数据”的好地方。

最好将所有其他逻辑(例如,从任何服务保存/缓存/检索数据,我的意思是self.remote_service.get (...)等)存储在另一层上。

我试着用一个小例子来解释我的意思。

笔记!我没有与nameko 合作,我不知道最佳实践等。所以这只是一个愿景。

请参阅回购中的评论。希望这可以帮助。

于 2019-01-16T15:19:21.807 回答