Files
jdescopingtool/openspec/changes/archive/2026-01-01-setup-solution-foundation/design.md
T
Joseph Doherty 26ff8d9b4f Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
2026-01-02 07:43:29 -05:00

295 lines
8.5 KiB
Markdown

# Solution Foundation Design
## Overview
This document describes the infrastructure architecture for the .NET 10 solution, including project structure, dependency injection patterns, and configuration management.
## Project Structure
```
NEW/src/
├── JdeScoping.Core/ # Domain models, interfaces, shared logic
│ ├── Models/ # Entity classes (Search, WorkOrder, Lot, etc.)
│ ├── Interfaces/ # Service contracts (ISearchRepository, etc.)
│ ├── Options/ # Configuration binding classes
│ └── Extensions/ # Service registration extension methods
├── JdeScoping.Host/ # ASP.NET Core host (Web API + BackgroundServices)
│ ├── Program.cs # Application entry point, DI configuration
│ ├── appsettings.json # Production configuration
│ ├── appsettings.Development.json # Development overrides
│ └── Controllers/ # API endpoints
├── JdeScoping.Client/ # Blazor WebAssembly UI
│ └── (deferred to UI phase)
└── JdeScoping.Database/ # DbUp migrations (already exists)
├── Scripts/ # Migration SQL files
└── DatabaseMigrator.cs # DbUp configuration
```
## DI Registration Pattern
### Extension Method Convention
Each module provides an extension method on `IServiceCollection`:
```csharp
public static class DataAccessServiceExtensions
{
public static IServiceCollection AddDataAccess(
this IServiceCollection services,
IConfiguration configuration)
{
// Bind configuration
services.Configure<DataAccessOptions>(
configuration.GetSection(DataAccessOptions.SectionName));
// Register services
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
services.AddScoped<IJdeRepository, JdeRepository>();
services.AddScoped<ICmsRepository, CmsRepository>();
return services;
}
}
```
### Module Registration Order
Extensions are called in dependency order in Program.cs:
```csharp
builder.Services
.AddDataAccess(builder.Configuration) // 1. Database access
.AddDataSync(builder.Configuration) // 2. Cache synchronization
.AddSearchProcessing(builder.Configuration) // 3. Search execution
.AddExcelExport(builder.Configuration) // 4. Result export
.AddAuth(builder.Configuration); // 5. Authentication
```
### Lifetime Guidelines
| Service Type | Lifetime | Rationale |
|--------------|----------|-----------|
| Repository | Scoped | Database connection per request |
| DbContext (if used) | Scoped | EF Core default |
| Options classes | Singleton | Cached configuration |
| HttpClient | Singleton | Connection pooling |
| BackgroundService | Singleton | Long-running workers |
| Processors | Transient | Stateless operations |
## Configuration Sections
### appsettings.json Structure
```json
{
"ConnectionStrings": {
"LotFinder": "Server=...;Database=LotFinder;...",
"JDE": "Data Source=...;User ID=...;Password=...",
"CMS": "Data Source=...;Port=...;Database=..."
},
"DataAccess": {
"CommandTimeoutSeconds": 120,
"EnableDetailedLogging": false
},
"DataSync": {
"MassRefreshCronSchedule": "0 0 6 * * SAT",
"DailyRefreshCronSchedule": "0 0 4 * * *",
"HourlyRefreshCronSchedule": "0 0 * * * *",
"MaxConcurrentUpdates": 4
},
"Auth": {
"LdapUrl": "LDAP://directory.company.com",
"LdapGroup": "CN=LotFinderUsers,OU=Groups,DC=company,DC=com",
"CookieExpirationMinutes": 480
},
"ExcelExport": {
"TempDirectory": "/tmp/lotfinder",
"MaxRowsPerSheet": 1048576,
"DefaultDateFormat": "yyyy-MM-dd HH:mm:ss"
},
"SearchProcessing": {
"PollingIntervalSeconds": 5,
"MaxConcurrentSearches": 2,
"SearchTimeoutMinutes": 30
}
}
```
### appsettings.Development.json Overrides
```json
{
"ConnectionStrings": {
"LotFinder": "Server=localhost,1434;Database=LotFinder;User Id=scopingapp;Password=...;TrustServerCertificate=True"
},
"DataAccess": {
"EnableDetailedLogging": true
},
"DataSync": {
"MassRefreshCronSchedule": "",
"DailyRefreshCronSchedule": "",
"HourlyRefreshCronSchedule": ""
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
}
}
```
## Options Classes
### Naming Convention
- Class name: `{Module}Options`
- Section name: Same as class name without "Options" suffix
- Static constant: `SectionName` for configuration binding
### DataAccessOptions
```csharp
public class DataAccessOptions
{
public const string SectionName = "DataAccess";
public int CommandTimeoutSeconds { get; set; } = 120;
public bool EnableDetailedLogging { get; set; } = false;
}
```
### DataSyncOptions
```csharp
public class DataSyncOptions
{
public const string SectionName = "DataSync";
public string MassRefreshCronSchedule { get; set; } = "0 0 6 * * SAT";
public string DailyRefreshCronSchedule { get; set; } = "0 0 4 * * *";
public string HourlyRefreshCronSchedule { get; set; } = "0 0 * * * *";
public int MaxConcurrentUpdates { get; set; } = 4;
}
```
### AuthOptions
```csharp
public class AuthOptions
{
public const string SectionName = "Auth";
public string LdapUrl { get; set; } = string.Empty;
public string LdapGroup { get; set; } = string.Empty;
public int CookieExpirationMinutes { get; set; } = 480;
}
```
### ExcelExportOptions
```csharp
public class ExcelExportOptions
{
public const string SectionName = "ExcelExport";
public string TempDirectory { get; set; } = "/tmp/lotfinder";
public int MaxRowsPerSheet { get; set; } = 1048576;
public string DefaultDateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
}
```
### SearchProcessingOptions
```csharp
public class SearchProcessingOptions
{
public const string SectionName = "SearchProcessing";
public int PollingIntervalSeconds { get; set; } = 5;
public int MaxConcurrentSearches { get; set; } = 2;
public int SearchTimeoutMinutes { get; set; } = 30;
}
```
## NuGet Package Dependencies
### JdeScoping.Core
```xml
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
```
### JdeScoping.Host
```xml
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="EPPlus" Version="7.0.0" />
<PackageReference Include="DbUp-SqlServer" Version="5.0.37" />
<PackageReference Include="Quartz" Version="3.8.0" />
```
## Program.cs Structure
```csharp
var builder = WebApplication.CreateBuilder(args);
// Database migrations
DatabaseMigrator.Migrate(builder.Configuration.GetConnectionString("LotFinder")!);
// Module registration
builder.Services
.AddDataAccess(builder.Configuration)
.AddDataSync(builder.Configuration)
.AddSearchProcessing(builder.Configuration)
.AddExcelExport(builder.Configuration)
.AddAuth(builder.Configuration);
// ASP.NET Core services
builder.Services.AddControllers();
builder.Services.AddSignalR();
var app = builder.Build();
// Middleware pipeline
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub<StatusHub>("/statushub");
app.Run();
```
## Validation Approach
### Startup Validation
Validate critical services are registered at startup:
```csharp
// In Program.cs after building
using var scope = app.Services.CreateScope();
_ = scope.ServiceProvider.GetRequiredService<ILotFinderRepository>();
_ = scope.ServiceProvider.GetRequiredService<IOptions<DataAccessOptions>>();
// ... validate other critical services
```
### Configuration Validation
Use DataAnnotations or IValidateOptions for configuration:
```csharp
public class DataAccessOptionsValidator : IValidateOptions<DataAccessOptions>
{
public ValidateOptionsResult Validate(string? name, DataAccessOptions options)
{
if (options.CommandTimeoutSeconds <= 0)
return ValidateOptionsResult.Fail("CommandTimeoutSeconds must be positive");
return ValidateOptionsResult.Success;
}
}
```