Skip to content

Commit f4f55b8

Browse files
Refactor: Agent Accessor Chain (#39)
* refactor: use singleton instead of scoped need to use async local to avoid collisions * refactor: rename NoAgent to UnknownAgent, make publicly usable * feat: agent accessor chain an implementation of the IAgentAccessor that expects to handle multiple implementations of IAgentAccessor in a private service collection.
1 parent dd4b666 commit f4f55b8

File tree

13 files changed

+126
-43
lines changed

13 files changed

+126
-43
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
namespace EntityDb.Abstractions.Agents;
22

33
/// <summary>
4-
/// Represents a type that can access an instance of <see cref="IAgent" /> within a service scope.
4+
/// Represents a type that can access an instance of <see cref="IAgent" />.
55
/// </summary>
66
public interface IAgentAccessor
77
{
88
/// <summary>
9-
/// Returns the agent of the service scope.
9+
/// Returns the agent.
1010
/// </summary>
11-
/// <returns>The agent of the service scope.</returns>
11+
/// <returns>The agent.</returns>
1212
IAgent GetAgent();
1313
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
using EntityDb.Abstractions.Agents;
2+
using System.Threading;
23

34
namespace EntityDb.Common.Agents;
45

56
internal abstract class AgentAccessorBase : IAgentAccessor
67
{
7-
private IAgent? _agent;
8+
private readonly AsyncLocal<IAgent?> _agent = new();
89

910
protected abstract IAgent CreateAgent();
1011

1112
public IAgent GetAgent()
1213
{
13-
return _agent ??= CreateAgent();
14+
return _agent.Value ??= CreateAgent();
1415
}
1516
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using EntityDb.Abstractions.Agents;
2+
using EntityDb.Common.Exceptions;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Logging;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Linq;
8+
9+
namespace EntityDb.Common.Agents;
10+
11+
/// <summary>
12+
/// Represents a type that chains together multiple instances of <see cref="IAgentAccessor" /> and returns the
13+
/// <see cref="IAgent"/> returned by the first <see cref="IAgentAccessor" /> that does not throw an exception.
14+
///
15+
/// If all instances of <see cref="IAgentAccessor"/> throw an exception, this type will throw a
16+
/// <see cref="NoAgentException"/>.
17+
/// </summary>
18+
public class AgentAccessorChain : IAgentAccessor
19+
{
20+
private readonly ILogger<AgentAccessorChain> _logger;
21+
private readonly IAgentAccessor[] _agentAccessors;
22+
23+
/// <ignore/>
24+
public AgentAccessorChain(ILogger<AgentAccessorChain> logger, IOptions<AgentAccessorChainOptions> options) : this(logger, options.Value)
25+
{
26+
}
27+
28+
internal AgentAccessorChain(ILogger<AgentAccessorChain> logger, AgentAccessorChainOptions options)
29+
{
30+
var serviceProvider = options.ServiceCollection
31+
.BuildServiceProvider();
32+
33+
_logger = logger;
34+
_agentAccessors = serviceProvider
35+
.GetServices<IAgentAccessor>()
36+
.ToArray();
37+
}
38+
39+
/// <inheritdoc />
40+
public IAgent GetAgent()
41+
{
42+
foreach (var agentAccessor in _agentAccessors)
43+
{
44+
try
45+
{
46+
return agentAccessor.GetAgent();
47+
}
48+
catch (Exception exception)
49+
{
50+
_logger.LogDebug(exception, "Agent accessor threw an exception. Moving on to next agent accessor");
51+
}
52+
}
53+
54+
throw new NoAgentException();
55+
}
56+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using EntityDb.Abstractions.Agents;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace EntityDb.Common.Agents;
5+
6+
/// <summary>
7+
/// Options for configuring the <see cref="AgentAccessorChain"/>.
8+
/// </summary>
9+
public sealed class AgentAccessorChainOptions
10+
{
11+
/// <summary>
12+
/// A service collection used to resolve multiple instances of <see cref="IAgentAccessor"/>.
13+
/// </summary>
14+
public IServiceCollection ServiceCollection { get; } = new ServiceCollection();
15+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using EntityDb.Abstractions.Agents;
2+
using EntityDb.Abstractions.ValueObjects;
3+
4+
namespace EntityDb.Common.Agents;
5+
6+
/// <summary>
7+
/// Represents an unknown actor who can interact with transactions.
8+
/// </summary>
9+
public class UnknownAgent : IAgent
10+
{
11+
/// <inheritdoc/>
12+
public TimeStamp GetTimeStamp()
13+
{
14+
return TimeStamp.UtcNow;
15+
}
16+
17+
/// <inheritdoc/>
18+
public object GetSignature(string signatureOptionsName)
19+
{
20+
return new UnknownAgentSignature();
21+
}
22+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using EntityDb.Abstractions.Agents;
2+
3+
namespace EntityDb.Common.Agents;
4+
5+
/// <summary>
6+
/// Represents a type that indicates there is no known actor.
7+
/// </summary>
8+
public class UnknownAgentAccessor : IAgentAccessor
9+
{
10+
/// <inheritdoc/>
11+
public IAgent GetAgent()
12+
{
13+
return new UnknownAgent();
14+
}
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace EntityDb.Common.Agents;
2+
3+
/// <summary>
4+
/// Represents the signature of an unknown actor.
5+
/// </summary>
6+
public record UnknownAgentSignature;

src/EntityDb.Common/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static void AddLifoTypeResolver(this IServiceCollection serviceCollection
6868
public static void AddAgentAccessor<TAgentAccessor>(this IServiceCollection serviceCollection)
6969
where TAgentAccessor : class, IAgentAccessor
7070
{
71-
serviceCollection.AddScoped<IAgentAccessor, TAgentAccessor>();
71+
serviceCollection.AddSingleton<IAgentAccessor, TAgentAccessor>();
7272
}
7373

7474
/// <summary>

test/EntityDb.Common.Tests/Implementations/Agents/NoAgent.cs

Lines changed: 0 additions & 17 deletions
This file was deleted.

test/EntityDb.Common.Tests/Implementations/Agents/NoAgentAccessor.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)