# Newsletterin tilauspalvelun osa 2 - Palvelujen uudistaminen ( ja pieni Hangfire)

# Esitys

Siihen [osa 1 tästä sarjasta](/blog/anewslettersubscriptionservicept1) Olen osoittanut teille, miten olen luonut uuden Newsletterin tilaussivun. Tässä kohdassa kerron, kuinka olen rakentanut uudelleen ratkaisun, jotta voimme jakaa palveluja ja malleja verkkosivustohankkeen M SK2Mostlylucid) ja aikataulupalveluhankkeen kanssa.

[TOC]

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

# Hankkeet

Alun perin minulla oli vain yksi, monolittinen hanke, johon sisältyi kaikki verkkosivustoa koskevat koodit. Tämä on kohtuullinen lähestymistapa pienille sovelluksilleM SK2 se antaa yksinkertaisen navigointimenetelmän , ratkaisun rakentaminen ja käyttöönottoM Sk4 kaikki nämä ovat järkeviä näkökohtiaM sk5 Jos kuitenkin laajennette ratkaisua, haluatte ryhtyä jakamaan hankkeenne erottamiseen, jotta huolenaiheet voidaan erottaa toisistaan ja jotta testit olisivat helpompiaM S K 7 navigointi M K 8 suuria bitejä sisältävä hanke voi olla hankala navigoidaM KS 9
Lisäksi scheduler-palvelun jakaminen on järkevää minulle, koska voin käyttää sitä erillisenä dockerin konttina, jonka avulla voin päivittää verkkosivustoa aiheuttamatta schedulerin uudelleenkäynnistämistä.

Tätä varten ryhmitin huolenaiheet loogisesti ”5” -hankkeisiin“.” -lähestymistapaan ASP:ssa”.“NET- sovelluksissa”

Voin nyt lisätä kullekin näistä testaushankkeista ja testata ne erikseen. Tämä on suuri etu, koska se antaa minulle mahdollisuuden testata palveluja ilman, että tarvitsee syventää koko sovellusta

## Useimminselvä

Tämä hanke on ASP.NET Core | (8) | Web-hanke

## Useimmin selväsanainen.DbContext

Siihen sisältyy pääasiallinen konteksti määritelmä, jota käytetään vuorovaikutukseen tietokannan kanssa. EF Core DbContextin mukaan lukien

## Useimmin selvennetyt.palvelut

Tämä on pääluokkainen kirjastohanke, johon sisältyy kaikki tietokannan kanssa vuorovaikuttavat palvelut / Markdown-tiedostot / sähköpostipalvelut jne.

## Useimmin selvää.Shared

Tämä on luokkakirjaston hanke, joka sisältää kaikki yhteiset mallit, joita käytetään sekä Mostlylucid- että Mostly Lucid-hankkeissa.

## Eniten selvennetty.SchedulerService

Tämä on web-sovellus, joka valvoo Hangfire-palvelua, joka hoitaa aikataulussa olevat tehtävät sekä loppukohdat sähköpostien lähettämiseksi.

Sen rakenne on jäljempänä.
Voidaan nähdä, että SchedulerService ja pää Mostlylucid käyttävät vain DbContext-luokkaa alkuperäiseen asennukseen.

```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]

```

Kuten tavallista, käytän laajentumismenetelmää tietokannan perustamista varten..

```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);
        });
    }
}
```

Tämän menetelmän kutsumisen lisäksi kummallakaan huipputasolla olevalla hankkeella ei ole mitään riippuvuutta DbContext-hankkeesta.

PANEE MERKILLE: On tärkeää välttää kilpailutilanne DbContextin initialisoinnin yhteydessä M SK2erityisesti, jos teette muuttotoimia) tehdä tämä yksinkertaisesti täällä.Lisäsin vain riippuvuuden Docker Compose -tietokantaan varmistaakseni, että tärkein Mostlylucid-hanke on otettu käyttöön ja käynnissä ennen schedulerService-ni käynnistämistä.

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

Voitte tehdä tämän myös muilla tavoilla, esimerkiksi menneisyydessä, kun käynnistettiin useat saman appin instansit, joita olen käyttänyt [Redis](https://redis.io/) varmistaa, että vain yksi instance voi toimia aikanaan

Tietysti Redisillä on myös monia muita käyttötarkoituksia; erityisesti ASP:n kanssaM SK1NETin 8' uuden `IDistributedCache` liittymä, jonka avulla Redis voidaan käyttää Cache-toimittajana, jolla on merkintäpohjainen päättyminen.

# Hangfire-järjestelmä

Päätin käyttää Hangfirea aikatauluni hoitamiseen, koska sillä on helppo integroida ASP.NET Core ja helppo käyttää

Tätä varten lisäsin SchedulerService-hankkeessa seuraavat NuGet-paketit.

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

Tämä tarkoittaa, että voin käyttää Postgres-palvelua varastona aikataulutehtävilleni.

Nyt minulla on Hangfire-seetup, jonka avulla voin alkaa lisätä suunniteltuja työtehtäviäni.

```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 * * *");
    }
}
```

Kuten näette, minulla on työ jokaiselle. `SubscriptionType` joka on lippu, jolla määritellään, kuinka usein ilmoitus lähetetään. käytän `Cron` luokka, jossa määritetään työn taajuus.

Koska minulla on sekä Monthly että Weekly -ohjelmien päivämäärää koskeva vaihtoehto, asetan ajankohdan 17:00 (5pmM SK2, koska nyt on hyvä aika lähettää lehtitiedotteita SSK3 päivän päätteeksi Yhdistyneessä kuningaskunnassa ja aloittaa Yhdysvalloissa
Minulla on myös työ, joka jatkuu joka tunti ja jonka tarkoituksena on lähettää newsletteri niille, jotka ovat allekirjoittaneet jokaisen postin.

Tämä kutsuu sitten `SendNewsletter` menetelmä `NewsletterSendingService` luokka. Mitkä ovat ne, joista aion puhua yksityiskohtaisemmin myöhemmin

# Dtos ja kartoitus

Käytän [DTO:t](https://en.wikipedia.org/wiki/Data_transfer_object) liittää tietoja soveltamiseni eri kerroksiin. Tämä ei lisää monimutkaisuutta, koska minun on kartoitava ( usein kahdestiM SK2 yksiköt Dtosiin ja sitten Dtos ViewModelseihin ( ja takaisinMSC4 Olen kuitenkin sitä mieltä, että tämä huolenaiheiden erottaminen kannattaa, sillä se antaa minulle mahdollisuuden muuttaa taustalla olevaa tietorakennetta vaikuttamatta UI:hen

Voitte käyttää AutoMapperin kaltaisia lähestymistapoja / Mapster jne. tämän kartoituksen tekemiseksiM SK1 Näiden käyttämisellä voi olla myös merkittäviä suoritusetuja .AsNoTracking() kysymyksille, koska voitte kartoittaa suoraan Dto-alueelle ja välttää jäljitettävien muutosten ylimääräiset kustannuksetMSC4 Automapperilla on esimerkiksi [Pyydyttävä laajentaminen](https://docs.automapper.org/en/stable/Queryable-Extensions.html) menetelmä, jonka avulla voidaan kartoittaa suoraan Dto-alueelle kysymyksessä.

Tässä tapauksessa päätin kuitenkin vain lisätä mapper-laajennuksia siellä, missä tarvittiin.. Tämä antaa minulle enemmän mahdollisuutta valvoa mappausta kullakin tasolla.

```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>()
        };
    }
}
```

Tässä voitte nähdä, että kartoitan `BlogPostEntity` pääomaani `BlogPostDto` , joka on minun siirron kohteeni.  
Tavoitteena on, että etupäässä olevat palvelut eivät tiedä mitään Entity-objektista ja että ne ovat 'ja jätetty ulkopuolelle

Näissä korkeimman tason palveluissa minulla on sitten koodi, jonka avulla nämä Dtos-järjestelmät voidaan kartoittaa ViewModeleihin, joita käytetään sitten valvojoissa.

```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
        };
    }
```

Jälleen kerran, käyttämällä mapper-välinettä voi välttää tämän boilerplate-koodin ( ja vähentää virheitäM SK2, mutta minusta tämä lähestymistapa toimii hyvin minulle tässä tapauksessa .

Nyt minulla on kaikki nämä mallit järjestetty voin alkaa lisätä valvontamenetelmiä käyttää niitä.
I' Käsittelen tätä virtaa seuraavassa osassa.

# Päätelmissä

Meillä on nyt järjestelmä, jonka avulla voimme alkaa tarjota newsletterin tilaajaa käyttäjillemme.
Tämä muuttaminen oli hieman tuskallista, mutta se on todellisuutta useimmissa hankkeissa.. Start simple and add architectural complication when required
Tulevissa työpaikoissa käsitellään jäljellä olevaa , -järjestelmää, mukaan lukien jatkotoimet, jotka koskevat minun käyttämistäni [FluentMail] lähettää sähköpostit ja Hosted Service-palvelun...ja ehkä hieman enemmän Hangfire.