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