claude-meta/memory/projects/kbsV3/job-system.md

91 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# KbsV3 Job System How It Works
## Job Lifecycle (end-to-end)
1. **Job created** via UI (`/adsyncjob`), REST controller, or Kbs3SchedulerService
- Sets `JobDto.JobType`, `InputData` (JSON string), `JobSubtype`, `CreatedBy`
- Persisted to DB via `IJobDataService.AddJob()`
2. **Job picked up** background poller in Kbs3ServerService finds pending jobs
3. **JobStartService.StartJob(job)** resolves `JobRequirementFactoryService`, checks `CanStart()`
4. **gRPC call** server calls microservice via `IJobSubscribeService.Subscribe(JobDataRequest)`
- `JobDataRequest.InputData` = the JSON string from step 1
5. **Worker execution** microservice's `ILongRunningJobService.Run(request, ct)` or `IKbsJob` host
6. **Streaming** protocol updates stream back via gRPC `IAsyncEnumerable<JobProtocolUpdate>`
7. **Persistence** server saves `Protocol`/`ProtocolLine` entities
## Creating a Job Programmatically
### From UI page (direct DB)
```csharp
job.JobType = JobType.DeleteInactiveUsers;
job.InputData = "{\"DryRun\":true}"; // raw JSON string
job.JobSubtype = JobSubtypes.SyncZadGroup;
await JobService.AddJob(job); // IJobDataService
```
### From microservice / scheduler (via gRPC IApiComposer)
```csharp
await apiComposer.CreateKnownJob(new CreateJobRequest
{
Jobtype = JobType.DeleteInactiveUsers.GetStringValue(), // "DeleteInactiveUsers"
PayLoad = "{\"DryRun\":false}", // string → JobDto.InputData as-is
Priority = 5
});
```
`PayLoad` (string) is stored directly as `JobDto.InputData` in `CreateAndEnqueueJob` (no extra serialization).
No `IKnownJob` definition required for this approach.
## Multi-Service Jobs (e.g. DeleteInactiveUsers)
- Multiple microservices (AD, Azure, DW, Helpdesk, SQL) each register a worker for the same `JobType`
- All fire automatically when one job with that `JobType` is created server broadcasts to all registered services
- No special routing needed; just one `CreateKnownJob` call triggers all
## ILongRunningJobService Workers
- Dedicated `JobType` enum entry (e.g. `JobType.DeleteInactiveUsers`)
- Implement `Run(JobDataRequest request, CancellationToken ct)`
- Read input: `JsonConvert.DeserializeObject<T>(request.InputData)`
- Auto-registered as keyed service by `JobType` string value in `IntegrationServiceBase.AutoInjectServices()`
- Log via `protocolLogger.LogHeadline / LogInfo / LogWarn / LogError`
## IKbsJob Workers (Kbs3SqlService pattern)
- All share `JobType.CallServiceHost`
- Auto-registered by class name (e.g. `"SyncOpexPurchaseOrdersService"`)
- `IKnownJob.ServiceName` = `"modulename.ClassName"` → host resolves last segment to find worker
- Do NOT add new `JobType` enum entries for these
## Kbs3SchedulerService
### Adding a new scheduled job
1. Create `MyJob : BaseJob` in `Kbs3SchedulerService/Jobs/`
- Constructor: `(ILogger<MyJob> logger, IApiComposer apiComposer) : base(logger)`
- Implement `override string Name` and `override async Task Execute(CancellationToken, KeyValue[] settings)`
- Auto-registered via `AddAllTypesOf<IJob>()` no manual DI
2. Add entry to `appsettings.Development.json` (IsEnabled: false) and `appsettings.Production.json` (IsEnabled: true)
under `SchedulerSettings.Items`
### Schedule patterns
| Type | Meaning | Key fields |
|------|---------|------------|
| 1 | Daily | `Interval`, `StartTime` |
| 2 | Weekly | `Interval`, `DaysOfWeek: ["Monday","Friday",...]` |
| 3 | Monthly | `DaysOfMonth` |
| 5 | Interval | `CustomInterval: "HH:mm:ss"` |
- `StartTime` on the root item = time of day for execution
- `Settings` array → read in `Execute` via `settings.FirstOrDefault(s => s.Key == "Foo")?.Value`
### Internal flow
`SchedulerHostedService``SeriesSchedulerManager``SeriesJobScheduler` (infinite loop)
`_schedule.GetNextExecution(DateTime.Now)``Task.Delay(...)``job.ExecuteAsync(...)`
## InputData / Payload flow
```
CreateJobRequest.PayLoad (string)
→ ApiComposerService.CreateKnownJob()
→ CreateJobService.CreateAndEnqueueJob()
→ if (payload is string) input = payload as string ← no extra serialization!
→ JobDto.InputData
→ JobDataRequest.InputData (sent to microservice via gRPC)
→ worker reads request.InputData
```