c# - ¿Por qué las interfaces de marcador INotification/IRequest para Mediator?

CorePress2024-01-25  165

Si bien soy un gran admirador de MediatR, me resulta extraño tomarlo como una dependencia de todos mis proyectos (incluso aquellos que no "envían" y simplemente publican POCO que hacen referencia a las interfaces de los marcadores).

Si uno tuviera que escribir una biblioteca mediadora similar que hiciera otras cosas pero tuviera funciones comunes de envío/publicación, ¿cuál es la necesidad de interfaces de marcador cuando el siguiente código funciona con POCO simples?

El código delimita entre una solicitud/notificación a través de las funciones Enviar/Publicar, entonces, ¿son redundantes estas interfaces de marcador?

Si tuviera que implementar mi propio mediador, ¿hay algún problema al eliminar las interfaces de marcador de Notificación y Solicitud?

Se trata más del principio de diseño de las interfaces de los marcadores que de querer cambiarme.diatR en sí (para evitar ofensas, eliminé MediatR, la biblioteca que repito para que conste es increíble y la estoy adaptando para mis propios propósitos con envío basado en flujo de fondo sincronizado multiproceso, de ahí las preguntas de diseño).

Interfaces del controlador

public interface INotificationHandler<in TNotification> where TNotification : class
{
    Task Handle(TNotification notification, CancellationToken cancellationToken);
}

public interface IRequestHandler<in TRequest, TResponse> where TRequest : class
{
    Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}

POCO simple sin interfaces de marcador

class TestNotification
{
    public int UserId { get; set; }
    public int Age { get; set; }
}

class TestRequest
{
    public int UserId { get; set; }
}

Manejadores

class TestRequestHandler : IRequestHandler<TestRequest, int>
{
    public Task<int> Handle(TestRequest request, CancellationToken cancellationToken)
    {
        return Task.FromResult(request.UserId);
    }
}

class TestNotificationHandler : INotificationHandler<TestNotification>
{
    public Task Handle(TestNotification notification, CancellationToken cancellationToken)
    {
        Console.WriteLine("hello");
        return Task.CompletedTask;
    }
}

Principal

static void Main(string[] args)
{
    _ = new TestNotificationHandler()
                .Handle(new TestNotification(), CancellationToken.None);

    var result = new TestRequestHandler()
                        .Handle(new TestRequest() { UserId = 111 }, CancellationToken.None)
                        .Result;

    Console.WriteLine($"result is {result}");
}

violín C#

https://dotnetfiddle.net/HTkfh9

¿Estás preguntando sobre la eliminación de las interfaces de la biblioteca?

- Nkosi

30 de marzo de 2021 a las 23:59

Nkosi, gracias por la respuesta y la buena pregunta; no es de la biblioteca, además es una pregunta de diseño, ya que si se implementara el mismo patrón, parecería que la interfaz no es necesaria (y las interfaces de marcador pueden ser un antipatrón)

- morleyc

31 de marzo de 2021 a las 6:47



------------------------------------

Tienes razón acerca de INotification. Si tuvieras que escribir tu propia implementación de MediatR, básicamente podrías eliminar esa interfaz de marcador.

Para IRequest, sin embargo, necesita el tipo de respuesta en la definición de su solicitud para poder manejar la respuesta después de llamar al mediador.

Considere una solicitud sin un tipo de respuesta definido:

public class TestRequest
{
}

Ahora, si llamara al mediador, no habría forma de determinar el tipo de respuesta:

IMediator mediator = GetMediator();

// There is no way to type the response here.
var response = mediator.Send(new TestRequest());

La única forma en que el mediador puede escribir la respuesta es porque la definición de la respuesta está en la definición de la solicitud:

public interface IRequest<out TResponse> : IBaseRequest { }

Y el envío de IMediator se define así:

Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);

Eso permite conocer el tipo de devolución.

En pocas palabras:

No necesitas INotificación Necesita IRequest<TResponse>



------------------------------------

1. Por lo tanto, ¿cuál es la necesidad de interfaces de marcador en primer lugar, cuando el siguiente código funciona con POCO simples?

Echemos un vistazo a MediatR

namespace MediatR
{
    /// <summary>
    /// Marker interface to represent a request with a void response
    /// </summary>
    public interface IRequest : IRequest<Unit> { }

    /// <summary>
    /// Marker interface to represent a request with a response
    /// </summary>
    /// <typeparam name="TResponse">Response type</typeparam>
    public interface IRequest<out TResponse> : IBaseRequest { }

    /// <summary>
    /// Allows for generic type constraints of objects implementing IRequest or IRequest{TResponse}
    /// </summary>
    public interface IBaseRequest { }
}

namespace MediatR
{
    /// <summary>
    /// Marker interface to represent a notification
    /// </summary>
    public interface INotification { }
}

Son simplemente interfaces vacías, así que creo que MediatR tal vez agregue alguna función a IRequest en el futuro como esta:

public interface IRequestType {
    int GetId();

    string GetName();
}
/// <summary>
/// Marker interface to represent a request with a void response
/// </summary>
public interface IRequest : IRequest<Unit> {
    IRequestType GetRequestType();
}

Así que creo que deberías implementar IRequest e INotification para evitar una gran actualización en tu código fuente en el futuro

2. ¿Hay algún problema con la eliminación de las interfaces de marcador de Notificación y Solicitud?

Actualmente, según tus ejemplos, está bien. Pero cuando usas la función Publicar no está bien

/// <summary>
/// Asynchronously send a notification to multiple handlers
/// </summary>
/// <param name="notification">Notification object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the publish operation.</returns>
Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
    where TNotification : INotification;

Por cierto, creo que MediatR necesita una actualización para crear un constructor para Mediator como este:

IMediator mediator = new Mediator.Builder()
            .AddRequestHandler(typeof(PingRequest), new PingRequestHandler())
            .Build();

3

Si agregan funcionalidad a esas interfaces, de todos modos estás viendo una gran actualización de tu código fuente. NO querrás implementar las interfaces para evitar ese impacto.

- Rex Henderson

1 de abril de 2021 a las 4:44

Si cambian las interfaces, I thInk, usarán el método predeterminado y no necesitas actualizar si no lo deseas

– estiércol ta van

1 de abril de 2021 a las 5:44

Estoy de acuerdo, tenía que volver atrás y refrescarme sobre el tema. learn.microsoft.com/en-us/dotnet/core/compatibility learn.microsoft.com/en-us/dotnet/core/compatibility/categories

- Rex Henderson

1 de abril de 2021 a las 5:54



------------------------------------

De hecho, tuve el mismo problema hace un tiempo. Realmente quería evitar que todas mis bibliotecas hicieran referencia al paquete MediatR. Tampoco vi el sentido de tenerlos, ya que se podía solucionar simplemente envolviéndolos en un objeto genérico como este:

public class NotificationWrapper<TNotification> : INotification {
    public TNofication Data { get; }
    public NotificationWrapper(TNotification notification) {
        Data = notification ?? throw new ArgumentNullException(notification);
    }
}

Y luego manejarlo usando una implementación de INotificationHandler<NotificationWrapper<SomeType>>

Así que terminé copiando la biblioteca y escribiendo una versión sin esas interfaces. Resulta que no necesitas INotification ni IRequest<out TResponse>... Pero con una compensación.

La notificación sigue lo que Jesse dijo anteriormente. Pero, con IRequest, notarás que el método Enviar tiene un parámetro genérico para la respuesta. Sin embargo, está usando IRequest<out TResponse> para inferir el parámetro. Si elimina la interfaz, aún puede usar TResponse para buscar el controlador, solo tiene que definir explícitamente el tipo de respuesta de la persona que llama. En mi opinión, esta es una compensación bastante buena, ya que tendrá que hacerlo de todos modos si el tipo de solicitud implementa IRequest<out TResponse> varias veces con diferentes tipos de respuesta. Y, si acepta la condición de que tendrá que definir explícitamente la respuesta que espera, puede eliminar el mensaje IRequest<out TResponse>. interfaz y llame así:

IMediator mediator = GetMediator();
mediator.Send<ExpectedResponse>(request);

La versión en la que hice esto está alojada aquí y se basó en MediatR 8.0.0: https://github.com/user040F019F/Mediación

Es cierto que probablemente debería haber hecho un fork/PR, pero no estaba muy acostumbrado a github en ese momento. También,ignora el archivo Léame, no tiene sentido.

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare