2024. 2. 26. 00:00ㆍASPNET
dotnet core 에는 여러가지 message library 들이 있다.
자체 event 및 delegate 를 통한 처리 방식도 있고 command pattern 을 이용한 구현 방식도 있다.
3rd party library 를 사용할 경우 MediatR 을 많이 사용했을 것이다.
오늘은 이 MediatR 대신 사용가능한 Wolverine 에 대해 알아보겠다.
우선 MediatR 로 만들어는 경우는 핵심 코드가 다음과 비슷 할 것이다.
MediatR 을 사용할 경우
dotnet add package MediatR
Program.cs
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
Controller.cs
[HttpGet]
public async Task<IActionResult> Get([FromServices] IMediator mediator)
{
var response = await mediator.Send(new Weather.Get.Command());
return Ok(response);
}
Handler.cs
public class Get
{
public class Command : IRequest<Response>
{
}
public class Response : WeatherForecast
{
public List<WeatherForecast> Forecasts { get; set; }
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
public class CommandHandler : IRequestHandler<Command, Response>
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<Response> Handle(Command request, CancellationToken cancellationToken)
{
var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToList();
return Task.FromResult(new Response { Forecasts = forecasts });
}
}
}
return type 이 있는 Command 를 선언 하고 (IRequest<Response> 를 Implementation)
public class Command : IRequest<Response>
Handler 를 선언한 후 (IRequestHandler<Command, Response> 를 Implementation)
public class CommandHandler : IRequestHandler<Command, Response>
...
Handle Method 를 Implementation 하자
public Task<Response> Handle(Command request, CancellationToken cancellationToken)
위와 같이 하면 MediatR 을 이용하여 Controller 에서 Handler 쪽으로 Message 가 전달 된다.
Wolverine 을 사용 할 경우
dotnet add package WolverineFx
Program.cs
builder.Host.UseWolverine()
자이제 여기서 차이점이 있는데 wolverine 같은 경우는 command 를 특정한 interface 를 이용하여 구현하지 않아도 된다.
public class Command {} // 이게 다다.. 빈 class 하나 선언하면 된다.
public record Command {} // record 도 가능하다
return type 도 필요 없고 IRequest 같은 interface 도 필요 없다. (의존성 없음)
Handler 도 마찬가지다 아래를 확인 하자
Handler.cs
public static Task<Response> Handle(Command request)
그런데 주의 할것은 있다. Handle 의 이름을 다른 형태로 변경하면 오류가 날 것이다.
그 이유는 source generator 같은 것으로 동적으로 처리 코드를 compile 시점에 생성하기 때문이다.
그래서 아래와 같은 코드가 generated 된 code 에 존재하게 된다.
// The actual message execution
var outgoing1 = await Wolverine.Features.Weather.GetWithWolverine.CommandHandler.Handle(command).ConfigureAwait(false);
이와 같이 사용하면 wolverine 을 통해 Message 가 전달될 것이다.
그런데 이제 문제가 하나 생긴다.
wolverine 으로 실행한 프로젝트인 경우 처음 실행 시에
2~3 초의 message (cold stat) 전달 시간이 생긴다.
그 이유는 위에 handler 들을 찾아서 source 를 동적으로 생성하기 때문이다.
물론 첫 실행 이후는 아주 빠르게 동작하지만 .. 우리가 원하는 동작은 아니다.
이런경우를 예방하기 위에 다음과 같은 option 을 wolverine 에 주자
Program.cs
builder.Host.UseWolverine(opts=>
{
opts.ApplicationAssembly = Assembly.GetExecutingAssembly();
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
});
자 이제 실행해 보자 . 그런데 실행해 보면 처음 실행이 느린게 똑같은걸 알 수 있다.
화내지 말고 침작 하자.
solution 을 확인해보자 다음과 같은 folder 와 code 들이 추가되어 있을 것이다.

이제 project 를 다운 시켰다가 다시 실행해보자
처음 실행에도 무척 빠르게 실행되는것을 확인 할 수 있다.
이상태에서 TypeLoadMode.Static 으로 변경해보자
이제 처음 실행에도 아주 빠르게 실행되는 것을 확인 할 수 있다.
자 그런데 이런경우 문제가 또 있다.
cold start 를 방지하기 위해 모든 handler 들을 다 실행해 봐야 한다.
그리고 수정이라도 하게 되면 다시 그 과정을 거쳐댜 한다.
너무 불편하다.
그래서 사실 wolverine 은 미리 코드를 생성하도록 지원하고 있다.
코드를 다음과 같이 수정하자
Program.cs
//app.Run();
await app.RunOaktonCommands(args);
이제 project folder 로 이동해서 command line 을 이용해서 다음을 입력하자
dotnet run -- codegen write
이렇게 하게 되면 모든 handler 가 자동으로 생성되게 된다.

여기에 FluenentValidtion 이라는 validation check library 와도 연계된다.
dotnet add package WolverineFx.FluentValidation
다음과 같이 설정을 추가 하자
builder.Host.UseWolverine(opts =>
{
...
opts.UseFluentValidation();
opts.Services.AddSingleton(typeof(IFailureAction<>), typeof(CustomFailureAction<>));
});
public class CustomFailureAction<T> : IFailureAction<T>
{
public void Throw(T message, IReadOnlyList<ValidationFailure> failures)
{
throw new Exception("Validation failed!: " + failures.Select(x => x.ErrorMessage).Join(", "));
}
}
Handler 에 다음 코드도 추가한다.
public class Validator : AbstractValidator<Command>
{
public Validator()
{
RuleFor(c => c.EmailAddress).NotNull().NotEmpty();
}
}
이제 실행시 EmailAddress 가 null 이거나 empty 이면 error 가 발생한다.
MediatR 을 오랫동안 사용해본 입장에서 조금더 편리해진 library 라는 생각이 든다.
꽤 오래전에 만들어진 library 이기도 하여 MediatR 을 변경해봐야 하는 시점이 된것 같다.
그동안 정말 편리하게 사용했던 library 이다.
그동안 고마웠다. ㅠㅠ
fare well MediatR
관련영상
'ASPNET' 카테고리의 다른 글
Dotnet Object Mapper (Automapper vs Mapster) (0) | 2024.03.04 |
---|---|
Dotnet 8 - native aot (1) | 2024.01.01 |
dotnet 8 으로 migration 하기 (1) | 2023.11.27 |