Skip to content

Commit

Permalink
feat: create tag and config mapster, mediatR
Browse files Browse the repository at this point in the history
  • Loading branch information
phihc116 committed Nov 22, 2024
1 parent b5c5efb commit ec3fd99
Show file tree
Hide file tree
Showing 36 changed files with 477 additions and 55 deletions.
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ardalis.Result" Version="10.1.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
<PackageVersion Include="Mapster" Version="7.4.1-pre01" />
<PackageVersion Include="Mapster.DependencyInjection" Version="1.0.2-pre01" />
<PackageVersion Include="MediatR" Version="12.4.1" />
<PackageVersion Include="Microsoft.AspNet.Identity.EntityFramework" Version="2.2.4" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
Expand Down
6 changes: 6 additions & 0 deletions src/Core/BuildingBlocks/Application/ICommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using MediatR;

namespace BuildingBlocks.Application;

public interface ICommand : IRequest<Result>;
public interface ICommand<TResponse> : IRequest<Result<TResponse>>;
11 changes: 11 additions & 0 deletions src/Core/BuildingBlocks/Application/ICommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using MediatR;

namespace BuildingBlocks.Application;

public interface ICommandHandler<in TCommand>
: IRequestHandler<TCommand, Result>
where TCommand : ICommand;

public interface ICommandHandler<in TCommand, TResponse>
: IRequestHandler<TCommand, Result<TResponse>>
where TCommand : ICommand<TResponse>;
4 changes: 4 additions & 0 deletions src/Core/BuildingBlocks/Application/IQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using MediatR;

namespace BuildingBlocks.Application;
public interface IQuery<TResponse> : IRequest<Result<TResponse>>;
6 changes: 6 additions & 0 deletions src/Core/BuildingBlocks/Application/IQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using MediatR;

namespace BuildingBlocks.Application;
public interface IQueryHandler<TQuery, TResponse>
: IRequestHandler<TQuery, Result<TResponse>>
where TQuery : IQuery<TResponse>;
16 changes: 16 additions & 0 deletions src/Core/BuildingBlocks/Application/IUnitOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;

namespace BuildingBlocks.Application;
public interface IUnitOfWork
{
DatabaseFacade Database { get; }

IDbContextTransaction GetCurrentTransaction();

Task CommitTransactionAsync(IDbContextTransaction transaction);

Task<IDbContextTransaction> BeginTransactionAsync();

Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
}
5 changes: 1 addition & 4 deletions src/Core/BuildingBlocks/BuildingBlocks.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Ardalis.Result" />
<PackageReference Include="MediatR" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>
<Folder Include="Extentions\" />
</ItemGroup>
</Project>
49 changes: 49 additions & 0 deletions src/Core/BuildingBlocks/Error.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace BuildingBlocks;

public enum ErrorType
{
Failure = 0,
NotFound = 1,
Validation = 2,
Conflict = 3,
AccessUnAuthorized = 4,
AccessForbidden = 5
}

public class Error
{
private Error(
string code,
string description,
ErrorType errorType
)
{
Code = code;
Description = description;
ErrorType = errorType;
}

public string Code { get; }

public string Description { get; }

public ErrorType ErrorType { get; }

public static Error Failure(string code, string description) =>
new(code, description, ErrorType.Failure);

public static Error NotFound(string code, string description) =>
new(code, description, ErrorType.NotFound);

public static Error Validation(string code, string description) =>
new(code, description, ErrorType.Validation);

public static Error Conflict(string code, string description) =>
new(code, description, ErrorType.Conflict);

public static Error AccessUnAuthorized(string code, string description) =>
new(code, description, ErrorType.AccessUnAuthorized);

public static Error AccessForbidden(string code, string description) =>
new(code, description, ErrorType.AccessForbidden);
}
25 changes: 25 additions & 0 deletions src/Core/BuildingBlocks/Extentions/GenericTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace BuildingBlocks.Extentions;
public static class GenericTypeExtensions
{
public static string GetGenericTypeName(this Type type)
{
string typeName;

if (type.IsGenericType)
{
var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray());
typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>";
}
else
{
typeName = type.Name;
}

return typeName;
}

public static string GetGenericTypeName(this object @object)
{
return @object.GetType().GetGenericTypeName();
}
}
65 changes: 65 additions & 0 deletions src/Core/BuildingBlocks/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Text.Json.Serialization;

namespace BuildingBlocks;
public class Result
{
protected Result()
{
IsSuccess = true;
Error = default;
}

protected Result(Error error)
{
IsSuccess = false;
Error = error;
}

public bool IsSuccess { get; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Error? Error { get; }

public static implicit operator Result(Error error) =>
new(error);

public static Result Success() =>
new();

public static Result Failure(Error error) =>
new(error);
}

public sealed class Result<TValue> : Result
{
private readonly TValue? _value;

private Result(
TValue value
) : base()
{
_value = value;
}

private Result(
Error error
) : base(error)
{
_value = default;
}

public TValue Value =>
IsSuccess ? _value! : throw new InvalidOperationException("Value can not be accessed when IsSuccess is false");

public static implicit operator Result<TValue>(Error error) =>
new(error);

public static implicit operator Result<TValue>(TValue value) =>
new(value);

public static Result<TValue> Success(TValue value) =>
new(value);

public static new Result<TValue> Failure(Error error) =>
new(error);
}
51 changes: 51 additions & 0 deletions src/Core/EnglishNote.Application/AddServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using EnglishNote.Application.Behaviors;
using FluentValidation;
using Mapster;
using MapsterMapper;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

namespace EnglishNote.Application;

public static class AddServiceExtensions
{
private readonly static Assembly[] Assemblies = AppDomain.CurrentDomain.GetAssemblies();

public static IServiceCollection AddApplicationLayer(this IServiceCollection services)
{
services
.ConfigureMediatR()
.ConfigureMapster()
.ConfigureFluentValidation();

return services;
}

public static IServiceCollection ConfigureMediatR(this IServiceCollection services)
{
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(IAssemblyMarker).Assembly);

cfg.AddOpenBehavior(typeof(TransactionBehavior<,>));
});

return services;
}

public static IServiceCollection ConfigureMapster(this IServiceCollection services) {
var globalConfig = TypeAdapterConfig.GlobalSettings;
globalConfig.Scan(Assemblies);

services.AddSingleton(globalConfig);
services.AddScoped<IMapper, ServiceMapper>();

return services;
}

public static IServiceCollection ConfigureFluentValidation(this IServiceCollection services)
{
services.AddValidatorsFromAssembly(typeof(IAssemblyMarker).Assembly, includeInternalTypes: true);
return services;
}
}
55 changes: 55 additions & 0 deletions src/Core/EnglishNote.Application/Behaviors/TransactionBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using BuildingBlocks.Application;
using BuildingBlocks.Extentions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace EnglishNote.Application.Behaviors;
public class TransactionBehavior<TRequest, TResponse>(
IUnitOfWork unitOfWork,
ILogger<TransactionBehavior<TRequest, TResponse>> logger) :
IPipelineBehavior<TRequest, TResponse> where TRequest : class
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var response = default(TResponse);
var typeName = request.GetGenericTypeName();

try
{
var currentTransaction = unitOfWork.GetCurrentTransaction();
bool hasActiveTransaction = currentTransaction is not null;

if (hasActiveTransaction)
return await next();

var strategy = unitOfWork.Database.CreateExecutionStrategy();

await strategy.ExecuteAsync(async () =>
{
Guid transactionId;

await using var transaction = await unitOfWork.BeginTransactionAsync();
using (logger.BeginScope(new List<KeyValuePair<string, object>> { new("TransactionContext", transaction.TransactionId) }))
{
logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request);

response = await next();

logger.LogInformation("Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName);

await unitOfWork.CommitTransactionAsync(transaction);

transactionId = transaction.TransactionId;
}
});

return response;
}
catch (Exception ex)
{
logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command})", typeName, request);
throw;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Mapster" />
<PackageReference Include="Mapster.DependencyInjection" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnglishNote.Domain\EnglishNote.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="UseCases\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
namespace EnglishNote.Application;

public class Class1
internal interface IAssemblyMarker
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using BuildingBlocks;
using BuildingBlocks.Application;

namespace EnglishNote.Application.UseCases.Tags.CreateTag;
public record CreateTagCommand(
string Name,
string? Description
) : ICommand<Guid>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using BuildingBlocks;
using BuildingBlocks.Application;
using EnglishNote.Domain.Tags;

namespace EnglishNote.Application.UseCases.Tags.CreateTag;
internal class CreateTagCommandHandler(ITagRepository tagRepository) : ICommandHandler<CreateTagCommand, Guid>
{
public async Task<Result<Guid>> Handle(CreateTagCommand request, CancellationToken cancellationToken)
{
var tag = Tag.CreateTag(request.Name, request.Description, Guid.CreateVersion7());
tagRepository.Add(tag);

return tag.Id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace EnglishNote.Application.UseCases.Tags.CreateTag;
internal class CreateTagCommandValidator
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace EnglishNote.Application.UseCases.Tags.GetOneTag;
internal class GetOneTagQuery
{
}
2 changes: 1 addition & 1 deletion src/Core/EnglishNote.Domain/Identity/ApplicationUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using EnglishNote.Domain.Words;
using Microsoft.AspNetCore.Identity;

namespace EnglishNote.Domain.Users;
namespace EnglishNote.Domain.Identity;
public class ApplicationUser : IdentityUser<Guid>
{
public string FirstName { get; private set; }
Expand Down
Loading

0 comments on commit ec3fd99

Please sign in to comment.