通讯订阅服务部分 @2_#@-}重新描述服务内容 @MS K2}和点燃的一点
一. 导言导言 导言 导言 导言
内 本系列的 1部分 我向大家展示了我如何创建新的通讯订阅页面 . 。 在这部分, I' 会介绍我是如何调整解决方案的,以便分享网站项目“(”和调度服务项目“(”之间的服务和模型共享。
项目
最初我只有一个包含网站所有代码的“,”单一语言项目。 这是用于较小应用程序的一个体面的方法 @,} 它使您能够轻松地浏览, 构建和部署解决方案@; 所有这些都是合理的考虑. 然而当你扩大一个解决方案时,你'将想要开始拆开您的工程以孤立关注事项并允许更容易测试\MS K7 导航\ (Big 项目与许多位元可能难以浏览). 此外,拆分调度器服务对于我来说是有道理的,因为我可以将它作为一个单独的 docker 容器来部署,让我能够更新网站,而不会让排程器重新启动@. @
为了做到这一点,我按逻辑将关注问题归为“5”项目 .}这是ASP=.}NET应用程序中的一种常见做法
现在,我可以为其中每一个添加测试项目 并单独进行测试..."."这是一个很大的优势 它允许我测试服务
最美味的
此工程是一个 ASP.NET 核心 &(8)Web Project < ,} 它持有所有控制器{&View> 以显示用户的页面@.}
最优lucid.}DbContext
包括EF Core DbContext@.}。
多数服务
这是主类图书馆项目, 保存所有与数据库 {/} Markdown 文件 {/} email service et .} 互动的服务 。
最美味的 . 分享
这是一个班级图书馆项目, 持有Mostlylucid 和 Mostlylucid.+Scheduler Service项目使用的所有共享模型@.}
Mostlylucid.Scheduler Service
这是一个网络应用程序, 用于控制运行指定任务的“ 挂牌火警服务” 以及处理邮件发送端点@ . @
此结构的结构显示于下@. @% 您可以看到,调度服务以及主要最精密的服务器在初始设置时只使用 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 项目有任何依赖性@.
在初始化 DbContext 时, 它对于避免种族条件很重要, 尤其是如果您在此运行 Migrations@ ) 仅此而已, 我刚刚在我的 Docker Cropomee 文件中添加了一个依赖性, 以确保主要的 Mostlylucid 项目在开始我的调度服务前已经启动并正在运行 @ MS K4\ 。 这被加入到 Docker *-compositice 文件中的我的调度服务的服务定义中 =.\ }
depends_on:
- mostlylucid
healthcheck:
test: ["CMD", "curl", "-f", "http://mostlylucid:80/healthy"]
interval: 30s
timeout: 10s
retries: 5
您也可以以其他方式完成此任务@ , @% 例如过去, 当运行同一应用程序 I' @ ve 的多个实例时 重新重订 以设置一个锁定标记, 每个实例在运行迁移前先检查该标记@ @ / 其它任务确保只有一个例子可以在时间上运行@ MS K1 _
当然 Redis 还有其他许多用途@;, IDistributedCache 允许您使用 Redis 作为基于标签过期的缓存提供者@ . @
防火挂机装置
我选择使用Hangfire来处理我的日程安排,因为它方便地与 ASP.NET Core 连接,而且很容易使用@.
为此,我在调度服务项目中添加了以下Nuget 软件包@.
dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.PostgreSql
这意味着我可以用Postgres做我的商店 来完成预定的任务
现在我有挂火渗漏,我可以开始 增加我的预定工作了
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 用来决定通讯多久发一次的旗帜 Cron 类以设定任务频率@. @%
由于我可以选择月刊和周刊的订费, 我设定了时间给 @17:00@ (5_pm)}因为现在是时候在英国寄发每日末日通讯@(+% 我每小时都有一份工作,
然后,这等于 SendNewsletter 方法中 NewsletterSendingService @._}I'}在稍后的一篇文章中,
Dtos和绘图
我用的是 地区贸易机会组织 以在应用程序的层层之间传送数据@.}这个DOES增加了复杂性,因为我需要为 Dtos 绘制“(” 地图时, 我经常两次将实体 ) 映射到 dtos, 然后又把“Dtos” 映像“Z(” 和回溯“MS K4 ” 。 然而, “, ” 我认为这种关注分离是值得的,因为它允许我在不影响 UI @.的情况下改变基本的数据结构
您可以使用 AutoMapper {/} Master 等方法来进行此映射@ .}使用这些选项也可以为 @ MS K2_AsNoTracking}()}查询提供显著的性能优势, 您可以直接绘制到 Dto 并避免跟踪更改的间接操作@.}例如 Automapper 有 无法查询的扩展名 允许您直接映射到查询中的 Dto 的方法@ .}
但在此情况下, 我决定只要在需要的地方添加映像师扩展名, 这样我就可以对每个级别 的绘图有更多的控制了 @ ( @but is more work@,}(尤其是如果你有很多实体的话) @).#
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 是我的传输对象@. @
目的是让前端服务对实体对象有所了解,
在这些顶级服务中,我然后有代码来绘制这些 Dtos 用于查看模式的图形。 这些代码随后被控制器@.}使用
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 工具可以避免此锅炉牌代码 @ ( @ 并减少错误@ MS K2}( AgainMSMK0),
现在我已经设置了所有这些模型, 我可以开始添加控制器方法 来使用它们@. I'将覆盖下一部分的流量 .
在结论中的结论
我们现在有了一个结构 允许我们开始向用户提供通讯订阅服务 开始简单,在需要时添加建筑复杂因素. 包括跟进我使用 [流利邮件] 发送电子邮件和主机服务... ..."...,也许再多发一点 "Hangfire"