Files
pos-system/microservices/.agent/skills/dotnet-microservice-workflow/references/REFERENCE.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

18 KiB

Technical Reference: .NET Microservice Workflow

Chi tiết kỹ thuật cho từng giai đoạn phát triển .NET Microservices.


Phase 1: Domain Layer Technical Details

1.1 Entity Base Class

/// <summary>
/// EN: Base class for all entities with domain events support.
/// VI: Base class cho entities với hỗ trợ domain events.
/// </summary>
public abstract class Entity
{
    private int? _requestedHashCode;
    private List<IDomainEvent>? _domainEvents;

    public virtual Guid Id { get; protected set; }
    
    public IReadOnlyCollection<IDomainEvent> DomainEvents 
        => _domainEvents?.AsReadOnly() ?? Array.Empty<IDomainEvent>().AsReadOnly();

    public void AddDomainEvent(IDomainEvent eventItem)
    {
        _domainEvents ??= new List<IDomainEvent>();
        _domainEvents.Add(eventItem);
    }

    public void ClearDomainEvents() => _domainEvents?.Clear();

    public bool IsTransient() => Id == default;

    public override bool Equals(object? obj)
    {
        if (obj is not Entity other) return false;
        if (ReferenceEquals(this, other)) return true;
        if (GetType() != other.GetType()) return false;
        if (IsTransient() || other.IsTransient()) return false;
        return Id.Equals(other.Id);
    }

    public override int GetHashCode()
    {
        if (!IsTransient())
        {
            _requestedHashCode ??= Id.GetHashCode() ^ 31;
            return _requestedHashCode.Value;
        }
        return base.GetHashCode();
    }
}

1.2 Aggregate Root Interface

/// <summary>
/// EN: Marker interface for aggregate roots.
/// VI: Interface đánh dấu aggregate roots.
/// </summary>
public interface IAggregateRoot { }

1.3 Value Object Base Class

/// <summary>
/// EN: Base class for value objects.
/// VI: Base class cho value objects.
/// </summary>
public abstract class ValueObject
{
    protected abstract IEnumerable<object?> GetEqualityComponents();

    public override bool Equals(object? obj)
    {
        if (obj is null || obj.GetType() != GetType())
            return false;

        var other = (ValueObject)obj;
        return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
    }

    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Select(x => x?.GetHashCode() ?? 0)
            .Aggregate((x, y) => x ^ y);
    }

    public static bool operator ==(ValueObject? left, ValueObject? right)
        => left?.Equals(right) ?? right is null;

    public static bool operator !=(ValueObject? left, ValueObject? right)
        => !(left == right);
}

1.4 Domain Event Interface

/// <summary>
/// EN: Domain event interface, integrates with MediatR.
/// VI: Interface domain event, tích hợp với MediatR.
/// </summary>
public interface IDomainEvent : INotification
{
    Guid Id { get; }
    DateTime OccurredOn { get; }
}

1.5 Domain Exception

/// <summary>
/// EN: Exception for domain rule violations.
/// VI: Exception cho vi phạm quy tắc domain.
/// </summary>
public class DomainException : Exception
{
    public DomainException(string message) : base(message) { }
    
    public DomainException(string message, Exception innerException) 
        : base(message, innerException) { }
}

Phase 2: Infrastructure Layer Technical Details

2.1 Repository Interface

/// <summary>
/// EN: Base repository interface for aggregate roots.
/// VI: Interface repository cơ bản cho aggregate roots.
/// </summary>
public interface IRepository<T> where T : class, IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
    
    Task<T?> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task<T> AddAsync(T entity, CancellationToken ct = default);
    void Update(T entity);
    void Delete(T entity);
}

2.2 Unit of Work Interface

/// <summary>
/// EN: Unit of Work interface - DbContext implements this.
/// VI: Interface Unit of Work - DbContext implement interface này.
/// </summary>
public interface IUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken ct = default);
}

2.3 EF Core Configuration Example

/// <summary>
/// EN: EF Core configuration for Order aggregate.
/// VI: Cấu hình EF Core cho Order aggregate.
/// </summary>
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> builder)
    {
        builder.ToTable("Orders");
        
        builder.HasKey(o => o.Id);
        
        builder.Property(o => o.Status)
            .HasConversion<string>()
            .HasMaxLength(50);
        
        builder.Property(o => o.TotalAmount)
            .HasPrecision(18, 2);
        
        // EN: Configure owned entity (Value Object)
        // VI: Cấu hình owned entity (Value Object)
        builder.OwnsOne(o => o.ShippingAddress, a =>
        {
            a.Property(p => p.Street).HasMaxLength(200);
            a.Property(p => p.City).HasMaxLength(100);
            a.Property(p => p.PostalCode).HasMaxLength(20);
        });
        
        // EN: Configure child entities
        // VI: Cấu hình entities con
        builder.HasMany(o => o.OrderItems)
            .WithOne()
            .HasForeignKey("OrderId")
            .OnDelete(DeleteBehavior.Cascade);
        
        // EN: Use backing field for collection
        // VI: Dùng backing field cho collection
        builder.Navigation(o => o.OrderItems)
            .UsePropertyAccessMode(PropertyAccessMode.Field);
    }
}

2.4 Repository Implementation

/// <summary>
/// EN: EF Core repository implementation.
/// VI: Triển khai repository với EF Core.
/// </summary>
public class OrderRepository : IOrderRepository
{
    private readonly ApplicationDbContext _context;

    public IUnitOfWork UnitOfWork => _context;

    public OrderRepository(ApplicationDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
    {
        return await _context.Orders.FindAsync(new object[] { id }, ct);
    }

    public async Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct = default)
    {
        return await _context.Orders
            .Include(o => o.OrderItems)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }

    public async Task<Order> AddAsync(Order order, CancellationToken ct = default)
    {
        var entry = await _context.Orders.AddAsync(order, ct);
        return entry.Entity;
    }

    public void Update(Order order)
    {
        _context.Entry(order).State = EntityState.Modified;
    }

    public void Delete(Order order)
    {
        _context.Orders.Remove(order);
    }
}

Phase 3: Application Layer Technical Details

3.1 Command Definition

/// <summary>
/// EN: Command to create a new order.
/// VI: Command tạo order mới.
/// </summary>
public record CreateOrderCommand(
    string UserId,
    Address ShippingAddress,
    List<OrderItemDto> Items) : IRequest<OrderResult>;

public record OrderResult(Guid OrderId);

3.2 Command Handler

/// <summary>
/// EN: Handler for CreateOrderCommand.
/// VI: Handler cho CreateOrderCommand.
/// </summary>
public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<CreateOrderCommandHandler> _logger;

    public CreateOrderCommandHandler(
        IOrderRepository orderRepository,
        ILogger<CreateOrderCommandHandler> logger)
    {
        _orderRepository = orderRepository;
        _logger = logger;
    }

    public async Task<OrderResult> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        // EN: Create order through domain model
        // VI: Tạo order qua domain model
        var order = new Order(request.UserId, request.ShippingAddress);

        foreach (var item in request.Items)
        {
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        }

        await _orderRepository.AddAsync(order, ct);
        await _orderRepository.UnitOfWork.SaveChangesAsync(ct);

        _logger.LogInformation(
            "EN: Order created / VI: Order đã tạo: {OrderId}", 
            order.Id);

        return new OrderResult(order.Id);
    }
}

3.3 Query with Dapper

/// <summary>
/// EN: Query handler using Dapper for optimized reads.
/// VI: Query handler dùng Dapper cho đọc tối ưu.
/// </summary>
public class GetUserOrdersQueryHandler 
    : IRequestHandler<GetUserOrdersQuery, PagedResult<OrderSummaryDto>>
{
    private readonly IDbConnection _connection;

    public GetUserOrdersQueryHandler(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<PagedResult<OrderSummaryDto>> Handle(
        GetUserOrdersQuery request,
        CancellationToken ct)
    {
        const string countSql = "SELECT COUNT(*) FROM Orders WHERE UserId = @UserId";
        var total = await _connection.ExecuteScalarAsync<int>(countSql, new { request.UserId });

        const string sql = @"
            SELECT o.Id, o.Status, o.TotalAmount, o.CreatedAt,
                   COUNT(oi.Id) as ItemCount
            FROM Orders o
            LEFT JOIN OrderItems oi ON o.Id = oi.OrderId
            WHERE o.UserId = @UserId
            GROUP BY o.Id, o.Status, o.TotalAmount, o.CreatedAt
            ORDER BY o.CreatedAt DESC
            OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY";

        var orders = await _connection.QueryAsync<OrderSummaryDto>(sql, new
        {
            request.UserId,
            request.Skip,
            request.Take
        });

        return new PagedResult<OrderSummaryDto>(orders.ToList(), total);
    }
}

3.4 Validation Behavior

/// <summary>
/// EN: Pipeline behavior for FluentValidation.
/// VI: Pipeline behavior cho FluentValidation.
/// </summary>
public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        if (!_validators.Any())
            return await next();

        var context = new ValidationContext<TRequest>(request);
        var failures = (await Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, ct))))
            .SelectMany(r => r.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
            throw new ValidationException(failures);

        return await next();
    }
}

Phase 4: API Layer Technical Details

4.1 Slim Controller

/// <summary>
/// EN: Slim controller using MediatR.
/// VI: Controller gọn nhẹ với MediatR.
/// </summary>
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/orders")]
[SwaggerTag("Order Management")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;

    public OrdersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    /// <summary>
    /// EN: Create a new order.
    /// VI: Tạo order mới.
    /// </summary>
    [HttpPost]
    [SwaggerOperation(Summary = "Create order")]
    [SwaggerResponse(201, "Created")]
    [SwaggerResponse(400, "Validation error")]
    public async Task<ActionResult<ApiResponse<OrderResult>>> CreateOrder(
        CreateOrderRequest request,
        CancellationToken ct)
    {
        var command = new CreateOrderCommand(
            GetUserId(),
            request.ShippingAddress,
            request.Items);

        var result = await _mediator.Send(command, ct);

        return CreatedAtAction(
            nameof(GetOrder),
            new { orderId = result.OrderId },
            ApiResponse<OrderResult>.Ok(result));
    }

    private string GetUserId() =>
        User.FindFirstValue(ClaimTypes.NameIdentifier) 
        ?? throw new UnauthorizedAccessException();
}

4.2 Health Checks

/// <summary>
/// EN: Health check configuration in Program.cs.
/// VI: Cấu hình health check trong Program.cs.
/// </summary>

// EN: Register health checks
// VI: Đăng ký health checks
builder.Services.AddHealthChecks()
    .AddNpgSql(connectionString, name: "postgresql")
    .AddRedis(redisConnectionString, name: "redis")
    .AddRabbitMQ(rabbitConnectionString, name: "rabbitmq");

// EN: Map health check endpoint
// VI: Map endpoint health check
app.MapHealthChecks("/hc", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

4.3 Resilience with Polly

/// <summary>
/// EN: Configure HTTP client with Polly resilience.
/// VI: Cấu hình HTTP client với Polly resilience.
/// </summary>
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(catalogServiceUrl);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt => 
                TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
            onRetry: (outcome, timespan, retryAttempt, context) =>
            {
                // Log retry attempt
            });
}

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(
            handledEventsAllowedBeforeBreaking: 5,
            durationOfBreak: TimeSpan.FromSeconds(30));
}

4.4 DI Registration

/// <summary>
/// EN: DI registration in Program.cs.
/// VI: Đăng ký DI trong Program.cs.
/// </summary>

// EN: Register MediatR with behaviors
// VI: Đăng ký MediatR với behaviors
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});

// EN: Register FluentValidation
// VI: Đăng ký FluentValidation
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);

// EN: Register repositories
// VI: Đăng ký repositories
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

// EN: Register DbContext
// VI: Đăng ký DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(connectionString));

// EN: Register Dapper connection for queries
// VI: Đăng ký Dapper connection cho queries
builder.Services.AddScoped<IDbConnection>(_ =>
    new NpgsqlConnection(connectionString));

Anti-Patterns to Avoid

1. Anemic Domain Model

// ❌ AVOID: No behavior, just data
public class Order
{
    public Guid Id { get; set; }
    public string Status { get; set; }
    public List<OrderItem> Items { get; set; } = new();
}

// Then in service:
order.Status = "Submitted"; // No validation!

2. God Service

// ❌ AVOID: All logic in one service
public class OrderService
{
    public void CreateOrder() { /* 500 lines */ }
    public void UpdateOrder() { /* 300 lines */ }
    public void ProcessPayment() { /* 400 lines */ }
    // ...
}

3. Direct Database Access in Controller

// ❌ AVOID: DbContext in controller
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    var order = new Order { ... };
    _context.Orders.Add(order);
    await _context.SaveChangesAsync();
    return Ok();
}

4. Shared Database Between Services

// ❌ AVOID: Multiple services accessing same database
// OrderService -> SharedDb.Orders
// InventoryService -> SharedDb.Orders (reading Order table!)

File Structure Summary

src/
├── ServiceName.API/
│   ├── Controllers/
│   │   └── OrdersController.cs
│   ├── Application/
│   │   ├── Commands/
│   │   │   ├── CreateOrderCommand.cs
│   │   │   └── CreateOrderCommandHandler.cs
│   │   ├── Queries/
│   │   │   ├── GetOrderQuery.cs
│   │   │   └── GetOrderQueryHandler.cs
│   │   ├── Validators/
│   │   │   └── CreateOrderCommandValidator.cs
│   │   └── Behaviors/
│   │       ├── LoggingBehavior.cs
│   │       └── ValidationBehavior.cs
│   └── Program.cs
├── ServiceName.Domain/
│   ├── AggregatesModel/
│   │   └── OrderAggregate/
│   │       ├── Order.cs
│   │       ├── OrderItem.cs
│   │       ├── OrderStatus.cs
│   │       └── IOrderRepository.cs
│   ├── Events/
│   │   └── OrderCreatedDomainEvent.cs
│   ├── Exceptions/
│   │   └── DomainException.cs
│   └── SeedWork/
│       ├── Entity.cs
│       ├── ValueObject.cs
│       └── IAggregateRoot.cs
└── ServiceName.Infrastructure/
    ├── Data/
    │   ├── ApplicationDbContext.cs
    │   └── Configurations/
    │       └── OrderConfiguration.cs
    └── Repositories/
        └── OrderRepository.cs