# 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` - Constructor: `(GrpcChannel, ServiceRegistry, IServiceScopeFactory)` - Methods: `return Client?.MethodName(request);` - **NOT registered in DI** — resolved via `ServiceRegistry.GetClient()` (async) - To use in a service: inject `ServiceRegistry`, then `var client = await serviceRegistry.GetClient()` - 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` 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` → 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()` in `Program.cs` ### Microservice workers - Auto-discovered by `IntegrationServiceBase.AutoInjectServices()` - Keyed service: `services.AddKeyedScoped(jobType, workerType)`