Skip to content

Commit

Permalink
Merge pull request #9 from Advanced-Systems/new_feature_set
Browse files Browse the repository at this point in the history
New Feature Set
  • Loading branch information
StefanGreve authored Aug 16, 2024
2 parents 546ce24 + 23185bb commit 993ad6a
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Mediator.Abstractions" Version="3.0.0-preview.27" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
</ItemGroup>

Expand Down
10 changes: 7 additions & 3 deletions AdvancedSystems.Core.Abstractions/CacheOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

namespace AdvancedSystems.Core.Abstractions;

/// <summary>
/// Provides the cache options for an entry in <seealso cref="ICachingService"/>.
/// </summary>
public class CacheOptions
{
/// <summary>
/// Provides the cache options for an entry in <seealso cref="ICachingService"/>.
/// Gets or sets a value that indicates whether a cache entry should be evicted at a specified
/// point in time.
/// </summary>
public DateTimeOffset? AbsoluteExpiration { get; set; }

Expand All @@ -17,8 +21,8 @@ public class CacheOptions
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }

/// <summary>
/// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed.
/// This will not extend the entry lifetime beyond the absolute expiration (if set).
/// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed.
/// This will not extend the entry lifetime beyond the absolute expiration (if set).
/// </summary>
public TimeSpan? SlidingExpiration { get; set; }

Expand Down
67 changes: 47 additions & 20 deletions AdvancedSystems.Core.Abstractions/ICachingService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;

namespace AdvancedSystems.Core.Abstractions;
Expand All @@ -12,33 +11,61 @@ public interface ICachingService
/// <summary>
/// Sets a values in the cache asynchronously.
/// </summary>
/// <typeparam name="T">The type argument must be a reference type and have a public parameterless constructor.</typeparam>
/// <param name="key">A string identifying the requested values.</param>
/// <param name="value">The values to set in the cache.</param>
/// <param name="cancellationToken">Propagates notification that operations should be cancelled.</param>
/// <returns>A ValueTask representing the asynchronous operation.</returns>
/// <exception cref="OperationCanceledException">The cancellation token was canceled. This exception is stored into the returned task.</exception>
/// <typeparam name="T">
/// The type argument must be a reference type and have a public parameterless constructor.
/// </typeparam>
/// <param name="key">
/// A string identifying the requested values.
/// </param>
/// <param name="value">
/// The values to set in the cache.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that operations should be cancelled.
/// </param>
/// <returns>
/// A ValueTask representing the asynchronous operation.
/// </returns>
ValueTask SetAsync<T>(string key, T value, CancellationToken cancellationToken = default) where T : class;

/// <summary>
/// Sets a values in the cache asynchronously.
/// </summary>
/// <typeparam name="T">The type argument must be a reference type and have a public parameterless constructor.</typeparam>
/// <param name="key">A string identifying the requested values.</param>
/// <param name="value">The values to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="cancellationToken">Propagates notification that operations should be cancelled.</param>
/// <returns>A ValueTask representing the asynchronous operation.</returns>
/// <exception cref="OperationCanceledException">The cancellation token was canceled. This exception is stored into the returned task.</exception>
/// <typeparam name="T">
/// The type argument must be a reference type and have a public parameterless constructor.
/// </typeparam>
/// <param name="key">
/// A string identifying the requested values.
/// </param>
/// <param name="value">
/// The values to set in the cache.
/// </param>
/// <param name="options">
/// The cache options for the value.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that operations should be cancelled.
/// </param>
/// <returns>
/// A ValueTask representing the asynchronous operation.
/// </returns>
ValueTask SetAsync<T>(string key, T value, CacheOptions options, CancellationToken cancellationToken = default) where T : class;

/// <summary>
/// Gets a values from the cache asynchronously.
/// </summary>
/// <typeparam name="T">The type argument must be a reference type and have a public parameterless constructor.</typeparam>
/// <param name="key">A string identifying the requested values.</param>
/// <param name="cancellationToken">Propagates notification that operations should be cancelled.</param>
/// <returns>A ValueTask containing the result of type <typeparamref name="T"/> representing the asynchronous operation. The result is null if <paramref name="key"/> can not be identified in the cache.</returns>
/// <exception cref="OperationCanceledException">The cancellation token was canceled. This exception is stored into the returned task.</exception>
/// <typeparam name="T">
/// The type argument must be a reference type and have a public parameterless constructor.
/// </typeparam>
/// <param name="key">
/// A string identifying the requested values.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that operations should be cancelled.
/// </param>
/// <returns>
/// A ValueTask containing the result of type <typeparamref name="T"/> representing the asynchronous
/// operation. The result is null if <paramref name="key"/> can not be identified in the cache.
/// </returns>
ValueTask<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class;
}
24 changes: 24 additions & 0 deletions AdvancedSystems.Core.Abstractions/IMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

using Mediator;

namespace AdvancedSystems.Core.Abstractions;

/// <summary>
/// Represents a message in the system, which includes an identifier and is used for communication purposes.
/// </summary>
/// <remarks>
/// This interface extends <see cref="INotification"/> to integrate with notification-based systems.
/// Implementations of this interface include a unique identifier to distinguish different messages.
/// </remarks>
public interface IMessage : INotification
{
/// <summary>
/// Gets the unique identifier of the message.
/// </summary>
/// <value>
/// A <see cref="Guid"/> that uniquely identifies the message.
/// This property is intended to provide a unique reference for each message instance.
/// </value>
Guid Id { get; init; }
}
54 changes: 54 additions & 0 deletions AdvancedSystems.Core.Abstractions/IMessageBus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace AdvancedSystems.Core.Abstractions;

/// <summary>
/// Defines an in-memory message bus for publishing and subscribing to messages of type <see cref="IMessage"/>.
/// </summary>
public interface IMessageBus : IAsyncDisposable
{
/// <summary>
/// Publishes a message to the message bus.
/// </summary>
/// <typeparam name="T">
/// The type of the message to subscribe to. Must implement <see cref="IMessage"/>.
/// </typeparam>
/// <param name="message">
/// The message to be published.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that operations should be cancelled.
/// </param>
/// <returns>
/// A <see cref="ValueTask"/> representing the asynchronous operation.
/// </returns>
/// <remarks>
/// This method is used to send messages to the bus. Subscribers will receive these messages asynchronously.
/// </remarks>
ValueTask PublishAsync<T>(T message, CancellationToken cancellationToken = default) where T : class, IMessage;

/// <summary>
/// Subscribes to messages of type <typeparamref name="T"/> from the message bus.
/// </summary>
/// <typeparam name="T">
/// The type of the message to subscribe to. Must implement <see cref="IMessage"/>.
/// </typeparam>
/// <param name="cancellationToken">
/// Propagates notification that operations should be cancelled.
/// </param>
/// <returns>
/// This method is used to receive messages of the specified type from the bus. If no messages
/// of the type are currently available, it will wait until one becomes available.
/// </returns>
/// <exception cref="InvalidCastException">
/// Thrown when a message retrieved from the channel cannot be cast to the type <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ChannelClosedException">
/// Thrown when the channel is closed and no more messages are available. This indicates that the channel
/// has been marked as complete and will not accept any more messages.
/// </exception>
ValueTask<T> SubscribeAsync<T>(CancellationToken cancellationToken = default) where T : class, IMessage;
}
6 changes: 3 additions & 3 deletions AdvancedSystems.Core.Tests/AdvancedSystems.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0-release-24373-02" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="XUnit" Version="2.9.0" />
<PackageReference Include="XUnit.Analyzers" Version="1.15.0">
<PackageReference Include="XUnit.Analyzers" Version="1.16.0-pre.22">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="XUnit.Runner.Console" Version="2.9.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="XUnit.Runner.VisualStudio" Version="2.8.2">
<PackageReference Include="XUnit.Runner.VisualStudio" Version="3.0.0-pre.24">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
13 changes: 9 additions & 4 deletions AdvancedSystems.Core.Tests/Fixtures/CachingServiceFixture.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;

using AdvancedSystems.Core.Abstractions;
using AdvancedSystems.Core.Services;

using Microsoft.Extensions.Caching.Distributed;
Expand All @@ -15,10 +16,6 @@ public sealed class CachingServiceFixture
{
private readonly MemoryDistributedCache _memoryCache;

public Mock<IDistributedCache> DistributedCache { get; private set; } = new Mock<IDistributedCache>();

public CachingService CachingService { get; private set; }

public CachingServiceFixture()
{
var options = Options.Create(new MemoryDistributedCacheOptions());
Expand Down Expand Up @@ -52,4 +49,12 @@ public CachingServiceFixture()

this.CachingService = new CachingService(this.DistributedCache.Object);
}

#region Properties

public Mock<IDistributedCache> DistributedCache { get; private set; } = new Mock<IDistributedCache>();

public ICachingService CachingService { get; private set; }

#endregion
}
18 changes: 18 additions & 0 deletions AdvancedSystems.Core.Tests/Fixtures/MessageBusFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using AdvancedSystems.Core.Abstractions;
using AdvancedSystems.Core.Services;

namespace AdvancedSystems.Core.Tests.Fixtures;

public sealed class MessageBusFixture
{
public MessageBusFixture()
{
this.MessageBus = new MessageBus();
}

#region Properties

public IMessageBus MessageBus { get; private set; }

#endregion
}
61 changes: 61 additions & 0 deletions AdvancedSystems.Core.Tests/Services/MessageBusTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Threading.Tasks;

using AdvancedSystems.Core.Abstractions;
using AdvancedSystems.Core.Tests.Fixtures;

using Xunit;

namespace AdvancedSystems.Core.Tests.Services;

public class MessageBusTests : IClassFixture<MessageBusFixture>
{
private readonly MessageBusFixture _fixture;

public MessageBusTests(MessageBusFixture fixture)
{
this._fixture = fixture;
}

public record Message(Guid Id) : IMessage;

public record Passport(Guid Id) : IMessage;

#region Tests

[Fact]
public async Task TestPublishSubscribeRoundtrip_HappyPath()
{
// Arrange
var messageBus = this._fixture.MessageBus;
var expected = new Message(Guid.NewGuid());

// Act
await messageBus.PublishAsync(expected);
Message actual = await messageBus.SubscribeAsync<Message>();

// Assert
Assert.Equal(expected.Id, actual.Id);
}

[Fact]
public async Task TestPublishSubscribeRoundtrip_UnhappyPath()
{
// Arrange
var messageBus = this._fixture.MessageBus;
var expected = new Message(Guid.NewGuid());

// Act
await messageBus.PublishAsync(expected);

async Task SubscribeAsync()
{
await messageBus.SubscribeAsync<Passport>();
}

// Assert
await Assert.ThrowsAsync<InvalidCastException>(SubscribeAsync);
}

#endregion
}
2 changes: 1 addition & 1 deletion AdvancedSystems.Core/AdvancedSystems.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Title>Advanced Systems Core Library</Title>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdvancedSystems.Core.Abstractions" Version="8.0.0-alpha.1" />
<PackageReference Include="AdvancedSystems.Core.Abstractions" Version="8.0.0-alpha.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
Expand Down
39 changes: 39 additions & 0 deletions AdvancedSystems.Core/Common/ExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;

namespace AdvancedSystems.Core.Common;

/// <summary>
/// Utility class for capturing additional structured data to enhance the debugging experience.
/// Exception filters run where the exception is thrown, not where the exception is caught, which
/// makes it possible to capture contextual data during logging.
/// </summary>
/// <remarks>
/// The functions defined here don't work well with <c>async</c> code, because <c>async</c> will
/// cause exceptions to be caught and then re-thrown at the point of the <c>await</c>. So, the
/// exception filter runs at the point of the <c>await</c> instead of where the exception was
/// originally thrown.
/// </remarks>
public static class ExceptionFilter
{
/// <summary>
/// Applies side effects to exception filters when the <c>catch</c> block handles the exception.
/// </summary>
/// <param name="action">The side effect to run in the exception filter.</param>
/// <returns>Returns <c>true</c>.</returns>
public static bool True(Action action)
{
action();
return true;
}

/// <summary>
/// Applies side effects to exception filters when the <c>catch</c> block rethrows the exception.
/// </summary>
/// <param name="action">The side effect to run in the exception filter.</param>
/// <returns>Returns <c>false</c>.</returns>
public static bool False(Action action)
{
action();
return false;
}
}
Loading

0 comments on commit 993ad6a

Please sign in to comment.