This document outlines the strategy for migrating the DDEC (Detroit Diesel Electronic Controls) application from Java/Spring Boot to ASP.NET Core MVC with Entity Framework, Repository Pattern, and Unit of Work Pattern.
| Metric | Count | Notes |
|---|---|---|
| Java Source Files | ~485 | Application code |
| JSP Files | 278 | Terminal emulation screens |
| QRE Framework Files | ~200+ | Proprietary mainframe emulation |
| Database Tables | 20+ | SQL Server (already in place) |
| REST Endpoints | 4 | Engineering + Admin APIs |
| DAO Classes | 26 | Data access layer |
| COBOL-Generated Models | 150+ | Legacy data structures |
The primary complexity comes from:
The legacy SFTP/batch architecture (used when mainframe was the data source) is no longer needed. Since all data now resides in SQL Server, calibration processing can be done in real-time via direct API calls, eliminating:
| Java/Spring Component | ASP.NET Core Equivalent |
|---|---|
| Spring Boot | ASP.NET Core (Minimal API or MVC) |
| Spring MVC | ASP.NET Core MVC |
| Spring Security | ASP.NET Core Identity + IdentityServer/Duende |
| Spring Data JPA | Entity Framework Core |
| Hibernate | Entity Framework Core |
| Spring Cache | IMemoryCache / IDistributedCache |
| Keycloak | IdentityServer4/Duende, Azure AD B2C, or Auth0 |
| JSP/JSTL | Razor Pages / Blazor |
| Maven | NuGet + MSBuild/dotnet CLI |
| Logback/SLF4J | Serilog / NLog / Microsoft.Extensions.Logging |
| JMS/ActiveMQ | Not needed - Direct database access replaces messaging |
| JSCH (SFTP) | Not needed - Direct API replaces file transfers |
| KSH Batch Scripts | Not needed - In-process or Hangfire background jobs |
| Current Pattern | Recommended .NET Pattern |
|---|---|
| Custom DAO classes | Repository Pattern with interfaces |
| Direct JDBC | Entity Framework Core DbContext |
| No explicit transactions | Unit of Work Pattern |
| Spring Cache | IMemoryCache with decorator pattern |
| IMS DLI calls | Entity Framework LINQ queries |
Since the mainframe is decommissioned and all data resides in SQL Server, we can eliminate the legacy SFTP/batch architecture entirely:
Before (Legacy):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DRS Client │────►│ DDEC Web │──SFTP──►│Batch Server│────►│ SQL Server │
│ │◄────│ │◄──SFTP──│ (KSH Jobs) │◄────│ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
After (Simplified):
┌─────────────┐ ┌─────────────────────────────────┐ ┌─────────────┐
│ DRS Client │────►│ DDEC Web API │────►│ SQL Server │
│ (C++ App) │◄────│ (Real-time processing) │◄────│ Database │
└─────────────┘ └─────────────────────────────────┘ └─────────────┘
│ │
│ ┌─────────────┴─────────────┐
│ │ Web Browser Users │
│ └───────────────────────────┘
│
Direct API calls - no SFTP, no polling, no batch server
┌─────────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ Razor Pages │ │ Web API │ │ Blazor Server │ │
│ │ (Admin/Forms) │ │ (REST/Client) │ │ (Interactive UI) │ │
│ └────────┬────────┘ └────────┬────────┘ └──────────┬──────────┘ │
└───────────┼─────────────────────┼─────────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Services / Use Cases │ │
│ │ CalibrationService, CustomerService, EngineeringService │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ DTOs / ViewModels │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ DOMAIN LAYER │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Domain Entities │ │
│ │ Calibration, Customer, EngineParameter, ComponentSegment │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Repository Interfaces │ │
│ │ ICalibrationRepository, ICustomerRepository, etc. │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Domain Services │ │
│ │ CalibrationValidator, CompatibilityChecker │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ ┌───────────────────┐ ┌───────────────────┐ ┌─────────────────┐ │
│ │ EF Core DbContext │ │ Repository │ │ Calibration │ │
│ │ + Configurations │ │ Implementations │ │ Processor │ │
│ └───────────────────┘ └───────────────────┘ └─────────────────┘ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌─────────────────┐ │
│ │ Unit of Work │ │ Compression │ │ Background │ │
│ │ Implementation │ │ Service │ │ Jobs (Optional)│ │
│ └───────────────────┘ └───────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
DDEC.Solution/
├── src/
│ ├── DDEC.Domain/ # Domain entities, interfaces
│ │ ├── Entities/
│ │ │ ├── Calibration.cs
│ │ │ ├── ComponentSegment.cs
│ │ │ ├── CompatibilitySegment.cs
│ │ │ ├── Customer.cs
│ │ │ └── EngineParameter.cs
│ │ ├── Interfaces/
│ │ │ ├── IRepository.cs
│ │ │ ├── IUnitOfWork.cs
│ │ │ └── ICalibrationRepository.cs
│ │ └── Services/
│ │ └── CalibrationValidator.cs
│ │
│ ├── DDEC.Application/ # Application services, DTOs
│ │ ├── Services/
│ │ │ ├── CalibrationService.cs
│ │ │ ├── EngineeringService.cs
│ │ │ └── CustomerService.cs
│ │ ├── DTOs/
│ │ │ ├── CalibrationDto.cs
│ │ │ └── DownloadRequestDto.cs
│ │ └── Interfaces/
│ │ └── ICalibrationService.cs
│ │
│ ├── DDEC.Infrastructure/ # Data access, services
│ │ ├── Data/
│ │ │ ├── DdecDbContext.cs
│ │ │ ├── Configurations/ # EF Fluent API configs
│ │ │ └── Migrations/
│ │ ├── Repositories/
│ │ │ ├── RepositoryBase.cs
│ │ │ ├── CalibrationRepository.cs
│ │ │ └── UnitOfWork.cs
│ │ └── Services/
│ │ ├── CalibrationProcessor.cs # Real-time calibration processing
│ │ ├── CalibrationSegmentBuilder.cs # Build calibration segments
│ │ ├── CompressionService.cs # ZIP/compression handling
│ │ └── EncryptionService.cs # Encryption/decryption
│ │
│ ├── DDEC.Web/ # ASP.NET Core Web Application
│ │ ├── Controllers/
│ │ │ ├── EngineeringController.cs
│ │ │ ├── AdminController.cs
│ │ │ └── CalibrationController.cs
│ │ ├── Pages/ # Razor Pages
│ │ │ ├── Calibration/
│ │ │ ├── Engineering/
│ │ │ └── Admin/
│ │ ├── Components/ # Blazor components (optional)
│ │ ├── wwwroot/
│ │ └── Program.cs
│ │
│ └── DDEC.API/ # REST API for C++ client
│ ├── Controllers/
│ │ └── EngineeringApiController.cs
│ └── Program.cs
│
├── tests/
│ ├── DDEC.Domain.Tests/
│ ├── DDEC.Application.Tests/
│ ├── DDEC.Infrastructure.Tests/
│ └── DDEC.Web.Tests/
│
└── DDEC.sln
// DDEC.Domain/Interfaces/IRepository.cs
public interface IRepository<TEntity> where TEntity : class
{
Task<TEntity?> GetByIdAsync(object id);
Task<IEnumerable<TEntity>> GetAllAsync();
Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate);
Task AddAsync(TEntity entity);
Task AddRangeAsync(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Remove(TEntity entity);
void RemoveRange(IEnumerable<TEntity> entities);
}
// DDEC.Domain/Interfaces/IUnitOfWork.cs
public interface IUnitOfWork : IDisposable
{
ICalibrationRepository Calibrations { get; }
ICustomerRepository Customers { get; }
IComponentSegmentRepository ComponentSegments { get; }
ICompatibilitySegmentRepository CompatibilitySegments { get; }
IEngineParameterRepository EngineParameters { get; }
Task<int> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitTransactionAsync();
Task RollbackTransactionAsync();
}
// DDEC.Infrastructure/Repositories/UnitOfWork.cs
public class UnitOfWork : IUnitOfWork
{
private readonly DdecDbContext _context;
private IDbContextTransaction? _transaction;
public UnitOfWork(DdecDbContext context)
{
_context = context;
Calibrations = new CalibrationRepository(_context);
Customers = new CustomerRepository(_context);
// ... other repositories
}
public ICalibrationRepository Calibrations { get; }
public ICustomerRepository Customers { get; }
// ... other repository properties
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
public async Task BeginTransactionAsync()
{
_transaction = await _context.Database.BeginTransactionAsync();
}
public async Task CommitTransactionAsync()
{
if (_transaction != null)
{
await _transaction.CommitAsync();
await _transaction.DisposeAsync();
_transaction = null;
}
}
public async Task RollbackTransactionAsync()
{
if (_transaction != null)
{
await _transaction.RollbackAsync();
await _transaction.DisposeAsync();
_transaction = null;
}
}
public void Dispose()
{
_transaction?.Dispose();
_context.Dispose();
}
}
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
| Duende IdentityServer | Full-featured, OIDC compliant, self-hosted | License cost for commercial | Best for enterprise |
| Azure AD B2C | Managed service, scalable | Cloud dependency, cost | Good for Azure shops |
| Auth0 | Easy setup, many features | SaaS cost, vendor lock-in | Good for quick start |
| Keycloak | Free, feature-rich | Requires maintenance | Keep existing |
| ASP.NET Core Identity | Built-in, simple | Basic features only | Good for internal apps |
Recommendation: Keep Keycloak if it works well, or migrate to Duende IdentityServer for .NET-native solution.
With the simplified architecture, most operations are real-time. Background jobs are only needed for scheduled tasks like VEPS updates.
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
| Hangfire | Dashboard, persistent, retries | SQL Server dependency | Best for scheduled jobs |
| In-Process | Simple, no dependencies | No persistence | Best for real-time |
| Worker Services | Built-in .NET, simple | Manual state management | Alternative |
Recommendation:
The legacy architecture required SFTP for file transfers to/from the batch server.
With the simplified architecture, SFTP is eliminated entirely:
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
| System.IO.Compression | Built-in, fast | No DCL Implode | Standard compression |
| SharpZipLib | Many formats | Older library | Alternative |
| Custom P/Invoke | Use existing XCompress | Complexity | If DCL required |
| Rewrite algorithm | Pure .NET | Development effort | Long-term solution |
Recommendation: For PKWARE DCL Implode compatibility, either P/Invoke the existing C library or port the algorithm to C#.
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
| IMemoryCache | Built-in, simple | Single server only | Dev/small scale |
| Redis | Distributed, fast | Additional infrastructure | Production |
| SQL Server Cache | Uses existing DB | Slower | Simple distributed |
| NCache | Enterprise features | License cost | Enterprise |
Recommendation: Start with IMemoryCache, upgrade to Redis for production clustering.
The entire QRE framework exists to emulate mainframe IMS behavior. With modern .NET:
| QRE Component | Modern Replacement |
|---|---|
| IMS Transaction Manager | Standard MVC Controllers |
| DLI Database Calls | Entity Framework LINQ |
| COBOL Data Types | Standard C# types |
| Terminal Emulation | Modern Web UI (Razor/Blazor) |
| Conversational Processing | Standard HTTP sessions |
Estimated Savings: ~22,000 lines of framework code eliminated.
Current: 150+ classes implementing CobolStructure with mainframe data type semantics.
Streamlined Approach:
// Before (Java/QRE)
public class Sdd2unit extends CobolStructure {
private PackedDecimal unitCode;
private FixedLengthString unitDescription;
// ... complex type handling
}
// After (C#/EF Core)
public class EngineUnit
{
public int UnitCode { get; set; }
public string UnitDescription { get; set; } = string.Empty;
// Standard C# types with EF Core mapping
}
Estimated Savings: Eliminate all COBOL type handling complexity.
Current: 278 JSP files emulating 3270 terminal screens (80x24 fixed layout).
Streamlined Options:
| Option | Effort | User Impact |
|---|---|---|
| Keep similar UI | Low | Minimal retraining |
| Modern responsive | Medium | Better UX, mobile support |
| Blazor components | Medium | Interactive, SPA-like |
Recommendation: Modernize gradually - start with Razor Pages that look similar, then enhance.
Current (Legacy):
Why this existed: When the mainframe held the data, batch jobs were the only way to extract and process calibration files.
Simplified Approach (Real-time API):
[HttpPost("calibration/download")]
public async Task<IActionResult> DownloadCalibration(CalibrationRequest request)
{
// 1. Query database directly (no SFTP needed)
var calibrationData = await _unitOfWork.Calibrations
.GetByNameAsync(request.CalibrationName, request.CalibrationType);
// 2. Build calibration segments in-memory
var segments = _calibrationBuilder.BuildSegments(calibrationData);
// 3. Compress (standard ZIP or legacy DCL if needed)
var compressed = _compressionService.Compress(segments);
// 4. Encrypt if required for C++ client compatibility
var encrypted = _encryptionService.Encrypt(compressed);
// 5. Return directly - single HTTP response
return File(encrypted, "application/zip",
$"calibration_{request.CalibrationName}.zip");
}
[HttpPost("calibration/release")]
public async Task<IActionResult> ReleaseCalibration(IFormFile calibrationFile)
{
// 1. Read uploaded file directly
using var stream = calibrationFile.OpenReadStream();
// 2. Decrypt and decompress
var decrypted = _encryptionService.Decrypt(stream);
var decompressed = _compressionService.Decompress(decrypted);
// 3. Parse and validate
var calibration = _calibrationParser.Parse(decompressed);
// 4. Save to database directly
await _unitOfWork.Calibrations.UpdateAsync(calibration);
await _unitOfWork.SaveChangesAsync();
// 5. Return success immediately
return Ok(new { Status = "OK", Message = "Calibration released" });
}
What Gets Eliminated:
| Component | Status |
|---|---|
| SFTP Server | ELIMINATED |
| Batch Server | ELIMINATED |
| KSH Shell Scripts | ELIMINATED |
| File-based polling | ELIMINATED |
| Temp file management | ELIMINATED |
| JSCH/SSH.NET library | NOT NEEDED |
| Request/response files | ELIMINATED |
Scheduled Jobs (still needed for maintenance):
// VEPS Update - runs daily, queries external source and updates DB
RecurringJob.AddOrUpdate<VepsUpdateJob>(
"veps-update",
job => job.ProcessVepsUpdatesAsync(),
"0 4 * * 1-5"); // 4 AM Mon-Fri
public class VepsUpdateJob
{
public async Task ProcessVepsUpdatesAsync()
{
// Query VEPS data source directly
var vepsData = await _vepsSource.GetLatestParametersAsync();
// Update database
await _calibrationService.UpdateVepsParametersAsync(vepsData);
}
}
Benefits:
Current: 26 DAO classes with 100+ methods in DdecBaseDao.
Streamlined with Repository Pattern:
// Generic repository handles most operations
public interface ICalibrationRepository : IRepository<Calibration>
{
Task<Calibration?> GetBySerialNumberAsync(string serialNumber);
Task<IEnumerable<Calibration>> GetByCustomerCodeAsync(string customerCode);
}
// Complex queries in repository implementation
public class CalibrationRepository : RepositoryBase<Calibration>, ICalibrationRepository
{
public async Task<Calibration?> GetBySerialNumberAsync(string serialNumber)
{
return await _context.Calibrations
.Include(c => c.Components)
.Include(c => c.History)
.FirstOrDefaultAsync(c => c.SerialNumber == serialNumber);
}
}
Risk Level: HIGH
The calibration file processing uses PKWARE DCL Implode algorithm via native C library.
Options:
Mitigation: Investigate if newer calibration files use standard compression. If not, P/Invoke initially.
Risk Level: MEDIUM-HIGH
COBOL has specific behaviors for:
Mitigation: Create utility classes for COBOL-to-.NET conversion. Test extensively with real data.
public static class CobolTypeConverter
{
// COBOL fixed-length string to C# string
public static string FromFixedLength(string cobolString, int length)
{
return cobolString.PadRight(length).TrimEnd();
}
// COBOL date (CYYDDD) to DateTime
public static DateTime FromCobolDate(string cyyddd)
{
int century = int.Parse(cyyddd[0..1]);
int year = int.Parse(cyyddd[1..3]);
int dayOfYear = int.Parse(cyyddd[3..6]);
int fullYear = (century == 1 ? 2000 : 1900) + year;
return new DateTime(fullYear, 1, 1).AddDays(dayOfYear - 1);
}
}
Risk Level: MEDIUM
The database has mainframe-style naming (MGAPROOT, CMPNTSEG, etc.) and VARCHAR for everything.
Options:
Recommendation: Keep existing schema, use EF Core mapping.
// EF Core configuration for legacy table
public class ComponentSegmentConfiguration : IEntityTypeConfiguration<ComponentSegment>
{
public void Configure(EntityTypeBuilder<ComponentSegment> builder)
{
builder.ToTable("CMPNTSEG");
builder.HasKey(c => new { c.RootKey, c.Lcp, c.Key, c.Priority });
builder.Property(c => c.RootKey)
.HasColumnName("CMPNTSEG_FK_MGAPROOT")
.HasMaxLength(10);
builder.Property(c => c.Key)
.HasColumnName("CMPNTSEG_KEY")
.HasMaxLength(10);
// ... other mappings
}
}
Risk Level: MEDIUM
IMS conversational transactions maintain state in Scratch Pad Area (SPA).
Mitigation: Use ASP.NET Core Session or TempData for equivalent functionality.
// Session-based conversation state
public class ConversationState
{
public string TransactionCode { get; set; }
public Dictionary<string, object> ScratchPad { get; set; } = new();
public int ConversationStep { get; set; }
}
// In controller
public async Task<IActionResult> ProcessTransaction(TransactionRequest request)
{
var state = HttpContext.Session.Get<ConversationState>("ConvState")
?? new ConversationState();
// Process based on conversation step
state.ConversationStep++;
HttpContext.Session.Set("ConvState", state);
return View(state);
}
Risk Level: MEDIUM
The existing C++ DRS client expects specific API behavior and data formats.
Mitigation:
// Maintain exact API compatibility
[ApiController]
[Route("engineering/ddec")]
public class EngineeringApiController : ControllerBase
{
// Same endpoint, same response format as Java version
[HttpPost("basecal/process")]
[Consumes("multipart/form-data")]
[Produces("application/json")]
public async Task<ActionResult<EngineeringBatchJobResponse>> ProcessCalibration(
[FromForm] DownloadCalibrationRequest request)
{
// Exact same response structure
return Ok(new EngineeringBatchJobResponse
{
Status = "OK",
Message = "Calibration processed",
ArchiveFileName = archiveFile,
JobParameter = request.CalibrationType
});
}
}
Multiple date formats exist (CYYDDD, YYYYMMDD, YYDDD).
Mitigation: Create DateTimeConverter utilities, use DateTimeOffset for storage.
Some data may still be EBCDIC or have mainframe-specific characters.
Mitigation: Use System.Text.Encoding for EBCDIC (Code Page 037) conversion.
public static class EbcdicConverter
{
private static readonly Encoding Ebcdic = Encoding.GetEncoding(37);
private static readonly Encoding Ascii = Encoding.ASCII;
public static string EbcdicToAscii(byte[] ebcdicBytes)
{
return Ebcdic.GetString(ebcdicBytes);
}
public static byte[] AsciiToEbcdic(string asciiString)
{
byte[] asciiBytes = Ascii.GetBytes(asciiString);
return Encoding.Convert(Ascii, Ebcdic, asciiBytes);
}
}
COBOL/IMS screens have specific field-level validation rules.
Mitigation: Implement as FluentValidation rules or Data Annotations.
SLF4J → Microsoft.Extensions.Logging is straightforward.
Spring profiles → appsettings.{Environment}.json is straightforward.
Goals: Set up .NET solution structure and core infrastructure.
Tasks:
Deliverables:
Goals: Migrate business logic and services.
Tasks:
Deliverables:
Goals: Implement real-time calibration processing (no SFTP/batch).
Tasks:
Deliverables:
Key Simplification: The entire SFTP/polling/batch architecture is replaced with direct database queries and in-memory processing returned via HTTP response.
Goals: Recreate web interface in Razor/Blazor.
Tasks:
Deliverables:
Goals: Ensure C++ client compatibility.
Tasks:
Deliverables:
Goals: Complete testing and production deployment.
Tasks:
Deliverables:
| Role | Count | Duration |
|---|---|---|
| Tech Lead / Architect | 1 | Full project |
| Senior .NET Developer | 2 | Full project |
| .NET Developer | 2 | Phases 2-6 |
| QA Engineer | 1 | Phases 2-6 |
| DevOps Engineer | 0.5 | Phases 1, 6 |
| DBA | 0.25 | As needed |
| Phase | Original Estimate | Simplified Estimate | Savings |
|---|---|---|---|
| Phase 1: Foundation | 16 | 16 | - |
| Phase 2: Core Business Logic | 30 | 28 | 2 |
| Phase 3: Engineering Module | 20 | 12 | 8 |
| Phase 4: Web Interface | 30 | 28 | 2 |
| Phase 5: API and Integration | 16 | 12 | 4 |
| Phase 6: Testing and Cutover | 20 | 16 | 4 |
| Total | 132 | 112 person-weeks | 20 |
Savings from Simplified Architecture:
Architecture: Eliminate SFTP/Batch - Use Real-Time APIs
UI Approach: Recommend Razor Pages with potential Blazor components
Background Jobs: Only for scheduled maintenance
Compression: Evaluate if DCL Implode is still needed
Testing Strategy: Invest heavily in integration tests
public class ComponentSegment
{
public string RootKey { get; set; } = string.Empty;
public string Lcp { get; set; } = string.Empty;
public string Key { get; set; } = string.Empty;
public string Priority { get; set; } = string.Empty;
public string? Type { get; set; }
public string? Status { get; set; }
public string? StatusAction { get; set; }
public string? StatusDate { get; set; }
public string? Code { get; set; }
public string? CodeAction { get; set; }
public string? CodeDate { get; set; }
public string? RestrictFlag { get; set; }
public string? RestrictDate { get; set; }
public string? RatingOverride { get; set; }
// Navigation properties
public virtual MgapRoot Root { get; set; } = null!;
public virtual ICollection<CompatibilitySegment> Compatibilities { get; set; }
= new List<CompatibilitySegment>();
}
public record DownloadCalibrationRequest(
string CalibrationName,
string CalibrationType,
IFormFile? HistoryFile
);
public record EngineeringBatchJobResponse(
string Status,
string Message,
string? DownloadFileName,
string? ArchiveFileName,
string? JobParameter
);
| Purpose | Current Location | Migration Notes |
|---|---|---|
| REST API | src/ddec-web/.../utils/EngineeringController.java |
Port to ASP.NET Core controller |
| Calibration Processing | src/ddec-web/.../engineering/V2DdecCalibrationSplitter.java |
Port parsing logic to C# service |
| Base DAO | src/ddec-web/.../dao/DdecBaseDao.java |
Replace with EF Core repositories |
| Configuration | src/ddec-web/.../configurations/DdecConfiguration.java |
Convert to appsettings.json |
| Encryption | src/ddec-web/.../engineering/util/EncodingUtil.java |
Port XOR algorithm to C# |
| Batch Job Builders | src/ddec-web/.../engineering/R24kd500shDownloadRequestBuilder.java |
Eliminate - replace with direct DB queries |
| SFTP Client | src/ddec-web/.../engineering/util/SftpClient.java |
Eliminate - no longer needed |
| Database Schema | database/schemas/DDEC Table Schema.sql |
Keep as-is, map with EF Core |
The KSH batch jobs contain business logic that needs to be ported to real-time services:
| Batch Job | Logic to Extract | New Location |
|---|---|---|
| KSH 500 (Download) | Calibration segment generation | CalibrationSegmentBuilder.cs |
| KSH 500 (Download) | Data formatting/encoding | CalibrationProcessor.cs |
| KSH 505 (Release) | Validation rules | CalibrationValidator.cs |
| KSH 505 (Release) | Database update logic | CalibrationRepository.cs |
| Component | Legacy Architecture | Simplified Architecture |
|---|---|---|
| Web Server | Required | Required |
| Batch Server | Required | Not needed |
| SFTP Server | Required | Not needed |
| SQL Server | Required | Required |
| Message Queue | ActiveMQ | Not needed |
| Total Servers | 4+ | 2 |
| Metric | Legacy | Simplified | Reduction |
|---|---|---|---|
| External dependencies | 5+ (JSCH, ActiveMQ, etc.) | 2 (EF Core, Hangfire) | 60% |
| Integration points | 4 (Web↔SFTP↔Batch↔DB) | 1 (Web↔DB) | 75% |
| Failure modes | 8+ (each transfer can fail) | 2 (Web, DB) | 75% |
| Temp file handling | Complex cleanup logic | None | 100% |
| Async polling | 10 retries × 6 seconds | None | 100% |
| Operation | Legacy (SFTP/Batch) | Simplified (Real-time) |
|---|---|---|
| Download calibration | 60-90 seconds (polling) | 2-5 seconds |
| Release calibration | 60-90 seconds (polling) | 1-3 seconds |
| Error feedback | After all retries fail | Immediate |
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-28 | Claude | Initial migration plan |
| 1.1 | 2026-01-28 | Claude | Major revision: Simplified architecture eliminating SFTP/batch processing. Reduced effort estimate by 20 person-weeks. |
This document should be updated as the C++ client code is received and analyzed, and as migration progresses.