Skip to content

Commit

Permalink
Merge pull request #10 from lenndewolten/feature/add-aws-sqs
Browse files Browse the repository at this point in the history
Add AWS SQS
  • Loading branch information
lenndewolten committed Apr 28, 2023
2 parents 5c1c8ae + 4ae5290 commit e4d4135
Show file tree
Hide file tree
Showing 70 changed files with 3,406 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
on:
push:
pull_request:
branches:
- main
- "main"
paths:
- "lib/Dequeueable.AmazonSQS/**"
- "tests/Dequeueable.AmazonSQS.**/"

jobs:
build:
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/aqs-sqs-publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
on:
push:
tags:
- "aws-sqs_v[0-9]+.[0-9]+.[0-9]"
- "aws-sqs_v[0-9]+.[0-9]+.[0-9]-preview*"
- "aws-sqs_v[0-9]+.[0-9]+.[0-9]-beta*"
- "aws-sqs_v[0-9]+.[0-9]+.[0-9]-alpha*"

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Show the Github context for the triggered event
run: echo "$GITHUB_CONTEXT"
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
- name: Set VERSION variable from tag
run: echo "VERSION=${GITHUB_REF_NAME/aws-sqs_v/}" >> $GITHUB_ENV
- name: Show default environment variables
run: |
echo "The job_id is: $GITHUB_JOB" # reference the default environment variables
echo "The id of this action is: $GITHUB_ACTION" # reference the default environment variables
echo "The run id is: $GITHUB_RUN_ID"
echo "The GitHub Actor's username is: $GITHUB_ACTOR"
echo "GitHub SHA: $GITHUB_SHA"
echo "GitHub REF: $GITHUB_REF"
echo "GitHub REF NAME: $GITHUB_REF_NAME"
echo "VERSION: $VERSION"
echo "dotnet version ${DOTNET_VERSION}"
env:
DOTNET_VERSION: ${{ vars.DOTNET_VERSION }}
- uses: actions/checkout@v3
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.x.x"

- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release /p:Version=${VERSION} --no-restore
- name: Test
run: dotnet test --configuration Release /p:Version=${VERSION} --no-build
- name: Pack
run: dotnet pack lib/Dequeueable.AmazonSQS/Dequeueable.AmazonSQS.csproj --configuration Release /p:Version=${VERSION} --no-build --output .
- name: Push
run: dotnet nuget push Dequeueable.AmazonSQS.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${NUGET_KEY}
env:
NUGET_KEY: ${{secrets.NUGET_KEY}}
34 changes: 34 additions & 0 deletions Dequeueable.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dequeueable.AzureQueueStora
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dequeueable.AzureQueueStorage.SampleListener", "samples\Dequeueable.AzureQueueStorage.SampleListener\Dequeueable.AzureQueueStorage.SampleListener.csproj", "{9663C5AD-A70B-49B9-A764-599119EEDEDA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dequeueable.AmazonSQS", "lib\Dequeueable.AmazonSQS\Dequeueable.AmazonSQS.csproj", "{952BD452-FFBE-4253-81BE-A85B955EC94A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dequeueable.AmazonSQS.SampleJob", "samples\Dequeueable.AmazonSQS.SampleJob\Dequeueable.AmazonSQS.SampleJob.csproj", "{8B16AFB4-6124-4D59-86CE-386559E51C7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dequeueable.AmazonSQS.SampleListener", "samples\Dequeueable.AmazonSQS.SampleListener\Dequeueable.AmazonSQS.SampleListener.csproj", "{8EBE6EF9-3A32-4C5D-B617-D231CCDC332D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dequeueable.AmazonSQS.UnitTests", "tests\Dequeueable.AmazonSQS.UnitTests\Dequeueable.AmazonSQS.UnitTests.csproj", "{3B23FE9B-4AEC-4EF0-8B7C-7A14F36B09DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dequeueable.AmazonSQS.IntegrationTests", "tests\Dequeueable.AmazonSQS.IntegrationTests\Dequeueable.AmazonSQS.IntegrationTests.csproj", "{70FB0E93-C10E-45CB-8E82-5C1370B92CE2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,6 +53,26 @@ Global
{9663C5AD-A70B-49B9-A764-599119EEDEDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9663C5AD-A70B-49B9-A764-599119EEDEDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9663C5AD-A70B-49B9-A764-599119EEDEDA}.Release|Any CPU.Build.0 = Release|Any CPU
{952BD452-FFBE-4253-81BE-A85B955EC94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{952BD452-FFBE-4253-81BE-A85B955EC94A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{952BD452-FFBE-4253-81BE-A85B955EC94A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{952BD452-FFBE-4253-81BE-A85B955EC94A}.Release|Any CPU.Build.0 = Release|Any CPU
{8B16AFB4-6124-4D59-86CE-386559E51C7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B16AFB4-6124-4D59-86CE-386559E51C7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B16AFB4-6124-4D59-86CE-386559E51C7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B16AFB4-6124-4D59-86CE-386559E51C7C}.Release|Any CPU.Build.0 = Release|Any CPU
{8EBE6EF9-3A32-4C5D-B617-D231CCDC332D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EBE6EF9-3A32-4C5D-B617-D231CCDC332D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EBE6EF9-3A32-4C5D-B617-D231CCDC332D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EBE6EF9-3A32-4C5D-B617-D231CCDC332D}.Release|Any CPU.Build.0 = Release|Any CPU
{3B23FE9B-4AEC-4EF0-8B7C-7A14F36B09DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B23FE9B-4AEC-4EF0-8B7C-7A14F36B09DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B23FE9B-4AEC-4EF0-8B7C-7A14F36B09DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B23FE9B-4AEC-4EF0-8B7C-7A14F36B09DF}.Release|Any CPU.Build.0 = Release|Any CPU
{70FB0E93-C10E-45CB-8E82-5C1370B92CE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70FB0E93-C10E-45CB-8E82-5C1370B92CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70FB0E93-C10E-45CB-8E82-5C1370B92CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70FB0E93-C10E-45CB-8E82-5C1370B92CE2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -52,6 +82,10 @@ Global
{B89684E3-D232-4819-9B1F-8CF0CD937E8C} = {BF8F3BFE-5BF7-472E-A8B5-6F9957FDE3C0}
{347E828A-0A4B-493E-BE44-E1A387323DC3} = {F6808A89-2B57-49B1-9D5C-E4ACE0CEC44A}
{9663C5AD-A70B-49B9-A764-599119EEDEDA} = {F6808A89-2B57-49B1-9D5C-E4ACE0CEC44A}
{8B16AFB4-6124-4D59-86CE-386559E51C7C} = {F6808A89-2B57-49B1-9D5C-E4ACE0CEC44A}
{8EBE6EF9-3A32-4C5D-B617-D231CCDC332D} = {F6808A89-2B57-49B1-9D5C-E4ACE0CEC44A}
{3B23FE9B-4AEC-4EF0-8B7C-7A14F36B09DF} = {BF8F3BFE-5BF7-472E-A8B5-6F9957FDE3C0}
{70FB0E93-C10E-45CB-8E82-5C1370B92CE2} = {BF8F3BFE-5BF7-472E-A8B5-6F9957FDE3C0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FA24EF80-390F-45DE-B60C-2632F0952E02}
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ A framework to simplify event driven applications in containerized host environm
## Libraries
- [Azure Queue Storage](lib/Dequeueable.AzureQueueStorage/README.md)
Framework that handles the messages on the Azure Queue. A function will be invoked when new messages are detected on the queue. Dequeueing, exception handling and distributed singleton are handled for you.
- [Amazon Simple Queue Service](lib/Dequeueable.AmazonSQS/README.md)
Framework that handles the messages on the AWS SQS. A function will be invoked when new messages are detected on the queue.
85 changes: 85 additions & 0 deletions lib/Dequeueable.AmazonSQS/Configurations/HostBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Dequeueable.AmazonSQS.Services.Hosts;
using Dequeueable.AmazonSQS.Services.Queues;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Dequeueable.AmazonSQS.Configurations
{
internal class HostBuilder : IDequeueableHostBuilder
{
private readonly IServiceCollection _services;

public HostBuilder(IServiceCollection services)
{
_services = services;
}

public IDequeueableHostBuilder RunAsJob(Action<HostOptions>? options = null)
{
_services.AddOptions<HostOptions>().BindConfiguration(HostOptions.Dequeueable)
.ValidateDataAnnotations()
.ValidateOnStart();

if (options is not null)
{
_services.Configure(options);
}

_services.AddHostedService<JobHost>();
_services.AddSingleton<IHostExecutor, JobExecutor>();

_services.TryAddSingleton<IHostOptions>(provider =>
{
var opt = provider.GetRequiredService<IOptions<HostOptions>>();
return opt.Value;
});

return this;
}

public IDequeueableHostBuilder RunAsListener(Action<ListenerHostOptions>? options = null)
{
_services.AddOptions<ListenerHostOptions>().BindConfiguration(HostOptions.Dequeueable)
.Validate(ListenerHostOptions.ValidatePollingInterval, $"The '{nameof(ListenerHostOptions.MinimumPollingIntervalInMilliseconds)}' must not be greater than the '{nameof(ListenerHostOptions.MaximumPollingIntervalInMilliseconds)}'.")
.Validate(ListenerHostOptions.ValidateNewBatchThreshold, $"The '{nameof(ListenerHostOptions.NewBatchThreshold)}' must not be greater than the '{nameof(ListenerHostOptions.BatchSize)}'.")
.ValidateDataAnnotations()
.ValidateOnStart();

if (options is not null)
{
_services.Configure(options);
}

_services.AddHostedService<QueueListenerHost>();
_services.AddSingleton<IHostExecutor, QueueListenerExecutor>();

_services.TryAddSingleton<IHostOptions>(provider =>
{
var opt = provider.GetRequiredService<IOptions<ListenerHostOptions>>();
return opt.Value;
});

return this;
}

public IDequeueableHostBuilder AsSingleton()
{
_services.AddTransient<SingletonManager>();
_services.AddTransient<QueueMessageExecutor>();
_services.AddTransient<IQueueMessageExecutor>(provider =>
{
var singletonManager = provider.GetRequiredService<SingletonManager>();
var executor = provider.GetRequiredService<QueueMessageExecutor>();
return new SingletonQueueMessageExecutor(executor, singletonManager);
});

_services.PostConfigure<HostOptions>(options => options.AttributeNames = options.AttributeNames.Concat(new List<string> { "MessageGroupId" }).ToList());
_services.PostConfigure<ListenerHostOptions>(options => options.AttributeNames = options.AttributeNames.Concat(new List<string> { "MessageGroupId" }).ToList());
_services.PostConfigure<ListenerHostOptions>(options => options.NewBatchThreshold = 0);

return this;
}
}
}
49 changes: 49 additions & 0 deletions lib/Dequeueable.AmazonSQS/Configurations/HostOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.ComponentModel.DataAnnotations;

namespace Dequeueable.AmazonSQS.Configurations
{
/// <summary>
/// HostOptions to configure the settings of the host
/// </summary>
public class HostOptions : IHostOptions
{
private List<string> _attributeNames = new();

/// <summary>
/// Constant string used to bind the appsettings.*.json
/// </summary>
public static string Dequeueable => nameof(Dequeueable);

/// <summary>
/// The URL of the Amazon SQS queue from which messages are received.
/// </summary>
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} cannot be empty.")]
public string QueueUrl { get; set; } = string.Empty;

/// <summary>
/// The maximum number of messages processed in parallel. Valid values: 1 to 10.
/// </summary>
[Range(1, 10,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int BatchSize { get; set; } = 4;

/// <summary>
/// The timeout after the queue message is visible again for other services. Valid values: 30 to 43200 (12 hours) seconds.
/// </summary>
[Range(30, 43200,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int VisibilityTimeoutInSeconds { get; set; } = 300;

/// <summary>
/// A list of attributes that need to be returned along with each message <see cref="Amazon.SQS.Model.Message.Attributes"/>.
/// </summary>
public List<string> AttributeNames
{
get => _attributeNames.Distinct().ToList();
set
{
_attributeNames = value ?? new();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Dequeueable.AmazonSQS.Configurations
{
/// <summary>
/// Interface to builds and setup the dequeueable host
/// </summary>
public interface IDequeueableHostBuilder
{
/// <summary>
/// Runs the function as a Distributed Singleton. Queue messages containing the same MessageGroupId will not run in parallel <see href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html"/>
/// </summary>
/// <returns><see cref="IDequeueableHostBuilder"/></returns>
IDequeueableHostBuilder AsSingleton();
/// <summary>
/// The application will run as a job, from start to finish, and will automatically shutdown when the messages are executed.
/// </summary>
/// <param name="options">Action to configure the <see cref="HostOptions"/></param>
/// <returns><see cref="IDequeueableHostBuilder"/></returns>
IDequeueableHostBuilder RunAsJob(Action<HostOptions>? options = null);
/// <summary>
/// The application will run as a listener, the queue will periodically be polled for new message.
/// </summary>
/// <param name="options">Action to configure the <see cref="ListenerHostOptions"/></param>
/// <returns><see cref="IDequeueableHostBuilder"/></returns>
IDequeueableHostBuilder RunAsListener(Action<ListenerHostOptions>? options = null);
}
}
25 changes: 25 additions & 0 deletions lib/Dequeueable.AmazonSQS/Configurations/IHostOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Dequeueable.AmazonSQS.Configurations
{
/// <summary>
/// Use the IHostOptions to configure the settings of the host
/// </summary>
public interface IHostOptions
{
/// <summary>
/// The maximum number of messages processed in parallel. Valid values: 1 to 10.
/// </summary>
int BatchSize { get; set; }
/// <summary>
/// The timeout after the queue message is visible again for other services. Valid values: 30 to 43200 (12 hours) seconds.
/// </summary>
int VisibilityTimeoutInSeconds { get; set; }
/// <summary>
/// The URL of the Amazon SQS queue from which messages are received.
/// </summary>
string QueueUrl { get; set; }
/// <summary>
/// A list of attributes that need to be returned along with each message <see cref="Amazon.SQS.Model.Message.Attributes"/>.
/// </summary>
List<string> AttributeNames { get; set; }
}
}
41 changes: 41 additions & 0 deletions lib/Dequeueable.AmazonSQS/Configurations/ListenerHostOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;

namespace Dequeueable.AmazonSQS.Configurations
{
/// <summary>
/// HostOptions to configure the settings of the host
/// </summary>
public class ListenerHostOptions : HostOptions
{
/// <summary>
/// The threshold at which a new batch of messages will be fetched.
/// </summary>
public int? NewBatchThreshold { get; set; }

/// <summary>
/// The minimum polling interval to check the queue for new messages.
/// </summary>
[Range(1, long.MaxValue, ErrorMessage = "Value for {0} must be lower than {1}.")]
public long MinimumPollingIntervalInMilliseconds { get; set; } = 5;

/// <summary>
/// The maximum polling interval to check the queue for new messages.
/// </summary>
[Range(1, long.MaxValue, ErrorMessage = "Value for {0} must be lower than {1}.")]
public long MaximumPollingIntervalInMilliseconds { get; set; } = 10000;

/// <summary>
/// The delta used to randomize the polling interval.
/// </summary>
public TimeSpan? DeltaBackOff { get; set; }

internal static bool ValidatePollingInterval(ListenerHostOptions options)
{
return options.MinimumPollingIntervalInMilliseconds < options.MaximumPollingIntervalInMilliseconds;
}
internal static bool ValidateNewBatchThreshold(ListenerHostOptions options)
{
return options.NewBatchThreshold is null || options.NewBatchThreshold <= options.BatchSize;
}
}
}
35 changes: 35 additions & 0 deletions lib/Dequeueable.AmazonSQS/Dequeueable.AmazonSQS.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Dequeueable.AmazonSQS</PackageId>
<Version>1.0.0</Version>
<Authors>Lennart ten Wolde</Authors>
<PackageTags>Amazon;AWS;Simple Queue Service;SQS;Queues;Queue;QueueMessage;QueueMessages;Message;Events;Event</PackageTags>
<Title>Dequeueable for AWS Simple Queue Service library</Title>
<Description>
This client library simplifies dequeuing queue messages from Amazon Simple Queue Service. It makes it easy to retrieve messages from the queue: dequeueing, exception handling and distributed singleton are handled for you.
</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>./README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/lenndewolten/Dequeueable</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<ProjectUrl>https://github.com/lenndewolten/Dequeueable</ProjectUrl>
<PackageProjectUrl>https://github.com/lenndewolten/Dequeueable</PackageProjectUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.SQS" Version="3.7.100.62" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="6.0.0" />
<PackageReference Include="System.Memory.Data" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).UnitTests" />
<InternalsVisibleTo Include="$(AssemblyName).IntegrationTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" />
</ItemGroup>
</Project>

0 comments on commit e4d4135

Please sign in to comment.