mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-08-22 19:28:34 +00:00
Merge pull request #23 from alex289/feature/redis
feat: Add redis cache
This commit is contained in:
commit
864d1e812c
@ -7,26 +7,28 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.ApplicationStatus" Version="7.0.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.ApplicationStatus" Version="7.0.0" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="7.0.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="7.0.0" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="7.1.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10"/>
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="7.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10"/>
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.10"/>
|
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="7.0.10" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.10" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
|
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
|
||||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
|
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||||
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj"/>
|
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" />
|
||||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
|
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -14,6 +14,7 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -30,7 +31,8 @@ if (builder.Environment.IsProduction())
|
|||||||
{
|
{
|
||||||
builder.Services
|
builder.Services
|
||||||
.AddHealthChecks()
|
.AddHealthChecks()
|
||||||
.AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")!);
|
.AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")!)
|
||||||
|
.AddRedis(builder.Configuration["RedisHostName"]!, "Redis");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
@ -59,6 +61,19 @@ builder.Services.AddLogging(x => x.AddSimpleConsole(console =>
|
|||||||
console.IncludeScopes = true;
|
console.IncludeScopes = true;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (builder.Environment.IsProduction())
|
||||||
|
{
|
||||||
|
builder.Services.AddStackExchangeRedisCache(options =>
|
||||||
|
{
|
||||||
|
options.Configuration = builder.Configuration["RedisHostName"];
|
||||||
|
options.InstanceName = "clean-architecture";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Services.AddDistributedMemoryCache();
|
||||||
|
}
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
|
@ -13,5 +13,6 @@
|
|||||||
"Issuer": "CleanArchitectureServer",
|
"Issuer": "CleanArchitectureServer",
|
||||||
"Audience": "CleanArchitectureClient",
|
"Audience": "CleanArchitectureClient",
|
||||||
"Secret": "sD3v061gf8BxXgmxcHssasjdlkasjd87439284)@#(*"
|
"Secret": "sD3v061gf8BxXgmxcHssasjdlkasjd87439284)@#(*"
|
||||||
}
|
},
|
||||||
|
"RedisHostName": "redis"
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,26 @@ using CleanArchitecture.Application.Queries.Tenants.GetAll;
|
|||||||
using CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
using CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||||
using CleanArchitecture.Application.ViewModels;
|
using CleanArchitecture.Application.ViewModels;
|
||||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||||
|
using CleanArchitecture.Domain;
|
||||||
using CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
using CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||||
using CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
using CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||||
using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||||
|
using CleanArchitecture.Domain.Entities;
|
||||||
|
using CleanArchitecture.Domain.Extensions;
|
||||||
using CleanArchitecture.Domain.Interfaces;
|
using CleanArchitecture.Domain.Interfaces;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
|
||||||
namespace CleanArchitecture.Application.Services;
|
namespace CleanArchitecture.Application.Services;
|
||||||
|
|
||||||
public sealed class TenantService : ITenantService
|
public sealed class TenantService : ITenantService
|
||||||
{
|
{
|
||||||
private readonly IMediatorHandler _bus;
|
private readonly IMediatorHandler _bus;
|
||||||
|
private readonly IDistributedCache _distributedCache;
|
||||||
|
|
||||||
public TenantService(IMediatorHandler bus)
|
public TenantService(IMediatorHandler bus, IDistributedCache distributedCache)
|
||||||
{
|
{
|
||||||
_bus = bus;
|
_bus = bus;
|
||||||
|
_distributedCache = distributedCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Guid> CreateTenantAsync(CreateTenantViewModel tenant)
|
public async Task<Guid> CreateTenantAsync(CreateTenantViewModel tenant)
|
||||||
@ -47,7 +53,16 @@ public sealed class TenantService : ITenantService
|
|||||||
|
|
||||||
public async Task<TenantViewModel?> GetTenantByIdAsync(Guid tenantId, bool deleted)
|
public async Task<TenantViewModel?> GetTenantByIdAsync(Guid tenantId, bool deleted)
|
||||||
{
|
{
|
||||||
return await _bus.QueryAsync(new GetTenantByIdQuery(tenantId, deleted));
|
var cachedTenant = await _distributedCache.GetOrCreateJsonAsync(
|
||||||
|
CacheKeyGenerator.GetEntityCacheKey<Tenant>(tenantId),
|
||||||
|
async () => await _bus.QueryAsync(new GetTenantByIdQuery(tenantId, deleted)),
|
||||||
|
new DistributedCacheEntryOptions
|
||||||
|
{
|
||||||
|
SlidingExpiration = TimeSpan.FromDays(3),
|
||||||
|
AbsoluteExpiration = DateTimeOffset.Now.AddDays(30)
|
||||||
|
});
|
||||||
|
|
||||||
|
return cachedTenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResult<TenantViewModel>> GetAllTenantsAsync(PageQuery query, string searchTerm = "")
|
public async Task<PagedResult<TenantViewModel>> GetAllTenantsAsync(PageQuery query, string searchTerm = "")
|
||||||
|
@ -18,7 +18,7 @@ public sealed class TenantViewModel
|
|||||||
{
|
{
|
||||||
Id = tenant.Id,
|
Id = tenant.Id,
|
||||||
Name = tenant.Name,
|
Name = tenant.Name,
|
||||||
Users = tenant.Users.Select(UserViewModel.FromUser)
|
Users = tenant.Users.Select(UserViewModel.FromUser).ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ namespace CleanArchitecture.Application.ViewModels.Users;
|
|||||||
public sealed class UserViewModel
|
public sealed class UserViewModel
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
public Guid TenantId { get; set; }
|
||||||
public string Email { get; set; } = string.Empty;
|
public string Email { get; set; } = string.Empty;
|
||||||
public string FirstName { get; set; } = string.Empty;
|
public string FirstName { get; set; } = string.Empty;
|
||||||
public string LastName { get; set; } = string.Empty;
|
public string LastName { get; set; } = string.Empty;
|
||||||
@ -18,6 +19,7 @@ public sealed class UserViewModel
|
|||||||
return new UserViewModel
|
return new UserViewModel
|
||||||
{
|
{
|
||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
|
TenantId = user.TenantId,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
FirstName = user.FirstName,
|
FirstName = user.FirstName,
|
||||||
LastName = user.LastName,
|
LastName = user.LastName,
|
||||||
|
13
CleanArchitecture.Domain/CacheKeyGenerator.cs
Normal file
13
CleanArchitecture.Domain/CacheKeyGenerator.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using CleanArchitecture.Domain.Entities;
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Domain;
|
||||||
|
|
||||||
|
public static class CacheKeyGenerator
|
||||||
|
{
|
||||||
|
public static string GetEntityCacheKey<TEntity>(TEntity entity) where TEntity : Entity =>
|
||||||
|
$"{typeof(TEntity)}-{entity.Id}";
|
||||||
|
|
||||||
|
public static string GetEntityCacheKey<TEntity>(Guid id) where TEntity : Entity =>
|
||||||
|
$"{typeof(TEntity)}-{id}";
|
||||||
|
}
|
@ -6,11 +6,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
|
||||||
<PackageReference Include="FluentValidation" Version="11.7.1" />
|
<PackageReference Include="FluentValidation" Version="11.7.1"/>
|
||||||
<PackageReference Include="MediatR" Version="12.1.1" />
|
<PackageReference Include="MediatR" Version="12.1.1"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1"/>
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.1"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -100,7 +100,7 @@ public sealed class CreateUserCommandHandler : CommandHandlerBase,
|
|||||||
|
|
||||||
if (await CommitAsync())
|
if (await CommitAsync())
|
||||||
{
|
{
|
||||||
await Bus.RaiseEventAsync(new UserCreatedEvent(user.Id));
|
await Bus.RaiseEventAsync(new UserCreatedEvent(user.Id, user.TenantId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,7 +62,7 @@ public sealed class DeleteUserCommandHandler : CommandHandlerBase,
|
|||||||
|
|
||||||
if (await CommitAsync())
|
if (await CommitAsync())
|
||||||
{
|
{
|
||||||
await Bus.RaiseEventAsync(new UserDeletedEvent(request.UserId));
|
await Bus.RaiseEventAsync(new UserDeletedEvent(request.UserId, user.TenantId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -100,7 +100,7 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
|
|||||||
|
|
||||||
if (await CommitAsync())
|
if (await CommitAsync())
|
||||||
{
|
{
|
||||||
await Bus.RaiseEventAsync(new UserUpdatedEvent(user.Id));
|
await Bus.RaiseEventAsync(new UserUpdatedEvent(user.Id, user.TenantId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CleanArchitecture.Domain.Entities;
|
||||||
using CleanArchitecture.Domain.Events.Tenant;
|
using CleanArchitecture.Domain.Events.Tenant;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.EventHandler;
|
namespace CleanArchitecture.Domain.EventHandler;
|
||||||
|
|
||||||
@ -10,18 +12,29 @@ public sealed class TenantEventHandler :
|
|||||||
INotificationHandler<TenantDeletedEvent>,
|
INotificationHandler<TenantDeletedEvent>,
|
||||||
INotificationHandler<TenantUpdatedEvent>
|
INotificationHandler<TenantUpdatedEvent>
|
||||||
{
|
{
|
||||||
|
private readonly IDistributedCache _distributedCache;
|
||||||
|
|
||||||
|
public TenantEventHandler(IDistributedCache distributedCache)
|
||||||
|
{
|
||||||
|
_distributedCache = distributedCache;
|
||||||
|
}
|
||||||
|
|
||||||
public Task Handle(TenantCreatedEvent notification, CancellationToken cancellationToken)
|
public Task Handle(TenantCreatedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Handle(TenantDeletedEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(TenantDeletedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
await _distributedCache.RemoveAsync(
|
||||||
|
CacheKeyGenerator.GetEntityCacheKey<Tenant>(notification.AggregateId),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Handle(TenantUpdatedEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(TenantUpdatedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
await _distributedCache.RemoveAsync(
|
||||||
|
CacheKeyGenerator.GetEntityCacheKey<Tenant>(notification.AggregateId),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CleanArchitecture.Domain.Entities;
|
||||||
using CleanArchitecture.Domain.Events.User;
|
using CleanArchitecture.Domain.Events.User;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.EventHandler;
|
namespace CleanArchitecture.Domain.EventHandler;
|
||||||
|
|
||||||
@ -11,23 +13,36 @@ public sealed class UserEventHandler :
|
|||||||
INotificationHandler<UserUpdatedEvent>,
|
INotificationHandler<UserUpdatedEvent>,
|
||||||
INotificationHandler<PasswordChangedEvent>
|
INotificationHandler<PasswordChangedEvent>
|
||||||
{
|
{
|
||||||
|
private readonly IDistributedCache _distributedCache;
|
||||||
|
|
||||||
|
public UserEventHandler(IDistributedCache distributedCache)
|
||||||
|
{
|
||||||
|
_distributedCache = distributedCache;
|
||||||
|
}
|
||||||
|
|
||||||
public Task Handle(PasswordChangedEvent notification, CancellationToken cancellationToken)
|
public Task Handle(PasswordChangedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
await _distributedCache.RemoveAsync(
|
||||||
|
CacheKeyGenerator.GetEntityCacheKey<Tenant>(notification.TenantId),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Handle(UserDeletedEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(UserDeletedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
await _distributedCache.RemoveAsync(
|
||||||
|
CacheKeyGenerator.GetEntityCacheKey<Tenant>(notification.TenantId),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
await _distributedCache.RemoveAsync(
|
||||||
|
CacheKeyGenerator.GetEntityCacheKey<Tenant>(notification.TenantId),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,10 @@ namespace CleanArchitecture.Domain.Events.User;
|
|||||||
|
|
||||||
public sealed class UserCreatedEvent : DomainEvent
|
public sealed class UserCreatedEvent : DomainEvent
|
||||||
{
|
{
|
||||||
public UserCreatedEvent(Guid userId) : base(userId)
|
public Guid TenantId { get; }
|
||||||
|
|
||||||
|
public UserCreatedEvent(Guid userId, Guid tenantId) : base(userId)
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,10 @@ namespace CleanArchitecture.Domain.Events.User;
|
|||||||
|
|
||||||
public sealed class UserDeletedEvent : DomainEvent
|
public sealed class UserDeletedEvent : DomainEvent
|
||||||
{
|
{
|
||||||
public UserDeletedEvent(Guid userId) : base(userId)
|
public Guid TenantId { get; }
|
||||||
|
|
||||||
|
public UserDeletedEvent(Guid userId, Guid tenantId) : base(userId)
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,10 @@ namespace CleanArchitecture.Domain.Events.User;
|
|||||||
|
|
||||||
public sealed class UserUpdatedEvent : DomainEvent
|
public sealed class UserUpdatedEvent : DomainEvent
|
||||||
{
|
{
|
||||||
public UserUpdatedEvent(Guid userId) : base(userId)
|
public Guid TenantId { get; }
|
||||||
|
|
||||||
|
public UserUpdatedEvent(Guid userId, Guid tenantId) : base(userId)
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Domain.Extensions;
|
||||||
|
|
||||||
|
public static class DistributedCacheExtensions
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerSettings s_jsonSerializerSettings = new()
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
|
||||||
|
public static async Task<T?> GetOrCreateJsonAsync<T>(
|
||||||
|
this IDistributedCache cache,
|
||||||
|
string key,
|
||||||
|
Func<Task<T?>> factory,
|
||||||
|
DistributedCacheEntryOptions options,
|
||||||
|
CancellationToken cancellationToken = default) where T : class
|
||||||
|
{
|
||||||
|
var json = await cache.GetStringAsync(key, cancellationToken);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<T>(json, s_jsonSerializerSettings)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = await factory();
|
||||||
|
|
||||||
|
if (value == default)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
json = JsonConvert.SerializeObject(value, s_jsonSerializerSettings);
|
||||||
|
|
||||||
|
await cache.SetStringAsync(key, json, options, cancellationToken);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ public sealed class InMemoryBusTests
|
|||||||
|
|
||||||
var inMemoryBus = new InMemoryBus(mediator, domainEventStore);
|
var inMemoryBus = new InMemoryBus(mediator, domainEventStore);
|
||||||
|
|
||||||
var userDeletedEvent = new UserDeletedEvent(Guid.NewGuid());
|
var userDeletedEvent = new UserDeletedEvent(Guid.NewGuid(), Guid.NewGuid());
|
||||||
|
|
||||||
await inMemoryBus.RaiseEventAsync(userDeletedEvent);
|
await inMemoryBus.RaiseEventAsync(userDeletedEvent);
|
||||||
|
|
||||||
|
16
Readme.md
16
Readme.md
@ -38,6 +38,19 @@ To run the project, follow these steps:
|
|||||||
|
|
||||||
### Using docker
|
### Using docker
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
> This is only needed if running the API locally or only the docker image
|
||||||
|
1. Redis: `docker run --name redis -d -p 6379:6379 -e ALLOW_EMPTY_PASSWORD=yes redis:latest`
|
||||||
|
2. Add this to the redis configuration in the Program.cs
|
||||||
|
```csharp
|
||||||
|
options.ConfigurationOptions = new ConfigurationOptions
|
||||||
|
{
|
||||||
|
AbortOnConnectFail = false,
|
||||||
|
EndPoints = { "localhost", "6379" }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Running the container
|
||||||
1. Build the Dockerfile: `docker build -t clean-architecture .`
|
1. Build the Dockerfile: `docker build -t clean-architecture .`
|
||||||
2. Run the Container: `docker run -p 80:80 clean-architecture`
|
2. Run the Container: `docker run -p 80:80 clean-architecture`
|
||||||
|
|
||||||
@ -50,7 +63,8 @@ To run the project, follow these steps:
|
|||||||
### Using Kubernetes
|
### Using Kubernetes
|
||||||
|
|
||||||
1. Change the ConnectionString in the appsettings.json to `Server=clean-architecture-db-service;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#`
|
1. Change the ConnectionString in the appsettings.json to `Server=clean-architecture-db-service;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#`
|
||||||
2. Build the docker image and push it to the docker hub (Change the image name in the `k8s-deployment.yml` to your own)
|
2. Change the RedisHostName in the appsettings.json to `redis-service`
|
||||||
|
3. Build the docker image and push it to the docker hub (Change the image name in the `k8s-deployment.yml` to your own)
|
||||||
Apply the deployment file: `kubectl apply -f k8s-deployment.yml` (Delete: `kubectl delete -f k8s-deployment.yml`)
|
Apply the deployment file: `kubectl apply -f k8s-deployment.yml` (Delete: `kubectl delete -f k8s-deployment.yml`)
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,3 +22,16 @@ services:
|
|||||||
- SA_PASSWORD=Password123!#
|
- SA_PASSWORD=Password123!#
|
||||||
ports:
|
ports:
|
||||||
- 1433:1433
|
- 1433:1433
|
||||||
|
redis:
|
||||||
|
image: docker.io/bitnami/redis:7.2
|
||||||
|
environment:
|
||||||
|
# ALLOW_EMPTY_PASSWORD is recommended only for development.
|
||||||
|
- ALLOW_EMPTY_PASSWORD=yes
|
||||||
|
- REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
volumes:
|
||||||
|
- 'redis_data:/bitnami/redis/data'
|
||||||
|
volumes:
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
|
@ -14,6 +14,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: clean-architecture-app
|
- name: clean-architecture-app
|
||||||
|
# Replace this with the path to your built image
|
||||||
image: alexdev28/clean-architecture
|
image: alexdev28/clean-architecture
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
@ -70,4 +71,50 @@ spec:
|
|||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 1433
|
port: 1433
|
||||||
targetPort: 1433
|
targetPort: 1433
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: redis-deployment
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: redis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: redis
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: docker.io/bitnami/redis:7.2
|
||||||
|
env:
|
||||||
|
# ALLOW_EMPTY_PASSWORD is recommended only for development.
|
||||||
|
- name: ALLOW_EMPTY_PASSWORD
|
||||||
|
value: "yes"
|
||||||
|
- name: REDIS_DISABLE_COMMANDS
|
||||||
|
value: "FLUSHDB,FLUSHALL"
|
||||||
|
ports:
|
||||||
|
- containerPort: 6379
|
||||||
|
volumeMounts:
|
||||||
|
- name: redis-data
|
||||||
|
mountPath: /bitnami/redis/data
|
||||||
|
volumes:
|
||||||
|
- name: redis-data
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: redis-service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: redis
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 6379
|
||||||
|
targetPort: 6379
|
||||||
|
Loading…
Reference in New Issue
Block a user