Chuyển tới nội dung chính

project-02-crm-backend-api

Phương pháp học tập

Bài học này được dẫn dắt theo chu trình học tập chủ động:

  1. Khởi động tư duy: bắt đầu bằng câu hỏi gợi mở để kích hoạt kiến thức nền.
  2. Kiến tạo kiến thức: học khái niệm cốt lõi đi kèm ví dụ có ngữ cảnh.
  3. Luyện tập có hướng dẫn: áp dụng qua mini case và ví dụ thực tế ngắn.
  4. Tự đánh giá và phản tư: dùng checklist + bài thực hành để chốt năng lực.

Mục tiêu là bạn hiểu sâu, dùng được ngay, và tự đánh giá được mức độ nắm bài.


title: "Project 2 — CRM Backend API" slug: dotnet-project-02-crm-backend-api description: "Project CRM Backend API: aggregate roots, use cases, authZ, SignalR hooks — API production-shaped cho miền khách hàng và cơ hội bán hàng." sidebar_position: 5 tags:

  • crm-backend
  • capstone
  • aspnet-core-api
  • domain-services
  • enterprise-integration keywords:
  • CRM REST API
  • sales opportunity domain
  • customer aggregate
  • authorization policies CRM
  • real-time CRM notifications enableComments: true draft: false

Tóm tắt (abstract)

Abstract (capstone). Tích hợp Stage 3 thành backend CRM: ranh giới bounded context, use case layer, bảo mật theo vai trò và kênh thời gian thực — mô phỏng đặc tả triển khai gần với sản phẩm thương mại.

1. Tổng quan dự án

CRM Backend API v1 là hệ thống quản lý quan hệ khách hàng (Customer Relationship Management) tập trung vào ba nghiệp vụ cốt lõi: quản lý khách hàng tiềm năng (Lead), khách hàng chính thức (Customer), và đầu mối liên hệ (Contact). Hệ thống hỗ trợ phân quyền theo vai trò, notification real-time khi có sự kiện quan trọng, và audit trail đầy đủ.

Mapping Module → Feature

ModuleNội dungFeature trong Project 2
Module 8REST API, Routing, MiddlewareToàn bộ endpoint CRUD, global error handler, request logging
Module 9JWT Auth, IdentityLogin/Register, refresh token, role-based access (Admin, SalesRep)
Module 10Authorization nâng caoSalesRep chỉ thấy Customer của mình (resource-based auth), Policy
Module 11SignalR, Real-timeNotification đẩy tới SalesRep khi có Lead mới được assign

2. Mục tiêu học tập

Sau khi hoàn thành Project 2, học viên có thể:

  1. Thiết kế REST API theo chuẩn RESTful với đầy đủ HTTP verb, status code, và ProblemDetails response.
  2. Triển khai JWT authentication với access token + refresh token, xử lý token rotation và revocation.
  3. Áp dụng resource-based authorization để giới hạn dữ liệu theo từng người dùng (SalesRep chỉ xem Customer của mình).
  4. Xây dựng state machine cho Lead status, đảm bảo chuyển trạng thái hợp lệ và ghi audit log.
  5. Tích hợp SignalR để gửi notification real-time khi có sự kiện nghiệp vụ (Lead được assign, trạng thái thay đổi).
  6. Viết integration test cho auth flow và CRUD operations, cùng unit test cho service layer.
  7. Tổ chức codebase theo 3-layer architecture sạch sẽ, dễ mở rộng, tuân thủ SOLID principles.

3. Domain Overview

Các Entity chính

EntityMô tảQuan hệ
UserNgười dùng hệ thống (Admin / SalesRep)Sở hữu nhiều Customer, Lead
CustomerKhách hàng chính thứcCó nhiều Contact, Lead
LeadKhách hàng tiềm năngThuộc Customer hoặc độc lập; assign cho User
ContactĐầu mối liên hệ của CustomerThuộc một Customer
NotificationThông báo hệ thốngGửi tới một User cụ thể

ERD (đơn giản)

User
├── id (PK)
├── email
├── passwordHash
├── role (Admin | SalesRep)
└── [...]

Customer
├── id (PK)
├── name
├── email
├── phone
├── industry
├── ownerId (FK → User)
├── isDeleted
├── createdBy, updatedBy
└── createdAt, updatedAt

Lead
├── id (PK)
├── title
├── value (decimal)
├── status (New | Contacted | Qualified | Converted | Lost)
├── customerId (FK → Customer, nullable)
├── assignedToId (FK → User)
├── isDeleted
└── [audit fields]

Contact
├── id (PK)
├── fullName
├── email
├── phone
├── position
├── customerId (FK → Customer)
└── [audit fields]

Notification
├── id (PK)
├── userId (FK → User)
├── message
├── isRead
├── createdAt
└── referenceId (nullable, dùng cho deep-link)

4. API Endpoints đầy đủ

Auth

MethodEndpointMô tảAuth
POST/api/auth/registerĐăng ký tài khoản mớiPublic
POST/api/auth/loginĐăng nhập, trả về access + refresh tokenPublic
POST/api/auth/refresh-tokenLàm mới access tokenPublic (refresh token)
POST/api/auth/logoutThu hồi refresh token hiện tạiBearer

Customers

MethodEndpointMô tảAuth
GET/api/customersDanh sách (phân trang, filter theo name, industry)Bearer
GET/api/customers/{id}Chi tiết CustomerBearer
POST/api/customersTạo mớiBearer
PUT/api/customers/{id}Cập nhật toàn bộBearer
PATCH/api/customers/{id}Cập nhật một phầnBearer
DELETE/api/customers/{id}Soft deleteBearer

Leads

MethodEndpointMô tảAuth
GET/api/leadsDanh sách (phân trang, filter theo status, assignedTo)Bearer
GET/api/leads/{id}Chi tiếtBearer
POST/api/leadsTạo mớiBearer
PUT/api/leads/{id}Cập nhậtBearer
DELETE/api/leads/{id}Soft deleteBearer
POST/api/leads/{id}/assignAssign Lead cho SalesRepAdmin
POST/api/leads/{id}/statusChuyển trạng thái (state machine)Bearer

Contacts

MethodEndpointMô tảAuth
GET/api/customers/{customerId}/contactsDanh sách Contact của CustomerBearer
GET/api/customers/{customerId}/contacts/{id}Chi tiếtBearer
POST/api/customers/{customerId}/contactsTạo mới ContactBearer
PUT/api/customers/{customerId}/contacts/{id}Cập nhậtBearer
DELETE/api/customers/{customerId}/contacts/{id}XóaBearer

Notifications

MethodEndpointMô tảAuth
GET/api/notificationsDanh sách (chỉ của user hiện tại)Bearer
POST/api/notifications/{id}/readĐánh dấu một notification là đã đọcBearer
POST/api/notifications/read-allĐánh dấu tất cả đã đọcBearer

5. Kiến trúc đề xuất

Folder Structure

CrmApi/
├── Controllers/
│ ├── AuthController.cs
│ ├── CustomersController.cs
│ ├── LeadsController.cs
│ ├── ContactsController.cs
│ └── NotificationsController.cs

├── Services/
│ ├── Interfaces/
│ │ ├── IAuthService.cs
│ │ ├── ICustomerService.cs
│ │ ├── ILeadService.cs
│ │ ├── IContactService.cs
│ │ └── INotificationService.cs
│ ├── AuthService.cs
│ ├── CustomerService.cs
│ ├── LeadService.cs
│ ├── ContactService.cs
│ └── NotificationService.cs

├── Repositories/
│ ├── Interfaces/
│ │ ├── ICustomerRepository.cs
│ │ └── ILeadRepository.cs
│ └── CustomerRepository.cs
│ └── LeadRepository.cs

├── Models/
│ ├── Entities/
│ │ ├── User.cs
│ │ ├── Customer.cs
│ │ ├── Lead.cs
│ │ ├── Contact.cs
│ │ └── Notification.cs
│ └── Enums/
│ ├── UserRole.cs
│ └── LeadStatus.cs

├── DTOs/
│ ├── Auth/
│ │ ├── LoginRequestDto.cs
│ │ ├── RegisterRequestDto.cs
│ │ └── TokenResponseDto.cs
│ ├── Customer/
│ │ ├── CreateCustomerDto.cs
│ │ ├── UpdateCustomerDto.cs
│ │ └── CustomerResponseDto.cs
│ ├── Lead/
│ │ ├── CreateLeadDto.cs
│ │ ├── LeadResponseDto.cs
│ │ └── ChangeLeadStatusDto.cs
│ └── Common/
│ └── PagedResult.cs

├── Hubs/
│ └── NotificationHub.cs

├── Middleware/
│ ├── ExceptionHandlingMiddleware.cs
│ └── RequestLoggingMiddleware.cs

├── Data/
│ └── AppDbContext.cs

└── Program.cs

Giải thích từng tầng:

  • Controllers: Chỉ xử lý HTTP — parse request, validate, gọi service, trả response. Không chứa business logic.
  • Services: Toàn bộ business logic, state machine, gọi notification. Phụ thuộc vào Repository interface.
  • Repositories: Truy vấn database (EF Core). Mỏng — chủ yếu là LINQ query và pagination.
  • Models/Entities: Plain class, mapping với database. Kế thừa BaseEntity để có audit fields.
  • DTOs: Tách biệt hoàn toàn với Entity — không bao giờ trả Entity trực tiếp từ API.
  • Hubs: SignalR hub, xác thực người dùng qua JWT claim khi kết nối.
  • Middleware: Cross-cutting concerns — log request, bắt exception chưa xử lý và format ProblemDetails.

6. Domain Models và DTOs

Base Entity

public abstract class BaseEntity
{
public Guid Id { get; set; } = Guid.NewGuid();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public string CreatedBy { get; set; } = string.Empty;
public string? UpdatedBy { get; set; }
public bool IsDeleted { get; set; } = false;
}

User Entity

public class User : BaseEntity
{
public string Email { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public UserRole Role { get; set; } = UserRole.SalesRep;
public string? RefreshToken { get; set; }
public DateTime? RefreshTokenExpiresAt { get; set; }

public ICollection<Customer> OwnedCustomers { get; set; } = [];
public ICollection<Lead> AssignedLeads { get; set; } = [];
public ICollection<Notification> Notifications { get; set; } = [];
}

public enum UserRole { Admin, SalesRep }

Customer Entity

public class Customer : BaseEntity
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? Phone { get; set; }
public string? Industry { get; set; }
public string? Address { get; set; }

public Guid OwnerId { get; set; }
public User Owner { get; set; } = null!;

public ICollection<Contact> Contacts { get; set; } = [];
public ICollection<Lead> Leads { get; set; } = [];
}

Lead Entity

public class Lead : BaseEntity
{
public string Title { get; set; } = string.Empty;
public decimal? Value { get; set; }
public LeadStatus Status { get; set; } = LeadStatus.New;
public string? Notes { get; set; }

public Guid? CustomerId { get; set; }
public Customer? Customer { get; set; }

public Guid? AssignedToId { get; set; }
public User? AssignedTo { get; set; }
}

public enum LeadStatus
{
New,
Contacted,
Qualified,
Converted,
Lost
}

Contact Entity

public class Contact : BaseEntity
{
public string FullName { get; set; } = string.Empty;
public string? Email { get; set; }
public string? Phone { get; set; }
public string? Position { get; set; }

public Guid CustomerId { get; set; }
public Customer Customer { get; set; } = null!;
}

Notification Entity

public class Notification
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserId { get; set; }
public User User { get; set; } = null!;
public string Message { get; set; } = string.Empty;
public bool IsRead { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public Guid? ReferenceId { get; set; } // deep-link tới Lead/Customer
}

Key DTOs

// Auth
public record LoginRequestDto(
[Required, EmailAddress] string Email,
[Required, MinLength(6)] string Password
);

public record RegisterRequestDto(
[Required, EmailAddress] string Email,
[Required, MinLength(6)] string Password,
[Required] string FullName,
UserRole Role = UserRole.SalesRep
);

public record TokenResponseDto(
string AccessToken,
string RefreshToken,
DateTime ExpiresAt
);

// Customer
public record CreateCustomerDto(
[Required, MaxLength(200)] string Name,
[Required, EmailAddress] string Email,
[Phone] string? Phone,
[MaxLength(100)] string? Industry,
string? Address
);

public record CustomerResponseDto(
Guid Id,
string Name,
string Email,
string? Phone,
string? Industry,
string OwnerName,
DateTime CreatedAt
);

// Lead
public record CreateLeadDto(
[Required, MaxLength(300)] string Title,
decimal? Value,
string? Notes,
Guid? CustomerId
);

public record ChangeLeadStatusDto(
[Required] LeadStatus NewStatus,
string? Reason
);

// Common Pagination
public record PagedResult<T>(
IEnumerable<T> Items,
int TotalCount,
int Page,
int PageSize
)
{
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
public bool HasNextPage => Page < TotalPages;
public bool HasPreviousPage => Page > 1;
}

7. Key Implementation Notes

7.1 JWT Setup (step-by-step)

Đây là tổng hợp JWT từ Module 9 — bạn tự implement theo các bước:

  1. Cài package: Microsoft.AspNetCore.Authentication.JwtBearer
  2. Cấu hình appsettings.json:
    "JwtSettings": {
    "SecretKey": "your-256-bit-secret-here",
    "Issuer": "CrmApi",
    "Audience": "CrmApiUsers",
    "AccessTokenExpiryMinutes": 15,
    "RefreshTokenExpiryDays": 7
    }
  3. Đăng ký authentication trong Program.cs với AddJwtBearer, cấu hình TokenValidationParameters.
  4. ITokenService: method GenerateAccessToken(User user) dùng JwtSecurityTokenHandler, thêm claims: sub, email, role, jti.
  5. Refresh token: lưu vào cột RefreshToken (hashed) + RefreshTokenExpiresAt trong bảng User. Khi client gọi /refresh-token, kiểm tra refresh token còn hạn và chưa bị thu hồi, cấp access token mới.
  6. Logout: xóa (null) RefreshToken trong database — đơn giản và hiệu quả.

Lưu ý bảo mật: Không lưu refresh token dạng plain text. Hash bằng SHA-256 trước khi lưu; khi verify, hash token nhận được rồi so sánh.

7.2 Lead State Machine

Lead status tuân theo sơ đồ chuyển trạng thái nghiêm ngặt:

New ──► Contacted ──► Qualified ──► Converted
│ │
└──────────────────────────────► Lost

Triển khai:

Tạo một Dictionary<LeadStatus, LeadStatus[]> ánh xạ trạng thái hiện tại → các trạng thái được phép chuyển tới:

private static readonly Dictionary<LeadStatus, LeadStatus[]> AllowedTransitions = new()
{
[LeadStatus.New] = [LeadStatus.Contacted, LeadStatus.Lost],
[LeadStatus.Contacted] = [LeadStatus.Qualified, LeadStatus.Lost],
[LeadStatus.Qualified] = [LeadStatus.Converted, LeadStatus.Lost],
[LeadStatus.Converted] = [], // terminal state
[LeadStatus.Lost] = [], // terminal state
};

Trong LeadService.ChangeStatusAsync(): kiểm tra chuyển trạng thái hợp lệ, nếu không ném InvalidOperationException (sẽ được bắt bởi middleware và format thành ProblemDetails 422). Sau khi chuyển trạng thái thành công, ghi audit log vào bảng AuditLog hoặc thêm vào Notes với timestamp.

7.3 Notification Flow khi Lead được Assign

Khi Admin gọi POST /api/leads/{id}/assign:

  1. LeadController nhận request, gọi LeadService.AssignLeadAsync(leadId, salesRepId).
  2. LeadService cập nhật Lead.AssignedToId, lưu database.
  3. LeadService gọi NotificationService.CreateAndSendAsync(userId, message, referenceId).
  4. NotificationService tạo bản ghi Notification trong database.
  5. NotificationService gọi IHubContext<NotificationHub> để push real-time:
    await _hubContext.Clients
    .User(userId.ToString())
    .SendAsync("ReceiveNotification", notificationDto);
  6. Client (web/mobile) nhận event ReceiveNotification qua kết nối SignalR.

Yêu cầu cho SignalR Hub:

  • Thêm [Authorize] lên NotificationHub.
  • Dùng IUserIdProvider tùy chỉnh để map claim sub trong JWT thành connection userId.
  • Đăng ký AddSignalR() và map hub tại /hubs/notifications.

7.4 Resource-Based Authorization (SalesRep chỉ thấy Customer của mình)

Không dùng [Authorize(Roles = "...")] đơn thuần — dùng resource-based authorization:

  1. Tạo CustomerAuthorizationHandler : AuthorizationHandler<SameOwnerRequirement, Customer>.
  2. Trong handler, lấy userId từ context.User, so sánh với resource.OwnerId.
  3. Đăng ký requirement và handler trong Program.cs.
  4. Trong CustomerService, sau khi lấy Customer từ DB, gọi:
    var authResult = await _authorizationService
    .AuthorizeAsync(user, customer, "SameOwnerPolicy");
    if (!authResult.Succeeded)
    throw new ForbiddenException();
  5. Admin bypass bằng cách check role trước trong handler.

8. Yêu cầu kỹ thuật

Validation

  • Dùng DataAnnotations trên DTO kết hợp với [ApiController] để tự động trả 400 + ProblemDetails khi validation thất bại.
  • Với logic phức tạp (ví dụ: email đã tồn tại), ném ValidationException trong service layer và bắt tại middleware.

ProblemDetails chuẩn RFC 7807

Tất cả lỗi phải trả về dạng:

{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Validation Error",
"status": 400,
"detail": "Email đã được sử dụng.",
"traceId": "0HMVBCA0ERQFD:00000001"
}

Cấu hình trong Program.cs:

builder.Services.AddProblemDetails();
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
new UnprocessableEntityObjectResult(/* custom ProblemDetails */);
});

Audit Fields

Mọi entity kế thừa BaseEntity đều có CreatedBy, UpdatedBy, CreatedAt, UpdatedAt. Tự động điền trong AppDbContext.SaveChangesAsync():

// Override SaveChangesAsync trong AppDbContext
// Lấy current user từ IHttpContextAccessor
// Gán CreatedBy/UpdatedBy/UpdatedAt cho các entry bị thêm/sửa

Soft Delete

Không dùng DELETE xóa vật lý. Set IsDeleted = true và lọc trong query:

// Global query filter trong AppDbContext
modelBuilder.Entity<Customer>().HasQueryFilter(c => !c.IsDeleted);
modelBuilder.Entity<Lead>().HasQueryFilter(l => !l.IsDeleted);

9. Testing Requirements

Integration Tests (bắt buộc)

Dùng WebApplicationFactory<Program> + in-memory database:

  • Auth flow: Register → Login → nhận token → gọi protected endpoint → Refresh → Logout → gọi lại bị 401.
  • Customer CRUD: tạo, đọc, cập nhật, soft delete, đảm bảo bản ghi không xuất hiện trong GET list sau khi delete.
  • Lead state machine: thử chuyển trạng thái hợp lệ và không hợp lệ, kiểm tra response code và message.
  • Authorization: SalesRep A không thể đọc Customer của SalesRep B (expect 403).

Unit Tests

Service layer phải được test độc lập (mock Repository):

  • LeadService.ChangeStatusAsync — test tất cả transition hợp lệ/không hợp lệ.
  • AuthService.LoginAsync — test email không tồn tại, sai mật khẩu, tài khoản bị khóa.
  • CustomerService.GetByIdAsync — test resource-based auth pass/fail.

Postman Collection

Export collection JSON có ít nhất:

  • Folder Auth: 4 request (register, login, refresh, logout) với test script kiểm tra status code và lưu token vào environment variable.
  • Folder Customers: 6 request với pre-request script tự động attach Bearer token.
  • Folder Leads: 7 request, bao gồm assign và đổi status.

10. Bonus Features

Hoàn thành các yêu cầu cơ bản xong, có thể nâng cấp để demo ấn tượng hơn:

Bulk Import từ CSV

  • POST /api/customers/import nhận file .csv (multipart/form-data).
  • Parse bằng CsvHelper hoặc thủ công, validate từng dòng.
  • Trả về kết quả: số dòng thành công, danh sách dòng lỗi kèm lý do.
  • Dùng IFormFile và xử lý bất đồng bộ để không block thread.

Export Báo cáo

  • GET /api/reports/leads?from=2024-01-01&to=2024-12-31 trả về file Excel hoặc CSV.
  • Dùng ClosedXML (Excel) hoặc viết CSV thủ công với StreamWriter.
  • Set header Content-Disposition: attachment; filename="leads-report.xlsx".

Dashboard Endpoint

GET /api/dashboard

Response mẫu:

{
"totalCustomers": 142,
"totalLeads": 87,
"leadsByStatus": {
"New": 20, "Contacted": 18, "Qualified": 15,
"Converted": 30, "Lost": 4
},
"conversionRate": 0.345,
"myOpenLeads": 12
}

Dữ liệu thay đổi tùy role: Admin thấy toàn hệ thống, SalesRep chỉ thấy của mình.


11. Checklist nộp bài

Kiến trúc & Code Quality

  • Folder structure tuân theo 3-layer như đề xuất
  • Không có business logic trong Controller
  • Không có truy vấn DB trực tiếp trong Service
  • Dùng Interface cho mọi dependency (DI-friendly)
  • Không có magic string — dùng const hoặc enum

API & Behavior

  • Tất cả 24 endpoint hoạt động đúng HTTP method và status code
  • Pagination hoạt động cho Customer và Lead list
  • State machine Lead từ chối transition không hợp lệ với 422
  • Soft delete hoạt động — bản ghi không xuất hiện trong GET list
  • Audit fields được điền tự động (CreatedBy, UpdatedBy)

Auth & Security

  • Register/Login trả về access + refresh token
  • Refresh token endpoint hoạt động và cấp token mới
  • Logout thu hồi refresh token
  • SalesRep không thể xem Customer của SalesRep khác (test bằng Postman)
  • Admin có thể xem tất cả

Real-time

  • SignalR hub được bảo vệ bằng JWT
  • Khi assign Lead, SalesRep nhận notification real-time (test bằng 2 tab browser)
  • Notification lưu vào DB, đọc được qua REST API

Testing

  • Ít nhất 3 integration test pass
  • Ít nhất 5 unit test cho service layer pass
  • Postman collection chạy được end-to-end

Documentation

  • Swagger UI có description cho mọi endpoint
  • README hướng dẫn dotnet run và seed data mẫu

11b. Kiểm tra & Thực hành (100 điểm)

Bối cảnh giáo trình (mục tiêu xuyên suốt)

Trục CRM — một mục tiêu rõ: bạn hướng tới CRM production trong From Zero → Senior .NET (Backend-first). Giáo trình ưu tiên backend, bám chuỗi API → dữ liệu → vận hành → kiến trúc phân tán, thay vì trải rộng theo hướng full-stack. Mỗi module củng cố một lớp kỹ năng trên cùng một CRM học tập bạn phát triển xuyên suốt khóa.

Chuỗi năng lựcĐóng góp vào CRM học tập
Giai đoạn 1 — FoundationĐọc hiểu yêu cầu, mô hình client–server, Git — nền collaboration khi CRM lớn dần.
Giai đoạn 2 — C# + P1Domain, OOP, LINQ, async, DI — rule nghiệp vụ & tầng ứng dụng (Inventory là bản mẫu trước CRM).
Giai đoạn 3 — ASP.NET + P2Host, routing, validation, JWT, SignalR — CRM Backend API thực tế.
Giai đoạn 4 — Data + P3SQL, EF, cache, job, container — CRM ổn định & có thể triển khai.
Giai đoạn 5 — Senior + FinalClean Architecture, event/outbox, gateway, observability, perf — CRM/ERP chịu tải & có chủ.

Vai trò giai đoạn này trong chuỗi

ASP.NET Core + Project 2 (CRM API): pipeline, Web API, JWT, SignalR — bề mặt backend mà client/tenant gọi vào.

Vị trí bài học (trước / sau)

MốcĐiểm nối trong giáo trình
TrướcModule 11 — SignalR
Tiếp theoModule 12 — SQL Deep Dive

Quy trình làm bài (hệ thống)

BướcViệc cần làmOutput nên có
1Làm Quiz trước, tự ghi đáp án + 1 dòng lý doKhông xem đáp án; giữ bản nháp
2Đối chiếu Đáp án; sửa hiểu sai, ghi takeaway vào README hoặc nhật ký học3–5 bullet “tôi đã hiểu…”
3Làm Lab theo rubric; trong README nêu phần CRM nào được chạm (Lead, Customer, Deal, Billing…)Repo / zip + ảnh dotnet run hoặc log
4Tự chấm theo bảng điểm; chuẩn bị 1 phút “vấn đáp” nếu mentor hỏi ngẫu nhiênĐiểm + chỗ còn yếu

Phần A — Quiz trắc nghiệm (20 điểm)

Gợi ý làm bài: mỗi câu 4 điểm. Liên tưởng CRM API (P2): auth đa vai, tenant, SignalR — các rủi ro thật khi CRM mở rộng.

Mỗi câu 4 điểm.

  1. Refresh token pattern chủ yếu giải quyết?
    A. Tăng kích thước JWT B. Access token TTL ngắn + thu hồi/rotate refresh C. Bỏ HTTPS D. Thay EF

    Đáp án: B.

  2. SalesRep không xem được Customer của người khác — kiểm tra này thuộc?
    A. Chỉ UX B. Authorization (resource/data scope) C. Compiler D. Docker

    Đáp án: B.

  3. SignalR hub cần JWT thường vì?
    A. WebSocket không có header Authorization mặc định như fetch B. JWT thay SQL C. Bắt buộc HTTP/1.0 D. Swagger yêu cầu

    Đáp án: A (tinh thần client negotiate).

  4. State machine Lead trả 422 cho transition sai nghĩa là?
    A. Server crash B. Business rule từ chối — client gửi trạng thái không hợp lệ C. Chưa đăng nhập D. DB full

    Đáp án: B.

  5. WebApplicationFactory trong project này để?
    A. Deploy Azure B. Integration test in-process host + HttpClient C. Tạo migration D. Build NPM

    Đáp án: B.

Phần B — Lab thực tế (80 điểm)

Lab & rubric: trong README, map từng nhóm điểm rubric sang phần CRM (Lead, Customer, Deal, Billing, Notification…) và artifact (test, log, screenshot, migration).

Cộng từ mục 11. Checklist: Architecture 10, API & Behavior 25, Auth 20, Real-time 10, Testing 10, Documentation 5 — trừ nếu bullet tương ứng chưa đạt (đề xuất −3 mỗi bullet “lớn”, −1–2 bullet nhỏ).

Ngưỡng tổng điểm (Quiz 20 + Lab 80)

MứcĐiểmÝ nghĩa
Đạt≥ 70Đủ nền để học module kế mà không “lủng” kiến thức cốt lõi.
Khá≥ 85Có minh chứng code + nêu được liên hệ CRM rõ ràng trong README.
Giỏi≥ 95Có mở rộng / edge case / ADR ngắn / test bổ trợ (tùy rubric từng bài).

Phần C — Reflect & nối chuỗi (không chấm điểm)

Viết 5–8 câu (README hoặc docs/learning-log.md):

  1. Kiến thức module / project này sẽ được tái sử dụng trực tiếp ở module hoặc project CRM nào tiếp theo? (ghi tên module / P2 / P3 / Final).
  2. Nếu bỏ qua phần lab, rủi ro lớn nhất cho CRM ở giai đoạn sau là gì?
  3. Một quyết định kỹ thuật (nhỏ) bạn sẵn sàng ghi thành ADR một đoạn sau khi làm lab.

12. Cầu nối sang Project 3

Project 2 dùng in-memory database (hoặc SQLite đơn giản) để tập trung vào API design và auth. Project 3 sẽ nâng cấp cùng codebase này với:

Thành phầnProject 2 (hiện tại)Project 3 (sắp tới)
DatabaseIn-memory / SQLiteSQL Server với EF Core Migrations
CachingKhôngRedis (distributed cache cho token blacklist)
Background JobsKhôngHangfire (gửi email reminder, report schedule)
ContainerizationKhôngDocker + docker-compose (api + db + redis)
Multi-tenantKhôngSchema-per-tenant hoặc row-level isolation
PerformanceKhông đoBenchmark, query optimization, index

Khi chuyển sang Project 3, bạn không viết lại — bạn refactor và mở rộng. Đây là cách senior developer làm việc thật: incremental improvement trên nền tảng vững chắc.

Mở rộng và đào sâu

Mục đích của phần này

Phần này giúp bạn nắm bản chất của chủ đề: học để giải quyết vấn đề gì, đặt ở đâu trong kiến trúc backend, và vì sao cách làm này quan trọng trong dự án thật.

Khung hiểu nhanh

  • Bài toán: vấn đề thực tế cần giải quyết.
  • Cách tiếp cận: kỹ thuật/chuẩn áp dụng trong bài.
  • Kết quả mong đợi: đầu ra đúng, dễ bảo trì, dễ mở rộng.

Mini case study

Tình huống

Chọn một use case nhỏ trong CRM/ERP liên quan trực tiếp đến bài học này, mô tả rõ đầu vào, đầu ra và tiêu chí thành công.

Đáp án gợi ý

  • Tách bài toán thành các bước xử lý rõ ràng.
  • Chọn điểm đặt logic đúng tầng (API, service, data, job...).
  • Bổ sung ít nhất một trường hợp biên để kiểm tra tính ổn định.

Ví dụ thực tế nhanh

  • Một ví dụ áp dụng trực tiếp vào luồng CRM/ERP hiện tại.
  • Một lỗi phổ biến dễ gặp khi triển khai thật.
  • Một cách kiểm tra nhanh để tự xác nhận bạn đã hiểu đúng.

Checklist trước khi sang bài tiếp theo

  • Bạn đã nắm được các khái niệm chính và giải thích lại được bằng ví dụ của riêng mình.
  • Bạn đã chạy hoặc mô phỏng ít nhất một ví dụ trong bài.
  • Bạn đã hoàn thành phần quiz/lab cơ bản (hoặc ít nhất tự làm lại theo trí nhớ).
  • Bạn đã ghi lại 3 ý chính: học được gì, còn vướng gì, sẽ áp dụng ở đâu trong CRM.