# 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 ```csharp /// /// EN: Base class for all entities with domain events support. /// VI: Base class cho entities với hỗ trợ domain events. /// public abstract class Entity { private int? _requestedHashCode; private List? _domainEvents; public virtual Guid Id { get; protected set; } public IReadOnlyCollection DomainEvents => _domainEvents?.AsReadOnly() ?? Array.Empty().AsReadOnly(); public void AddDomainEvent(IDomainEvent eventItem) { _domainEvents ??= new List(); _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 ```csharp /// /// EN: Marker interface for aggregate roots. /// VI: Interface đánh dấu aggregate roots. /// public interface IAggregateRoot { } ``` ### 1.3 Value Object Base Class ```csharp /// /// EN: Base class for value objects. /// VI: Base class cho value objects. /// public abstract class ValueObject { protected abstract IEnumerable 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 ```csharp /// /// EN: Domain event interface, integrates with MediatR. /// VI: Interface domain event, tích hợp với MediatR. /// public interface IDomainEvent : INotification { Guid Id { get; } DateTime OccurredOn { get; } } ``` ### 1.5 Domain Exception ```csharp /// /// EN: Exception for domain rule violations. /// VI: Exception cho vi phạm quy tắc domain. /// 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 ```csharp /// /// EN: Base repository interface for aggregate roots. /// VI: Interface repository cơ bản cho aggregate roots. /// public interface IRepository where T : class, IAggregateRoot { IUnitOfWork UnitOfWork { get; } Task GetByIdAsync(Guid id, CancellationToken ct = default); Task AddAsync(T entity, CancellationToken ct = default); void Update(T entity); void Delete(T entity); } ``` ### 2.2 Unit of Work Interface ```csharp /// /// EN: Unit of Work interface - DbContext implements this. /// VI: Interface Unit of Work - DbContext implement interface này. /// public interface IUnitOfWork : IDisposable { Task SaveChangesAsync(CancellationToken ct = default); } ``` ### 2.3 EF Core Configuration Example ```csharp /// /// EN: EF Core configuration for Order aggregate. /// VI: Cấu hình EF Core cho Order aggregate. /// public class OrderConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Orders"); builder.HasKey(o => o.Id); builder.Property(o => o.Status) .HasConversion() .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 ```csharp /// /// EN: EF Core repository implementation. /// VI: Triển khai repository với EF Core. /// 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 GetByIdAsync(Guid id, CancellationToken ct = default) { return await _context.Orders.FindAsync(new object[] { id }, ct); } public async Task GetWithItemsAsync(Guid id, CancellationToken ct = default) { return await _context.Orders .Include(o => o.OrderItems) .FirstOrDefaultAsync(o => o.Id == id, ct); } public async Task 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 ```csharp /// /// EN: Command to create a new order. /// VI: Command tạo order mới. /// public record CreateOrderCommand( string UserId, Address ShippingAddress, List Items) : IRequest; public record OrderResult(Guid OrderId); ``` ### 3.2 Command Handler ```csharp /// /// EN: Handler for CreateOrderCommand. /// VI: Handler cho CreateOrderCommand. /// public class CreateOrderCommandHandler : IRequestHandler { private readonly IOrderRepository _orderRepository; private readonly ILogger _logger; public CreateOrderCommandHandler( IOrderRepository orderRepository, ILogger logger) { _orderRepository = orderRepository; _logger = logger; } public async Task 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 ```csharp /// /// EN: Query handler using Dapper for optimized reads. /// VI: Query handler dùng Dapper cho đọc tối ưu. /// public class GetUserOrdersQueryHandler : IRequestHandler> { private readonly IDbConnection _connection; public GetUserOrdersQueryHandler(IDbConnection connection) { _connection = connection; } public async Task> Handle( GetUserOrdersQuery request, CancellationToken ct) { const string countSql = "SELECT COUNT(*) FROM Orders WHERE UserId = @UserId"; var total = await _connection.ExecuteScalarAsync(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(sql, new { request.UserId, request.Skip, request.Take }); return new PagedResult(orders.ToList(), total); } } ``` ### 3.4 Validation Behavior ```csharp /// /// EN: Pipeline behavior for FluentValidation. /// VI: Pipeline behavior cho FluentValidation. /// public class ValidationBehavior : IPipelineBehavior where TRequest : IRequest { private readonly IEnumerable> _validators; public ValidationBehavior(IEnumerable> validators) { _validators = validators; } public async Task Handle( TRequest request, RequestHandlerDelegate next, CancellationToken ct) { if (!_validators.Any()) return await next(); var context = new ValidationContext(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 ```csharp /// /// EN: Slim controller using MediatR. /// VI: Controller gọn nhẹ với MediatR. /// [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; } /// /// EN: Create a new order. /// VI: Tạo order mới. /// [HttpPost] [SwaggerOperation(Summary = "Create order")] [SwaggerResponse(201, "Created")] [SwaggerResponse(400, "Validation error")] public async Task>> 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.Ok(result)); } private string GetUserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new UnauthorizedAccessException(); } ``` ### 4.2 Health Checks ```csharp /// /// EN: Health check configuration in Program.cs. /// VI: Cấu hình health check trong Program.cs. /// // 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 ```csharp /// /// EN: Configure HTTP client with Polly resilience. /// VI: Cấu hình HTTP client với Polly resilience. /// builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri(catalogServiceUrl); }) .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); static IAsyncPolicy 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 GetCircuitBreakerPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .CircuitBreakerAsync( handledEventsAllowedBeforeBreaking: 5, durationOfBreak: TimeSpan.FromSeconds(30)); } ``` ### 4.4 DI Registration ```csharp /// /// EN: DI registration in Program.cs. /// VI: Đăng ký DI trong Program.cs. /// // 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(); // EN: Register DbContext // VI: Đăng ký DbContext builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); // EN: Register Dapper connection for queries // VI: Đăng ký Dapper connection cho queries builder.Services.AddScoped(_ => new NpgsqlConnection(connectionString)); ``` --- ## Anti-Patterns to Avoid ### 1. Anemic Domain Model ```csharp // ❌ AVOID: No behavior, just data public class Order { public Guid Id { get; set; } public string Status { get; set; } public List Items { get; set; } = new(); } // Then in service: order.Status = "Submitted"; // No validation! ``` ### 2. God Service ```csharp // ❌ 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 ```csharp // ❌ AVOID: DbContext in controller [HttpPost] public async Task CreateOrder(CreateOrderRequest request) { var order = new Order { ... }; _context.Orders.Add(order); await _context.SaveChangesAsync(); return Ok(); } ``` ### 4. Shared Database Between Services ```csharp // ❌ 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 ```