# Una parte del servicio de subscripción al boletín 2 - Revitalización de los servicios (y un poco de fuego hangfire)

# El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. [La Comisión Consultiva en Asuntos Administrativos y de Presupuesto recomienda a la Asamblea General que apruebe el siguiente proyecto de resolución:](/blog/anewslettersubscriptionservicept1) Le he mostrado cómo he creado una nueva página de Subscripción al Newsletter. En esta parte I'me referiré a cómo he reestructurado la solución para permitir el intercambio de Servicios y Modelos entre el proyecto de Web M SK2MostlylucidMSC3 y el proyecto del Servicio Scheduler (MostslylucideMska5SchedulerServiceM Ska6

[TOC]

<!--category-- ASP.NET, Hangfire, Email Newsletter -->
<datetime class="hidden">2024-09-23T10:45</datetime>

# El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Originalmente solo tuve un proyecto monolitico, que contenía todo el código para el sitio web. Este es un enfoque decente para aplicaciones más pequeñasM SK2 le da una fácil navegación , construcción y despliegue de la soluciónMSC4 todas ellas son consideraciones razonables
Además, dividir el servicio de programador tiene sentido para mí ya que puedo desplegarlo como un contenedor docker separado que me permite actualizar el sitio web sin hacer reiniciar el programador.

Para ello, he agrupado las preocupaciones lógicamente en proyectos 5 . Este es un enfoque común en ASP.Applications NETM SK3

Ahora puedo añadir proyectos de prueba para cada uno de ellos y probarlos de forma independiente. Esto es un gran beneficio, ya que me permite probar los servicios sin tener que redoblar toda la aplicación.

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Este proyecto es un proyecto web ASP.NET Core (8) , que contiene todos los Controllers & Visualizaciones para mostrar páginas al usuario

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Esto contiene mi definición principal de contexto utilizada para interactuar con la base de datos. Incluyendo el Contexto Db Core EF.

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Este es el proyecto principal de biblioteca de clase que contiene todos los servicios que interactúan con la base de datos / archivos de marcación / servicios de correo electrónico etc.

## El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Este es un proyecto de biblioteca de clase que contiene todos los modelos compartidos que se utilizan tanto por el Mostlylucid como por los proyectos Mostlylucid.SchedulerService.

## El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre hombres y mujeres en materia de empleo, incluida la eliminación del trabajo forzoso.

Esta es una aplicación web que controla el servicio Hangfire que ejecuta las tareas programadas así como los puntos finales para manejar el envío de correos electrónicos.

La estructura de este se muestra a continuación.
Usted puede ver que SchedulerService y el principal Mostlylucid sólo utilizan la clase DbContext para la configuración inicial.

```mermaid
graph LR
  Mostlylucid[Mostlylucid] --> |Intialize| Mostlylucid_DbContext[Mostlylucid.DbContext]
  Mostlylucid[Mostlylucid] --> Mostlylucid_Shared[Mostlylucid.Shared]
  Mostlylucid[Mostlylucid] --> Mostlylucid_Services[Mostlylucid.Services]
  
  Mostlylucid_DbContext[Mostlylucid.DbContext] --> Mostlylucid_Shared[Mostlylucid.Shared]
  
  Mostlylucid_Services[Mostlylucid.Services] --> Mostlylucid_DbContext[Mostlylucid.DbContext]
  Mostlylucid_Services[Mostlylucid.Services] --> Mostlylucid_Shared[Mostlylucid.Shared]
  


  Mostlylucid_SchedulerService[Mostlylucid.SchedulerService] --> Mostlylucid_Shared[Mostlylucid.Shared]
  Mostlylucid_SchedulerService[Mostlylucid.SchedulerService] --> |Intialize|Mostlylucid_DbContext[Mostlylucid.DbContext]
  Mostlylucid_SchedulerService[Mostlylucid.SchedulerService] --> Mostlylucid_Services[Mostlylucid.Services]

```

Como de costumbre, uso un método de extensión como punto de entrada para configurar la base de datos.

```csharp
public static class Setup
{
    public static void SetupDatabase(this IServiceCollection services, IConfiguration configuration,
        IWebHostEnvironment env, string applicationName="mostlylucid")
    {
        services.AddDbContext<IMostlylucidDBContext, MostlylucidDbContext>(options =>
        {
            if (env.IsDevelopment())
            {
                options.EnableDetailedErrors(true);
                options.EnableSensitiveDataLogging(true);
            }
            var connectionString = configuration.GetConnectionString("DefaultConnection");
            var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
            {
                ApplicationName = applicationName
            };
            options.UseNpgsql(connectionStringBuilder.ConnectionString);
        });
    }
}
```

Además de llamar este método, ningún proyecto de nivel superior tiene ninguna dependencia del proyecto DbContext.

NOTA: Es'importante evitar una condición de carrera al inicializar DbContexto M SK2especialmente si ejecutas migraciones ) para hacer esto simplemente aquí He agregado una dependencia en mi archivo Docker Compose para asegurar que el proyecto principal Mostlylucid esté instalado y funciona antes de iniciar mi SchedulerServiceMSC4 Esto se ha agregado a la definición de servicio del meu Schedulerservice en el dockerMNK5compose fileMRK6

```dockerfile
  depends_on:
      - mostlylucid 
  healthcheck:
      test: ["CMD", "curl", "-f", "http://mostlylucid:80/healthy"]
      interval: 30s
      timeout: 10s
      retries: 5
```

También se puede realizar esto de otras maneras, por ejemplo en el pasado cuando ejecutamos múltiples instancias de la misma aplicación que he utilizado [El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.](https://redis.io/) para establecer una bandera de bloqueo que cada instancia controle antes de ejecutar las migraciones / otras tareas asegurando que sólo una instancia pueda ejecutarse en un momento .

Por supuesto, Redis también tiene muchos otros usos; especialmente con ASPM SK1NET 8' el nuevo `IDistributedCache` interfaz que le permite utilizar Redis como proveedor de caches con expiración basada en etiqueta.

# El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

He optado por usar Hangfire para manejar mi programación ya que tiene una integración conveniente con ASP.NET Core y es fácil de usar.

Para ello en el proyecto SchedulerService he agregado los siguientes paquetes NuGet.

```bash
dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.PostgreSql
```

Esto significa que puedo usar Postgres como mi almacén para las tareas programadas.

Ahora tengo el seetup de Hangfire y puedo empezar a añadir mis trabajos programados.

```csharp
public static class JobInitializer
{
    private const string AutoNewsletterJob = "AutoNewsletterJob";
    private const string DailyNewsletterJob = "DailyNewsletterJob";
    private const string WeeklyNewsletterJob = "WeeklyNewsletterJob";
    private const string MonthlyNewsletterJob = "MonthlyNewsletterJob";
    public static void InitializeJobs(this IApplicationBuilder app)
    {
      var scope=  app.ApplicationServices.CreateScope();
        var recurringJobManager = scope.ServiceProvider.GetRequiredService<RecurringJobManager>();
      
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(AutoNewsletterJob,  x =>  x.SendNewsletter(SubscriptionType.EveryPost), Cron.Hourly);
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(DailyNewsletterJob,  x =>  x.SendNewsletter(SubscriptionType.Daily), "0 17 * * *");
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(WeeklyNewsletterJob,  x =>  x.SendNewsletter(SubscriptionType.Weekly), "0 17 * * *");
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(MonthlyNewsletterJob,  x => x.SendNewsletter(SubscriptionType.Monthly), "0 17 * * *");
    }
}
```

Aquí puedes ver que tengo un trabajo para cada uno de ellos. `SubscriptionType` que es una bandera que determina cuántas veces se envía el boletín. Uso el `Cron` class para establecer la frecuencia del trabajo.

Puesto que tengo la opción de día para las subscripciones mensuales y semanales, he fijado el tiempo a 17:00
También tengo un trabajo que funciona cada hora para enviar el boletín a aquellos que han suscrito a todos los mensajes.

El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `SendNewsletter` El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. `NewsletterSendingService` El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre los hombres y las mujeres en materia de empleo, incluida la eliminación del trabajo forzoso.

# El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre hombres y mujeres en materia de empleo, incluida la adopción de medidas legislativas, administrativas, judiciales y de otra índole.

El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer. [El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre hombres y mujeres en materia de empleo, incluida la adopción de medidas legislativas, administrativas, judiciales y de otra índole.](https://en.wikipedia.org/wiki/Data_transfer_object) para transportar datos entre las capas de mi aplicación. Esto NO incrementa la complejidad, ya que necesito mapear (a menudo dos vecesM SK2 las Entidades a Dtos y luego los Dtos a ViewModels (y de vueltaMSC4 Sin embargoMST5 Creo que esta separación de preocupaciones vale la pena, ya qume permite cambiar la estructura de datos subyacente sin afectar al UIMst6

Usted puede usar enfoques como AutoMapper / Mapster etc para hacer esta mappingM SK1 Using these can also have significant performance advantages for .AsNoTracking() queries as you can map directly to the Dto and avoid the overhead of tracking changesMSC4 Por ejemplo, el Automapper tiene un [El Comité recomienda que el Estado Parte adopte todas las medidas necesarias para garantizar la igualdad de oportunidades entre los hombres y las mujeres en materia de empleo, incluida la eliminación del trabajo forzoso.](https://docs.automapper.org/en/stable/Queryable-Extensions.html) método que le permite mapear directamente al Dto en la consulta.

Sin embargo, en este caso he decidido añadir extensiones de mapper donde necesitaba. Esto me permite tener más control sobre la mapping para cada nivel ( pero es más trabajoM SK2 especialmente si tienes muchas entidadesMSC3

```csharp
public static class BlogPostEntityMapper
{
   public static BlogPostDto ToDto(this BlogPostEntity entity, string[] languages = null)
    {
        return new BlogPostDto
        {
            Id = entity.Id.ToString(),
            Title = entity.Title,
            Language = entity.LanguageEntity?.Name,
            Markdown = entity.Markdown,
            UpdatedDate = entity.UpdatedDate.DateTime,
            HtmlContent = entity.HtmlContent,
            PlainTextContent = entity.PlainTextContent,
            Slug = entity.Slug,
            WordCount = entity.WordCount,
            PublishedDate = entity.PublishedDate.DateTime,
            Languages = languages ?? Array.Empty<string>()
        };
    }
}
```

Aquí usted puede ver que yo mapo el `BlogPostEntity` a mi principal `BlogPostDto` que es mi objeto de transferencia.  
El objetivo es que los servicios frontend no saben nada sobre el objeto de Entidad y sean 'abstraídosM SK2 de la estructura de datos subyacente.

En estos servicios de nivel superior tengo entonces el código para mapear estos Dtos a ViewModels que luego se utilizan en los Controladores.

```csharp
    public static PostListModel ToPostListModel(this BlogPostDto dto)
    {
        return new PostListModel
        {
            Id = dto.Id,
            Title = dto.Title,
            Language = dto.Language,
            UpdatedDate = dto.UpdatedDate.DateTime,
            Slug = dto.Slug,
            WordCount = dto.WordCount,
            PublishedDate = dto.PublishedDate,
            Languages = dto.Languages
        };
    }
    
    public static BlogPostViewModel ToViewModel(this BlogPostDto dto)
    {
        return new BlogPostViewModel
        {
            Id = dto.Id,
            Title = dto.Title,
            Language = dto.Language,
            Markdown = dto.Markdown,
            UpdatedDate = dto.UpdatedDate.DateTime,
            HtmlContent = dto.HtmlContent,
            PlainTextContent = dto.PlainTextContent,
            Slug = dto.Slug,
            WordCount = dto.WordCount,
            PublishedDate = dto.PublishedDate,
            Languages = dto.Languages
        };
    }
```

Una vez más, usando una herramienta Mapper puede evitar este código de boilerplate M SK1 y reducir errores) pero creo que esta aproximación funciona bien para mí en este caso

Ahora tengo todos estos modelos instalados puedo empezar a añadir métodos de control para usarlos.
I'I'll cover this flow in the next part.

# El Comité recomienda al Estado Parte que adopte todas las medidas necesarias para garantizar la igualdad entre los géneros y el empoderamiento de la mujer.

Ahora tenemos la estructura que nos permite empezar a ofrecer boletines de noticias a nuestros usuarios.
Esta refactorización fue ligeramente dolorosa, pero es un hecho de vida para la mayoría de los proyectos.
Los futuros puestos cubrirán el resto de este,, incluyendo un seguimiento sobre mi uso del [El Presidente (habla en inglés): Doy las gracias al representante de la República Árabe Siria por su declaración.] para enviar los correos electrónicos y el Servicio Hostado...y quizás un poco más Hangfire.