Saltar a contenido

.Net core 3.1

Overview

La arquitectura de software que se explicara en este apartado fue adaptada a partir del siguiente repositorio : https://github.com/jasontaylordev/CleanArchitecture

Arquitectura

Para el desarrollo de un microservicio web api se desarrollo el siguiente árbol de directorios

├───Application
│   ├───Boopstrap
│   ├───Common
│      ├───Interfaces
│      └───Models
│   └───UseCase
│       ├───V1
│          ├───PersonOperation
│             ├───Commands
│                ├───Create
│                └───Update
│             └───Queries
│                 └───GetList
│          └───WeatherForecasts
│              └───Queries
│                  └───GetWeatherForecasts
│       └───V2
├───Domain
│   ├───Common
│   ├───Entities
│   ├───Enums
│   ├───Exceptions
│   └───ValueObjects
├───Infrastructure
│   ├───Boopstrap
│   ├───EventHandler
│   ├───Migrations
│   ├───Persistence
│      └───Configurations
│   └───Services
└───WebApi
    ├───Controllers
       ├───V1
       └───V2
    ├───Logs
    ├───Models

Domain

├───Common
├───Entities
├───Enums
├───Exceptions
└───ValueObjects
La capa de dominio, tiene la responsabilidad de almacenar todas las clases comunes de nuestra aplicación, como se explicó en CleanArchitecture y DDD.

Decidimos dividir en estos directorios. Quizás los más relevantes a detallar sean:

  • Entities: almacena las clases que se persisten en base de datos.
  • ValueObjects: Objetos requeridos para transportar información y/o usar en la lógica del negocio.

Application

├───Boopstrap
├───Common
│   ├───Interfaces
│   └───Models
└───UseCase
    ├───V1
       ├───PersonOperation
          ├───Commands
             ├───Create
             └───Update
          └───Queries
              └───GetList
       └───WeatherForecasts
           └───Queries
               └───GetWeatherForecasts
    └───V2

La Capa de Application, tiene la responsabilidad de contener la lógica de negocio, interacciona con Domain para utilizar sus clases. Esta capa no debe tener dependencias ni lógica con recursos externos (Base de datos, micro services, bus event, etc). Para interactuar con el exterior esta capa utiliza el Principio de Inversión de Dependencias de SOLID.

Podemos explicar el objetivo de los directorios:

  • Boopstrap: Directorio de configuración de dependencias, es donde agregaremos la configuración de las dependencias que deseamos inyectar.
  • Common: Directorio para almacenar clases comunes, ejemplos services (que sólo interaccionan con el dominio), interfaces, modelos, excepciones, helpers, etc.
  • UseCase: Es el directorio principal de application, es donde almacenaremos todos los casos de uso de nuestra aplicación. Sugerimos dividir por versión y utilizado el patrón CQRS.

Infraestructure

├───Boopstrap
├───EventHandler
├───Migrations
├───Persistence
│   └───Configurations
└───Services
La capa de Infraestructure tiene la responsabilidad de tener la vinculación con los dispositivos externos a la aplicación. En esta capa debemos tener la lógica para la vinculación con servicios externos, base de datos, protocolos de comunicación que no sean http (GRPC, socket, etc), event bus.

  • Persistencia: Carpeta donde almacenaremos la interacción con la base de datos. Repositories, Queries, DbContext.

Web API

├───Controllers
│   ├───V1
│   └───V2
├───Logs
├───Models
└───Properties
Esta capa, es la capa de presentación de nuestra app, interactúa directamente con la aplicación para resolver los request que llegan.

Como manejar un request

Para la nueva arquitectura, utilizamos asp.net y MediatR. Por lo que para manejar un request simplemente debemos realizar los siguientes pasos:

    [ApiController]
    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class PersonController : ControllerBase
    {
        private readonly IMediator _mediator;
        public PersonController(IMediator mediator)
        {
            _mediator = mediator;
        }
    }
definimos el controlador e inyectamos IMediator

        [HttpGet]
        public async Task<IActionResult> Get() => this.Result(await _mediator.Send(new ListPerson()));
Generamos la API Get, ejecutamos el método Send() de mediator donde recibe un objeto de tipo IRequest.

Creamos la clase

    public class ListPerson : IRequest<Response<List<PersonDto>>>
    {
    }
Creamos el handler

public class ListPersonHandler : IRequestHandler<ListPerson, Response<List<PersonDto>>>
    {
        private readonly IReadOnlyQuery _query;

        public ListPersonHandler(IReadOnlyQuery query)
        {
            _query = query;
        }

        public async Task<Response<List<PersonDto>>> Handle(ListPerson request, CancellationToken cancellationToken)
        {
            // ... handler request
        }
    }

Dentro de la función Handle desarrollamos la lógica de negocio.

MediatR

La librería MediatR permite implementar el patrón Mediator de los patrones de diseño de GOF.

El patrón de diseño Mediator define un objeto que encapsula cómo interactúa un conjunto de objetos. Mediator promueve el acoplamiento flexible al evitar que los objetos se refieran entre sí de forma explícita y le permite variar su interacción de forma independiente.

Como se usa la librería

Básicamente para implementar la librería tenemos 2 tipos de objeto, el request y un Handler. El request es, en principio, el objeto que transporta el mensaje mientras que el handler es aquel que atiende ese mensaje, procesa y devuelve un resultado.

La interface IMediator dependiendo el objeto IRequest que se le provea, ejecutará el IRequestHandler configurado.

Cómo configurar:

Para crear un request, simplemente debemos implementar la interface IRequest<TResponse> donde TResponse es el tipo devuelto de ese request. en el siguiente ejemplo el objeto que se devuelve es una lista de PersonDto

    public class ListPerson : IRequest<List<PersonDto>>
    {
    }

Para crear un Handler debemos crear un objeto que implemente la interface IRequestHandler<IRequest, TResponse> donde debemos declarar cual es el request que espera y cual es la respuesta de ese request

NOTA La respuesta debe ser la misma que el declarado en el request.

public class ListPersonHandler : IRequestHandler<ListPerson, List<PersonDto>>
    {
    }

La ventaja de usar el mediator es que podemos definir al Handler las dependencias que necesitemos y el mediator se las inyectará cuando sea necesario.

La interface IRequestHandler necesita que implementemos el método Handler que es el que se ejecutará cuando llegue un mensaje.

        public async Task<List<PersonDto>> Handle(ListPerson request, CancellationToken cancellationToken)
        {
            // ... handler request
        }
Podemos ver que la función espera el IRequest que configuramos y devuelve el TResponse que definimos.

Para ejecutar esto, simplemente debemos inyectar la interface IMediator y ejecutar el método Send(IRequest)

await _mediator.Send(new ListPerson());