Back to "Newsletterin tilauspalvelun osa 2 - Palvelujen uudistaminen ( ja pieni Hangfire)"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Email Newsletter Hangfire

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

Monday, 23 September 2024

Esitys

Siihen osa 1 tästä sarjasta 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.

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.

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..

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ä.

  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 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.

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.

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 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 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.

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.

    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.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.