Как я могу передать зависимость nameko обработчику событий SqlAlchemy? - PullRequest
0 голосов
/ 14 января 2019

Я пишу небольшой 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 и необходимость использовать зависимость в указанном обработчике?

1 Ответ

0 голосов
/ 16 января 2019

Интересный вопрос. Не уверен, что мой ответ - то, что тебе нужно. Но, возможно, это будет полезно. В любом случае, вы можете просто увидеть другой подход (лучше, чем ничего).

Модели и зависимости.

Не уверен, что 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 (...) и т. Д.) На другом уровне.

Я пытаюсь объяснить, что я имею в виду, используя маленький пример .

Внимание! Я не работал с nameko и не знаю лучших практик и т. Д. Так что это всего лишь видение.

Смотрите комментарии в репо. Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...