Chapter 05

Best Practices
& Lưu ý

Do/Don't, pitfalls thường gặp, production checklist

Exception Handling

DO — Custom exception classes

throw new NotFoundException("Product", id);

Rõ ràng, pattern matching dễ dàng, map tự động sang status code.

DON'T — Generic exception

throw new Exception("Not found");

Không phân biệt được loại lỗi, luôn trả 500.

DO — Global ExceptionHandler

UseExceptionHandler() + IExceptionHandler

Một nơi duy nhất xử lý tất cả exceptions. DRY, nhất quán.

DON'T — Try-catch mỗi action

try { ... } catch { return 500; }

Copy-paste khắp nơi. Quên một chỗ = lộ stack trace.

DO — throw; (giữ stack trace)

catch (Exception ex) { Log(ex); throw; }

Stack trace nguyên vẹn, dễ debug đến dòng gây lỗi.

DON'T — throw ex; (mất stack trace)

catch (Exception ex) { Log(ex); throw ex; }

Stack trace bị reset, chỉ thấy dòng throw, mất nguồn gốc lỗi.

DO — ProblemDetails format

{ "type", "title", "status", "detail" }

Chuẩn RFC 7807. Client parse một lần, dùng mọi nơi.

DON'T — Ad-hoc format

{ "error": "...", "success": false }

Mỗi endpoint format khác nhau. Client phải xử lý nhiều dạng.

Logging

DO — Message template

"User {UserId} logged in", userId

Structured properties, queryable, performance tốt.

DON'T — String interpolation

$"User {userId} logged in"

Mất structured data, không query được, allocate string.

DO — Đúng log level

LogWarning("Product {Id} not found")

Warning cho "expected failure", Error cho "unexpected failure".

DON'T — Mọi thứ là LogInformation

LogInformation("ERROR: Product not found")

Không filter được, mất ý nghĩa của log levels.

DO — Log exception object

LogError(ex, "Failed to process {OrderId}", id)

Exception object được log riêng với stack trace đầy đủ.

DON'T — Chỉ log message

LogError("Error: " + ex.Message)

Mất stack trace, mất inner exception, mất exception type.

Pitfalls thường gặp

1 Swallowing Exceptions +

Empty catch block che giấu bug. App có vẻ chạy tốt nhưng thực ra đang fail silently. Dữ liệu bị mất, logic bị skip mà không ai biết.

C#
// NGUY HIỂM: nuốt exception
try { await ProcessOrder(); }
catch { } // Bug ẩn nấp ở đây!

// ĐÚNG: log rồi xử lý
try { await ProcessOrder(); }
catch (Exception ex)
{
    _logger.LogError(ex, "Order processing failed");
    throw; // Hoặc xử lý phù hợp
}
2 Logging Sensitive Data +

Log chứa password, token, PII = vi phạm bảo mật + GDPR. Log files thường ít được bảo vệ hơn database. Attacker đọc log = có credentials.

C#
// NGUY HIỂM: lộ password trong log
_logger.LogInformation(
    "Login attempt: {Email} / {Password}",
    email, password); // KHÔNG BAO GIỜ!

// ĐÚNG: chỉ log thông tin an toàn
_logger.LogInformation(
    "Login attempt for {Email}", email);
3 throw ex; mất Stack Trace +

throw ex; reset stack trace về dòng hiện tại. Bạn mất thông tin exception ban đầu throw từ đâu — debug cực khó.

C#
// SAI: throw ex; mất nguồn gốc
catch (Exception ex)
{
    _logger.LogError(ex, "Error");
    throw ex; // Stack trace chỉ thấy dòng này!
}

// ĐÚNG: throw; giữ nguyên stack trace
catch (Exception ex)
{
    _logger.LogError(ex, "Error");
    throw; // Stack trace nguyên vẹn từ nguồn
}
4 Over-logging +

Log mọi thứ = log không gì cả. Quá nhiều log khiến: disk đầy nhanh, performance giảm, tìm thông tin quan trọng như mò kim đáy bể.

  • Đừng log mỗi dòng code — chỉ log tại ranh giới (entry/exit point, error, quyết định quan trọng)
  • Đừng log toàn bộ request/response body — chỉ log ID và metadata
  • Đừng log trong vòng lặp — log trước/sau vòng lặp với count
5 Missing Correlation ID +

Không có Correlation ID = không thể trace request. Khi production có 1000 requests/giây, làm sao bạn tìm tất cả log liên quan đến một request cụ thể?

Giải pháp: thêm CorrelationId middleware (xem Kết hợp trong Thực tế). Mỗi request có một ID duy nhất xuyên suốt mọi log entry.

6 Wrong Log Levels per Environment +

Debug logs trong production = thảm họa. EF Core log mọi SQL query ở Debug level. Nếu production set Default: Debug, mỗi request tạo hàng chục dòng log SQL.

JSON
// appsettings.Production.json — PHẢI configure riêng
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Warning"
    }
  }
}

Production Checklist

Global ExceptionHandler middleware configured (UseExceptionHandler())
ProblemDetails service registered (AddProblemDetails())
Custom exception hierarchy tạo xong (NotFoundException, ValidationException...)
Structured logging với message templates (KHÔNG string interpolation)
Serilog hoặc logging provider production-ready đã cấu hình
Log levels configured riêng cho Production (appsettings.Production.json)
Correlation ID middleware added
Sensitive data KHÔNG có trong log (passwords, tokens, PII)
DeveloperExceptionPage CHỈ trong Development
Health checks endpoint configured (/health)
Log retention policy đã set (xóa log cũ sau N ngày)