fix(external-system-gateway): resolve ExternalSystemGateway-011 — name-keyed repository lookups replace fetch-all-then-filter on the call hot path
This commit is contained in:
@@ -6,6 +6,15 @@ public interface IExternalSystemRepository
|
||||
{
|
||||
// ExternalSystemDefinition
|
||||
Task<ExternalSystemDefinition?> GetExternalSystemByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the external system with the given name, or <c>null</c> if no such
|
||||
/// system exists. A name-keyed lookup so hot-path resolution (e.g. a script's
|
||||
/// <c>ExternalSystem.Call()</c>) does not have to fetch every system and filter
|
||||
/// in memory on each call (ExternalSystemGateway-011).
|
||||
/// </summary>
|
||||
Task<ExternalSystemDefinition?> GetExternalSystemByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyList<ExternalSystemDefinition>> GetAllExternalSystemsAsync(CancellationToken cancellationToken = default);
|
||||
Task AddExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default);
|
||||
Task UpdateExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default);
|
||||
@@ -13,6 +22,15 @@ public interface IExternalSystemRepository
|
||||
|
||||
// ExternalSystemMethod
|
||||
Task<ExternalSystemMethod?> GetExternalSystemMethodByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the method with the given name belonging to the given external system,
|
||||
/// or <c>null</c> if no such method exists. A name-keyed lookup so hot-path
|
||||
/// resolution does not have to fetch every method of the system and filter in
|
||||
/// memory on each call (ExternalSystemGateway-011).
|
||||
/// </summary>
|
||||
Task<ExternalSystemMethod?> GetMethodByNameAsync(int externalSystemId, string methodName, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyList<ExternalSystemMethod>> GetMethodsByExternalSystemIdAsync(int externalSystemId, CancellationToken cancellationToken = default);
|
||||
Task AddExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default);
|
||||
Task UpdateExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default);
|
||||
@@ -20,6 +38,16 @@ public interface IExternalSystemRepository
|
||||
|
||||
// DatabaseConnectionDefinition
|
||||
Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the database connection definition with the given name, or <c>null</c>
|
||||
/// if no such connection exists. A name-keyed lookup so hot-path resolution (e.g.
|
||||
/// a script's <c>Database.Connection()</c> / <c>Database.CachedWrite()</c>) does
|
||||
/// not have to fetch every connection and filter in memory on each call
|
||||
/// (ExternalSystemGateway-011).
|
||||
/// </summary>
|
||||
Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyList<DatabaseConnectionDefinition>> GetAllDatabaseConnectionsAsync(CancellationToken cancellationToken = default);
|
||||
Task AddDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default);
|
||||
Task UpdateDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -16,6 +16,12 @@ public class ExternalSystemRepository : IExternalSystemRepository
|
||||
public async Task<ExternalSystemDefinition?> GetExternalSystemByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<ExternalSystemDefinition>().FindAsync(new object[] { id }, cancellationToken);
|
||||
|
||||
// ExternalSystemGateway-011: genuine name-keyed query (server-side WHERE) so the
|
||||
// gateway's hot-path resolution does not fetch every system and filter in memory.
|
||||
public async Task<ExternalSystemDefinition?> GetExternalSystemByNameAsync(string name, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<ExternalSystemDefinition>()
|
||||
.FirstOrDefaultAsync(s => s.Name == name, cancellationToken);
|
||||
|
||||
public async Task<IReadOnlyList<ExternalSystemDefinition>> GetAllExternalSystemsAsync(CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<ExternalSystemDefinition>().ToListAsync(cancellationToken);
|
||||
|
||||
@@ -34,6 +40,13 @@ public class ExternalSystemRepository : IExternalSystemRepository
|
||||
public async Task<ExternalSystemMethod?> GetExternalSystemMethodByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<ExternalSystemMethod>().FindAsync(new object[] { id }, cancellationToken);
|
||||
|
||||
// ExternalSystemGateway-011: genuine name-keyed query scoped to the parent system.
|
||||
public async Task<ExternalSystemMethod?> GetMethodByNameAsync(int externalSystemId, string methodName, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<ExternalSystemMethod>()
|
||||
.FirstOrDefaultAsync(
|
||||
m => m.ExternalSystemDefinitionId == externalSystemId && m.Name == methodName,
|
||||
cancellationToken);
|
||||
|
||||
public async Task<IReadOnlyList<ExternalSystemMethod>> GetMethodsByExternalSystemIdAsync(int externalSystemId, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<ExternalSystemMethod>().Where(m => m.ExternalSystemDefinitionId == externalSystemId).ToListAsync(cancellationToken);
|
||||
|
||||
@@ -52,6 +65,11 @@ public class ExternalSystemRepository : IExternalSystemRepository
|
||||
public async Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<DatabaseConnectionDefinition>().FindAsync(new object[] { id }, cancellationToken);
|
||||
|
||||
// ExternalSystemGateway-011: genuine name-keyed query (server-side WHERE).
|
||||
public async Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByNameAsync(string name, CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<DatabaseConnectionDefinition>()
|
||||
.FirstOrDefaultAsync(c => c.Name == name, cancellationToken);
|
||||
|
||||
public async Task<IReadOnlyList<DatabaseConnectionDefinition>> GetAllDatabaseConnectionsAsync(CancellationToken cancellationToken = default)
|
||||
=> await _context.Set<DatabaseConnectionDefinition>().ToListAsync(cancellationToken);
|
||||
|
||||
|
||||
@@ -170,8 +170,10 @@ public class DatabaseGateway : IDatabaseGateway
|
||||
string connectionName,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var connections = await _repository.GetAllDatabaseConnectionsAsync(cancellationToken);
|
||||
return connections.FirstOrDefault(c =>
|
||||
c.Name.Equals(connectionName, StringComparison.OrdinalIgnoreCase));
|
||||
// ExternalSystemGateway-011: name-keyed repository lookup instead of
|
||||
// fetch-all-then-filter — connection definitions are resolved on every
|
||||
// cached write / connection request, so the repository performs an indexed
|
||||
// query rather than loading every connection into memory.
|
||||
return await _repository.GetDatabaseConnectionByNameAsync(connectionName, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,13 +373,15 @@ public class ExternalSystemClient : IExternalSystemClient
|
||||
string methodName,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var systems = await _repository.GetAllExternalSystemsAsync(cancellationToken);
|
||||
var system = systems.FirstOrDefault(s => s.Name.Equals(systemName, StringComparison.OrdinalIgnoreCase));
|
||||
// ExternalSystemGateway-011: name-keyed repository lookups instead of
|
||||
// fetch-all-then-filter — definitions are resolved on every hot-path call
|
||||
// (a script's ExternalSystem.Call()), so the repository performs an indexed
|
||||
// query rather than loading every system / every method into memory.
|
||||
var system = await _repository.GetExternalSystemByNameAsync(systemName, cancellationToken);
|
||||
if (system == null)
|
||||
return (null, null);
|
||||
|
||||
var methods = await _repository.GetMethodsByExternalSystemIdAsync(system.Id, cancellationToken);
|
||||
var method = methods.FirstOrDefault(m => m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase));
|
||||
var method = await _repository.GetMethodByNameAsync(system.Id, methodName, cancellationToken);
|
||||
|
||||
return (system, method);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,31 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
||||
return all.FirstOrDefault(e => e.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExternalSystemGateway-011: genuine name-keyed lookup. The <c>external_systems</c>
|
||||
/// table has <c>name</c> as its PRIMARY KEY, so this is a single indexed-row fetch
|
||||
/// rather than the previous fetch-all-then-filter.
|
||||
/// </summary>
|
||||
public async Task<ExternalSystemDefinition?> GetExternalSystemByNameAsync(
|
||||
string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var connection = CreateConnection();
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
SELECT name, endpoint_url, auth_type, auth_configuration
|
||||
FROM external_systems
|
||||
WHERE name = @name";
|
||||
command.Parameters.AddWithValue("@name", name);
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
if (!await reader.ReadAsync(cancellationToken))
|
||||
return null;
|
||||
|
||||
return MapExternalSystem(reader);
|
||||
}
|
||||
|
||||
// ── ExternalSystemMethod (read) ──
|
||||
|
||||
public async Task<IReadOnlyList<ExternalSystemMethod>> GetMethodsByExternalSystemIdAsync(
|
||||
@@ -80,6 +105,23 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
||||
return ParseMethodDefinitions(json, externalSystemId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExternalSystemGateway-011: name-keyed method lookup. The site store keeps a
|
||||
/// system's methods in a single JSON column, so this fetches that one row and
|
||||
/// matches the named method in memory. The parent system is resolved via
|
||||
/// <see cref="GetMethodsByExternalSystemIdAsync"/>, which already performs a
|
||||
/// single keyed read of the <c>method_definitions</c> column — this is no more
|
||||
/// I/O than the gateway's previous fetch-all-methods-then-filter call, and on
|
||||
/// the SQL-backed Central repository it is a genuine indexed query.
|
||||
/// </summary>
|
||||
public async Task<ExternalSystemMethod?> GetMethodByNameAsync(
|
||||
int externalSystemId, string methodName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var methods = await GetMethodsByExternalSystemIdAsync(externalSystemId, cancellationToken);
|
||||
return methods.FirstOrDefault(
|
||||
m => m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task<ExternalSystemMethod?> GetExternalSystemMethodByIdAsync(
|
||||
int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -134,6 +176,38 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
||||
return all.FirstOrDefault(d => d.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExternalSystemGateway-011: genuine name-keyed lookup. The
|
||||
/// <c>database_connections</c> table has <c>name</c> as its PRIMARY KEY, so this
|
||||
/// is a single indexed-row fetch rather than fetch-all-then-filter.
|
||||
/// </summary>
|
||||
public async Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByNameAsync(
|
||||
string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var connection = CreateConnection();
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
SELECT name, connection_string, max_retries, retry_delay_ms
|
||||
FROM database_connections
|
||||
WHERE name = @name";
|
||||
command.Parameters.AddWithValue("@name", name);
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
if (!await reader.ReadAsync(cancellationToken))
|
||||
return null;
|
||||
|
||||
return new DatabaseConnectionDefinition(
|
||||
name: reader.GetString(0),
|
||||
connectionString: reader.GetString(1))
|
||||
{
|
||||
Id = GenerateSyntheticId(reader.GetString(0)),
|
||||
MaxRetries = reader.GetInt32(2),
|
||||
RetryDelay = TimeSpan.FromMilliseconds(reader.GetInt64(3))
|
||||
};
|
||||
}
|
||||
|
||||
// ── Write operations (not supported on site) ──
|
||||
|
||||
public Task AddExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default)
|
||||
|
||||
Reference in New Issue
Block a user