Skip to content
This repository has been archived by the owner on Dec 18, 2023. It is now read-only.

Update Surreal.Net Dependency Injection to enable both Multi Tenanted and Multi User scenarios #79

Open
Du-z opened this issue Oct 3, 2022 · 5 comments
Assignees
Labels
feature New feature or request in-progress Work on the issue has commenced

Comments

@Du-z
Copy link
Collaborator

Du-z commented Oct 3, 2022

Preamble

Currently Surreal.Net functions as a standard DB Driver where a single set of credentials is used for all connections to the database. However SurrealDB can do a lot more than your standard DB like generating and consuming access tokens. For this reason we must be able to isolate each users session.

Consideration

  • The Drivers can be used with a single DB login for the liftime of the application. (This the the current state of the drivers) (This would generally (maybe always?) be done with Basic Auth)
  • The Drivers can be used with a different namespace and database for each request.
  • The Drivers can be used with a login per user for each request (Using JWT Auth)

The Changes

Dependency Injection

Update the projects Dependency Injection to allow for the service and options lifetime to be configured when being defined.

EF core uses the ServiceLifetime Enum to define both the contextLifetime and optionsLifetime.

By setting the ServiceLifetime to scoped each request (context of ASP.net) will have it's own instance of the SurrealDB driver.

Some Considerations

The Rest and RPC drivers are currently setup to be consumed as it they are singletons and are not lightweight enough to be created and destroyed repeatedly.

Fixing this should be as simple as setting the Drivers to use DI for HttpClient and WsClient (See typed clients).

Config will need to be injected also.

Driver Changes

Don't use default headers for the HttpClient, set them on a per request basis based on what is in the supplied config. (Is there an equivalent for WsClient?)

Middleware

Create a simple bit of middleware to extract a JWT from an incoming request and insert it into the Surreal.Net configuration for the driver to use during that request.

@ProphetLamb
Copy link
Collaborator

Right, so I've been mediating on the best practices to implement this. The current IDatabase implementations are about as heavy as a HttpClient, so we should use a pool for injecting a IDatabase, (on that note, the interface should really be called ISurrealClient to avoid confusion):

services.AddSingelton<ISurrealClientPool, SurrealClientPool>();
services.AddScoped<ISurrealClientHandle>(s => s.GetSingelton<ISurrealClientPool>().Rent()); // obtains a stale client from the pool, or create a new client 

public sealed class SurrealClientHandle : ISurrealClient {
  private readonly ISurrealClientPool _pool;
  
  public ISurrealClient Client { get; } \\ gets the client instance, if not disposed

\\ Proxy for client implementation 

  void Dispose(); \\ returns the client to the pool, closes the connection, doesn't dispose the client
  
}

We expect the ISurrealClient to be injected into a context object, which defined a strong typed API for a repository to interact with (Here we copy from the MongoDB driver/orm).

public sealed class SurveyContext : IDisposable {
  private readonly ISurrealClient _survey;
  private readonly ISurreadClient _user;
  
  .ctor(ISurrealClient survey, ISurrealClient user, IOptions<SurveySettings> surveySettings, IOptions<UsersSettings> userSettingd) {
    survey.SetConfig(Config.Create()[...]);
    user.SetConfig(Config.Create()[...])
  }
}

@ProphetLamb
Copy link
Collaborator

ProphetLamb commented Oct 5, 2022

For this purpose we reduce the featureset of ISurrealClient ONLY to allow for query, signin, authenticate. #, Change, Update and the other methods are just aliases for queries and do not require additional client sided functionality.

The interface will also return a IAsyncEnumerable<T> using DeserializeAsyncEnumerable wrapping the networkstream instead of evaluating the entire response preemptively.

@ProphetLamb
Copy link
Collaborator

The reduction in functionality of the ISurrealClient interface is groundwork for implementing an ORM, to interact with the database. Here I plan on copying the approach of the MongoDb driver for basic queries.

@ProphetLamb
Copy link
Collaborator

@Du-z Anything to add, before I start hacking it together?

@Du-z
Copy link
Collaborator Author

Du-z commented Oct 8, 2022

In general I think stuff needs to be renamed to reduce ambiguity. Config is probably the most obvious one that needs a better name.

I am always an advocate and go out of my way to make using a service as simple as possible. To that end i would say we should move the pooling down a layer of abstraction so the end user doesn't even have to know about the pooling if they don't want to know about it.

It's not clear to me how DatabaseRpc and DatabaseRest would be heavy if they get their respective WsClient and HttpClient injected along with their Config.

DatabaseRest would be able to reuse the exact same HttpClient (even simultaneously). As long as we set the headers in the request using the config rather than the DefaultRequestHeaders. I believe we are using the thread safe methods already https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0#thread-safety.

DatabaseRpc would probably need to use a pool for WsClient.

Ultimately either way works for me though. If you can work in the ServiceLifetime concept that would be great for those that can simply use a singleton IDatabase

@ProphetLamb ProphetLamb self-assigned this Oct 9, 2022
@ProphetLamb ProphetLamb added in-progress Work on the issue has commenced feature New feature or request and removed hacktoberfest labels Oct 9, 2022
# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
feature New feature or request in-progress Work on the issue has commenced
Projects
None yet
Development

No branches or pull requests

2 participants