mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
Scaffold vertical-slice migration: Shared.Contracts, Shared.Application, MediatR
Begins the move to vertical-slice architecture. ResponseBody moves out of API.Core into Shared.Contracts so slices won't have to depend on the API host project for it. Shared.Application adds the MediatR pipeline's ValidationBehavior plus the cross-slice email commands that Features.Auth will send and Features.Emails will handle, keeping slices decoupled from each other.
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
Include="FluentValidation.AspNetCore"
|
Include="FluentValidation.AspNetCore"
|
||||||
Version="11.3.0"
|
Version="11.3.0"
|
||||||
/>
|
/>
|
||||||
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -33,6 +34,8 @@
|
|||||||
<ProjectReference Include="..\..\Service\Service.Auth\Service.Auth.csproj" />
|
<ProjectReference Include="..\..\Service\Service.Auth\Service.Auth.csproj" />
|
||||||
<ProjectReference Include="..\..\Service\Service.Breweries\Service.Breweries.csproj" />
|
<ProjectReference Include="..\..\Service\Service.Breweries\Service.Breweries.csproj" />
|
||||||
<ProjectReference Include="..\..\Service\Service.UserManagement\Service.UserManagement.csproj" />
|
<ProjectReference Include="..\..\Service\Service.UserManagement\Service.UserManagement.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using Infrastructure.Jwt;
|
using Infrastructure.Jwt;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
|
||||||
namespace API.Core.Contracts.Auth;
|
namespace API.Core.Contracts.Auth;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
|
||||||
namespace API.Core.Contracts.Auth;
|
namespace API.Core.Contracts.Auth;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using API.Core.Contracts.Auth;
|
using API.Core.Contracts.Auth;
|
||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using Domain.Entities;
|
using Domain.Entities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using API.Core.Contracts.Breweries;
|
using API.Core.Contracts.Breweries;
|
||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using Domain.Entities;
|
using Domain.Entities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// API.Core/Filters/GlobalExceptionFilter.cs
|
// API.Core/Filters/GlobalExceptionFilter.cs
|
||||||
|
|
||||||
using API.Core.Contracts.Common;
|
using Shared.Contracts;
|
||||||
using Domain.Exceptions;
|
using Domain.Exceptions;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Service.Auth;
|
|||||||
using Service.Breweries;
|
using Service.Breweries;
|
||||||
using Service.Emails;
|
using Service.Emails;
|
||||||
using Service.UserManagement.User;
|
using Service.UserManagement.User;
|
||||||
|
using Shared.Application.Behaviors;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -31,6 +32,14 @@ builder.Services.AddOpenApi();
|
|||||||
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
|
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
|
||||||
builder.Services.AddFluentValidationAutoValidation();
|
builder.Services.AddFluentValidationAutoValidation();
|
||||||
|
|
||||||
|
// Add MediatR. Each Features.* slice's assembly is registered here as it's introduced;
|
||||||
|
// ValidationBehavior runs FluentValidation validators in the MediatR pipeline for command/query handlers.
|
||||||
|
builder.Services.AddMediatR(cfg =>
|
||||||
|
{
|
||||||
|
cfg.RegisterServicesFromAssemblyContaining<Program>();
|
||||||
|
cfg.AddOpenBehavior(typeof(ValidationBehavior<,>));
|
||||||
|
});
|
||||||
|
|
||||||
// Add health checks
|
// Add health checks
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
|||||||
@@ -29,4 +29,8 @@
|
|||||||
<Project Path="Service/Service.Auth/Service.Auth.csproj" />
|
<Project Path="Service/Service.Auth/Service.Auth.csproj" />
|
||||||
<Project Path="Service/Service.Breweries/Service.Breweries.csproj" />
|
<Project Path="Service/Service.Breweries/Service.Breweries.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
<Folder Name="/Shared/">
|
||||||
|
<Project Path="Shared/Shared.Contracts/Shared.Contracts.csproj" />
|
||||||
|
<Project Path="Shared/Shared.Application/Shared.Application.csproj" />
|
||||||
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Shared.Application.Behaviors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MediatR pipeline behavior that runs all registered FluentValidation validators for a
|
||||||
|
/// request before invoking its handler, short-circuiting with a <see cref="ValidationException"/>
|
||||||
|
/// when any validator reports failures.
|
||||||
|
/// </summary>
|
||||||
|
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
|
||||||
|
: IPipelineBehavior<TRequest, TResponse>
|
||||||
|
where TRequest : IRequest<TResponse>
|
||||||
|
{
|
||||||
|
public async Task<TResponse> Handle(
|
||||||
|
TRequest request,
|
||||||
|
RequestHandlerDelegate<TResponse> next,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!validators.Any())
|
||||||
|
{
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = new ValidationContext<TRequest>(request);
|
||||||
|
|
||||||
|
var failures = (
|
||||||
|
await Task.WhenAll(
|
||||||
|
validators.Select(v => v.ValidateAsync(context, cancellationToken))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.SelectMany(result => result.Errors)
|
||||||
|
.Where(failure => failure != null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (failures.Count > 0)
|
||||||
|
{
|
||||||
|
throw new ValidationException(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Shared.Application.Emails;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cross-slice command sent by Features.Auth and handled by Features.Emails to dispatch a
|
||||||
|
/// registration confirmation email, without either slice taking a project reference on the other.
|
||||||
|
/// </summary>
|
||||||
|
public record SendRegistrationEmailCommand(
|
||||||
|
string FirstName,
|
||||||
|
string Email,
|
||||||
|
string ConfirmationToken
|
||||||
|
) : IRequest;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Shared.Application.Emails;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cross-slice command sent by Features.Auth and handled by Features.Emails to dispatch a
|
||||||
|
/// fresh confirmation-link email, without either slice taking a project reference on the other.
|
||||||
|
/// </summary>
|
||||||
|
public record SendResendConfirmationEmailCommand(
|
||||||
|
string FirstName,
|
||||||
|
string Email,
|
||||||
|
string ConfirmationToken
|
||||||
|
) : IRequest;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Shared.Application</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace API.Core.Contracts.Common;
|
namespace Shared.Contracts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic envelope used to wrap API responses that carry a data payload alongside a human-readable message.
|
/// Generic envelope used to wrap API responses that carry a data payload alongside a human-readable message.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Shared.Contracts</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user