feat: melhorias e otimizações
This commit is contained in:
@@ -360,3 +360,4 @@ MigrationBackup/
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
.idea/
|
||||
.run/*
|
||||
@@ -19,12 +19,12 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0-1.final" />
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.2.0">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0-1.final"/>
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.3.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.53.0.62665">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.0.0.68202">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -30,6 +30,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shop.Query", "src\Shop.Query\Shop.Query.csproj", "{C2A82AAD-0348-4B29-B7E7-E36509EE068C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{157AD845-5FAB-4FF2-9980-80FEC878701C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
docker-compose.yml = docker-compose.yml
|
||||
docker-compose.override.yml = docker-compose.override.yml
|
||||
.dockerignore = .dockerignore
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{45B3273F-DCE8-48C2-B27A-05AF1343C69C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml
|
||||
.github\workflows\devskim-analysis.yml = .github\workflows\devskim-analysis.yml
|
||||
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -76,5 +90,7 @@ Global
|
||||
{D957E297-38EA-402F-B603-25F3EC156E18} = {DFB189FE-F11B-46EA-A5CD-3571CEC8193A}
|
||||
{E94C4296-DC64-4C22-87A1-B5A054FAADAC} = {8011CDDD-0814-4C74-9B74-C3E3365D50CC}
|
||||
{C2A82AAD-0348-4B29-B7E7-E36509EE068C} = {8011CDDD-0814-4C74-9B74-C3E3365D50CC}
|
||||
{157AD845-5FAB-4FF2-9980-80FEC878701C} = {1DAF7B63-F776-4466-908D-56FD1FD02F42}
|
||||
{45B3273F-DCE8-48C2-B27A-05AF1343C69C} = {1DAF7B63-F776-4466-908D-56FD1FD02F42}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
|
||||
shop-webapi:
|
||||
ports:
|
||||
- 5072:5072
|
||||
- "5072:5072"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Docker
|
||||
volumes:
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@ services:
|
||||
expose:
|
||||
- 5072
|
||||
ports:
|
||||
- 5072:5072
|
||||
- "5072:5072"
|
||||
links:
|
||||
- sqlserver
|
||||
- mongoserver
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
expose:
|
||||
- 1433
|
||||
ports:
|
||||
- 1433:1433
|
||||
- "1433:1433"
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- SA_PASSWORD=pMA63033n6tF
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27017:27017
|
||||
- "27017:27017"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
@@ -67,6 +67,6 @@ services:
|
||||
expose:
|
||||
- 6379
|
||||
ports:
|
||||
- 6379:6379
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using Shop.Core.Common;
|
||||
using Shop.Core.Abstractions;
|
||||
|
||||
namespace Shop.Application.Customer.Responses;
|
||||
|
||||
public class CreatedCustomerResponse : BaseResponse
|
||||
public class CreatedCustomerResponse : IResponse
|
||||
{
|
||||
public CreatedCustomerResponse(Guid id) => Id = id;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace Shop.Core.Common;
|
||||
namespace Shop.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Classe base usada para representar uma seção do AppSettings.
|
||||
/// </summary>
|
||||
public abstract class BaseOptions
|
||||
public interface IAppOptions
|
||||
{
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace Shop.Core.Common;
|
||||
namespace Shop.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Classe base usada pelas respostas da API.
|
||||
/// </summary>
|
||||
public abstract class BaseResponse
|
||||
public interface IResponse
|
||||
{
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using Shop.Core.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Shop.Core.Abstractions;
|
||||
|
||||
namespace Shop.Core.AppSettings;
|
||||
|
||||
public sealed class CacheOptions : BaseOptions
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
public sealed class CacheOptions : IAppOptions
|
||||
{
|
||||
public const string ConfigSectionPath = nameof(CacheOptions);
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Shop.Core.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Shop.Core.Abstractions;
|
||||
|
||||
namespace Shop.Core.AppSettings;
|
||||
|
||||
public sealed class ConnectionOptions : BaseOptions
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
public sealed class ConnectionOptions : IAppOptions
|
||||
{
|
||||
public const string ConfigSectionPath = "ConnectionStrings";
|
||||
|
||||
[Required]
|
||||
public string SqlConnection { get; private init; }
|
||||
[Required] public string SqlConnection { get; private init; }
|
||||
|
||||
[Required]
|
||||
public string NoSqlConnection { get; private init; }
|
||||
[Required] public string NoSqlConnection { get; private init; }
|
||||
|
||||
[Required]
|
||||
public string CacheConnection { get; private init; }
|
||||
[Required] public string CacheConnection { get; private init; }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Shop.Core.Common;
|
||||
using Shop.Core.Abstractions;
|
||||
|
||||
namespace Shop.Core.Extensions;
|
||||
|
||||
@@ -10,7 +10,7 @@ public static class ConfigurationExtensions
|
||||
= options => options.BindNonPublicProperties = true;
|
||||
|
||||
public static TOptions GetOptions<TOptions>(this IConfiguration configuration, string configSectionPath)
|
||||
where TOptions : BaseOptions
|
||||
where TOptions : IAppOptions
|
||||
{
|
||||
return configuration.GetSection(configSectionPath).Get<TOptions>(ConfigureBinderOptions);
|
||||
}
|
||||
|
||||
@@ -15,23 +15,6 @@ public static class JsonExtensions
|
||||
private static readonly PrivateSetterContractResolver ContractResolver = new() { NamingStrategy = NamingStrategy };
|
||||
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings().Configure();
|
||||
|
||||
/// <summary>
|
||||
/// Desserializa o JSON para o tipo especificado.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">O tipo de objeto para o qual desserializar.</typeparam>
|
||||
/// <param name="value">O objeto a ser desserializado.</param>
|
||||
/// <returns>O objeto desserializado da string JSON.</returns>
|
||||
public static T FromJson<T>(this string value)
|
||||
=> value != null ? JsonConvert.DeserializeObject<T>(value, JsonSettings) : default;
|
||||
|
||||
/// <summary>
|
||||
/// Serializa o objeto especificado em uma string JSON.
|
||||
/// </summary>
|
||||
/// <param name="value">O objeto a ser serializado.</param>
|
||||
/// <returns>Uma representação de string JSON do objeto.</returns>
|
||||
public static string ToJson<T>(this T value)
|
||||
=> value != null ? JsonConvert.SerializeObject(value, JsonSettings) : default;
|
||||
|
||||
public static JsonSerializerSettings Configure(this JsonSerializerSettings jsonSettings)
|
||||
{
|
||||
jsonSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
@@ -42,4 +25,21 @@ public static class JsonExtensions
|
||||
jsonSettings.Converters.Add(EnumConverter);
|
||||
return jsonSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desserializa o JSON para o tipo especificado.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">O tipo de objeto para o qual desserializar.</typeparam>
|
||||
/// <param name="value">O objeto a ser desserializado.</param>
|
||||
/// <returns>O objeto desserializado da string JSON.</returns>
|
||||
public static T FromJson<T>(this string value) =>
|
||||
value != null ? JsonConvert.DeserializeObject<T>(value, JsonSettings) : default;
|
||||
|
||||
/// <summary>
|
||||
/// Serializa o objeto especificado em uma string JSON.
|
||||
/// </summary>
|
||||
/// <param name="value">O objeto a ser serializado.</param>
|
||||
/// <returns>Uma representação de string JSON do objeto.</returns>
|
||||
public static string ToJson<T>(this T value) =>
|
||||
value != null ? JsonConvert.SerializeObject(value, JsonSettings) : default;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Shop.Core.Abstractions;
|
||||
using Shop.Core.AppSettings;
|
||||
using Shop.Core.Common;
|
||||
|
||||
namespace Shop.Core.Extensions;
|
||||
|
||||
@@ -12,14 +12,13 @@ public static class ServicesCollectionExtensions
|
||||
services.AddOptionsWithValidation<CacheOptions>(CacheOptions.ConfigSectionPath);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddOptionsWithValidation<TOptions>(this IServiceCollection services, string configSectionPath)
|
||||
where TOptions : BaseOptions
|
||||
private static void AddOptionsWithValidation<TOptions>(this IServiceCollection services, string configSectionPath)
|
||||
where TOptions : class, IAppOptions
|
||||
{
|
||||
return services
|
||||
services
|
||||
.AddOptions<TOptions>()
|
||||
.BindConfiguration(configSectionPath, options => options.BindNonPublicProperties = true)
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart()
|
||||
.Services;
|
||||
.ValidateOnStart();
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,10 @@ namespace Shop.Infrastructure.Extensions;
|
||||
|
||||
public static class ServicesCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddMemoryCacheService(this IServiceCollection services)
|
||||
public static void AddMemoryCacheService(this IServiceCollection services)
|
||||
=> services.AddScoped<ICacheService, MemoryCacheService>();
|
||||
|
||||
public static IServiceCollection AddDistributedCacheService(this IServiceCollection services)
|
||||
public static void AddDistributedCacheService(this IServiceCollection services)
|
||||
=> services.AddScoped<ICacheService, DistributedCacheService>();
|
||||
|
||||
public static void AddInfrastructure(this IServiceCollection services)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ardalis.Result;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Shop.PublicApi.Models;
|
||||
@@ -19,11 +19,7 @@ public static class ResultExtensions
|
||||
|
||||
private static IActionResult ToHttpNonSuccessResult(this IResult result)
|
||||
{
|
||||
var errors = new List<ApiError>();
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
errors.Add(new ApiError(error));
|
||||
}
|
||||
var errors = result.Errors.Select(error => new ApiError(error)).ToList();
|
||||
|
||||
switch (result.Status)
|
||||
{
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace Shop.PublicApi.Extensions;
|
||||
|
||||
public static class ServicesCollectionExtensions
|
||||
{
|
||||
private static readonly string[] DatabaseTags = new[] { "database" };
|
||||
private const string MigrationsAssembly = "Shop.PublicApi";
|
||||
private static readonly string[] DatabaseTags = { "database" };
|
||||
|
||||
public static void AddSwagger(this IServiceCollection services)
|
||||
{
|
||||
@@ -29,7 +29,8 @@ public static class ServicesCollectionExtensions
|
||||
{
|
||||
Version = "v1",
|
||||
Title = "Shop (e-commerce)",
|
||||
Description = "ASP.NET Core C# CQRS Event Sourcing, REST API, DDD, Princípios SOLID e Clean Architecture",
|
||||
Description =
|
||||
"ASP.NET Core C# CQRS Event Sourcing, REST API, DDD, Princípios SOLID e Clean Architecture",
|
||||
Contact = new OpenApiContact
|
||||
{
|
||||
Name = "Jean Gatto",
|
||||
@@ -42,7 +43,8 @@ public static class ServicesCollectionExtensions
|
||||
{
|
||||
Name = "MIT License",
|
||||
#pragma warning disable S1075 // Refactor your code not to use hardcoded absolute paths or URIs.
|
||||
Url = new Uri("https://github.com/jeangatto/ASP.NET-Core-API-CQRS-EVENT-DDD-SOLID/blob/main/LICENSE")
|
||||
Url = new Uri(
|
||||
"https://github.com/jeangatto/ASP.NET-Core-API-CQRS-EVENT-DDD-SOLID/blob/main/LICENSE")
|
||||
#pragma warning restore S1075 // Refactor your code not to use hardcoded absolute paths or URIs.
|
||||
}
|
||||
});
|
||||
@@ -58,13 +60,15 @@ public static class ServicesCollectionExtensions
|
||||
var connectionOptions = configuration.GetOptions<ConnectionOptions>(ConnectionOptions.ConfigSectionPath);
|
||||
|
||||
var healthCheckBuilder = services
|
||||
.AddHealthChecks()
|
||||
.AddDbContextCheck<WriteDbContext>(tags: DatabaseTags)
|
||||
.AddDbContextCheck<EventStoreDbContext>(tags: DatabaseTags)
|
||||
.AddMongoDb(connectionOptions.NoSqlConnection, tags: DatabaseTags);
|
||||
.AddHealthChecks()
|
||||
.AddDbContextCheck<WriteDbContext>(tags: DatabaseTags)
|
||||
.AddDbContextCheck<EventStoreDbContext>(tags: DatabaseTags)
|
||||
.AddMongoDb(connectionOptions.NoSqlConnection, tags: DatabaseTags);
|
||||
|
||||
if (!connectionOptions.CacheConnection.IsInMemoryCache())
|
||||
{
|
||||
healthCheckBuilder.AddRedis(connectionOptions.CacheConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddShopDbContext(this IServiceCollection services)
|
||||
@@ -114,15 +118,18 @@ public static class ServicesCollectionExtensions
|
||||
|
||||
// Configurando a resiliência da conexão.
|
||||
// REF: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||
sqlServerOptions.EnableRetryOnFailure(maxRetryCount: 3);
|
||||
sqlServerOptions.EnableRetryOnFailure(3);
|
||||
});
|
||||
|
||||
// Log das tentativas de repetição.
|
||||
options.LogTo(
|
||||
filter: (eventId, _) => eventId.Id == CoreEventId.ExecutionStrategyRetrying,
|
||||
logger: eventData =>
|
||||
(eventId, _) => eventId.Id == CoreEventId.ExecutionStrategyRetrying,
|
||||
eventData =>
|
||||
{
|
||||
if (eventData is not ExecutionStrategyEventData retryEventData) return;
|
||||
if (eventData is not ExecutionStrategyEventData retryEventData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var exceptions = retryEventData.ExceptionsEncountered;
|
||||
|
||||
@@ -136,6 +143,8 @@ public static class ServicesCollectionExtensions
|
||||
// Quando o ambiente for o de "desenvolvimento" será logado informações detalhadas.
|
||||
var environment = serviceProvider.GetRequiredService<IHostEnvironment>();
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
options.EnableDetailedErrors().EnableSensitiveDataLogging();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public static class WebApplicationExtensions
|
||||
}
|
||||
else
|
||||
{
|
||||
app.Logger.LogInformation("----- {DbName}: Migrações estão em dia.", dbName);
|
||||
app.Logger.LogInformation("----- {DbName}: Migrações estão em dia", dbName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Shop.Core.AppSettings;
|
||||
using Shop.Core.Extensions;
|
||||
using Xunit;
|
||||
using Xunit.Categories;
|
||||
|
||||
namespace Shop.UnitTests.Core.Extensions;
|
||||
|
||||
[UnitTest]
|
||||
public class ServicesCollectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_ReturnClassOptions_WhenConfigureAppSettings()
|
||||
{
|
||||
// Arrange
|
||||
const int absoluteExpirationInHours = 4;
|
||||
const int slidingExpirationInSeconds = 120;
|
||||
|
||||
var configurationBuilder = new ConfigurationBuilder();
|
||||
configurationBuilder.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
{ "CacheOptions:AbsoluteExpirationInHours", absoluteExpirationInHours.ToString() },
|
||||
{ "CacheOptions:SlidingExpirationInSeconds", slidingExpirationInSeconds.ToString() }
|
||||
});
|
||||
|
||||
var configuratio = configurationBuilder.Build();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<IConfiguration>(_ => configuratio);
|
||||
services.ConfigureAppSettings();
|
||||
var serviceProvider = services.BuildServiceProvider(true);
|
||||
|
||||
// Act
|
||||
var act = serviceProvider.GetRequiredService<IOptions<CacheOptions>>();
|
||||
|
||||
// Assert
|
||||
act.Should().NotBeNull();
|
||||
act.Value.Should().NotBeNull();
|
||||
act.Value.AbsoluteExpirationInHours.Should().Be(absoluteExpirationInHours);
|
||||
act.Value.SlidingExpirationInSeconds.Should().Be(slidingExpirationInSeconds);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user