27 апр. 2009 г.

Несколько баз данных в SQLAlchemy

Так уж получается, что во многих проектах мне приходится сталкиваться с ситуацией, когда часть хранится в обдной базе данных, а часть в другой. И несмотря на мою нелюбовь к SQLAlchemy, он позволяет организовать такую работу просто и прозрачно. В SQLAlchemy есть объекты класса MetaData, которые служат в качестве реестра имеющихся таблиц. При описании таблиц (явно или при декларативном описании модели) можно указывать разные объекты MetaData для данных, которые предполагается хранить в разных местах. Далее вы можете привязать метаданные каждый к своему соединению с базой данных, а можно научить средства ORM SQLAlchemy выбирать нужное соединение во время выполнения. Для этого при создании сессии передаётся параметр binds. Так как схемы данных для разных баз совершенного естественным образом располагаются в разных модулях (пакетах), мне оказалось удобным использовать в качестве ссылки на метаданные имена модулей, в которых они определены:
binds = {}
for module_name, connection_string in db_config.items():
    metadata = __import__(module_name, None, None, ['metadata']).metadata
    engine = sqlalchemy.create_engine(connection_string, pool_recycle=True)
    for table in metadata.sorted_tables:
        binds[table] = engine
db = sqlalchemy.orm.sessionmaker(binds=binds)()
После этого я работаю с сессией (db) не заботясь о том, где на самом деле хранятся данные. Более того, SQLAlchemy позволяет использовать двуфазные транзакции (twophase=True в sessionmaker) для обеспечения сохранности данных при такой работе.
Ситуация, когда разные данные хранятся в отдельных базах, это только одна из возможных задач. SQLAlchemy также имеет средства для распределённого хранения данных или позволяет относительно легко такие средства создавать. Но на самом деле картина не такая радужная, как я здесь нарисовал. При использование некоторых средств проявляются баги и недоработки в SQLAlchemy. Но об этом позже.