Phân tích luồng khởi tạo dịch vụ và đăng ký EF Core trong kiến trúc Adnc

Đầu tiên, hệ thống khởi tạo thông tin dịch vụ từ assembly đang chạy:

var entryAssembly = Assembly.GetExecutingAssembly();
var baseName = entryAssembly.GetName().Name ?? string.Empty;
var suffix = baseName.Split('.').Last();
var migrationTarget = baseName.Replace($".{suffix}", ".Repository");
var serviceMetadata = ServiceMetadataFactory.Create(entryAssembly, migrationTarget);

Lớp ServiceMetadata sử dụng singleton pattern để quản lý thông tin dịch vụ toàn cục:

namespace Adnc.Shared.WebApi;

public class ServiceMetadata : IServiceMetadata
{
    private static readonly Lazy<ServiceMetadata> _lazyInstance = new(() => new ServiceMetadata());

    public string UniqueId { get; private set; } = string.Empty;
    public string DisplayName { get; private set; } = string.Empty;
    public string EnvironmentTag { get; private set; } = string.Empty;
    public string ApiPrefix { get; private set; } = string.Empty;
    public Assembly EntryPoint { get; private set; } = default!;
    public string MigrationTarget { get; private set; } = string.Empty;

    private ServiceMetadata() { }

    public static ServiceMetadata Create(Assembly entryPoint, string? migrationTarget = null)
    {
        var instance = _lazyInstance.Value;
        
        if (entryPoint == null)
            throw new ArgumentNullException(nameof(entryPoint));

        var versionInfo = entryPoint.GetName().Version ?? Version.Parse("0.0.0.0");
        var assemblyName = entryPoint.GetName().Name ?? "unknown";
        var displayName = assemblyName.Replace(".", "-").ToLowerInvariant();
        var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.ToLowerInvariant() ?? "development";
        var timestamp = DateTime.UtcNow.Ticks.ToString("x");

        instance.UniqueId = $"{displayName}-{env}-{timestamp}";
        instance.DisplayName = displayName;
        instance.EnvironmentTag = env;
        instance.ApiPrefix = string.Join("/", assemblyName.Split('.').TakeLast(2)).ToLowerInvariant();
        instance.EntryPoint = entryPoint;
        instance.MigrationTarget = migrationTarget ?? assemblyName.Replace($".{assemblyName.Split('.').Last()}", ".Migrations");

        return instance;
    }
}

Sau khi có metadata, ứng dụng gọi phương thức mở rộng để cấu hình mặc định:

var builder = WebApplication.CreateBuilder(args)
    .ConfigureAdncDefaults(serviceMetadata)
    .Build();

Phương thức ConfigureAdncDefaults sẽ kích hoạt quá trình đăng ký dịch vụ:

public static class WebApplicationBuilderExtensions
{
    public static WebApplicationBuilder ConfigureAdncDefaults(this WebApplicationBuilder builder, IServiceMetadata metadata)
    {
        builder.Services.RegisterAdncCore(metadata);
        return builder;
    }
}

Trong đó, RegisterAdncCore sử dụng reflection để tìm và khởi tạo registrar cụ thể cho từng module:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection RegisterAdncCore(this IServiceCollection services, IServiceMetadata metadata)
    {
        var registrarType = metadata.EntryPoint.ExportedTypes
            .FirstOrDefault(t => t.IsAssignableTo(typeof(IModuleRegistrar)) 
                              && t.IsAssignableTo(typeof(WebApiModuleRegistrarBase)) 
                              && !t.IsAbstract);

        if (registrarType == null)
            throw new InvalidOperationException("Không tìm thấy WebApi registrar");

        var registrar = Activator.CreateInstance(registrarType, services) as IModuleRegistrar;
        registrar?.RegisterServices();

        return services;
    }
}

Ví dụ với module User, lớp UserApiRegistrar sẽ kế thừa và ghi đè phương thức đăng ký:

public sealed class UserApiRegistrar : WebApiModuleRegistrarBase
{
    public UserApiRegistrar(IServiceCollection services) : base(services) { }

    public override void RegisterServices()
    {
        RegisterWebApiDefaults<JwtAuthHandler, RolePermissionValidator>();
        EnableHealthChecks(enableRedis: true, enableDb: true);
        Services.AddGrpc();
    }

    public override void ConfigurePipeline()
    {
        ConfigureWebApiDefaults(endpoints =>
        {
            endpoints.MapGrpcService<UserGrpcService>();
            endpoints.MapGrpcService<AuthService>();
        });
    }
}

Phương thức RegisterWebApiDefaults sẽ tiếp tục gọi đến việc đăng ký các dịch vụ ứng dụng:

protected virtual void RegisterWebApiDefaults<TAuth, TPerm>()
    where TAuth : AuthenticationHandlerBase
    where TPerm : PermissionValidatorBase
{
    Services.AddControllers();
    Services.AddHttpContextAccessor();
    
    RegisterApplicationLayer();
    RegisterAuthentication<TAuth>();
    RegisterAuthorization<TPerm>();
    RegisterCorsPolicy();
    
    if (Configuration.GetValue<bool>("Swagger:Enabled", true))
    {
        RegisterSwagger();
        RegisterProfiler();
    }
}

Quá trình đăng ký tầng Application được thực hiện qua reflection tương tự:

protected virtual void RegisterApplicationLayer()
{
    var appAssembly = ServiceMetadata.Current.GetApplicationAssembly();
    
    if (appAssembly != null)
    {
        var appRegistrarType = appAssembly.ExportedTypes
            .FirstOrDefault(t => t.IsAssignableTo(typeof(IModuleRegistrar)) 
                              && !t.IsAssignableTo(typeof(WebApiModuleRegistrarBase)) 
                              && !t.IsAbstract);

        if (appRegistrarType != null)
        {
            var appRegistrar = Activator.CreateInstance(appRegistrarType, Services) as IModuleRegistrar;
            appRegistrar?.RegisterServices();
        }
    }
}

Phương thức GetApplicationAssembly xác định assembly ứng dụng bằng cách thay đổi hậu tố tên:

public static Assembly GetApplicationAssembly(this IServiceMetadata metadata)
{
    var entryName = metadata.EntryPoint.GetName().Name ?? string.Empty;
    var appName = entryName.Replace(".WebApi", ".Application");
    
    return AppDomain.CurrentDomain.GetAssemblies()
        .FirstOrDefault(a => a.GetName().Name == appName) 
        ?? metadata.EntryPoint;
}

Lớp ApplicationRegistrar trong module User sẽ đăng ký các thành phần cốt lõi:

public sealed class ApplicationRegistrar : ApplicationModuleRegistrarBase
{
    public override Assembly ApplicationAssembly => Assembly.GetExecutingAssembly();
    public override Assembly ContractAssembly => typeof(IUserService).Assembly;
    public override Assembly DomainAssembly => typeof(DomainEntity).Assembly;

    public ApplicationRegistrar(IServiceCollection services) : base(services) { }

    public override void RegisterServices() => RegisterApplicationDefaults();
}

Phương thức RegisterApplicationDefaults thiết lập toàn bộ infrastructure cần thiết:

protected virtual void RegisterApplicationDefaults()
{
    Services.AddValidatorsFromAssembly(ContractAssembly)
            .AddAutoMapperProfiles(ApplicationAssembly)
            .AddDistributedIdGenerator(RedisConfig)
            .AddServiceDiscovery(ConsulConfig)
            .AddDatabaseAccess();

    RegisterSharedServices();
    RegisterInterceptedServices();
    RegisterBackgroundServices();
    RegisterDbContextAndRepositories();
    RegisterDocumentStore();
    RegisterCacheLayer();
}

Cuối cùng, việc đăng ký EF Core và repository được thực hiện như sau:

protected virtual void RegisterDbContextAndRepositories()
{
    var entityInfoType = DomainAssembly.ExportedTypes
        .FirstOrDefault(t => t.IsAssignableTo(typeof(IEntityMetadata)) && !t.IsAbstract);
    
    if (entityInfoType != null)
        Services.AddScoped(typeof(IEntityMetadata), entityInfoType);

    RegisterDbContext();
}

protected virtual void RegisterDbContext()
{
    var dbConfig = MySqlConfig.Get<DatabaseOptions>();
    var serverVersion = new MariaDbServerVersion(new Version(10, 5, 4));
    
    Services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseMySql(dbConfig.ConnectionString, serverVersion, mysqlOptions =>
        {
            mysqlOptions.MigrationsAssembly(ServiceMetadata.Current.MigrationTarget)
                       .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
        });
    });

    Services.AddScoped<IUnitOfWork, DatabaseUnitOfWork<ApplicationDbContext>>();
    Services.AddScoped(typeof(IRepository<>), typeof(EfCoreRepository<>));
}

Toàn bộ luồng khởi tạo diễn ra theo chuỗi: Web API → Application Layer → Infrastructure Layer, với mỗi tầng đều sử dụng reflection để tự động phát hiện và đăng ký các thành phần cần thiết.

Thẻ: Adnc EFCore DependencyInjection Reflection CQRS

Đăng vào ngày 1 tháng 6 lúc 16:25