claude-meta/memory/projects/kbsV3/kbsv3-architecture.md

126 lines
6.1 KiB
Markdown

# KbsV3 Architecture Patterns
## 1. Job System (IKnownJob → Worker)
### Creating a new Job (full checklist)
1. **JobType enum**`GrpcProtos/GrpcServices/Contracts/JobType.cs` — add `[StringValue("X")] X`
2. **IKnownJob**`GrpcProtos/GrpcServices/KnownJobs/MyJob.cs` — metadata class
- `ServiceName` format: `"modulename.WorkerClassName"` (e.g. `"sqlsync.UpdateCalcPriceService"`)
- Module names: `helpdesk`, `sqlsync`, `ad`, `azure`, `docuware`, `sap`, `scheduler`
3. **Worker**`Kbs3XxxService/Services/MyWorker.cs` — implements `ILongRunningJobService`
- Constructor: `ProtocolLogger`, `IApiComposer`, plus custom DI
- `JobType` property returns `GetJobType()` (static method using `JobType.X.GetStringValue()`)
- `Run(JobDataRequest, CancellationToken)` → business logic
- `GetHeadline()` → section title
4. **Auto-registration**`IntegrationServiceBase.AutoInjectServices()` discovers workers automatically
5. **No changes needed** in Program.cs of the microservice
### Worker capabilities
- `ProtocolLogger.LogHeadline/LogInfo/LogWarn/LogError` — streams to UI in real-time
- `apiComposer` — callback to server (see section 3)
- `request.InputData` — string input (usually username or JSON payload)
### IKnownJob with Payload
- `Payload` property returns a typed object (serialized as InputData)
- Example: `UpdateCalculationPriceDatabase.cs` returns `UpdateCalcPriceRequest`
## 2. SAP Data Access (RFC Calls)
### Where RFC calls live
- **Kbs3SapService/SapServices/** — e.g. `OrderService.cs`, `FiService.cs`, `TableService.cs`
- Uses SAP NCo3: `RfcDestinationManager.GetDestination(systemName)``repos.CreateFunction("Z_FUNC")``fnc.Invoke(dest)`
- `IRfcFunction` does NOT implement IDisposable
### RFC call pattern
```csharp
var dest = RfcDestinationManager.GetDestination(request.SystemName);
var repos = dest.Repository;
var fnc = repos.CreateFunction("Z_RFC_NAME");
fnc.SetValue("PARAM", value); // importing params
fnc.Invoke(dest);
var itResult = fnc.GetTable("ET_TABLE"); // exporting table
for (var i = 0; i < itResult.RowCount; i++) {
itResult.CurrentIndex = i;
var val = itResult.GetString("FIELD");
}
```
### Extension methods (Kbs3SapService/Extensions/)
- `DateTime.ToSapDate()` → SAP date string
- `itResult.GetDateTimeOrNull("FIELD")` → nullable DateTime
- `itResult.GetDecimal("FIELD")`, `GetBoolean("FIELD")`, etc.
### gRPC service wrapper
- `Kbs3SapService/GrpcServices/SapOrderService.cs` — thin proxy implementing `ISapOrderService`
- Delegates to the actual `OrderService` (SapServices layer)
## 3. gRPC Communication Between Modules
### Contract interfaces
- **Location:** `GrpcProtos/GrpcServices/Contracts/SapService/` (e.g. `ISapOrderService.cs`)
- Attributes: `[ServiceContract]`, `[OperationContract]`
- DTOs: `[DataContract]` + `[DataMember(Order = N)]`
### DTO conventions
- Namespace: `GrpcProtos.GrpcServices.Model` (data classes) or `Contracts/SapService/` (request/response)
- Collections: `Type[] prop = []`
- Nullable refs: `string?`, `DateTime?`
- Sequential `[DataMember(Order = N)]` starting at 1
- `BapiMessage[]` for error handling
### Server → Microservice (gRPC Client)
- **Location:** `Kbs3ServerService/GrpcClients/` (e.g. `SapOrderServiceClient.cs`)
- Inherits `GrpcClientBase<ISapOrderService>`
- Constructor: `(GrpcChannel, ServiceRegistry, IServiceScopeFactory)`
- Methods: `return Client?.MethodName(request);`
- **NOT registered in DI** — resolved via `ServiceRegistry.GetClient<T>()` (async)
- To use in a service: inject `ServiceRegistry`, then `var client = await serviceRegistry.GetClient<SapListServiceClient>()`
- Never inject a `*Client` class directly via constructor DI — it will throw at startup
### Microservice → Server (IApiComposer callback)
- **Interface:** `GrpcProtos/GrpcServices/Contracts/Server/IApiComposer.cs`
- **Implementation:** in Kbs3ServerService (ApiComposer class)
- Workers use `apiComposer.GetUser()`, `apiComposer.GetCalcPriceInfos()`, etc.
- To add new callback: add method to IApiComposer → implement in server ApiComposer
## 4. Database Read/Write
### Server-side (EF Core)
- **DbContext:** `Kbs3ServerService.Repository/Contexts/JobDbContext.cs` (inherits `ThreadSafeDbContext`)
- **Entities:** `Kbs3ServerService.Repository/Entities/`
- **Base classes:** `AuditableEntity` (manual audit) or `KbEntity` (auto SQL defaults via GETUTCDATE())
- **Global decimal precision:** 18,4
- **Configurations:** `IEntityTypeConfiguration<T>` via `ApplyAllConfigurations()`
- **Service layer:** `Kbs3ServerService.Service/Services/` — inject `JobDbContext`, use EF Core CRUD
### Migrations
```bash
dotnet ef migrations add Name --project Kbs3ServerService.Repository --startup-project Kbs3ServerService
dotnet ef database update -- Development --project Kbs3ServerService.Repository --startup-project Kbs3ServerService
```
### Microservice-side (Raw ADO.NET) — Kbs3SqlService pattern
- `IOptions<DatabaseOptions>` → connection strings for multiple DBs
- `ConnectionStringKbs` = main KBS app DB
- `ConnectionStringSapCalcData` = external SAP calc DB
- Uses `SqlConnection` + `SqlCommand` with parameterized queries
- Pattern: `new SqlConnection(connectionString)``OpenAsync()``ExecuteNonQueryAsync()``CloseAsync()`
- Example: `UpdateCalcPriceService.cs` — fetches via `apiComposer`, writes via raw SQL
### Key difference
| Location | Technology | Use case |
|----------|-----------|----------|
| Kbs3ServerService | EF Core (JobDbContext) | Main app CRUD, entities, migrations |
| Kbs3SqlService | Raw ADO.NET (SqlConnection) | Bulk sync to external/app DBs from workers |
| Kbs3SapService | None | No direct DB access — SAP only |
## 5. Service Registration
### Server services
- Interface in `Contract/`, implementation in `Services/`
- `builder.Services.AddScoped<IFoo, Foo>()` in `Program.cs`
### Microservice workers
- Auto-discovered by `IntegrationServiceBase.AutoInjectServices()`
- Keyed service: `services.AddKeyedScoped<ILongRunningJobService>(jobType, workerType)`