I am currently running IDS4 as a single instance in one region (single database for configuration and operational store). I now have to distribute the installation across two regions so that services/users in region A access IDS in region A and services/users in region B access IDS in region B.
Both instances should access the same datastore, but IDS in region B should not have to make cross-region read queries to the database in region A.
We use Azure SQL Server and the geo-replication feature which offers a single writable instance (either in region A or B) and multiple readable instances. We pointed IDS in region B to a read-only instance in the same region, but this does not work because IDS has to write operational data like persistent grants.
Is there a recommended architecture to achieve this or do you have any experience implementing a multi-region and load-balanced IDS deployment? Is it possible to configure IDS to use a different database for write operations and the database in the same region for read operations?
It is unlikely that you will find a recommended architecture for a scenario like this due to how much of this problem is in your business domain. Also, there is nothing out of the box in Identity Server 4 library or its supporting libraries that would satisfy your criteria.
Having said that, I've had a similar requirement (unrelated to Identity Server 4 but identical functional requirements in a nutshell) and it should be possible to adapt the same idea in your case.
Firstly, your only problem like you've said is the fact that out of the box, using the Identity Server 4 EF package, the PersistedGrantStore
uses one IPersistedGrantDbContext
which does both writes and reads from the database. So in order to solve this, you basically need to create your own implementation of IPersistedGrantStore
and in that custom implementation you could technically use two different DbContext
types, one of which would be created using a connection string to single writeable instance of database and would only be used for implementing interface methods that do writes and another one would be used for read methods only and would use connection string for read only instance of the database.
Basic idea of the above summary is below:
public class MyCustomPersistedGrantStore : IPersistedGrantStore
{
private readonly WriteOnlyPersistedGrantDbContext _writeContext;
private readonly ReadOnlyPersistedGrantDbContext _readContext;
public PersistedGrantStore(WriteOnlyPersistedGrantDbContext writeContext, ReadOnlyPersistedGrantDbContext readContext)
{
_writeContext = writeContext;
_readContext = readContext;
}
public Task StoreAsync(PersistedGrant token)
{
//Use _writeContext to implement storage logic
}
public Task<PersistedGrant> GetAsync(string key)
{
//Use _readContext to implement read logic
}
...other interface methods
}
All you need after implementing your custom version is to add your implementation of IPersistedGrantStore
as well as the DbContext
's into DI system .
Lastly, it is worthwhile to note that if you stop using .AddOperationalStore(...config)
then you also forfeit usage of TokenCleanupHostService
so you would need to implement that as well.