Lewati ke konten utama

Startup dan Pipa (Pipeline)

Pendahuluan

Dokumen ini memberikan panduan detail tentang urutan startup aplikasi, registrasi layanan, konfigurasi pipa middleware, dan mekanisme pemeriksaan kesehatan dependensi. Memahami alur ini sangat penting untuk memperluas aplikasi atau men-debug masalah startup.


Alur Bootstrap Program.cs

File Program.cs mengikuti urutan inisialisasi yang diatur secara ketat. Urutannya sangat penting: validasi konfigurasi harus selesai sebelum registrasi layanan, dan pemeriksaan kesehatan harus lulus sebelum migrasi.

Urutan Inisialisasi

  1. Konfigurasi Logging: Membuat pembangun (builder) dan menginisialisasi Serilog untuk menangkap semua peristiwa startup berikutnya.
  2. Validasi Konfigurasi: Memvalidasi semua kunci konfigurasi yang diperlukan. Gagal segera jika ada yang hilang.
  3. Pemeriksaan Keamanan: Mengeluarkan peringatan untuk konfigurasi yang tidak aman (misalnya, rahasia JWT yang pendek).
  4. Log Pengaturan: Mengeluarkan pengaturan migrasi otomatis saat ini untuk debugging.
  5. Daftarkan Layanan: Mengikat opsi, mengonfigurasi database, menghubungkan ke Redis, dan mendaftarkan semua layanan DI.
  6. Bangun Aplikasi: Mengompilasi koleksi layanan ke dalam instansi WebApplication.
  7. Pemeriksaan Kesehatan Dependensi: Melakukan ping ke PostgreSQL, Redis, InfluxDB, dan OpenSearch. Gagal segera jika tidak terjangkau.
  8. Terapkan Migrasi: Secara otomatis menerapkan migrasi EF Core yang tertunda (jika diaktifkan).
  9. Konfigurasi Pipa: Menyiapkan middleware (CORS, Rate Limiting, Auth) dan memulai server.

Implementasi

// 1. Konfigurasi logging
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext());

// 2. Validasi konfigurasi
var validatedConfigGroups = builder.Configuration.ValidateRequiredConfiguration();
Log.Information("Validasi konfigurasi lulus untuk grup: {ConfigGroups}",
string.Join(", ", validatedConfigGroups));

// 3. Pemeriksaan keamanan
var jwtSecretLength = builder.Configuration[BackendConfigurationKeys.JwtSecretKey]?.Length ?? 0;
if (jwtSecretLength < 32)
{
Log.Warning("Panjang JwtSettings:Secret adalah {SecretLength}. Gunakan setidaknya 32 karakter.",
jwtSecretLength);
}

// 4. Log pengaturan
Log.Information("Pengaturan migrasi otomatis: AutoMigrate={AutoMigrate}, MaxRetries={MaxRetries}...",
builder.Configuration.GetValue(BackendConfigurationKeys.AutoMigrateKey, true), ...);

// 5. Daftarkan layanan
await builder.Services.AddBackendServicesAsync(builder.Configuration);

// 6. Bangun aplikasi
var app = builder.Build();

// 7. Pemeriksaan kesehatan dependensi
await app.RunStartupDependencyHealthChecksAsync();

// 8. Terapkan migrasi
await app.ApplyDatabaseMigrationsAsync();

// 9. Konfigurasi pipa
app.UseBackendPipeline();
await app.RunAsync();

Registrasi Layanan — AddBackendServicesAsync

Metode ServiceCollectionExtensions.AddBackendServicesAsync mendaftarkan semua dependensi aplikasi dalam satu panggilan. Metode ini bersifat async karena secara aktif menghubungkan ke Redis.

Urutan Registrasi

  1. Ikat opsi ber-tipe dari bagian konfigurasi
    • PostgresqlSettingsOptions, RedisSettingsOptions, JwtSettingsOptions
    • InfluxDbOptions, OpenSearchOptions, AnsibleSettingsOptions
    • SensorApiSettingsOptions, DataCollectorOptions, DockerRegistryOptions
    • SensorSettingsOptions, DatabaseOptions
    • BackendAppOptions, SensorRuntimeOptions
  2. Daftarkan Controller dengan opsi serializer JSON
    • PropertyNameCaseInsensitive = true
  3. Daftarkan EndpointsApiExplorer
  4. Daftarkan Swagger dengan definisi keamanan JWT Bearer
  5. Daftarkan layanan gRPC
  6. Daftarkan pembatas laju (rate limiters)
    • auth-login: jendela tetap, 5 permintaan/menit
    • auth-register: jendela tetap, 3 permintaan/menit
  7. Konfigurasi PostgreSQL DbContext
    • Membangun string koneksi dari opsi ber-tipe
    • services.AddDbContext<ApplicationDbContext>(UseNpgsql)
  8. Hubungkan ke Redis (aktif, asinkron)
    • Membangun string koneksi dari opsi ber-tipe
    • ConnectionMultiplexer.ConnectAsync(...)
    • Daftarkan IConnectionMultiplexer sebagai singleton
    • Daftarkan IRedisService (implementasi RedisService) sebagai singleton
  9. Daftarkan layanan singleton
    • JwtService, InfluxDbService, OpenSearchService, ResilientHttpService
  10. Daftarkan repositori scoped
    • EfRepository<T> (basis generik)
    • PermissionRepository, RoleRepository, OrganizationRepository
    • UserRepository, UserRoleRepository, OrganizationUserRoleRepository
    • SensorRepository, SensorHeartbeatRepository, VirtualSensorRepository
    • AssetRepository
  11. Daftarkan layanan scoped
    • PermissionService, RoleService, LocationService, AnalyticsService
    • OrganizationService, UserService, AuthService
    • VirtualSensorsAnalyticsService, SensorHeartbeatService
    • SensorInterfaceAllocationService, SensorProvisioningScriptService
    • SensorActivationService, SensorsService, AssetService
    • VirtualSensorsService, OpenSearchAnalyticsService
  12. Daftarkan pabrik HttpClient
  13. Konfigurasi autentikasi JWT Bearer
    • ValidateIssuer, ValidateAudience, ValidateLifetime = true
    • ClockSkew = TimeSpan.Zero
    • OnTokenValidated: periksa blacklist token Redis
  14. Konfigurasi kebijakan otorisasi
    • "manage-users", "manage-sensors", "manage-assets", "manage-roles", "edit-organization"
  15. Daftarkan kebijakan CORS

Kelas Opsi Ber-tipe (Typed Options)

Semua bagian konfigurasi diikat ke kelas opsi ber-tipe kuat yang didefinisikan dalam BackendConfiguration.cs:

Kelas OpsiBagian KonfigurasiProperti Kunci
PostgresqlSettingsOptionsPostgresqlSettingsHost, Port, Username, Password, Database, MaxPoolSize
RedisSettingsOptionsRedisSettingsHost, Port, Password, DefaultDatabase
JwtSettingsOptionsJwtSettingsSecret, Issuer, Audience, ExpiryMinutes
InfluxDbOptionsInfluxDbUrl, Token, Org, Bucket
OpenSearchOptionsOpenSearchUrl, Username, Password, IndexName
AnsibleSettingsOptionsAnsibleSettingsServiceUrl
SensorApiSettingsOptionsSensorApiSettingsApiKey
DataCollectorOptionsDataCollectorEndpoint, Port
DockerRegistryOptionsDockerRegistryRegistry, Username, Password
SensorSettingsOptionsSensorSettingsHeartbeatTimeoutMinutes, ProvisioningSudoPassword
DatabaseOptionsDatabaseAutoMigrate, MigrationMaxRetries, MigrationRetryDelaySeconds
OpenSearchAnalyticsOptionsOpenSearchAnalyticsDefaultTimeoutSeconds, DashboardTimeoutSeconds, AggregationTimeoutSeconds, ListQueryTimeoutSeconds
AnalyticsCacheWarmingOptionsAnalyticsCacheWarmingEnabled, IntervalMinutes, InitialDelaySeconds
BackendAppOptions(komposit)Url
SensorRuntimeOptions(komposit)Menggabungkan beberapa bagian untuk operasi sensor

Pipa Middleware — UseBackendPipeline

Metode WebApplicationExtensions.UseBackendPipeline mengonfigurasi pipa pemrosesan permintaan:

public static WebApplication UseBackendPipeline(this WebApplication app)
{
app.UseSerilogRequestLogging(); // 1. Log semua permintaan HTTP
app.UseGlobalExceptionHandling(); // 2. Penangan pengecualian global
app.UseSwagger(); // 3. Sajikan spek OpenAPI
app.UseSwaggerUI(c => // 4. UI Swagger di /swagger
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ravenxcope Backend API v1");
c.DocExpansion(DocExpansion.None);
});
app.UseCors(); // 5. Terapkan kebijakan CORS
app.UseHttpsRedirection(); // 6. Alihkan HTTP → HTTPS
app.UseRateLimiter(); // 7. Terapkan kebijakan pembatasan laju
app.UseAuthentication(); // 8. Validasi token JWT
app.UseAuthorization(); // 9. Penegakan kebijakan
app.MapControllers(); // 10. Petakan controller berbasis atribut
app.MapGrpcService<SensorHealthcheckService>(); // 11. Petakan layanan gRPC
return app;
}

Urutan Pipa: Urutan middleware sangat penting. UseGlobalExceptionHandling harus diletakkan di awal untuk menangkap semua pengecualian hilir. UseRateLimiter harus diletakkan sebelum UseAuthentication agar pembatasan laju diterapkan sebelum validasi token. UseAuthentication harus diletakkan sebelum UseAuthorization, dan keduanya harus diletakkan sebelum MapControllers agar atribut auth berfungsi.


Validasi Konfigurasi

Metode ConfigurationValidationExtensions.ValidateRequiredConfiguration memvalidasi semua kunci yang diperlukan saat startup:

Kunci yang Divalidasi

Bagian KonfigurasiKunci yang Diperlukan
PostgresqlSettingsHost, Port, Username, Password, Database
RedisSettingsHost, Port
JwtSettingsSecret
InfluxDbUrl, Token, Org, Bucket
OpenSearchUrl, Username, Password, IndexName
AnsibleSettingsServiceUrl
SensorApiSettingsApiKey
SensorSettingsProvisioningSudoPassword
BackendUrl(kunci tingkat root)
DataCollectorEndpoint, Port

Aturan Validasi

  1. Nilai hilang atau kosong → melempar InvalidOperationException
  2. Nilai placeholder → pola kurung kurawal ganda {{...}} dideteksi dan dianggap hilang
  3. String placeholder yang dikenal"your-sensor-api-key-here" dianggap hilang
  4. Validasi port → Port PostgreSQL, Redis, dan DataCollector harus berupa integer positif
  5. Pengaturan migrasi → MigrationMaxRetries dan MigrationRetryDelaySeconds harus berupa integer positif

Pemeriksaan Kesehatan Dependensi

Metode StartupDependencyHealthChecksExtensions.RunStartupDependencyHealthChecksAsync memverifikasi semua dependensi eksternal sebelum melayani lalu lintas.

Detail Pemeriksaan

DependensiMetode PemeriksaanJumlah RetryBatas Waktu
PostgreSQLdbContext.Database.CanConnectAsync()310 detik
Redisredis.GetDatabase().PingAsync()310 detik
InfluxDBHTTP GET {url}/health310 detik
OpenSearchHTTP GET {url}/ dengan Basic Auth310 detik

Perilaku Retry

Pemeriksaan menggunakan HealthCheckHelper.RunHealthCheckWithRetry<T>:

public static async Task<bool> RunHealthCheckWithRetry<T>(
IServiceProvider serviceProvider,
ILogger logger,
Func<T, CancellationToken, Task> checkFunc,
string serviceName,
int maxRetries = 3,
int timeoutSeconds = 10)
  • Setiap percobaan memiliki CancellationTokenSource dengan batas waktu yang dikonfigurasi
  • Setelah batas waktu atau pengecualian, sistem menunggu 1 detik sebelum mencoba lagi
  • Jika semua percobaan gagal, sistem mencatat kesalahan dan mengembalikan false
  • Jika ada satu dependensi yang gagal, startup akan melempar InvalidOperationException

Penanganan Khusus OpenSearch

Pemeriksaan kesehatan OpenSearch menggunakan DangerousAcceptAnyServerCertificateValidator untuk melewati validasi sertifikat SSL, dan mengautentikasi dengan kredensial Basic Auth dari konfigurasi.


Migrasi Database

Metode DatabaseMigrationExtensions.ApplyDatabaseMigrationsAsync menangani migrasi skema database otomatis:

Alur Migrasi

  1. Periksa Konfigurasi: Baca Database:AutoMigrate dari opsi (default: true)
  2. Evaluasi: Jika false, lewati migrasi sepenuhnya.
  3. Loop Eksekusi: Untuk setiap percobaan (hingga MigrationMaxRetries):
    • Buat ApplicationDbContext scoped
    • Panggil dbContext.Database.MigrateAsync()
    • Jika berhasil: Log keberhasilan migrasi dan hentikan loop.
    • Jika gagal: Log peringatan, tunggu selama RetryDelaySeconds, lalu coba lagi.
  4. Evaluasi Akhir: Jika semua percobaan gagal, catat kesalahan kritis dan lempar pengecualian untuk menghentikan startup.

Konfigurasi Migrasi

KunciTipeDefaultDeskripsi
Database:AutoMigratebooltrueAktifkan/nonaktifkan migrasi otomatis
Database:MigrationMaxRetriesint10Jumlah maksimal percobaan ulang
Database:MigrationRetryDelaySecondsint5Penundaan antar percobaan ulang