6

Приложениям часто необходимо подключаться к другим службам (база данных, кэш, API и т. Д.). Для здравомыслия и DRY мы хотели бы сохранить все эти соединения в одном модуле, чтобы остальная часть нашей базы кода могла совместно использовать соединения.Управление созданием соединения в Python?

Для уменьшения шаблонного, использование вниз должно быть простым:

# app/do_stuff.py 
from .connections import AwesomeDB 

db = AwesomeDB() 

def get_stuff(): 
    return db.get('stuff') 

и настройки соединения также должны быть простыми:

# app/cli.py or some other main entry point 
from .connections import AwesomeDB 

db = AwesomeDB() 
db.init(username='stuff admin') # Or os.environ['DB_USER'] 

веб-рамки, как Django и колба сделать что-то вроде этого, но он чувствует себя немного неуклюжим:

Connect to a Database in Flask, Which Approach is better? http://flask.pocoo.org/docs/0.10/tutorial/dbcon/

Одна из серьезных проблем заключается в том, что мы хотим ссылаться на фактический объект соединения вместо прокси-сервера, потому что мы хотим сохранить завершение табуляции в среде iPython и других dev.

Так что же это за правильный путь (tm)? После нескольких итераций, вот моя идея:

#app/connections.py 
from awesome_database import AwesomeDB as RealAwesomeDB 
from horrible_database import HorribleDB as RealHorribleDB 


class ConnectionMixin(object): 
    __connection = None 

    def __new__(cls): 
     cls.__connection = cls.__connection or object.__new__(cls) 
     return cls.__connection 

    def __init__(self, real=False, **kwargs): 
     if real: 
      super().__init__(**kwargs) 

    def init(self, **kwargs): 
     kwargs['real'] = True 
     self.__init__(**kwargs) 


class AwesomeDB(ConnectionMixin, RealAwesomeDB): 
    pass 


class HorribleDB(ConnectionMixin, RealHorribleDB): 
    pass 

Номер для улучшения: Установка начальной __connection к родовому ConnectionProxy вместо None, который улавливает все доступы атрибута и генерирует исключение.

Я проделал здесь много шума и в различных проектах OSS и не видел ничего подобного. Он чувствует себя довольно солидно, хотя это означает, что куча модулей будет создавать объекты соединения в качестве побочного эффекта во время импорта. Это взорвется мне в лицо? Есть ли другие негативные последствия для этого подхода?

ответ

0

Во-первых, дизайн-мудрый, я мог бы что-то не хватает, но я не понимаю, почему вам нужен тяжелый подмешать + одноплодной машины, а не просто определения помощника, как так:

_awesome_db = None 
def awesome_db(**overrides): 
    global _awesome_db 
    if _awesome_db is None: 
     # Read config/set defaults. 
     # overrides.setdefault(...) 
     _awesome_db = RealAwesomeDB(**overrides) 
    return _awesome_db 

Кроме того, это ошибка, которая может не выглядеть поддерживаемом потребительной случае, но в любом случае: если вы сделаете следующие 2 вызова подряд, вы бы неправильно получить тот же объект соединения дважды, даже если вы прошли различные параметры:

db = AwesomeDB() 
db.init(username='stuff admin') 

db = AwesomeDB() 
db.init(username='not-admin') # You'll get admin connection here. 

Простым решением для этого было бы использование dict соединений, подключенных к входные параметры.

Теперь о сути вопроса.

Я думаю, что ответ зависит от того, как реализованы ваши классы «соединения».

Потенциальные недостатки с вашим подходом я вижу, являются:

  • В многопоточной среде вы можете получить проблемы с unsychronized одновременного доступа к глобальному объекту соединения из нескольких потоков, если он уже не потокобезопасным. Если вас это волнует, вы можете немного изменить свой код и интерфейс и использовать локальную переменную потока.

  • Что делать, если процесс вилки после создания соединения? Серверы веб-приложений, как правило, делают это, и это может быть небезопасно, опять же в зависимости от базового соединения.

  • Имеет ли объект соединения состояние? Что произойдет, если объект соединения станет недействительным (из-за ошибки подключения/тайм-аута)? Возможно, вам потребуется заменить сломанное соединение на новое, чтобы вернуться в следующий раз, когда запрашивается соединение.

Управление соединением часто уже эффективно и безопасно реализовать через connection pool в клиентских библиотеках.

Например, Redis-ру Redis клиент использует следующую реализацию:

https://github.com/andymccurdy/redis-py/blob/1c2071762ad9b9288e786665990083e61c1cf355/redis/connection.py#L974

Клиент Redis затем использует пул соединений следующим образом:

  • Requests a connection из пула подключений.
  • Tries to execute a command по соединению.
  • Если соединение не удалось, клиент closes it.
  • В любом случае, окончательно это returned to the connection pool, поэтому его можно повторно использовать последующими вызовами или другими потоками.

Так как клиент Redis обрабатывает все это под капотом, вы можете безопасно делать то, что хотите. Соединения будут создаваться лениво, пока пул соединений не достигнет полной емкости.

# app/connections.py 
def redis_client(**kwargs): 
    # Maybe read configuration/set default arguments 
    # kwargs.setdefault() 
    return redis.Redis(**kwargs) 

Аналогичным образом, можно использовать SQLAlchemy connection pooling as well.

Подводя итог, я понимаю, что:

  • Если ваша клиентская библиотека поддерживает пул соединений, вам не нужно делать ничего особенного, чтобы разделить соединения между модулями и даже нитями. Вы могли бы просто определить помощник, похожий на redis_client(), который считывает конфигурацию или задает параметры по умолчанию.

  • Если ваша клиентская библиотека предоставляет только объекты подключения низкого уровня, вам необходимо убедиться, что доступ к ним является потокобезопасным и безопасным fork. Кроме того, вам нужно убедиться, что каждый раз, когда вы возвращаете действительное соединение (или создаете исключение, если вы не можете установить или повторно использовать существующий).

+0

Спасибо за подробный ответ! Я действительно не считал безопасность ниток и вилок, я обязательно подумаю об этом. Re: ошибка соединения параметров, хорошая уловка и хорошее исправление. Re: пулы клиентов, также хорошая точка, но даже с пулом соединений вы хотите централизовать инициализацию и гарантировать, что вы используете один и тот же пул во всем приложении. – knite

+0

Re: простая вспомогательная функция, я рассматривал этот подход изначально. Это быстро становится раздражающим из-за того, что каждый модуль должен импортировать awesome_db * и * вызывать его. – knite

Смежные вопросы