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
Час із сервісу подписки на newsletter 2 - Переробка сервісів МSK2 і маленьке полум 'яття МSK3
Monday, 23 September 2024
Введение
В частина 1 цієї серії Я показала вам, як я створила нову сторінку Abonnування Newsletter. В цій частині яM SK1 розповім про те, як реструктуризувала рішення, щоб дозволити ділитися послугами та моделями між проектом Веб-сайту (Mostlylucid) і проектом Служби Планувальника МSK4MostslylucideMNK5SchedulerServiceMSC6
Проекти
Спочатку у мене був лише один монолітний проект, який містив весь код для веб-сайту. Це пристойний підхід для менших застосувань , він дає вам просту навігацію МSK3 побудувати та розмістити рішення M SK4 усі з яких є обґрунтованими consideraціями . Однак, коли ви масштабуєте рішення, ви' захочете почати розділяти ваш проект, щоб дозволити ізоляцію проблем і спрощувати тестування MSC7 навігування MSSK8 великий проект з великою кількістю бітів може бути складним для навігуї MСК9 Крім того, для мене це має сенс: я можу розмістити це як окремий контейнер для докерів, що дозволяє мені оновлювати вебсайт, не спричиняючи запуску програми.
Для цього я логічно склав ці проблеми в групи: 5 проекти . Це загальноприйнятий підхід у ASP
Тепер я можу додати тестові проекти для кожного з них і протестувати їх в ізоляції. Це велика перевага, тому що це дозволяє мені протестувати послуги без потреби запускати всю програмуM SK1
Найяскравіший
Цей проект - ASP.NET Core | МСК1 | веб-проект | , | він зберігає всі контролери |& | Просмотри, щоб відображати сторінки для користувача
Найяскравіший.DbКонтекст
Це є моїм основним контекстним визначенням для взаємодії з базою даних. Включно з ядром EF DbContext.
Найяскравіші.Сервиси
Це головна класна бібліотека, яка має всі послуги, які взаємодіють з базою даних. / Файлі відмітки / Услуги електронної пошти і т.д.
Найяскравіший.Shared
Це класний проект бібліотеки, який містить всі спільні моделі, які використовуються як Mostlylucid, так і Mostly Lucid.SchedulerService проекти.
Найяскравіший.Сервис планувальника
Це веб-приложение, яке контролює службу Hangfire, яка виконує заплановані завдання, а також кінцеві точки, які відповідають за відправлення електронних повідомлень.
Структура цього показана нижче. Ви бачите, що SchedulerService та головна Mostlylucid користуються лише класом DbContext для початкового налаштування.
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]
Як зазвичай, я використовую метод розширення як вхідну точку для створення бази даних.
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);
});
}
}
Крім того, щоб викликати цей метод, ни один з проектів верхнього рівня не має жодних залежності від проекту DbContext.
NOTA: Для того, щоб уникнути умов гонки при запуску DbContext M SK2 особливо, якщо ви виконуєте міграції ) зробити це просто тут Я тільки-но додав залежність в файлі Docker Compose для того, аби переконатися, що головний Mostlylucid проект працює перед запуском мого Сервісу Скидальників . Це додається до визначення мого Serвісу Зкидаліків у докері MSC5 файла скидальних M SK6
depends_on:
- mostlylucid
healthcheck:
test: ["CMD", "curl", "-f", "http://mostlylucid:80/healthy"]
interval: 30s
timeout: 10s
retries: 5
Ви також можете зробити це іншими способами, наприклад, в минулому під час запуску декількох instanцій тієї ж програми, яку я використав Рідіс встановити значок замика, який перевіряє кожен экземпляр перед запуском міграції. / інші завдання, які гарантують, що один экземпляр може працювати одночасно.
Звичайно, Redis також має багато інших застосувань; особливо з ASP.NET M SK2с новими IDistributedCache інтерфейс, який дозволяє використовувати Redis як постачальникキャッシュу, що базується на значку.
Установлення вогню
Я вирішила використати Hangfire, щоб керувати моєю схемою, тому що він має удобну інтеграцію з ASP.NET Core і є легким для використання
Для цього в проекті SchedulerService я додав następujące пакети NuGet.
dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.PostgreSql
Це означає, що я можу використовувати Postgres як мій магазин для запланованих завдань.
Тепер у мене є налаштування Hangfire, я можу почати додавати свої заплановані роботи.
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 * * *");
}
}
Ось ви бачите, у мене є робота для кожного SubscriptionType що є прапорцем, який визначає, як часто посилається newsletter. Я користуюсь Cron клас для набору частоти завдання.
Оскільки у мене є вибір на день для щомісяця та щотижневого abonamentу, я встановлю час на 17:00 (5pmM SK2 тому що це хороший час для відправлення повідомлень МSK3 закінчити день у Великобританії і розпочати в США МSK4 Я також маю роботу, яка працює щогодини, щоб надсилати бюллетень тим, хто підписувався на кожен лист.
Це потім викликає SendNewsletter метод в NewsletterSendingService клас. Який яM SK1 розповім детальніше пізніше
Дтоси та картографія
Я користуюсь ДПМ для передачі даних між шарами моєї додатки. Це НЕ додає складності, тому що мені потрібно відображати ( часто двічі МSK2 Ентіти до Dtos, а потім Dtos до ViewModels МSK3 і назад M SK4 Однак, Я вважаю, що це відокремлення занепокоєння варте того, адже воно дозволяє мені змінити базову структуру даних, не вплинувши на інтерфейс
Ви можете використати такі підходи, як AutoMapper / Mapster і т.д. для цього мапування . Використовуючи ці підходи також можете мати значні переваги в результатах для МSK2AsNoTrackingM SK3 запитів, тому що ви можете мапувати напряму до Dto і уникнути накладних витрат на зміни відслідковування МSK4 Наприклад, Automapper має Поширення, яка доступна для пошуку метод, який дозволяє відображати напряму на карті Dto у запиті.
Однак в цьому випадку я вирішив лише додати розширення картографа там, де мені потрібно.. Це дозволяє мені мати більше контролю над картографією для кожного рівня. M SK1 але це більше роботи. , особливо, якщо ви маєте багато об 'єктів.
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>()
};
}
}
Тут ви бачите, як я роблю карту BlogPostEntity до мого головного BlogPostDto який є моїм предметом передачі.
Мета полягає в тому, щоб фронтальні служби не знали нічого про об 'єкт Entity і були відокремлені від базової структури даних
В цих сервісах верхнього рівня я маю код, щоб відобразити ці Dtos у ViewModels, які потім будуть використані в контроллерах.
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
};
}
Знову ж таки, використовуючи Mapper, можна уникнути цього коду котла ( і зменшити помилки ), але я вважаю, що цей підхід працює добре для мене в цьому випадку
Тепер у мене є всі ці моделі, я можу почати додавати методи контроллера, щоб їх використовувати. I' Я покрию цей поток у наступній частині .
На завершення
Тепер у нас є структура, яка дає нам змогу почати надавати підписки на newsletter нашим користувачам. Це рефакторування було трохи боляче, але це - правда для більшості проектів. Наступні пости охоплять решту цього МСК0 , включно з подальшим розвитком мого використання [FluentMail] надіслати електронні листи та домашню службу...и, можливо, трохи більше Hangfire.