-
Notifications
You must be signed in to change notification settings - Fork 20
Description
I wanted to start a discussion around the idea of adding support for multiple Firestore clients in firedantic. I have spent some time thinking about ways that this might work, but I haven't settled on the right pattern yet. I am interested in thoughts and feedback from the community.
Background
Currently, firedantic is configured by creating a single Firestore Client object and passing it to the class along with an optional prefix. That Firestore Client (and prefix) is then shared by all the firedantic Models in the entire application.
The Firestore Client object defines the project, credentials, and database for the connection.
Firedantic offers support for both sync and async operations, accepting either a Client or an AsyncClient object as the db.
Observations
With just a single Firestore Client that is shared by all models, it is not possible to:
- Use a mix of
ClientandAsyncClientconfiguration objects in the same application - Access data from Firestore databases in different projects and/or with different credentials
- Use multiple Firestore databases (introduced last year)
Mixing sync and async models in the same app would make it easier to migrate an application from Client to AsyncClient one model at a time, instead of having to do it all at once.
Some applications are complex and access data in multiple projects, or with different credentials. For example, an application might have read/write access to data in its own project using its primary service account and then it may also have credentials stored in secret manager that allow it to get read access to a database in a different project. Accessing data in two different projects or with two different credentials is not possible with firedantic currently.
Google introduced support for multiple databases in Firestore in February of 2024. Instead of just using the (default) database, you can have additional named databases in the same project. As an example, you could have website database that includes all the Firestore collections related to your website and another one called billing that has all the collections related to your billing system. Accessing data in two different databases is not currently possible with firedantic.
Possible Solutions
I have explored a few different ways of trying to extend firedantic to provide support for multiple clients, but I haven't settled on anything yet. I'd love other suggestions people have that might improve on these options.
Option 1: Multiple Clients
With this option, we would extend firedantic's configurations module to allow you to run configure() multiple times.
For example, you could configure a default database using the existing pattern, and then add additional named databases as well:
client = Client()
billing_client = Client(database="billing")
configure(client)
configure(billing_client, name="billing")
And then when you are defining a model, you could add something there that would tell it to use a different database using the name you passed to configure():
class MyModel(Model):
__collection__ = "my_collection"
__database__ = "billing"
Option 2: No Clients
With this option, you would not define the Firestore Client at the top-level of your application and then pass it into the firedantic class. Instead, you would pass the default arguments that are needed to establish the client, and then you could override those arguments in individual models.
For example, when you configure firedantic at the top level of your application, you would pass it the default settings for creating a client connection:
configure(
mode="async",
project="my-default-project",
database="(default),
credentials=credentials,
prefix="test-"
)
Then, when you are defining a model, you could add something there that could override any of those defaults:
class MyModel(Model):
__collection__ = "my_collection"
__database__ = "billing"
__project__ = "billing-project"
__credentials__ = billing_credentials,
__client_mode__ = "sync",
__prefix__ = ""
The Model class could be extended so it creates or finds an appropriate client from the configuration based on the model configuration. If there is already a client defined inCONFIGURATION it uses that and otherwise it creates one and adds it to the dict.
Considerations for Transaction
Any solution that supports multiple Firestore clients is going to impact the support for transactions. The second option above is particularly problematic for transactions because each model could potentially have it's own client. All the operations in a transaction need to share the same client, so having one client for each model breaks that completely.
My goal is to find a way to do this that wouldn't completely break transactions support. I think it is okay if you can't do transactions that involve models that use multiple clients, as long as you can still do transactions when they all have the same client connection.