Files
pos-system/microservices/.agent/skills/repository-pattern/SKILL.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

11 KiB

name, description, compatibility, metadata
name description compatibility metadata
repository-pattern Entity Framework Core repository và data access patterns. Use for DbContext, repositories, migrations, aggregate roots, Unit of Work, và CQRS queries. .NET 10+, Entity Framework Core 8+, Dapper
author version
Velik Ho 1.0

Repository Pattern / Mẫu Repository

Repository pattern cho GoodGo microservices theo chuẩn Domain-Driven Design (DDD).

When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:

  • Creating new repositories for aggregates / Tạo repositories mới cho aggregates
  • Implementing data access with EF Core / Triển khai data access với EF Core
  • Setting up Unit of Work pattern / Cài đặt Unit of Work pattern
  • Separating read/write operations (CQRS) / Phân tách read/write (CQRS)
  • Creating database migrations / Tạo database migrations
  • Optimizing query performance / Tối ưu hiệu năng query

Core Concepts / Khái Niệm Cốt Lõi

1. One Repository Per Aggregate Root / Một Repository Cho Mỗi Aggregate Root

❌ WRONG: Repository cho từng table
   OrderRepository
   OrderItemRepository
   OrderPaymentRepository

✅ CORRECT: Repository chỉ cho Aggregate Root
   OrderRepository (quản lý Order + OrderItems + OrderPayments)

Nguyên tắc:

  • Aggregate Root chịu trách nhiệm duy trì tính nhất quán
  • Mọi thao tác với entities con phải thông qua Root
  • Repository chỉ expose Aggregate Root, không expose entities con

2. Separated Interface / Tách Giao Diện

Domain Layer (ServiceName.Domain/)
├── AggregatesModel/
│   └── OrderAggregate/
│       ├── Order.cs           # Aggregate Root
│       ├── OrderItem.cs       # Entity con
│       └── IOrderRepository.cs # Interface

Infrastructure Layer (ServiceName.Infrastructure/)
├── Repositories/
│   └── OrderRepository.cs     # Implementation
└── Data/
    └── ApplicationDbContext.cs

Lợi ích:

  • Domain không phụ thuộc vào công nghệ (EF Core, Dapper)
  • Dễ thay thế implementation
  • Tuân thủ Dependency Inversion Principle

3. Unit of Work / Đơn Vị Công Việc

// EN: DbContext IS the Unit of Work in EF Core
// VI: DbContext CHÍNH LÀ Unit of Work trong EF Core
public interface IUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken ct = default);
}

// EN: All repositories share the same DbContext
// VI: Tất cả repositories dùng chung một DbContext

4. CQRS Simplified / CQRS Đơn Giản

Operation Pattern Tool
Commands (Create, Update, Delete) Repository + EF Core Full domain model
Queries (Read) Dapper / Raw SQL Lightweight DTOs

Key Patterns / Mẫu Chính

Repository Interface / Giao Diện Repository

/// <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);
}

/// <summary>
/// EN: Order repository interface.
/// VI: Interface repository cho Order.
/// </summary>
public interface IOrderRepository : IRepository<Order>
{
    Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct = default);
    Task<IReadOnlyList<Order>> GetByUserIdAsync(string userId, CancellationToken ct = default);
}

EF Core Implementation / Triển Khai EF Core

/// <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);
    }
}

Command Handler with Repository / Handler với Repository

/// <summary>
/// EN: Command handler using repository pattern.
/// VI: Command handler sử dụng repository pattern.
/// </summary>
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepository;

    public CreateOrderCommandHandler(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public async Task<OrderResult> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        // EN: Create aggregate through domain model
        // VI: Tạo aggregate thông 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);
        }

        // EN: Persist through repository
        // VI: Lưu qua repository
        await _orderRepository.AddAsync(order, ct);
        await _orderRepository.UnitOfWork.SaveChangesAsync(ct);

        return new OrderResult(order.Id);
    }
}

Query with Dapper (CQRS) / Query với Dapper

/// <summary>
/// EN: Query handler using Dapper for read operations.
/// VI: Query handler dùng Dapper cho operations đọc.
/// </summary>
public class GetOrdersQueryHandler : IRequestHandler<GetOrdersQuery, IReadOnlyList<OrderDto>>
{
    private readonly IDbConnection _connection;

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

    public async Task<IReadOnlyList<OrderDto>> Handle(
        GetOrdersQuery request,
        CancellationToken ct)
    {
        // EN: Use Dapper for optimized read queries
        // VI: Dùng Dapper cho query đọc tối ưu
        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<OrderDto>(sql, new
        {
            request.UserId,
            request.Skip,
            request.Take
        });

        return orders.ToList();
    }
}

Common Mistakes / Lỗi Thường Gặp

1. Repository Per Table / Repository Cho Từng Table

// ❌ BAD: Multiple repositories for one aggregate
public class OrderItemRepository { }
public class OrderPaymentRepository { }

// ✅ GOOD: Only repository for aggregate root
public class OrderRepository
{
    // EN: Access child entities through aggregate
    // VI: Truy cập entities con thông qua aggregate
    public async Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct)
    {
        return await _context.Orders
            .Include(o => o.OrderItems)
            .Include(o => o.Payment)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }
}

2. Bypassing Aggregate Root / Bỏ Qua Aggregate Root

// ❌ BAD: Modifying child entity directly
var orderItem = await _context.OrderItems.FindAsync(itemId);
orderItem.Quantity = 5;

// ✅ GOOD: Modify through aggregate root
var order = await _orderRepository.GetWithItemsAsync(orderId, ct);
order.UpdateItemQuantity(itemId, 5);
await _orderRepository.UnitOfWork.SaveChangesAsync(ct);

3. Exposing IQueryable / Lộ IQueryable

// ❌ BAD: Leaking implementation details
public interface IOrderRepository
{
    IQueryable<Order> GetAll();
}

// ✅ GOOD: Encapsulated queries
public interface IOrderRepository
{
    Task<IReadOnlyList<Order>> GetByUserIdAsync(string userId, CancellationToken ct);
    Task<Order?> GetWithItemsAsync(Guid id, CancellationToken ct);
}

4. Missing Unit of Work / Thiếu Unit of Work

// ❌ BAD: SaveChanges in repository
public async Task<Order> AddAsync(Order order, CancellationToken ct)
{
    await _context.Orders.AddAsync(order, ct);
    await _context.SaveChangesAsync(ct);  // WRONG!
    return order;
}

// ✅ GOOD: SaveChanges in handler (Unit of Work)
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
{
    await _orderRepository.AddAsync(order, ct);
    await _orderRepository.UnitOfWork.SaveChangesAsync(ct);  // CORRECT!
}

Quick Reference / Tham Chiếu Nhanh

Project Structure

Component Location
Repository Interface Domain/AggregatesModel/{Aggregate}/I{Aggregate}Repository.cs
Repository Implementation Infrastructure/Repositories/{Aggregate}Repository.cs
DbContext Infrastructure/Data/ApplicationDbContext.cs
Entity Configurations Infrastructure/Data/Configurations/

DI Registration

// EN: Register repositories in Program.cs
// VI: Đăng ký repositories trong Program.cs
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUserRepository, UserRepository>();

// 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));

Migration Commands

# EN: Add migration / VI: Thêm migration
dotnet ef migrations add MigrationName --project src/Service.Infrastructure

# EN: Update database / VI: Cập nhật database
dotnet ef database update --project src/Service.Infrastructure

# EN: Generate SQL script / VI: Tạo SQL script
dotnet ef migrations script --project src/Service.Infrastructure

Resources / Tài Nguyên