Aplicación de referencia Dapr
Sugerencia
Este contenido es un extracto del libro electrónico "Dapr for .NET Developers" (Dapr para desarrolladores de .NET), disponible en Documentación de .NET o como un PDF descargable gratuito que se puede leer sin conexión.
A lo largo de este libro, ha obtenido información sobre las ventajas fundamentales de Dapr. Ha visto cómo Dapr puede ayudarle a usted y a su equipo a construir aplicaciones distribuidas y a reducir la complejidad de la arquitectura y el funcionamiento. A lo largo del proceso, ha tenido la oportunidad de crear algunas aplicaciones pequeñas de Dapr. Ahora es el momento de explorar de qué forma una aplicación más compleja puede beneficiarse de Dapr.
Pero primero veamos un poco de historia.
eShopOnContainers
Hace varios años, Microsoft publicó en colaboración con los principales expertos de la comunidad un libro de instrucciones popular, titulado Microservicios de .NET para aplicaciones .NET contenedorizadas. En la figura 12-1 se muestra el libro:
Figura 12-1. Microservicios de .NET: diseño de aplicaciones .NET contenedorizadas.
El libro profundizaba en los principios, patrones y procedimientos recomendados para compilar aplicaciones distribuidas. Incluía una aplicación de referencia de microservicio completa que mostraba los conceptos de arquitectura, denominada eShopOnContainers. Dicha aplicación hospeda un escaparate de comercio electrónico que vende varios artículos, incluida ropa y tazas. La aplicación está compilada en .NET, es multiplataforma y se puede ejecutar en contenedores de Linux o Windows. En la figura 12-2 se muestra la arquitectura original de la tienda electrónica.
Figura 12-2. Aplicación de referencia ShopOnContainers original.
Como puede ver, eShopOnContainers incluye muchos elementos móviles:
- Tres clientes front-end diferentes.
- Una puerta de enlace de aplicaciones para abstraer los servicios back-end del front-end.
- Varios microservicios principales de back-end.
- Un componente de bus de eventos que permite la mensajería asincrónica de publicación y subscripción.
La aplicación de referencia eShopOnContainers se ha aceptado ampliamente en toda la comunidad de .NET y se usa para modelar muchas aplicaciones de microservicios comerciales de gran tamaño.
eShopOnDapr
Este libro va acompañado de una versión actualizada de la tienda electrónica. Se denomina eShopOnDapr. La actualización desarrolla la aplicación eShopOnContainers anterior mediante la integración de bloques de creación de Dapr. En la figura 12-3 se muestra la nueva arquitectura de la solución:
Figura 12-3. Arquitectura de la aplicación de referencia eShopOnDapr.
Aunque eShopOnDapr se centra en Dapr, la arquitectura también se ha simplificado.
Una aplicación de página única que se ejecuta en Blazor WebAssembly envía solicitudes de usuario a una puerta de enlace de API.
La puerta de enlace de API abstrae los microservicios principales de back-end del cliente front-end. Se implementa mediante Envoy, un proxy de servicio de código abierto de alto rendimiento. Envoy enruta las solicitudes entrantes a los microservicios de back-end. La mayoría de las solicitudes son operaciones CRUD simples (por ejemplo, obtener la lista de marcas del catálogo) y se controlan mediante una llamada directa a un microservicio de back-end.
Otras solicitudes son más complejas lógicamente y requieren varias llamadas de microservicio para funcionar juntas. En estos casos, eShopOnDapr implementa un microservicio agregador que organiza un flujo de trabajo entre esos microservicios necesarios para completar la operación.
Los microservicios de back-end principales implementan la funcionalidad necesaria para un almacén de comercio electrónico. Cada uno es independiente de las demás. En función de patrones de descomposición de dominio ampliamente aceptados, cada microservicio aísla una funcionalidad empresarial específica:
- El servicio de cesta administra la experiencia de la cesta de la compra del cliente.
- El servicio de catálogo administra los artículos disponibles para la venta.
- El servicio de identidad administra la autenticación y la identidad.
- El servicio de pedidos controla todos los aspectos de la realización y administración de pedidos.
- El servicio de pago realiza transacciones relacionadas con el pago del cliente.
Siguiendo los procedimientos recomendados, cada microservicio mantiene su propio almacenamiento persistente. La aplicación no comparte un único almacén de datos.
Por último, el bus de eventos encapsula los componentes de publicación y suscripción de Dapr. Permite la mensajería asincrónica de publicación y suscripción en los microservicios. Los desarrolladores pueden conectar cualquier componente de agente de mensajes compatible con Dapr.
Aplicación de bloques de creación de Dapr
En eShopOnDapr, los bloques de creación de Dapr reemplazan una gran cantidad de código estructural complejo y propenso a errores.
En la figura 12-4 se muestra la integración de Dapr en la aplicación.
Figura 12-4. Integración de Dapr en eShopOnDapr.
En la figura anterior se muestran los bloques de creación de Dapr (representados como cuadros numerados verdes) que cada servicio eShopOnDapr consume.
- La puerta de enlace de API y los servicios de agregador de compras web usan el bloque de creación de invocación del servicio para invocar métodos en los servicios de back-end.
- Los servicios de back-end se comunican de forma asincrónica mediante el bloque de creación de publicación y suscripción.
- El servicio de cesta usa el bloque de creación de administración del estado para almacenar el estado de la cesta de la compra del cliente.
- La aplicación eShopOnContainers original muestra conceptos y patrones de DDD (diseño guiado por el dominio) en el servicio de pedidos. eShopOnDapr usa el bloque de creación de actores como una implementación alternativa. El modelo de acceso basado en turnos de los actores facilita la implementación de un proceso de pedidos con estado compatible con la cancelación.
- El servicio de pedidos envía correos electrónicos de confirmación de los pedidos mediante el bloque de creación de enlaces.
- La administración de secretos se realiza mediante el bloque de creación de secretos.
En las secciones siguientes se proporciona más información sobre cómo se aplican los bloques de creación de Dapr en eShopOnDapr.
Administración de estados
En eShopOnDapr, el servicio de cesta usa el bloque de creación de administración del estado para conservar el contenido de la cesta de la compra del cliente. La arquitectura original de eShopOnContainers usaba una interfaz IBasketRepository
para leer y escribir datos para el servicio de cesta. La clase RedisBasketRepository
proporcionaba la implementación mediante Redis como almacén de datos subyacente. Para que resulte más sencillo realizar una comparación, se muestra a continuación la implementación original de eShopOnContainers:
public class RedisBasketRepository : IBasketRepository
{
private readonly ConnectionMultiplexer _redis;
private readonly IDatabase _database;
public RedisBasketRepository(ConnectionMultiplexer redis)
{
_redis = redis;
_database = redis.GetDatabase();
}
public async Task<CustomerBasket> GetBasketAsync(string customerId)
{
var data = await _database.StringGetAsync(customerId);
if (data.IsNullOrEmpty)
{
return null;
}
return JsonConvert.DeserializeObject<CustomerBasket>(data);
}
// ...
}
Este código usa el paquete NuGet StackExchange.Redis
de terceros. Los pasos siguientes son necesarios para cargar la cesta de la compra para un cliente determinado:
Inserte un
ConnectionMultiplexer
de Redis en el constructor.ConnectionMultiplexer
se registra con el marco de inserción de dependencias en el archivo Program.cs.services.AddSingleton<ConnectionMultiplexer>(sp => { var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); configuration.ResolveDns = true; return ConnectionMultiplexer.Connect(configuration); });
Use
ConnectionMultiplexer
para crear una instancia deIDatabase
en cada clase de consumo.Use la instancia de
IDatabase
para ejecutar una llamada StringGet de Redis mediante elcustomerId
especificado como clave.Compruebe si los datos se cargan desde Redis; si no es así, se devuelve
null
.Deserialice los datos de Redis en un objeto
CustomerBasket
y devuelva el resultado.
En la aplicación de referencia eShopOnDapr actualizada, una nueva clase DaprBasketRepository
reemplaza a la clase RedisBasketRepository
:
public class DaprBasketRepository : IBasketRepository
{
private const string StoreName = "eshop-statestore";
private readonly DaprClient _daprClient;
public DaprBasketRepository(DaprClient daprClient)
{
_daprClient = daprClient;
}
public Task<CustomerBasket> GetBasketAsync(string customerId) =>
_daprClient.GetStateAsync<CustomerBasket>(StoreName, customerId);
// ...
}
El código actualizado usa el SDK de .NET de Dapr para leer y escribir datos mediante el bloque de creación de administración del estado. Los nuevos pasos para cargar la cesta de un cliente se simplifican drásticamente:
- Inserte
DaprClient
en el constructor.DaprClient
se registra con el marco de inserción de dependencias en el archivo Program.cs`_. - Use el método
DaprClient.GetStateAsync
para cargar los artículos de la cesta de la compra del cliente desde el almacén de estado configurado y devolver el resultado.
La implementación actualizada sigue usando Redis como almacén de datos subyacente. Pero observe cómo Dapr abstrae las referencias de StackExchange.Redis
y la complejidad de la aplicación. La aplicación ya no requiere una dependencia directa de Redis. Lo único que necesita es un archivo de configuración de Dapr:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: eshop-statestore
namespace: eshop
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
- name: redisPassword
secretKeyRef:
name: redisPassword
auth:
secretStore: eshop-secretstore
La implementación de Dapr también simplifica el cambio del almacén de datos subyacente. Para cambiar a Azure Table Storage, por ejemplo, solo hay que cambiar el contenido del archivo de configuración. No es necesario realizar ningún cambio en el código.
Invocación de servicio
La aplicación eShopOnContainers original usaba una combinación de servicios gRPC y HTTP/REST. El uso de gRPC se limitaba a la comunicación entre un servicio de agregador y los servicios de back-end principales. En la figura 12-5 se muestra la arquitectura original:
Figura 12-5. Llamadas HTTP/REST y gRPC en eShopOnContainers.
Observe los pasos de la figura anterior:
El front-end llama a la puerta de enlace de API mediante HTTP/REST.
La puerta de enlace de API reenvía solicitudes CRUD (crear, leer, actualizar y eliminar) simples directamente a un servicio de back-end principal mediante HTTP/REST.
La puerta de enlace de API reenvía solicitudes complejas que implican llamadas de servicio back-end coordinadas al servicio de agregador de compras web.
El servicio de agregador usa gRPC para llamar a los servicios de back-end principales.
En la implementación actualizada de eShopOnDapr, se agregan sidecares de Dapr a los servicios y la puerta de enlace de API. En la figura 12-6 se muestra la arquitectura actualizada:
Figura 12-6. Arquitectura de la tienda electrónica actualizada mediante Dapr.
Observe los pasos actualizados de la figura anterior:
El front-end sigue usando HTTP/REST para llamar a la puerta de enlace de API.
La puerta de enlace de API reenvía las solicitudes HTTP a su sidecar de Dapr.
El sidecar de la puerta de enlace de API envía la solicitud al sidecar del servicio agregador o de back-end.
El servicio agregador usa el SDK de .NET de Dapr para llamar a los servicios de back-end mediante su arquitectura sidecar.
Dapr implementa las llamadas entre los sidecares con gRPC. Por lo tanto, incluso si invoca un servicio remoto con la semántica HTTP/REST, una parte del transporte se implementa mediante gRPC.
La aplicación de referencia eShopOnDapr se beneficia del bloque de creación de invocación del servicio de Dapr. Las ventajas también incluyen la detección de servicios, mTLS automático y observabilidad integrada.
Reenvío de solicitudes HTTP mediante Envoy y Dapr
Tanto la aplicación de tienda electrónica original como la actualizada usan el proxy de Envoy como puerta de enlace de API. Envoy es un proxy de código abierto y bus de comunicación popular entre las aplicaciones distribuidas modernas. Lo creó originariamente Lyft y es propiedad de Cloud Native Computing Foundation, que se encarga de su mantenimiento.
En la implementación original de eShopOnContainers, la puerta de enlace de API de Envoy reenviaba las solicitudes HTTP entrantes directamente a los servicios agregadores o de back-end. En la nueva aplicación eShopOnDapr, el proxy de Envoy reenvía la solicitud a un sidecar de Dapr.
Envoy se configura mediante un archivo de definición YAML para controlar el comportamiento del proxy. Para permitir que Envoy reenvíe las solicitudes HTTP a un contenedor sidecar de Dapr, se agrega un clúster dapr
a la configuración. La configuración del clúster contiene un host que apunta al puerto HTTP en el que está escuchando el sidecar de Dapr:
clusters:
- name: dapr
connect_timeout: 0.25s
type: strict_dns
hosts:
- socket_address:
address: 127.0.0.1
port_value: 3500
La configuración de ruta de Envoy se actualiza para reescribir las solicitudes entrantes como llamadas al sidecar de Dapr (fíjese bien en el par clave-valor prefix_rewrite
):
- name: "c-short"
match:
prefix: "/c/"
route:
auto_host_rewrite: true
prefix_rewrite: "/v1.0/invoke/catalog-api/method/"
cluster: dapr
Imagine que el cliente de front-end quiere recuperar una lista de artículos del catálogo. La API Catalog proporciona un punto de conexión para obtener los artículos del catálogo:
[Route("api/v1/[controller]")]
[ApiController]
public class CatalogController : ControllerBase
{
[HttpGet("items/by_page")]
[ProducesResponseType(typeof(PaginatedItemsViewModel), (int)HttpStatusCode.OK)]
public async Task<PaginatedItemsViewModel> ItemsAsync(
[FromQuery] int typeId = -1,
[FromQuery] int brandId = -1,
[FromQuery] int pageSize = 10,
[FromQuery] int pageIndex = 0)
{
// ...
}
En primer lugar, el front-end realiza una llamada HTTP directa a la puerta de enlace de la API de Envoy.
GET http://<api-gateway>/c/api/v1/catalog/items
El proxy de Envoy busca una coincidencia con la ruta, reescribe la solicitud HTTP y la reenvía a la API invoke
de su sidecar de Dapr:
GET http://127.0.0.1:3500/v1.0/invoke/catalog-api/method/api/v1/catalog/items
El sidecar controla la detección de servicios y enruta la solicitud al sidecar de la API Catalog. Por último, el sidecar llama a la API Catalog para ejecutar la solicitud, capturar artículos del catálogo y devolver una respuesta:
GET http://localhost/api/v1/catalog/items
Realización de llamadas del servicio agregador mediante el SDK de .NET
La mayoría de las llamadas del front-end de la tienda electrónica son llamadas CRUD sencillas. La puerta de enlace de API las reenvía a un único servicio para su procesamiento. Aun así, algunos escenarios requieren que varios servicios de back-end trabajen juntos para completar una solicitud. En el caso de las llamadas más complejas, el servicio de agregador de compras web actúa como mediador en el flujo de trabajo entre servicios. En la figura 12-7 se muestra la secuencia de procesamiento al agregar un artículo a la cesta de la compra:
Figura 12-7. Llamada de back-end que requiere varios servicios.
El servicio agregador recupera primero los artículos del catálogo desde la API Catalog. Después, valida la disponibilidad y los precios de los artículos. Por último, el servicio agregador actualiza la cesta de la compra mediante una llamada a la API Basket.
El servicio agregador contiene un BasketController
que proporciona un punto de conexión para actualizar la cesta de la compra:
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
{
private readonly ICatalogService _catalog;
private readonly IBasketService _basket;
[HttpPost]
[HttpPut]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync(
[FromBody] UpdateBasketRequest data,
[FromHeader] string authorization)
{
BasketData basket;
if (data.Items is null || !data.Items.Any())
{
basket = new();
}
else
{
// Get the item details from the catalog API.
var catalogItems = await _catalog.GetCatalogItemsAsync(
data.Items.Select(x => x.ProductId));
if (catalogItems == null)
{
return BadRequest(
"Catalog items were not available for the specified items in the basket.");
}
// Check item availability and prices; store results in basket object.
basket = CreateValidatedBasket(data.Items, catalogItems);
}
// Save the updated shopping basket.
await _basket.UpdateAsync(basket, authorization.Substring("Bearer ".Length));
return basket;
}
// ...
}
El método UpdateAllBasketAsync
obtiene el encabezado Authorization de la solicitud entrante mediante un atributo FromHeader
. El encabezado Authorization contiene el token de acceso necesario para llamar a servicios de back-end protegidos.
Después de recibir una solicitud para actualizar la cesta, el servicio agregador llama a la API Catalog para obtener los detalles del artículo. El controlador Basket usa un objeto ICatalogService
insertado para realizar esa llamada y comunicarse con la API Catalog. La implementación original de la interfaz usaba gRPC para realizar la llamada. La implementación actualizada usa la invocación del servicio Dapr con compatibilidad con HttpClient:
public class CatalogService : ICatalogService
{
private readonly HttpClient _httpClient;
public CatalogService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids)
{
var requestUri = $"api/v1/catalog/items/by_ids?ids={string.Join(",", ids)}";
return _httpClient.GetFromJsonAsync<IEnumerable<CatalogItem>>(requestUri);
}
// ...
}
Observe que no se requiere código específico de Dapr para realizar la llamada de invocación del servicio. Toda la comunicación se realiza mediante el objeto HttpClient estándar.
El objeto HttpClient de Dapr está configurado para la clase CatalogService
en el inicio del programa:
builder.Services.AddSingleton<ICatalogService, CatalogService>(
_ => new CatalogService(DaprClient.CreateInvokeHttpClient("catalog-api")));
La otra llamada que realiza el servicio agregador es a la API Basket. Solo permite solicitudes autorizadas. El token de acceso se pasa en un encabezado de solicitud Authorization para garantizar que la llamada se realiza correctamente:
public class BasketService : IBasketService
{
public Task UpdateAsync(BasketData currentBasket, string accessToken)
{
var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/basket")
{
Content = JsonContent.Create(currentBasket)
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
}
// ...
}
También en este ejemplo solo se usa la funcionalidad HttpClient estándar para llamar al servicio. Esto permite a los desarrolladores que ya están familiarizados con HttpClient poner en práctica sus conocimientos. Incluso permite que el código HttpClient existente use la invocación del servicio Dapr sin realizar ningún cambio.
Publicación y suscripción
Tanto eShopOnContainers como eShopOnDapr usan el patrón de publicación y suscripción para transmitir eventos de integración entre los microservicios. Los eventos de integración incluyen:
- Cuando un usuario finaliza la compra de productos incluidos en la cesta de la compra.
- Cuando se realiza correctamente el pago de un pedido.
- Cuando el período de gracia de una compra expira.
Nota
Considere un evento de integración como un evento que tiene lugar en varios servicios.
Los eventos de eShopOnContainers se basan en la siguiente interfaz IEventBus
:
public interface IEventBus
{
void Publish(IntegrationEvent integrationEvent);
void Subscribe<T, THandler>()
where TEvent : IntegrationEvent
where THandler : IIntegrationEventHandler<T>;
}
En eShopOnContainers se encuentran implementaciones concretas de esta interfaz para RabbitMQ y Azure Service Bus. Cada implementación incluía una gran cantidad de código estructural personalizado que era difícil de entender y mantener.
La versión más reciente de eShopOnDapr simplifica considerablemente el comportamiento de publicación y suscripción mediante Dapr. Para empezar, la interfaz IEventBus
se ha reducido a un único método:
public interface IEventBus
{
Task PublishAsync(IntegrationEvent integrationEvent);
}
Publicación de eventos
En eShopOnDapr, una sola implementación de DaprEventBus
puede admitir cualquier agente de mensajes compatible con Dapr. En el bloque de código siguiente se muestra el método de publicación simplificado. Observe que el método PublishAsync
usa el cliente Dapr para publicar un evento:
public class DaprEventBus : IEventBus
{
private const string DAPR_PUBSUB_NAME = "pubsub";
private readonly DaprClient _dapr;
private readonly ILogger _logger;
public DaprEventBus(DaprClient dapr, ILogger<DaprEventBus> logger)
{
_dapr = dapr;
_logger = logger;
}
public async Task PublishAsync(IntegrationEvent integrationEvent)
{
var topicName = integrationEvent.GetType().Name;
_logger.LogInformation(
"Publishing event {@Event} to {PubsubName}.{TopicName}",
integrationEvent,
DAPR_PUBSUB_NAME,
topicName);
// We need to make sure that we pass the concrete type to PublishEventAsync,
// which can be accomplished by casting the event to dynamic. This ensures
// that all event fields are properly serialized.
await _dapr.PublishEventAsync(DAPR_PUBSUB_NAME, topicName, (object)integrationEvent);
}
}
Como puede ver en el fragmento de código, el nombre del tema deriva del nombre del tipo de evento. Dado que todos los servicios de la tienda electrónica usan la abstracción IEventBus
, el reajuste de Dapr no requiere ningún cambio en el código de la aplicación principal.
Importante
El SDK de Dapr usa System.Text.Json
para serializar o deserializar los mensajes. Aun así, System.Text.Json
no serializa las propiedades de las clases derivadas de forma predeterminada. En el código de la tienda electrónica, a veces un evento se declara explícitamente como IntegrationEvent
, que es la clase base para los eventos de integración. Esta construcción permite determinar dinámicamente el tipo de evento concreto en tiempo de ejecución en función de la lógica de negocios. Como resultado, el evento se serializa mediante la información de tipo de la clase base, y no de la clase derivada. Para forzar que System.Text.Json
serialice las propiedades de la clase base y la derivada, el código usa object
como parámetro de tipo genérico. Para obtener más información, consulte la documentación de .NET.
Con Dapr, el código de infraestructura de publicación y suscripción se simplifica considerablemente. La aplicación no necesita distinguir entre los agentes de mensajes. Dapr proporciona esta abstracción automáticamente. Si es necesario, puede intercambiar fácilmente agentes de mensajes o configurar varios componentes de agente de mensajes sin realizar cambios en el código.
Suscripción a los eventos
La anterior aplicación eShopOnContainers contiene SubscriptionManagers para controlar la implementación de la suscripción para cada agente de mensajes. Cada administrador contiene código complejo específico del agente de mensajes para controlar los eventos de suscripción. Con el fin de recibir eventos, cada servicio debe registrar explícitamente un controlador para cada tipo de evento.
eShopOnDapr simplifica el código estructural de las suscripciones de eventos mediante la integración de ASP.NET Core en Dapr. Cada evento se controla mediante un método de acción en un controlador. Un atributo Topic
decora el método de acción con el nombre del tema correspondiente. Este es un fragmento de código tomado de PaymentService
:
[Route("api/v1/[controller]")]
[ApiController]
public class IntegrationEventController : ControllerBase
{
private const string DAPR_PUBSUB_NAME = "pubsub";
[HttpPost("OrderStatusChangedToValidated")]
[Topic(DAPR_PUBSUB_NAME, nameof(OrderStatusChangedToValidatedIntegrationEvent))]
public Task HandleAsync(
OrderStatusChangedToValidatedIntegrationEvent integrationEvent,
[FromServices] OrderStatusChangedToValidatedIntegrationEventHandler handler) =>
handler.Handle(integrationEvent);
}
En el atributo Topic
, el nombre del tipo .NET del evento se usa como nombre del tema. Para controlar el evento, se resuelve mediante la inserción de dependencias un controlador de eventos que ya existía en la base de código anterior de eShopOnContainers y se invoca. En el ejemplo anterior, los mensajes que se reciben del tema OrderStatusChangedToValidatedIntegrationEvent
invocan al controlador de eventos OrderStatusChangedToValidatedIntegrationEventHandler
existente. Dado que Dapr implementa el código estructural subyacente para las suscripciones y los agentes de mensajes, una gran cantidad del código original quedó obsoleta y se quitó de la base de código. Gran parte de este código era difícil de entender y mantener.
Uso de componentes de publicación y suscripción
Dentro del repositorio de eShopOnDapr, hay una carpeta deployment
que contiene archivos para implementar la aplicación mediante diferentes modos de implementación: Docker Compose
y Kubernetes
. Dentro de cada una de estas carpetas, existe una carpeta dapr
que contiene una carpeta components
. Esta carpeta contiene un archivo eshop-pubsub.yaml
que especifica el componente de publicación y suscripción de Dapr que la aplicación usará para el comportamiento de publicación y suscripción. Como vio en los fragmentos de código anteriores, el nombre del componente de publicación y suscripción que se usa es pubsub
. Este es el contenido del archivo eshop-pubsub.yaml
de la carpeta deployment/compose/dapr/components
:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
namespace: eshop
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqp://rabbitmq:5672"
La configuración especifica RabbitMQ como infraestructura subyacente. Para cambiar los agentes de mensajes, solo debe configurar otro agente de mensajes, como NATS o Azure Service Bus, y actualizar el archivo YAML. Con Dapr, no se realiza ningún cambio en el código del servicio principal cuando se cambian los agentes de mensajes.
También puede usar fácilmente varios agentes de mensajes en una sola aplicación. Muchas veces, un sistema controlará cargas de trabajo con características diferentes. Un evento podría producirse 10 veces al día y otro, 5000 veces por segundo. Para aprovechar esta ventaja, cree particiones del tráfico de mensajería a diferentes agentes de mensajes. Con Dapr, puede agregar varias configuraciones del componente de publicación y suscripción, cada una con un nombre diferente.
Enlaces
eShopOnDapr usa el bloque de creación de enlaces para enviar correos electrónicos. Cuando un usuario realiza un pedido, la aplicación envía un correo electrónico de confirmación del pedido mediante el enlace de salida SMTP. Encontrará este enlace en el archivo eshop-email.yaml
en la carpeta components:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sendmail
namespace: eshop
spec:
type: bindings.smtp
version: v1
metadata:
- name: host
value: maildev
- name: port
value: 25
- name: user
secretKeyRef:
name: Smtp.User
key: Smtp.User
- name: password
secretKeyRef:
name: Smtp.Password
key: Smtp.Password
- name: skipTLSVerify
value: true
auth:
secretStore: eshop-secretstore
scopes:
- ordering-api
Dapr obtiene el nombre de usuario y la contraseña para conectarse al servidor SMTP desde una referencia secreta. Este enfoque guarda los secretos fuera del archivo de configuración. Para obtener más información sobre los secretos de Dapr, lea el capítulo sobre el bloque de creación de secretos.
La configuración del enlace especifica un componente de enlace que se puede invocar mediante el punto de conexión /sendmail
en el sidecar de Dapr. Este es un fragmento de código en el que se envía un correo electrónico cada vez que se inicia un pedido:
public Task Handle(OrderStartedDomainEvent notification, CancellationToken cancellationToken)
{
var message = CreateEmailBody(notification);
var metadata = new Dictionary<string, string>
{
["emailFrom"] = "eShopOn@dapr.io",
["emailTo" = notification.UserName,
["subject"] = $"Your eShopOnDapr order #{notification.Order.Id}"
};
return _daprClient.InvokeBindingAsync("sendmail", "create", message, metadata, cancellationToken);
}
public Task SendOrderConfirmationAsync(Order order)
{
var message = CreateEmailBody(order);
return _daprClient.InvokeBindingAsync(
"sendmail",
"create",
CreateEmailBody(order),
new Dictionary<string, string>
{
["emailFrom"] = "eshopondapr@example.com",
["emailTo"] = order.BuyerEmail,
["subject"] = $"Your eShopOnDapr Order #{order.OrderNumber}"
});
}
Como puede ver en este ejemplo, message
contiene el cuerpo del mensaje. El método CreateEmailBody
simplemente da formato a una cadena con el texto del cuerpo. El nombre del enlace que se va a invocar es sendmail
y la operación es create
. metadata
especifica el remitente, el destinatario y el asunto del mensaje de correo electrónico. Si estos valores son estáticos, también se pueden incluir en los campos de metadatos del archivo de configuración.
Actores
En la solución original eShopOnContainers, el servicio de pedidos proporciona un buen ejemplo de cómo usar patrones de DDD (diseño guiado por el dominio) en un microservicio de .NET. Como la aplicación eShopOnDapr actualizada se centra en Dapr, el servicio de pedidos ahora usa el bloque de creación de actores para implementar su lógica de negocios.
El proceso de pedidos consta de los pasos siguientes:
- El cliente envía el pedido. Hay un período de gracia antes de que se lleve a cabo cualquier otro tipo de procesamiento. Durante el período de gracia, el cliente puede cancelar el pedido.
- El sistema comprueba que hay existencias disponibles.
- El sistema procesa el pago.
- El sistema envía el pedido.
El proceso se implementa mediante un solo tipo de actor OrderingProcessActor
. Esta es la interfaz del actor:
public interface IOrderingProcessActor : IActor
{
Task SubmitAsync(
string userId, string userName, string street, string city,
string zipCode, string state, string country, CustomerBasket basket);
Task NotifyStockConfirmedAsync();
Task NotifyStockRejectedAsync(List<int> rejectedProductIds);
Task NotifyPaymentSucceededAsync();
Task NotifyPaymentFailedAsync();
Task<bool> CancelAsync();
Task<bool> ShipAsync();
Task<Order> GetOrderDetailsAsync();
}
El proceso se inicia cuando un cliente finaliza la compra de algunos productos. Tras la finalización de la compra, el servicio de cesta publica un mensaje UserCheckoutAcceptedIntegrationEvent
mediante el bloque de creación de publicación y suscripción de Dapr. El servicio de pedidos controla el mensaje de la clase OrderingProcessEventController
y llama al método SubmitAsync
del actor:
[HttpPost("UserCheckoutAccepted")]
[Topic(DaprPubSubName, "UserCheckoutAcceptedIntegrationEvent")]
public async Task HandleAsync(UserCheckoutAcceptedIntegrationEvent integrationEvent)
{
if (integrationEvent.RequestId != Guid.Empty)
{
var actorId = new ActorId(integrationEvent.RequestId.ToString());
var orderingProcess = _actorProxyFactory.CreateActorProxy<IOrderingProcessActor>(
actorId,
nameof(OrderingProcessActor));
await orderingProcess.SubmitAsync(integrationEvent.UserId, integrationEvent.UserName,
integrationEvent.Street, integrationEvent.City, integrationEvent.ZipCode,
integrationEvent.State, integrationEvent.Country, integrationEvent.Basket);
}
else
{
_logger.LogWarning(
"Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}",
integrationEvent);
}
}
En el ejemplo anterior, el servicio de pedidos usa primero el identificador de la solicitud original del mensaje UserCheckoutAcceptedIntegrationEvent
como identificador de actor. El controlador usa ActorId
para crear un proxy de actor e invoca el método SubmitAsync
. En el fragmento de código siguiente se muestra la implementación del método SubmitAsync
:
public async Task SubmitAsync(
string buyerId,
string buyerEmail,
string street,
string city,
string state,
string country,
CustomerBasket basket)
{
var orderState = new OrderState
{
OrderDate = DateTime.UtcNow,
OrderStatus = OrderStatus.Submitted,
Description = "Submitted",
Address = new OrderAddressState
{
Street = street,
City = city,
State = state,
Country = country
},
BuyerId = buyerId,
BuyerEmail = buyerEmail,
OrderItems = basket.Items
.Select(item => new OrderItemState
{
ProductId = item.ProductId,
ProductName = item.ProductName,
UnitPrice = item.UnitPrice,
Units = item.Quantity,
PictureFileName = item.PictureFileName
})
.ToList()
};
await StateManager.SetStateAsync(OrderDetailsStateName, orderState);
await StateManager.SetStateAsync(OrderStatusStateName, OrderStatus.Submitted);
await RegisterReminderAsync(
GracePeriodElapsedReminder,
null,
TimeSpan.FromSeconds(_settings.Value.GracePeriodTime),
TimeSpan.FromMilliseconds(-1));
await _eventBus.PublishAsync(new OrderStatusChangedToSubmittedIntegrationEvent(
OrderId,
OrderStatus.Submitted.Name,
buyerId,
buyerEmail));
}
Suceden muchas cosas en el método Submit
:
- El método toma los argumentos especificados para crear un objeto
OrderState
y lo guarda en el estado del actor. - El método guarda el estado actual del proceso (
OrderStatus.Submitted
) en el estado del actor. - El método registra un recordatorio para indicar el final del período de gracia. El procesamiento del pedido se retrasa hasta el final del período de gracia para tratar los casos de clientes que cambian de opinión.
- Por último, el método publica
OrderStatusChangedToSubmittedIntegrationEvent
para notificar a otros servicios el cambio del estado.
Cuando se activa el recordatorio de la finalización del período de gracia, el runtime del actor llama al método ReceiveReminderAsync
:
public Task ReceiveReminderAsync(
string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
{
return reminderName switch
{
GracePeriodElapsedReminder => OnGracePeriodElapsedAsync(),
StockConfirmedReminder => OnStockConfirmedSimulatedWorkDoneAsync(),
StockRejectedReminder => OnStockRejectedSimulatedWorkDoneAsync(
JsonConvert.DeserializeObject<List<int>>(Encoding.UTF8.GetString(state))),
PaymentSucceededReminder => OnPaymentSucceededSimulatedWorkDoneAsync(),
PaymentFailedReminder => OnPaymentFailedSimulatedWorkDoneAsync(),
_ => Task.CompletedTask
};
}
Como se muestra en el fragmento de código anterior, el método ReceiveReminderAsync
no solo controla el recordatorio del período de gracia. El actor también usa recordatorios para simular el trabajo en segundo plano e introducir algunos retrasos en el proceso de pedidos. Esto hace que sea más fácil seguir el proceso en la interfaz de usuario de eShopOnDapr, donde se muestran notificaciones para cada actualización del estado. El método ReceiveReminderAsync
usa el nombre del recordatorio para determinar qué método controla el recordatorio. El método OnGracePeriodElapsedAsync
controla el recordatorio del período de gracia:
public async Task OnGracePeriodElapsedAsync()
{
var statusChanged = await TryUpdateOrderStatusAsync(
OrderStatus.Submitted, OrderStatus.AwaitingStockValidation);
if (statusChanged)
{
var order = await StateManager.GetStateAsync<Order>(OrderDetailsStateName);
await _eventBus.PublishAsync(new OrderStatusChangedToAwaitingStockValidationIntegrationEvent(
OrderId,
OrderStatus.AwaitingStockValidation.Name,
"Grace period elapsed; waiting for stock validation.",
order.UserName,
order.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units))));
}
}
Primero, el método OnGracePeriodElapsedAsync
intenta actualizar el estado del pedido al nuevo estado AwaitingStockValidation
. Si esto se realiza correctamente, recupera los detalles del pedido desde el estado y publica OrderStatusChangedToAwaitingStockValidationIntegrationEvent
para informar a otro servicio del cambio del estado. Por ejemplo, el servicio Category se suscribe a este evento para comprobar las existencias disponibles.
Echemos un vistazo al método TryUpdateOrderStatusAsync
para ver en qué circunstancias podría no actualizar el estado del pedido:
private async Task<bool> TryUpdateOrderStatusAsync(OrderStatus expectedOrderStatus, OrderStatus newOrderStatus)
{
var orderStatus = await StateManager.TryGetStateAsync<OrderStatus>(OrderStatusStateName);
if (!orderStatus.HasValue)
{
_logger.LogWarning(
"Order with Id: {OrderId} cannot be updated because it doesn't exist",
OrderId);
return false;
}
if (orderStatus.Value.Id != expectedOrderStatus.Id)
{
_logger.LogWarning(
"Order with Id: {OrderId} is in status {Status} instead of expected status {ExpectedStatus}",
OrderId, orderStatus.Value.Name, expectedOrderStatus.Name);
return false;
}
await StateManager.SetStateAsync(OrderStatusStateName, newOrderStatus);
return true;
}
En primer lugar, el método TryUpdateOrderStatusAsync
comprueba si hay un estado de pedido actual. Si no lo hay, el pedido no existe. Se trata de una notificación de error que no debería producirse en el caso de un uso normal de la aplicación. Después, el método comprueba si el estado del pedido actual es el esperado. Recuerde que el proceso de pedidos se controla mediante eventos con el bloque de creación de publicación y suscripción de Dapr. La entrega de eventos usa la semántica de tipo "una vez como mínimo", por lo que un mismo mensaje podría recibirse varias veces. La comprobación del estado del pedido garantiza que, incluso si el mismo mensaje se recibe varias veces, solo se procesa una vez.
Los demás pasos del proceso de pedidos se implementan de forma muy similar al paso del período de gracia. En las secciones siguientes, veremos otros aspectos del proceso de pedidos, concretamente la cancelación y la visualización de los detalles del pedido.
Cancelación del pedido
Los clientes pueden cancelar cualquier pedido que aún no se haya pagado o enviado. La clase OrdersController
controla las cancelaciones de pedidos entrantes. Invoca el método CancelAsync
en la instancia de OrderingProcessActor
del pedido especificado.
public async Task<bool> CancelAsync()
{
var orderStatus = await StateManager.TryGetStateAsync<OrderStatus>(OrderStatusStateName);
if (!orderStatus.HasValue)
{
_logger.LogWarning(
"Order with Id: {OrderId} cannot be cancelled because it doesn't exist",
OrderId);
return false;
}
if (orderStatus.Value.Id == OrderStatus.Paid.Id || orderStatus.Value.Id == OrderStatus.Shipped.Id)
{
_logger.LogWarning(
"Order with Id: {OrderId} cannot be cancelled because it's in status {Status}",
OrderId, orderStatus.Value.Name);
return false;
}
await StateManager.SetStateAsync(OrderStatusStateName, OrderStatus.Cancelled);
var order = await StateManager.GetStateAsync<Order>(OrderDetailsStateName);
await _eventBus.PublishAsync(new OrderStatusChangedToCancelledIntegrationEvent(
OrderId,
OrderStatus.Cancelled.Name,
$"The order was cancelled by buyer.",
order.UserName));
return true;
}
El método CancelAsync
consta de los pasos siguientes:
- En primer lugar, el método recupera el estado del pedido actual para garantizar que el pedido existe.
- Si el pedido existe, el método comprueba si es apto para la cancelación. Los pedidos que no tengan el estado
Paid
oShipped
pueden cancelarse. - Si el pedido puede cancelarse, su estado cambia a
Cancelled
. - Por último, los detalles del pedido se recuperan del estado y se usan para publicar un evento
OrderStatusChangedToCancelledIntegrationEvent
con el fin de informar a los demás servicios.
El método CancelAsync
es un excelente ejemplo de la utilidad del modelo de acceso basado en turnos de los actores. No hay que preocuparse en ningún lugar del método por la ejecución de varios subprocesos al mismo tiempo. Por lo tanto, el método no requiere ningún mecanismo de bloqueo explícito para ser correcto.
Detalles de pedido
Los clientes pueden comprobar el estado y los detalles de su pedido en la interfaz de usuario de eShopOnDapr. También pueden ver el historial completo de los pedidos anteriores. No es recomendable consultar directamente las instancias de actor en busca de esta información por dos motivos:
- No se pueden garantizar las lecturas de baja latencia porque las operaciones de actor se ejecutan en serie.
- Es ineficaz realizar consultas en los actores porque el estado de cada actor debe leerse individualmente y esto puede introducir latencias más impredecibles.
Para corregir este problema, eShopOnDapr usa un modelo de lectura independiente para las consultas que se realicen en los datos de los pedidos. El modelo de lectura se almacena en una base de datos SQL independiente. Una clase de controlador de ASP.NET Core denominada UpdateOrderStatusEventController
se suscribe a los eventos de estado del pedido y crea el modelo de vista. La misma clase UpdateOrderStatusEventController
también envía notificaciones de inserción a la interfaz de usuario para informar al cliente de las actualizaciones de estado del pedido.
En el fragmento de código siguiente se muestra el código para controlar el mensaje OrderStatusChangedToSubmittedIntegrationEvent
:
[HttpPost("OrderStatusChangedToSubmitted")]
[Topic(DaprPubSubName, nameof(OrderStatusChangedToSubmittedIntegrationEvent))]
public async Task HandleAsync(
OrderStatusChangedToSubmittedIntegrationEvent integrationEvent,
[FromServices] IOptions<OrderingSettings> settings,
[FromServices] IEmailService emailService)
{
// Gets the order details from Actor state.
var actorId = new ActorId(integrationEvent.OrderId.ToString());
var orderingProcess = _actorProxyFactory.CreateActorProxy<IOrderingProcessActor>(
actorId,
nameof(OrderingProcessActor));
//
var actorOrder = await orderingProcess.GetOrderDetailsAsync();
var readModelOrder = new Order(integrationEvent.OrderId, actorOrder);
// Add the order to the read model so it can be queried from the API.
// It may already exist if this event has been handled before (at-least-once semantics).
readModelOrder = await _orderRepository.AddOrGetOrderAsync(readModelOrder);
// Send a SignalR notification to the client.
await SendNotificationAsync(readModelOrder.OrderNumber, integrationEvent.OrderStatus,
integrationEvent.BuyerId);
// Send a confirmation e-mail if enabled.
if (settings.Value.SendConfirmationEmail)
{
await emailService.SendOrderConfirmationAsync(readModelOrder);
}
}
El controlador contiene el código de todas las acciones que deben producirse después de que se envíe correctamente un pedido. Dado que los eventos se originan en OrderingProcessActor
, podemos tener la seguridad de que las validaciones realizadas por el actor se han realizado correctamente.
El controlador realiza los pasos siguientes:
- En primer lugar, el método crea un proxy de actor y lo usa para recuperar los detalles del pedido de la instancia de actor.
- El método asigna los detalles del pedido al modelo de lectura y lo almacena en la base de datos. Debido a la semántica de tipo "una vez como mínimo" del bloque de creación de publicación y suscripción de Dapr, es posible que el pedido ya exista en la base de datos. En ese caso, no se sobrescribirá.
- El método publica una notificación de inserción para la actualización del estado mediante SignalR.
- Por último, si está habilitado, el método envía un correo electrónico de confirmación al cliente.
Las actualizaciones posteriores del estado del pedido se controlan todas del mismo modo. En el fragmento de código siguiente se muestra lo que sucede cuando el estado del pedido se actualiza a AwaitingStockValidation
:
[HttpPost("OrderStatusChangedToAwaitingStockValidation")]
[Topic(DaprPubSubName, nameof(OrderStatusChangedToAwaitingStockValidationIntegrationEvent))]
public Task HandleAsync(
OrderStatusChangedToAwaitingStockValidationIntegrationEvent integrationEvent)
{
// Save the updated status in the read model and notify the client via SignalR.
return UpdateReadModelAndSendNotificationAsync(integrationEvent.OrderId,
integrationEvent.OrderStatus, integrationEvent.Description, integrationEvent.BuyerId);
}
private async Task UpdateReadModelAndSendNotificationAsync(
Guid orderId, string orderStatus, string description, string buyerId)
{
var order = await _orderRepository.GetOrderByIdAsync(orderId);
if (order is not null)
{
order.OrderStatus = orderStatus;
order.Description = description;
await _orderRepository.UpdateOrderAsync(order);
await SendNotificationAsync(order.OrderNumber, orderStatus, buyerId);
}
}
En el fragmento de código, el controlador llama al método auxiliar UpdateReadModelAndSendNotificationAsync
para controlar la actualización del estado:
- El método auxiliar carga primero el pedido actual de la base de datos.
- Si esto se realiza correctamente, actualiza los campos
OrderStatus
yDescription
y guarda el modelo actualizado de nuevo en la base de datos. - Por último, envía una notificación de inserción a la interfaz de usuario del cliente.
Observabilidad
eShopOnDapr usa Zipkin para visualizar los seguimientos distribuidos que recopila Dapr. Seq agrega los registros de la aplicación eShopOnDapr. Los distintos servicios emiten un registro estructurado mediante la biblioteca de registro Serilog. Serilog publica eventos de registro en una construcción denominada receptor. Un receptor es simplemente una plataforma de destino en la que Serilog escribe sus eventos de registro. Hay disponibles muchos receptores de Serilog, incluido uno para Seq. Seq es el receptor de Serilog que se usa en eShopOnDapr.
eShopOnDapr también incluye un panel de mantenimiento personalizado que proporciona información sobre el estado de los servicios de tienda electrónica. Este panel usa el mecanismo integrado de comprobaciones de estado de ASP.NET Core. El panel no solo proporciona el estado de mantenimiento de los servicios, sino también el estado de las dependencias de los servicios, incluidos los sidecares de Dapr.
Secretos
La aplicación de referencia eShopOnDapr usa el bloque de creación de secretos para varios secretos:
- La contraseña para conectarse a la caché de Redis.
- El nombre de usuario y la contraseña del servidor SMTP.
- Las cadenas de conexión para las bases de datos SQL.
Al ejecutar la aplicación mediante Docker Compose, se usa el almacén de secretos del archivo local. El archivo de configuración de componentes eshop-secretstore.yaml
se encuentra en la carpeta dapr/components
del repositorio eShopOnDapr:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: eshop-secretstore
namespace: eshop
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: ./components/eshop-secretstore.json
- name: nestedSeparator
value: "."
El archivo de configuración hace referencia al archivo del almacén local eshop-secretstore.json
ubicado en la misma carpeta:
{
"ConnectionStrings": {
"CatalogDB": "**********",
"IdentityDB": "**********",
"OrderingDB": "**********"
},
"Smtp": {
"User": "**********",
"Password": "**********"
},
"State": {
"RedisPassword": "**********"
}
}
La carpeta components
se especifica en la línea de comandos y se monta como una carpeta local dentro del contenedor sidecar de Dapr. Este es un fragmento de código del archivo docker-compose.override.yml
en la raíz del repositorio que especifica el montaje del volumen:
catalog-api-dapr:
command: ["./daprd",
"-app-id", "catalog-api",
"-app-port", "80",
"-components-path", "/components",
"-config", "/configuration/eshop-config.yaml"
]
volumes:
- "./dapr/components/:/components"
- "./dapr/configuration/:/configuration"
El montaje del volumen /components
y el argumento de la línea de comandos --components-path
se pasan al comando de inicio daprd
.
Una vez que se haya configurado, otros archivos de configuración de componentes también pueden hacer referencia a los secretos. Este es un ejemplo de la configuración del componente de almacén de estado que consume secretos:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: eshop-statestore
namespace: eshop
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
- name: redisPassword
secretKeyRef:
name: State.RedisPassword
key: State.RedisPassword
- name: actorStateStore
value: "true"
auth:
secretStore: eshop-secretstore
scopes:
- basket-api
- ordering-api
Ventajas de aplicar Dapr a la tienda electrónica
En general, el uso de bloques de creación de Dapr agrega observabilidad y flexibilidad a la aplicación:
- Observabilidad: mediante el uso de los bloques de creación de Dapr, se obtiene un seguimiento distribuido enriquecido de las llamadas entre los servicios y a los componentes de Dapr sin necesidad de escribir código. En eShopOnContainers, se usa una gran cantidad de registros personalizados para proporcionar información.
- Flexibilidad: ahora puede intercambiar la infraestructura con solo cambiar un archivo de configuración de componentes. No es necesario realizar ningún cambio en el código.
Estos son más ejemplos de las ventajas que ofrecen otros bloques de creación:
Invocación del servicio
- Debido a que Dapr es compatible con mTLS, los servicios ahora se comunican a través de canales cifrados.
- Cuando se producen errores transitorios, las llamadas de servicio se reintentan automáticamente.
- La detección automática de servicios reduce el trabajo de configuración necesario para que los servicios se encuentren entre sí.
Publicación y suscripción
- eShopOnContainers incluía una gran cantidad de código personalizado para admitir tanto Azure Service Bus como RabbitMQ. Los desarrolladores usaban Azure Service Bus para la producción y RabbitMQ para el desarrollo y las pruebas locales. Se creó una capa de abstracción de
IEventBus
para permitir el intercambio entre estos agentes de mensajes. Esta capa constaba aproximadamente de 700 líneas de código propenso a errores. La implementación actualizada con Dapr solo requiere 35 líneas de código, es decir, el 5 % de las líneas de código originales. Y, lo que es más importante, la implementación es sencilla y fácil de entender. - eShopOnDapr recurre a la integración enriquecida de ASP.NET Core de Dapr para usar la publicación y suscripción. Para suscribirse a los mensajes, se agregan atributos
Topic
a los métodos de controlador de ASP.NET Core. Por lo tanto, no es necesario escribir un bucle de controlador de mensajes independiente para cada agente de mensajes. - Los mensajes enrutados al servicio como llamadas HTTP permiten el uso de middleware de ASP.NET Core para agregar funcionalidad, sin necesidad de familiarizarse con conceptos o SDK nuevos.
- eShopOnContainers incluía una gran cantidad de código personalizado para admitir tanto Azure Service Bus como RabbitMQ. Los desarrolladores usaban Azure Service Bus para la producción y RabbitMQ para el desarrollo y las pruebas locales. Se creó una capa de abstracción de
Enlaces
- La solución eShopOnContainers contenía una tarea pendiente para enviar por correo electrónico una confirmación del pedido al cliente. Con Dapr, la implementación de la notificación por correo electrónico es tan fácil como configurar un enlace de recursos.
Actores
- El bloque de creación de actores facilita la creación de flujos de trabajo con estado y de larga duración. Gracias al modelo de acceso basado en turnos, no se requieren mecanismos de bloqueo explícitos.
- La complejidad de la implementación del período de gracia se reduce considerablemente, ya que se usan recordatorios para los actores, en lugar de sondear la base de datos.
Resumen
En este capítulo, le hemos presentado la aplicación de referencia eShopOnDapr. Se trata de una evolución de la aplicación de referencia de microservicios eShopOnContainers, enormemente popular. eShopOnDapr reemplaza una gran cantidad de funcionalidades personalizadas por componentes y bloques de creación de Dapr, lo que simplifica drásticamente la complejidad necesaria para crear una aplicación de microservicios.