Skip to content

Latest commit

Β 

History

History
488 lines (387 loc) Β· 11.8 KB

File metadata and controls

488 lines (387 loc) Β· 11.8 KB

Migration Guide

This guide helps you migrate between different versions of DKNet Framework and provides guidance for handling breaking changes.

πŸ“‹ Table of Contents


πŸš€ Current Migration Scenarios

From Legacy DKNet to 2024.12.0+

This is a major architectural migration from legacy packages to the new Domain-Driven Design framework.

Key Changes

  • Architecture: Complete shift to DDD/Onion Architecture
  • Technology: Upgrade to .NET 10.0 with C# 14 features
  • Patterns: Introduction of CQRS, Repository patterns, Domain Events
  • Testing: New testing strategy with TestContainers

Migration Strategy

1. Assessment Phase

# Analyze your current usage
git grep -r "DKNet" --include="*.cs" src/
# Review dependencies
dotnet list package --include-transitive | grep DKNet

2. Incremental Migration

  • Start with new projects using the SlimBus template
  • Migrate existing projects component by component
  • Run both old and new implementations in parallel during transition

3. Component Migration Order

  1. Core Extensions β†’ DKNet.Fw.Extensions
  2. Data Access β†’ DKNet.EfCore.* packages
  3. Business Logic β†’ Domain entities and services
  4. API Layer β†’ Controllers/endpoints
  5. Infrastructure β†’ External service integrations

πŸ“¦ Version-Specific Migrations

Upgrading to .NET 10.0

Prerequisites

# Install .NET 10.0 SDK
./src/dotnet-install.sh --version 10.0.100

# Update global.json
{
  "sdk": {
    "version": "10.0.100"
  }
}

Project Files

<!-- Update target framework -->
<TargetFramework>net10.0</TargetFramework>

<!-- Update package references -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />

C# 14 Language Features

Take advantage of new C# 14 features in your code:

// Params collections - more efficient parameter passing
public static void ProcessItems(params ReadOnlySpan<string> items)
{
    foreach (var item in items)
    {
        // Process item
    }
}

// Enhanced pattern matching
public decimal CalculateDiscount(Order order) => order switch
{
    { Total: > 1000, IsPremium: true } => 0.20m,
    { Total: > 500 } => 0.10m,
    { ItemCount: > 10 } => 0.05m,
    _ => 0m
};

// Lock object improvements - better performance
private readonly Lock _lock = new();

public void SafeOperation()
{
    lock (_lock)
    {
        // Thread-safe operations
    }
}

Entity Framework Core Migration

Before (Legacy)

public class ProductRepository
{
    private readonly DbContext _context;
    
    public ProductRepository(DbContext context)
    {
        _context = context;
    }
    
    public async Task<Product> GetAsync(int id)
    {
        return await _context.Set<Product>().FindAsync(id);
    }
}

After (DKNet 2024.12.0+)

public class ProductRepository : Repository<Product>, IProductRepository
{
    public ProductRepository(AppDbContext context) : base(context) { }
    
    public async Task<Product?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await GetByIdAsync(id, cancellationToken);
    }
    
    // Specification support
    public async Task<IEnumerable<Product>> FindAsync(Specification<Product> spec)
    {
        return await Gets().Where(spec.ToExpression()).ToListAsync();
    }
}

πŸ—οΈ Architecture Migration

From N-Layer to Onion Architecture

Legacy Structure

Solution/
β”œβ”€β”€ Web/              # Presentation
β”œβ”€β”€ Business/         # Business Logic
β”œβ”€β”€ Data/            # Data Access
└── Common/          # Shared

New Structure (Onion)

Solution/
β”œβ”€β”€ Api/             # Presentation Layer
β”œβ”€β”€ AppServices/     # Application Layer
β”œβ”€β”€ Domains/         # Domain Layer
└── Infra/           # Infrastructure Layer

Domain Entity Migration

Before

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public DateTime CreatedAt { get; set; }
}

After

[Table("Products", Schema = "catalog")]
public class Product : AggregateRoot
{
    public Product(string name, decimal price, string createdBy)
        : base(Guid.NewGuid(), createdBy)
    {
        Name = name;
        Price = price;
    }

    public string Name { get; private set; }
    public decimal Price { get; private set; }

    public void UpdatePrice(decimal newPrice, string updatedBy)
    {
        Price = newPrice;
        SetUpdatedBy(updatedBy);
        AddEvent(new ProductPriceChangedEvent(Id, Price));
    }
}

πŸ”„ CQRS Migration

Command/Query Separation

Before (Traditional Service)

public class ProductService
{
    public async Task<Product> CreateProductAsync(CreateProductDto dto)
    {
        // Create logic
    }
    
    public async Task<Product> GetProductAsync(int id)
    {
        // Get logic
    }
}

After (CQRS)

// Command
public record CreateProductCommand : IRequest<ProductResult>
{
    public string Name { get; init; }
    public decimal Price { get; init; }
}

public class CreateProductHandler : IRequestHandler<CreateProductCommand, ProductResult>
{
    public async Task<ProductResult> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        // Command handling logic
    }
}

// Query
public record GetProductQuery : IRequest<ProductResult>
{
    public Guid Id { get; init; }
}

public class GetProductHandler : IRequestHandler<GetProductQuery, ProductResult>
{
    public async Task<ProductResult> Handle(GetProductQuery request, CancellationToken cancellationToken)
    {
        // Query handling logic
    }
}

πŸ“Š Database Migration

Schema Updates

Add Migration for New Structure

# Create migration
dotnet ef migrations add UpgradeToOnionArchitecture

# Review generated migration
# Update database
dotnet ef database update

Data Migration Script

-- Migrate existing data to new schema
-- Add audit fields
ALTER TABLE Products ADD CreatedBy NVARCHAR(255) NOT NULL DEFAULT 'SYSTEM';
ALTER TABLE Products ADD CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE();
ALTER TABLE Products ADD UpdatedBy NVARCHAR(255) NULL;
ALTER TABLE Products ADD UpdatedAt DATETIME2 NULL;

-- Convert INT IDs to GUIDs (if needed)
-- This is a complex migration - consider keeping INT IDs if possible

πŸ§ͺ Testing Migration

From Legacy Testing to Modern Patterns

Before

[Test]
public async Task CreateProduct_ShouldReturnProduct()
{
    // In-memory database setup
    var options = new DbContextOptionsBuilder<DbContext>()
        .UseInMemoryDatabase(databaseName: "TestDb")
        .Options;
        
    using var context = new DbContext(options);
    var repository = new ProductRepository(context);
    
    // Test logic
}

After

[Test]
public async Task CreateProduct_ShouldReturnProduct()
{
    // TestContainers setup
    await using var container = new MsSqlBuilder()
        .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
        .Build();
        
    await container.StartAsync();
    
    var connectionString = container.GetConnectionString();
    var services = new ServiceCollection();
    services.AddDbContext<AppDbContext>(options => 
        options.UseSqlServer(connectionString));
    
    // Test with real database
}

πŸ› οΈ Migration Tools

Automated Migration Helper

public class MigrationHelper
{
    public static async Task MigrateDataAsync(IServiceProvider serviceProvider)
    {
        using var scope = serviceProvider.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        
        // Ensure database is created
        await context.Database.EnsureCreatedAsync();
        
        // Run custom migrations
        await MigrateProductsAsync(context);
        await MigrateUsersAsync(context);
    }
    
    private static async Task MigrateProductsAsync(AppDbContext context)
    {
        // Custom migration logic for products
        var products = await context.Set<OldProduct>().ToListAsync();
        foreach (var oldProduct in products)
        {
            var newProduct = new Product(
                oldProduct.Name, 
                oldProduct.Price, 
                "MIGRATION");
            context.Set<Product>().Add(newProduct);
        }
        await context.SaveChangesAsync();
    }
}

Configuration Migration

public static class ConfigurationMigration
{
    public static IServiceCollection MigrateFromLegacy(
        this IServiceCollection services, 
        IConfiguration configuration)
    {
        // Map old configuration to new structure
        var legacyConfig = configuration.GetSection("Legacy");
        var newConfig = new DKNetOptions
        {
            DatabaseConnectionString = legacyConfig["Database:ConnectionString"],
            EnableAuditFields = bool.Parse(legacyConfig["Audit:Enabled"] ?? "true"),
            // ... other mappings
        };
        
        services.Configure<DKNetOptions>(options =>
        {
            options.DatabaseConnectionString = newConfig.DatabaseConnectionString;
            options.EnableAuditFields = newConfig.EnableAuditFields;
        });
        
        return services;
    }
}

⚠️ Common Issues

1. ID Type Changes

Issue: Converting from int to Guid IDs Solution:

  • Keep existing int IDs if possible
  • Use mapping layer for external APIs
  • Consider gradual migration with both ID types

2. Breaking API Changes

Issue: Public API contracts change Solution:

  • Version your APIs (/api/v1/, /api/v2/)
  • Maintain compatibility layer
  • Use deprecation warnings

3. Performance Issues

Issue: New patterns may impact performance Solution:

  • Profile before and after migration
  • Optimize hot paths
  • Use caching where appropriate

4. Dependency Injection Changes

Issue: Service registration patterns change Solution:

// Old
services.AddScoped<ProductService>();

// New
services.AddScoped<IProductRepository, ProductRepository>();
services.AddMediatR(typeof(CreateProductHandler));

πŸ“‹ Migration Checklist

Pre-Migration

  • Backup production databases
  • Document current architecture
  • Identify critical business logic
  • Plan rollback strategy
  • Set up staging environment

During Migration

  • Migrate in small increments
  • Maintain comprehensive tests
  • Monitor performance metrics
  • Document changes as you go
  • Regular communication with stakeholders

Post-Migration

  • Verify all functionality works
  • Performance testing
  • Update documentation
  • Train team on new patterns
  • Plan for ongoing maintenance

πŸ†˜ Getting Help

If you encounter issues during migration:

  1. Check Documentation: Review Getting Started and Examples
  2. Search Issues: Look for similar problems in GitHub Issues
  3. Ask Questions: Create a new issue with the migration label
  4. Join Discussions: Participate in GitHub Discussions

πŸ’‘ Migration Tip: Take your time with migration. It's better to migrate correctly in stages than to rush and introduce bugs. Use the SlimBus template as your reference implementation!