[.Net Core WebApi] วิธีการตั้งเวลาให้ function หรือ jobs ทำงาน ด้วย Hangfire แบบ Step By Step
คุณเคยพบปัญหาเหล่านี้ไหมครับ อยากให้ function ของเรา ทำงานทุกๆ เที่ยงคืน อยากให้ function ของเรา ทำงานทุกๆชั่วโมง อยากให้ function ของเรา ทำงานตามเวลาที่เราต้องการ
จริงๆแล้ว มันก็มีหลายวิธีที่ทำได้ เช่น เขียน Script ตามภาษาที่ถนัด มา 1 ตัว และเขียน CronJob ให้ Run ตามเวลาที่เรากำหนด
หรือทำ API มาซักเส้นสำหรับ Function ที่เราอยากให้ Run แล้วสั่งให้ Jenkins Call API ตัวนั้น ตาม เวลาที่เราตั้งไว้ใน Jenkins ก็ได้อีกเหมือนกัน
แต่วันนี้ผมจะเสนออีก 1 วิธี ที่ทำได้ง่ายๆ ทำได้ใน Project ของตัว .Net Core WebApi ของเราโดยไม่ต้องเขียน Script แยกออกมาให้วุ่นวาย แถมยังมี Dashboard สวยๆ ให้เราได้ดูว่า function หรือ Job ของเราทำงาน สำเร็จ(success) หรือ ไม่สำเร็จ(fail) อีกด้วย
มาเริ่มต้นกันเลย
Step 0) HangFire คือ อะไร
HangFire คือ Open Source Framework ตัวนึงของ .Net และ .Net Core ที่มาคอยช่วยเราจัดการ Background Process Job ต่างๆของเรา ให้ง่ายขึ้น โดยมี Feature หลักในการจัดการ Job อยู่ 6 ตัวด้วยกัน
- Fire And Forget
- Delayed
- Recurring
- Continuations
- Batches
- Batch Continuations
เดี๋ยว Feature เหล่านี้จะไปอธิบายวิธีใช้อีกทีด่านล่าง
โดยใน Framework ได้มี Build-in Dashboard สวยๆ ให้เรา Monitoring Job ต่างๆที่เราสร้างขึ้น ว่า Process มันทำไหม สำเร็จหรือไม่ สามารถเขียนให้มันแจ้งเตือนเราเมื่อมันทำไม่สำเร็จก็ย่อมได้
และ Hangfire ยังรองรับการเก็บ Job ในฐานข้อมูลหลายเจ้า เพื่อให้ Make Sure ว่าเมื่อต่อให้ Restart Project .Net ของเราไปแล้ว Job ก็ยังคงทำงานได้อยู่เหมือนเดิม
และอีกหนึ่งจุดพีค เราไม่จำเป็นจะต้องไปสร้าง Windows Scheduler หรือใช้ Windows Service ใด ด้วยครับ
Step 1) สร้าง Project .Net Core WebApi ง่ายๆ มาซัก 1 ตัว แบบนี้
dotnet new webapi -o hangfireDemo
เมื่อ run คำสั่งด้านบนแล้วจะได้ผลลัพธ์แบบนี้
Step 2) เลือก Editor ที่ชอบแล้วเปิดโครงสร้าง Source Code ของโปรเจคขึ้นมาจะได้หน้าตาแบบนี้
Step 3) ทีนี้เรามาลองทดสอบกันก่อนว่า Project .Net Core WebApi ที่พึ่งสร้างไปเมื่อกี้นั้น ทำงานได้ ทดสอบดังนี้
เปิด Terminal แล้วเข้ามาให้อยู่ ภายใต้ Directory ชื่อ hangfireDemo แล้วพิมพ์ Command ดังนี้
dotnet run
ผลลัพธ์ที่ได้ควรจะเป็นแบบนี้ดังภาพ
Step 4) ทดลองยิง API เพื่อทดสอบว่ามันทำงานได้จริงนะ เปิด Browser ขึ้นมา แล้วใส่ URL :
[สำหรับ .Net Core 3]
URL : https://localhost:5001/WeatherForecast/[สำหรับ .Net Core 2]
URL : https://localhost:5001/api/Values/
หลังจากเปิดเข้าไปใน URL แล้วจะได้ Response เป็น JSON กลับมา ค่าของการ Response อาจจะไม่เหมือนกัน ตาม Version ของ .Net Core แต่โดยรวม ถ้ามีอะไร Response มา แปลว่า ใช้งานได้สมบูรณ์แล้ว
Step 5) มาแล้วครับ ตรงนี้หละเราจะเริ่ม ติดตั้ง Package Hangfire กันละ โดยเริ่มจาก ใช้คำสั่งนี้ใน Terminal ภายใต้ Directory hangFireDemo ในการติดตั้ง
dotnet add package HangFire --version 1.7.10
เมื่อ Run คำสั่งนี้ ผลลัพธ์ที่ได้จะเป็นแบบนี้
เพื่อตรวจสอบว่าติดตั้งเรียบร้อยแล้วจริงๆ ให้เข้าไปดูที่ไฟล์ hangfireDemo.csproj
จะเห็นว่ามีบรรทัดนี้ เพิ่มเข้ามาในไฟล์ hangfireDemo.csproj
Step 6) เปิดไฟล์ชื่อ Startup.cs ขึ้นมา จะเห็น Source Code ลักษณะดังนี้
Step 6.1) สิ่งที่เราต้องทำถัดมาก็คือ เพิ่ม Source Code ไม่เกิน 6 บรรทัดตามผมดังนี้
[เพื่ม Source Code ด้านล่างนี้ใน Method : ConfigureServies ไฟล์ Startup.cs]services.AddHangfire(config => { config.UseSqlServerStorage("Server=tcp:localhost,1433;Database=HangfireDemo;User Id=sa;Password=p@ssw0rd;"); }); ---> (1)services.AddHangfireServer(); ---> (2)
อธิบาย Source Code :
(1) : ช่วงที่ 1 นั้น HangFire ต้อง ConnectionString เพื่อใช้เก็บในฐานข้อมูล Job ต่างๆที่เราต้องการจะ Runโดยใน Example ที่ผมจัดทำนี้เราจะใช้ฐานข้อมูล SQLServer ไม่ใช่ MySQL นะครับ แต่เป็น SQLServer
จริงๆแล้ว HangFire สามารถใช้ฐานข้อมูลอื่นได้ เช่น Redis ก็ทำได้เช่นกัน หรือ SQL Azure ก็ได้
เพื่อให้เพื่อนๆใช้งาน Source Code ในตัวอย่างนี้ได้ทันที ผมได้เตรียม Command ในการ Run Docker มาให้ ถ้าเพื่อนๆมี Docker อยู่แล้ว Copy คำสั่งนี้ไปแปะได้เลย
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=p@ssw0rd' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latestเมื่อ image run แล้วอย่าลืมไปสร้าง Dabatabase : HangfireDemo ในฐานข้อมูลด้วยนะครับ
(2) : HangFire จะ Run Server เล็กๆขึ้นมาเอาไว้จัดการคิวของ Function ที่เราตั้งไว้ หรือ Job ที่เราตั้งไว้ให้ทำงานได้
ต่อมาครับเพิ่ม Code อีกเล็กน้อยตามภาพ
ต่อมาเพิ่ม อีก 1 บรรทัดใน Method : Configure
[อธิบาย Souce Code ด้านล่างนี้ใน Method : Configure]public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}IBackgroundJobClient backgroundJobs คือ การ Inject BackgroundJob ของ HangFire มาใช้งานbackgroundJobs.Enqueue คือ การ Execute Job แรกที่เราต้องการให้ทำงานทันทีหลังจาก Run .Net Core ขึ้นมา ณ ที่นี้ถ้าพวกเราทำสำเร็จ ใน Log จะพ่น คำว่า "Hello world from Hangfire!" ออกมา
สรุปหน้าตา Source Code โดยรวมหลังจากปรับแก้แล้ว จะได้ประมาณนี้
[หน้าตา Source Code ในไฟล์ Startup.cs]using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;namespace hangfireDemo {
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}public IConfiguration Configuration {
get;
}// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddControllers(); services.AddHangfire(config => {
config.UseSqlServerStorage("Server=tcp:localhost,1433;Database=HangfireDemo;User Id=sa;Password=p@ssw0rd;");
});
services.AddHangfireServer();
}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
}
}
Step 7) สั่ง dotnet run อีกครั้ง ถ้าได้หน้าตาแบบนี้ แปลว่า HangFire คุณพร้อมใช้งานแล้ว
ถ้ามีคำว่า Hello world from Hangfire! ขึ้นมาใน Console แปลว่า HireFire คุณพร้อมใช้งานแล้วครับ
Step 8) ลองไปดู Database HangFireDemo ที่เราสร้างไว้เมื่อกี้ก็จะมี Data เกิดขึ้นมาแล้ว
Step 9) ทีนี้เราลองมาทำความรู้จักอีกซัก 2 3 คำสั่งในการ ตั้งเวลาให้ Function หรือ Method ทำงาน กันอีกซักเล็กน้อย
9.1) Fire-and-forget jobs
คำสั่งนี้สิ่งที่มันทำก็คือ มันจะทำการ Run Function ที่เราต้องการ Trigger ทันทีเลย และจะไม่กลับมาทำอีกครั้งด้วย ทำทีเดียวเลิกแล้วต่อกันเลย และคำสั่งนี้เราก็ได้ใช้ไปในตัวอย่างเรียบร้อย
var jobId = BackgroundJob.Enqueue(
() => Console.WriteLine("Fire-and-forget!"));
9.2) Delayed jobs
คำสั่งนี้ จะทำให้เราสามารถ Trigger Function หรือ Method ของเรา หลังจากนี้อีก 7 วันข้างหน้า หรือ 3 วันข้างหน้า แล้วแต่เรากำหนด
ยกตัวอย่างเช่น ระบบเราทำการแจกคูปองให้ลูกค้า และถ้าลูกค้าไม่ใช้ภายใน 7 วัน ระบบจะทำการ Call Function ไป Update ฐานข้อมูลว่า คูปองของลูกค้าใบนี้ หมดอายุแล้วนะ
var jobId = BackgroundJob.Schedule(
() => Console.WriteLine("Delayed!"),
TimeSpan.FromDays(7));
9.3) Recurring jobs
อันนี้ทีเด็ดเลย คำสั่งนี้จะทำให้เรา ทำ Function หรือ Method ที่เราจะทำการ Trigger ทุกๆ นาที หรือ ทุกๆ ชั่วโมง หรือ ทุกๆเที่ยงคืนของทุกวัน ได้
RecurringJob.AddOrUpdate(
() => Console.WriteLine("Recurring!"),
Cron.Daily); // Daily, Minutely, Hourly, Weekly
9.4) Continuations
คำสั่งนี้เป็นคำสั่งสำหรับ เมื่อ Job ที่เราเคยสั่งไว้ไม่ว่าจะเป็น Delayjob หรือ RecurringJob พวกนี้ทำงานเสร็จหมดแล้ว เราจะให้มันไป Run Function อะไรต่อ
เช่น หลังจาก คูปองลูกค้าหมด อายุ ให้ไปทำ Update Generate Report รอไว้ให้หัวหน้าดูนะ อะไรทำนองนี้ โดยอ้างอิงจาก JobId
BackgroundJob.ContinueJobWith(
jobId,
() => Console.WriteLine("Continuation!"));
Step 10) มาลองใช้ Recurring jobs กันดูอีกนิด
สมมติว่าเราต้องการให้ Function เรา Run ทุก 1 นาที เลยนะ เราจะต้องทำแบบนี้มาดูกันครับ
Step 10.1) เปิดไฟล์ Startup.cs แล้วเพิ่ม Code ตรงนี้ลงไปที่ Method: Configure
[เพิ่ม Code ตรงนี้ในไฟล์ Startup.cs หลัง backgroundJobs.Enqueue]RecurringJob.AddOrUpdate(
() => Console.WriteLine($ "-- ทดสอบ Recurring ทุกๆ 1 นาที --"),
Cron.Minutely);
เมื่อเติม Code แล้ว จะได้แบบดังภาพ แบบนี้
Step 10.2) ลอง สั่ง dotnet run ใหม่อีกครั้งแล้วสังเกตผลลัพธ์
สรุปเอาเป็นว่า บทความนี้ ลองเอาไปใช้กันก่อนนะครับเพื่อเป็นประโยชน์ต่อเพื่อนๆ ในการหาวิธีตั้งเวลา Trigger Method ต่างๆ ด้วย .Net Core WebApi
Step 11) หลังจากที่เราสามารถตั้งเวลาใน Job ของเราได้แล้ว ถัดมาจะเป็นการเรียกใช้งาน Dashboard ของ HangFire เพื่อให้เราดู Log การทำงานของ Job เราได้ง่ายขึ้น มีวิธีการดังนี้
[เพิ่ม 1 บรรทัดด่านล่างในไฟล์ Startup.cs ภายใน Method : Configure()]app.UseHangfireDashboard();
พอเพิ่มแล้วก็จะได้ไฟล์ Startup.cs หน้าตาประมาณนี้
หลังจากนั้นลอง สั่ง dotnet run อีกครั้ง แล้วเข้าไปที่ URL นี้
https://localhost:5001/hangfire/
เราก็จะได้หน้าตา Dashboard สวยๆ เอาไว้ดู Log ของ Job ต่างๆที่เราตั้งเวลา หรือ กำหนด Delay ไว้แล้วแบบนี้
จบแล้วครับ ไว้บทความหน้าผมจะมาอธิบายวิธีการใช้งาน Dashboard ของ Hangfire และ วิธีการทำ Authentication เพื่อให้เมื่อเรา Deploy Hangfire ขึ้น Production แล้วเราไม่อยากให้ใครเข้ามาดู Dashboard ของเราตรงๆ ต้องทำอย่างไร
ลองเอาไปเล่นดูนะครับแล้วเจอกันใหม่บทความหน้า