Initial Commit;
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
13
.idea/.idea.InServiceQue/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.InServiceQue/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/.idea.InServiceQue.iml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
4
.idea/.idea.InServiceQue/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.InServiceQue/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.InServiceQue/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.InServiceQue/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/.idea.InServiceQue/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.InServiceQue/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
13
InServiceQue.Core/InServiceQue.Core.csproj
Normal file
13
InServiceQue.Core/InServiceQue.Core.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
7
InServiceQue.Core/Models/IQueueTask.cs
Normal file
7
InServiceQue.Core/Models/IQueueTask.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace InServiceQue.Core.Models;
|
||||
|
||||
public interface IQueueTask
|
||||
{
|
||||
string GetTypeString();
|
||||
string GetPayloadString();
|
||||
}
|
||||
45
InServiceQue.Core/Models/QueueTask.cs
Normal file
45
InServiceQue.Core/Models/QueueTask.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace InServiceQue.Core.Models;
|
||||
|
||||
public class QueueTask: IQueueTask
|
||||
{
|
||||
private string _type;
|
||||
public Guid Id { get; init; }
|
||||
public string TaskType { get; init; } = default!;
|
||||
public DateTime DateCreated { get; init; }
|
||||
public DateTime? DateProcessed { get; private set; }
|
||||
public DateTime? DateClosed { get; private set; }
|
||||
public int Attempts { get; private set; }
|
||||
public string? Payload { get; init; }
|
||||
|
||||
|
||||
public QueueTask(string taskType, string? payload)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
TaskType = taskType;
|
||||
DateCreated = DateTime.UtcNow;
|
||||
Payload = payload;
|
||||
}
|
||||
|
||||
public QueueTask(IQueueTask task): this(task.GetTypeString(), task.GetPayloadString()) { }
|
||||
|
||||
public void MarkAttempt()
|
||||
{
|
||||
DateProcessed = DateTime.UtcNow;
|
||||
Attempts++;
|
||||
}
|
||||
|
||||
public void SolveTask()
|
||||
{
|
||||
DateClosed = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
string IQueueTask.GetTypeString()
|
||||
{
|
||||
return TaskType;
|
||||
}
|
||||
|
||||
string IQueueTask.GetPayloadString()
|
||||
{
|
||||
return Payload ?? string.Empty;
|
||||
}
|
||||
}
|
||||
20
InServiceQue.Core/Repositories/ITaskRepository.cs
Normal file
20
InServiceQue.Core/Repositories/ITaskRepository.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Data;
|
||||
using InServiceQue.Core.Models;
|
||||
|
||||
namespace InServiceQue.Core.Repositories;
|
||||
|
||||
public interface ITaskRepository: IDisposable
|
||||
{
|
||||
void Insert(QueueTask task);
|
||||
Task InsertAsync(QueueTask task);
|
||||
IDbTransaction StartTransaction();
|
||||
Task<IDbTransaction> StartTransactionAsync();
|
||||
void CommitTransaction(IDbTransaction tx);
|
||||
Task CommitTransactionAsync(IDbTransaction tx);
|
||||
void RollbackTransaction(IDbTransaction tx);
|
||||
Task RollbackTransactionAsync(IDbTransaction tx);
|
||||
QueueTask? GetNextTask(IDbTransaction tx);
|
||||
Task<QueueTask?> GetNextTaskAsync(IDbTransaction tx);
|
||||
void SaveTask(QueueTask task, IDbTransaction tx);
|
||||
Task SaveTaskAsync(QueueTask task, IDbTransaction tx);
|
||||
}
|
||||
13
InServiceQue.InMemory/InServiceQue.InMemory.csproj
Normal file
13
InServiceQue.InMemory/InServiceQue.InMemory.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InServiceQue.Core\InServiceQue.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
82
InServiceQue.InMemory/TaskRepositoryInMemory.cs
Normal file
82
InServiceQue.InMemory/TaskRepositoryInMemory.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using InServiceQue.Core.Models;
|
||||
using InServiceQue.Core.Repositories;
|
||||
|
||||
namespace InServiceQue.InMemory;
|
||||
|
||||
public class TaskRepositoryInMemory: ITaskRepository
|
||||
{
|
||||
private static ConcurrentQueue<QueueTask> _que = new();
|
||||
public void Dispose()
|
||||
{
|
||||
// TODO release managed resources here
|
||||
}
|
||||
|
||||
public void Insert(QueueTask task)
|
||||
{
|
||||
_que.Enqueue(task);
|
||||
}
|
||||
|
||||
public async Task InsertAsync(QueueTask task)
|
||||
{
|
||||
Insert(task);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public IDbTransaction StartTransaction()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IDbTransaction> StartTransactionAsync()
|
||||
{
|
||||
return await Task.FromResult<IDbTransaction>(null);
|
||||
}
|
||||
|
||||
public void CommitTransaction(IDbTransaction tx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task CommitTransactionAsync(IDbTransaction tx)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void RollbackTransaction(IDbTransaction tx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task RollbackTransactionAsync(IDbTransaction tx)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public QueueTask? GetNextTask(IDbTransaction tx)
|
||||
{
|
||||
QueueTask? task;
|
||||
_que.TryDequeue(out task);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
public async Task<QueueTask?> GetNextTaskAsync(IDbTransaction tx)
|
||||
{
|
||||
QueueTask? task = null;
|
||||
_que.TryDequeue(out task);
|
||||
|
||||
return await Task.FromResult(task);
|
||||
}
|
||||
|
||||
public void SaveTask(QueueTask task, IDbTransaction tx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task SaveTaskAsync(QueueTask task, IDbTransaction tx)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
18
InServiceQue.Postgres/InServiceQue.Postgres.csproj
Normal file
18
InServiceQue.Postgres/InServiceQue.Postgres.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InServiceQue.Core\InServiceQue.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
122
InServiceQue.Postgres/TaskRepository.cs
Normal file
122
InServiceQue.Postgres/TaskRepository.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using InServiceQue.Core.Models;
|
||||
using InServiceQue.Core.Repositories;
|
||||
using Npgsql;
|
||||
|
||||
namespace InServiceQue.Postgres;
|
||||
|
||||
public class TaskRepository: ITaskRepository
|
||||
{
|
||||
private readonly NpgsqlConnection _connection;
|
||||
|
||||
public TaskRepository(string connectionString)
|
||||
{
|
||||
_connection = new NpgsqlConnection(connectionString);
|
||||
}
|
||||
|
||||
public void Insert(QueueTask task)
|
||||
{
|
||||
var sql =
|
||||
$"INSERT INTO QUEUE({nameof(QueueTask.Id)}, {nameof(QueueTask.TaskType)}, {nameof(QueueTask.DateCreated)}, {nameof(QueueTask.Payload)}) VALUES (@{nameof(QueueTask.Id)}, @{nameof(QueueTask.TaskType)}, @{nameof(QueueTask.DateCreated)}, @{nameof(QueueTask.Payload)})";
|
||||
|
||||
_connection.Execute(sql, task);
|
||||
}
|
||||
|
||||
public async Task InsertAsync(QueueTask task)
|
||||
{
|
||||
var sql =
|
||||
$"INSERT INTO QUEUE({nameof(QueueTask.Id)}, {nameof(QueueTask.TaskType)}, {nameof(QueueTask.DateCreated)}, {nameof(QueueTask.Payload)}) VALUES (@{nameof(QueueTask.Id)}, @{nameof(QueueTask.TaskType)}, @{nameof(QueueTask.DateCreated)}, @{nameof(QueueTask.Payload)})";
|
||||
|
||||
await _connection.ExecuteAsync(sql, task);
|
||||
}
|
||||
|
||||
public IDbTransaction StartTransaction()
|
||||
{
|
||||
return _connection.BeginTransaction();
|
||||
}
|
||||
|
||||
public async Task<IDbTransaction> StartTransactionAsync()
|
||||
{
|
||||
return await _connection.BeginTransactionAsync();
|
||||
}
|
||||
|
||||
public void CommitTransaction(IDbTransaction tx)
|
||||
{
|
||||
tx.Commit();
|
||||
}
|
||||
|
||||
public async Task CommitTransactionAsync(IDbTransaction tx)
|
||||
{
|
||||
await ((NpgsqlTransaction)tx).CommitAsync();
|
||||
}
|
||||
|
||||
public void RollbackTransaction(IDbTransaction tx)
|
||||
{
|
||||
tx.Rollback();
|
||||
}
|
||||
|
||||
public async Task RollbackTransactionAsync(IDbTransaction tx)
|
||||
{
|
||||
await ((NpgsqlTransaction)tx).RollbackAsync();
|
||||
}
|
||||
|
||||
public QueueTask? GetNextTask(IDbTransaction tx)
|
||||
{
|
||||
var sql = $@"select * from QUEUE where
|
||||
order by {nameof(QueueTask.DateCreated)}
|
||||
limit 1
|
||||
for update skip locked";
|
||||
|
||||
return _connection.QuerySingleOrDefault<QueueTask>(sql, tx);
|
||||
}
|
||||
|
||||
public async Task<QueueTask?> GetNextTaskAsync(IDbTransaction tx)
|
||||
{
|
||||
var sql = $@"select * from QUEUE
|
||||
order by {nameof(QueueTask.DateCreated)}
|
||||
limit 1
|
||||
for update skip locked";
|
||||
|
||||
return await _connection.QuerySingleOrDefaultAsync<QueueTask>(sql, tx);
|
||||
}
|
||||
|
||||
public void SaveTask(QueueTask task, IDbTransaction tx)
|
||||
{
|
||||
var sql = $@"UPDATE QUEUE
|
||||
SET
|
||||
{nameof(QueueTask.DateProcessed)} = @{nameof(QueueTask.DateProcessed)},
|
||||
{nameof(QueueTask.DateClosed)} = @{nameof(QueueTask.DateClosed)},
|
||||
{nameof(QueueTask.Attempts)} = @{nameof(QueueTask.Attempts)},
|
||||
{nameof(QueueTask.Payload)} = @{nameof(QueueTask.Payload)}";
|
||||
|
||||
_connection.Execute(sql, task, tx);
|
||||
}
|
||||
|
||||
public async Task SaveTaskAsync(QueueTask task, IDbTransaction tx)
|
||||
{
|
||||
var sql = $@"UPDATE QUEUE
|
||||
SET {nameof(QueueTask.DateProcessed)} = @{nameof(QueueTask.DateProcessed)},
|
||||
{nameof(QueueTask.DateClosed)} = @{nameof(QueueTask.DateClosed)},
|
||||
{nameof(QueueTask.Attempts)} = @{nameof(QueueTask.Attempts)},
|
||||
{nameof(QueueTask.Payload)} = @{nameof(QueueTask.Payload)}";
|
||||
|
||||
await _connection.ExecuteAsync(sql, task, tx);
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
_connection.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~TaskRepository()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
}
|
||||
}
|
||||
56
InServiceQue.Sample/DIExtensions.cs
Normal file
56
InServiceQue.Sample/DIExtensions.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Reflection;
|
||||
using InServiceQue.Core.Models;
|
||||
using InServiceQue.Core.Repositories;
|
||||
using InServiceQue.InMemory;
|
||||
using InServiceQue.Services;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace InServiceQue.Sample;
|
||||
|
||||
public static class DIExtensions
|
||||
{
|
||||
public static IServiceCollection RegisterInternals(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<ITypeRegistry, QueueTypeRegistry>();
|
||||
services.AddTransient<ITaskRepository, TaskRepositoryInMemory>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection RegisterQueues(this IServiceCollection services)
|
||||
{
|
||||
using var sp = services.BuildServiceProvider();
|
||||
// find all types in the assembly that implement IQueueHandler<T>
|
||||
var queueTypes = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces()
|
||||
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQueueHandler<>)));
|
||||
var hostedServiceRegistrator = new HostedServiceRegistrator();
|
||||
|
||||
// register each query type with its corresponding interface
|
||||
foreach (var queueType in queueTypes)
|
||||
{
|
||||
// get the T from IQueueHandler<T>
|
||||
var type = queueType.GetInterfaces()
|
||||
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQueueHandler<>));
|
||||
|
||||
// register the query type as a scoped service with its corresponding interface
|
||||
services.AddScoped(queueType);
|
||||
|
||||
var typeRegistry = sp.GetRequiredService<ITypeRegistry>();
|
||||
typeRegistry.RegisterTaskType(type.GenericTypeArguments.First().Name, type);
|
||||
|
||||
services.AddScoped(typeof(IQueueHandler<>)
|
||||
.MakeGenericType(type.GenericTypeArguments), queueType);
|
||||
|
||||
//todo: bug here
|
||||
var hostedServiceType = typeof(QueueService<>).MakeGenericType(type.GenericTypeArguments);
|
||||
hostedServiceRegistrator.RegisterHostedService(services, hostedServiceType);
|
||||
|
||||
// services.AddSingleton(typeof(IQueueService<>)
|
||||
// .MakeGenericType(type.GenericTypeArguments), );
|
||||
}
|
||||
return services;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
19
InServiceQue.Sample/HostedServiceRegistrator.cs
Normal file
19
InServiceQue.Sample/HostedServiceRegistrator.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace InServiceQue.Sample;
|
||||
|
||||
public class HostedServiceRegistrator
|
||||
{
|
||||
public void RegisterHostedService(IServiceCollection services, Type hostedServiceType)
|
||||
{
|
||||
Type servicesType = typeof(HostedServiceRegistrator);
|
||||
MethodInfo methodInfo = servicesType.GetMethod("AddHostedService");
|
||||
MethodInfo genericMethod = methodInfo.MakeGenericMethod(hostedServiceType);
|
||||
genericMethod.Invoke(this, new object[] { services });
|
||||
}
|
||||
|
||||
// Needed as a work-arround because we can't call the extension method with reflection.
|
||||
public IServiceCollection AddHostedService<THostedService>(IServiceCollection services)
|
||||
where THostedService : class, IHostedService =>
|
||||
services.AddHostedService<THostedService>();
|
||||
}
|
||||
16
InServiceQue.Sample/InServiceQue.Sample.csproj
Normal file
16
InServiceQue.Sample/InServiceQue.Sample.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InServiceQue.Core\InServiceQue.Core.csproj" />
|
||||
<ProjectReference Include="..\InServiceQue.InMemory\InServiceQue.InMemory.csproj" />
|
||||
<ProjectReference Include="..\InServiceQue.Postgres\InServiceQue.Postgres.csproj" />
|
||||
<ProjectReference Include="..\InServiceQue\InServiceQue.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
InServiceQue.Sample/Program.cs
Normal file
14
InServiceQue.Sample/Program.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using InServiceQue.Core.Models;
|
||||
using InServiceQue.Core.Repositories;
|
||||
using InServiceQue.Sample;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.RegisterInternals();
|
||||
builder.Services.RegisterQueues();
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
|
||||
app.MapGet("/", (string msg) => app.Services.GetService<ITaskRepository>().Insert(new QueueTask(new SendMessageTask(new SendMessagePayload(){To = "John", From = "Garry", Message = msg}))));
|
||||
|
||||
app.Run();
|
||||
37
InServiceQue.Sample/Properties/launchSettings.json
Normal file
37
InServiceQue.Sample/Properties/launchSettings.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:61951",
|
||||
"sslPort": 44361
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5121",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7149;http://localhost:5121",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
InServiceQue.Sample/SendMessageHandler.cs
Normal file
17
InServiceQue.Sample/SendMessageHandler.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using InServiceQue.Services;
|
||||
|
||||
namespace InServiceQue.Sample;
|
||||
|
||||
public class SendMessageHandler: IQueueHandler<SendMessageTask>
|
||||
{
|
||||
public bool Handle(string payload)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> HandleAsync(string payload)
|
||||
{
|
||||
Console.WriteLine(payload);
|
||||
return Task.FromResult<bool>(true);
|
||||
}
|
||||
}
|
||||
33
InServiceQue.Sample/SendMessageTask.cs
Normal file
33
InServiceQue.Sample/SendMessageTask.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InServiceQue.Core.Models;
|
||||
|
||||
namespace InServiceQue.Sample;
|
||||
|
||||
|
||||
public class SendMessagePayload
|
||||
{
|
||||
public string From { get; set; }
|
||||
public string To { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class SendMessageTask: IQueueTask
|
||||
{
|
||||
private SendMessagePayload _payload;
|
||||
|
||||
public SendMessageTask(SendMessagePayload payload)
|
||||
{
|
||||
_payload = payload;
|
||||
}
|
||||
|
||||
public string GetTypeString()
|
||||
{
|
||||
return nameof(SendMessageTask);
|
||||
}
|
||||
|
||||
public string GetPayloadString()
|
||||
{
|
||||
return JsonSerializer.Serialize<SendMessagePayload>(_payload);
|
||||
}
|
||||
}
|
||||
8
InServiceQue.Sample/appsettings.Development.json
Normal file
8
InServiceQue.Sample/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
InServiceQue.Sample/appsettings.json
Normal file
9
InServiceQue.Sample/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
40
InServiceQue.sln
Normal file
40
InServiceQue.sln
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InServiceQue.Core", "InServiceQue.Core\InServiceQue.Core.csproj", "{28B85073-3A07-41E0-9B94-9738280C63A6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InServiceQue.Postgres", "InServiceQue.Postgres\InServiceQue.Postgres.csproj", "{CF132CFC-3FEA-470E-B404-5FB9C4F3100C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InServiceQue", "InServiceQue\InServiceQue.csproj", "{42B7AE80-3C8C-478A-BC7F-0E86CCBC8AD9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InServiceQue.Sample", "InServiceQue.Sample\InServiceQue.Sample.csproj", "{8B68480F-D518-4DF8-BCF1-E77596B47B09}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InServiceQue.InMemory", "InServiceQue.InMemory\InServiceQue.InMemory.csproj", "{97879D5B-FE10-4153-81DD-6B922929C993}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{28B85073-3A07-41E0-9B94-9738280C63A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{28B85073-3A07-41E0-9B94-9738280C63A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{28B85073-3A07-41E0-9B94-9738280C63A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{28B85073-3A07-41E0-9B94-9738280C63A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF132CFC-3FEA-470E-B404-5FB9C4F3100C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF132CFC-3FEA-470E-B404-5FB9C4F3100C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF132CFC-3FEA-470E-B404-5FB9C4F3100C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF132CFC-3FEA-470E-B404-5FB9C4F3100C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{42B7AE80-3C8C-478A-BC7F-0E86CCBC8AD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{42B7AE80-3C8C-478A-BC7F-0E86CCBC8AD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{42B7AE80-3C8C-478A-BC7F-0E86CCBC8AD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{42B7AE80-3C8C-478A-BC7F-0E86CCBC8AD9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8B68480F-D518-4DF8-BCF1-E77596B47B09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B68480F-D518-4DF8-BCF1-E77596B47B09}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B68480F-D518-4DF8-BCF1-E77596B47B09}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8B68480F-D518-4DF8-BCF1-E77596B47B09}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{97879D5B-FE10-4153-81DD-6B922929C993}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{97879D5B-FE10-4153-81DD-6B922929C993}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{97879D5B-FE10-4153-81DD-6B922929C993}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{97879D5B-FE10-4153-81DD-6B922929C993}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
17
InServiceQue/InServiceQue.csproj
Normal file
17
InServiceQue/InServiceQue.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InServiceQue.Core\InServiceQue.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
InServiceQue/Services/IQueueHandler.cs
Normal file
14
InServiceQue/Services/IQueueHandler.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using InServiceQue.Core.Models;
|
||||
|
||||
namespace InServiceQue.Services;
|
||||
|
||||
public interface IQueueHandler
|
||||
{
|
||||
bool Handle(string payload);
|
||||
Task<bool> HandleAsync(string payload);
|
||||
}
|
||||
|
||||
public interface IQueueHandler<T>: IQueueHandler
|
||||
where T: IQueueTask
|
||||
{
|
||||
}
|
||||
15
InServiceQue/Services/IQueueService.cs
Normal file
15
InServiceQue/Services/IQueueService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using InServiceQue.Core.Models;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace InServiceQue.Services;
|
||||
|
||||
public interface IQueueService: IHostedService
|
||||
{
|
||||
void AddTask(IQueueTask task);
|
||||
void TryProcessTask();
|
||||
}
|
||||
|
||||
public interface IQueueService<T> : IQueueService where T : IQueueTask
|
||||
{
|
||||
|
||||
}
|
||||
9
InServiceQue/Services/ITypeRegistry.cs
Normal file
9
InServiceQue/Services/ITypeRegistry.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace InServiceQue.Services;
|
||||
|
||||
public interface ITypeRegistry
|
||||
{
|
||||
void RegisterTaskType(string taskType, Type type);
|
||||
IQueueHandler GetService(IServiceScope scope, string taskType);
|
||||
}
|
||||
118
InServiceQue/Services/QueueService.cs
Normal file
118
InServiceQue/Services/QueueService.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using InServiceQue.Core.Models;
|
||||
using InServiceQue.Core.Repositories;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace InServiceQue.Services;
|
||||
|
||||
public class QueueService<T> : BackgroundService, IQueueService<T>
|
||||
where T: IQueueTask
|
||||
{
|
||||
private readonly ITypeRegistry _typeRegistry;
|
||||
private readonly ITaskRepository _repository;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public QueueService(ITypeRegistry typeRegistry, ITaskRepository repository, IServiceProvider serviceProvider)
|
||||
{
|
||||
_typeRegistry = typeRegistry;
|
||||
_repository = repository;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void AddTask(IQueueTask task)
|
||||
{
|
||||
var queueTask = new QueueTask(task);
|
||||
_repository.Insert(queueTask);
|
||||
}
|
||||
|
||||
public async Task AddTaskAsync(IQueueTask task)
|
||||
{
|
||||
var queueTask = new QueueTask(task);
|
||||
await _repository.InsertAsync(queueTask);
|
||||
}
|
||||
|
||||
public void TryProcessTask()
|
||||
{
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
using var tx = _repository.StartTransaction();
|
||||
var queueTask = _repository.GetNextTask(tx);
|
||||
if (queueTask == null)
|
||||
{
|
||||
_repository.RollbackTransaction(tx);
|
||||
return;
|
||||
}
|
||||
|
||||
var taskType = queueTask.TaskType;
|
||||
var handler = _typeRegistry.GetService(scope, taskType);
|
||||
|
||||
try
|
||||
{
|
||||
Handle(queueTask, handler);
|
||||
_repository.SaveTask(queueTask, tx);
|
||||
_repository.CommitTransaction(tx);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TryProcessTaskAsync()
|
||||
{
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
using var tx = await _repository.StartTransactionAsync();
|
||||
var queueTask = await _repository.GetNextTaskAsync(tx);
|
||||
if (queueTask == null)
|
||||
{
|
||||
await _repository.RollbackTransactionAsync(tx);
|
||||
return;
|
||||
}
|
||||
|
||||
var taskType = queueTask.TaskType;
|
||||
var handler = _typeRegistry.GetService(scope, taskType);
|
||||
|
||||
try
|
||||
{
|
||||
await HandleAsync(queueTask, handler);
|
||||
await _repository.SaveTaskAsync(queueTask, tx);
|
||||
await _repository.CommitTransactionAsync(tx);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Handle(QueueTask queueTask, IQueueHandler handler)
|
||||
{
|
||||
queueTask.MarkAttempt();
|
||||
var success = handler.Handle(queueTask.Payload ?? string.Empty);
|
||||
if (success)
|
||||
{
|
||||
queueTask.SolveTask();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task HandleAsync(QueueTask queueTask, IQueueHandler handler)
|
||||
{
|
||||
queueTask.MarkAttempt();
|
||||
var success = await handler.HandleAsync(queueTask.Payload ?? string.Empty);
|
||||
if (success)
|
||||
{
|
||||
queueTask.SolveTask();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await TryProcessTaskAsync();
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
InServiceQue/Services/QueueTypeRegistry.cs
Normal file
19
InServiceQue/Services/QueueTypeRegistry.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace InServiceQue.Services;
|
||||
|
||||
public class QueueTypeRegistry: ITypeRegistry
|
||||
{
|
||||
private static Dictionary<string, Type> _typeRegistry = new Dictionary<string, Type>();
|
||||
|
||||
public void RegisterTaskType(string taskType, Type type)
|
||||
{
|
||||
_typeRegistry.Add(taskType, type);
|
||||
}
|
||||
|
||||
public IQueueHandler GetService(IServiceScope scope, string taskType)
|
||||
{
|
||||
var classType = _typeRegistry[taskType];
|
||||
return (IQueueHandler)scope.ServiceProvider.GetService(classType);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user