--- name: multi-vertical-architecture description: Multi-vertical Architecture patterns cho đa ngành hàng (Retail, F&B, Services). Use for Order Service orchestration, Strategy Pattern, Satellite Services, và Shop feature configuration. compatibility: ".NET 10, PostgreSQL, JSONB" metadata: author: Velik Ho version: "1.0" references: "Clean Architecture, Domain-Driven Design" --- # Multi-vertical Architecture / Kiến Trúc Đa Ngành Hàng ## When to Use This Skill / Khi Nào Sử Dụng Use this skill when: - Thiết kế hệ thống hỗ trợ nhiều ngành kinh doanh (Retail, F&B, Services) - Tạo Order Service với Strategy Pattern để xử lý đơn hàng đa loại - Thêm Vertical Engine mới (Inventory, Booking, F&B Engine...) - Cấu hình Shop features và business settings động ## Core Principle / Nguyên Tắc Cốt Lõi > **Merchant Service quản lý "AI" (Identity) và "LUẬT" (Configuration), còn "CÁCH" (Execution) được ủy quyền cho các service vệ tinh.** ## Architecture Layers / Các Tầng Kiến Trúc ``` ┌─────────────────────────────────────────────────────────┐ │ 📱 Client Layer │ │ (POS, Web Dashboard, Mobile App) │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ ⚡ API Gateway (Traefik) │ └─────────────────────────────────────────────────────────┘ │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ 🟡 Core Layer │ │ 🔴 Orchestration│ │ 🔵 Vertical │ │ │ │ Layer │ │ Engines │ │ • IAM │ │ • Catalog │ │ • Inventory │ │ • Merchant │ │ • Order │ │ • Booking │ │ • Wallet │ │ │ │ • F&B Engine │ └───────────────┘ └───────────────┘ └───────────────┘ ``` ### Layer Responsibilities | Layer | Trách Nhiệm | Services | |-------|-------------|----------| | **Core** | Identity & Configuration | IAM, Merchant, Wallet | | **Orchestration** | Business logic coordination | Catalog, Order | | **Vertical Engines** | Domain-specific execution | Inventory, Booking, F&B | ## Key Patterns / Mẫu Chính ### 1. Shop Feature Flags Sử dụng JSONB để lưu cấu hình động: ```csharp // Value Object mapping với JSONB public record ShopFeatures( bool HasInventory = false, // Bật Inventory Service bool HasBooking = false, // Bật Booking Service bool HasTables = false, // Bật quản lý bàn bool HasKitchen = false, // Bật KDS bool HasShipping = false, // Bật vận chuyển bool HasDelivery = false // Bật giao hàng ); // Entity sử dụng public class Shop : Entity, IAggregateRoot { public BusinessCategory Category { get; private set; } public ShopFeatures Features { get; private set; } public JsonDocument? Settings { get; private set; } } ``` ### 2. Business Category ```csharp public enum BusinessCategory { Retail = 1, // Bán lẻ hàng hóa FoodBeverage = 2, // Nhà hàng, quán cafe Services = 3, // Spa, salon, phòng khám Other = 99 // Hybrid } ``` ### 3. Strategy Pattern for Order Processing **Interface:** ```csharp public interface ILineItemStrategy { ProductType SupportedType { get; } Task ValidateAsync(OrderItem item, Guid shopId); Task ExecuteAsync(OrderItem item, Guid shopId); } ``` **Retail Strategy:** ```csharp public class RetailStrategy : ILineItemStrategy { private readonly IInventoryService _inventory; public ProductType SupportedType => ProductType.Physical; public async Task ValidateAsync(OrderItem item, Guid shopId) { // Gọi Inventory Service: Kiểm tra tồn kho var stock = await _inventory.CheckStockAsync(item.ProductId, shopId); if (stock.Available < item.Quantity) throw new InsufficientStockException(item.ProductId); } public async Task ExecuteAsync(OrderItem item, Guid shopId) { // Gọi Inventory Service: Trừ tồn kho await _inventory.DeductStockAsync(item.ProductId, item.Quantity, shopId); } } ``` **Service Strategy (Spa/Salon):** ```csharp public class ServiceStrategy : ILineItemStrategy { private readonly IBookingService _booking; public ProductType SupportedType => ProductType.Service; public async Task ValidateAsync(OrderItem item, Guid shopId) { // Kiểm tra slot + nhân viên khả dụng var available = await _booking.CheckAvailabilityAsync( item.ServiceId, item.RequestedTime, shopId); if (!available) throw new SlotNotAvailableException(item.RequestedTime); } public async Task ExecuteAsync(OrderItem item, Guid shopId) { // Tạo appointment await _booking.CreateAppointmentAsync(item, shopId); } } ``` **F&B Strategy:** ```csharp public class FnbStrategy : ILineItemStrategy { private readonly IEventBus _eventBus; public ProductType SupportedType => ProductType.PreparedFood; public async Task ExecuteAsync(OrderItem item, Guid shopId) { // Bắn event xuống bếp await _eventBus.PublishAsync(new KitchenTicketCreated { OrderItemId = item.Id, ProductName = item.ProductName, Quantity = item.Quantity, Notes = item.Notes, Station = item.KitchenStation }); } } ``` ### 4. Order Handler with Strategy Factory ```csharp public class CreateOrderHandler : IRequestHandler { private readonly IEnumerable _strategies; public async Task Handle(CreateOrderCommand request, CancellationToken ct) { var order = Order.Create(request.ShopId, request.CustomerId); // Phase 1: Validate all items foreach (var item in request.Items) { var strategy = _strategies.First(s => s.SupportedType == item.ProductType); await strategy.ValidateAsync(item, request.ShopId); order.AddItem(item); } // Phase 2: Process payment await ProcessPaymentAsync(order); // Phase 3: Execute all items foreach (var item in order.Items) { var strategy = _strategies.First(s => s.SupportedType == item.ProductType); await strategy.ExecuteAsync(item, request.ShopId); } return order.ToResponse(); } } ``` ## Database Patterns / Mẫu Database ### JSONB Columns for Dynamic Config ```sql ALTER TABLE shops ADD COLUMN features_config JSONB DEFAULT '{}', ADD COLUMN vertical_settings JSONB DEFAULT '{}', ADD COLUMN business_category VARCHAR(50) DEFAULT 'Other'; -- GIN index for JSONB queries CREATE INDEX idx_shops_features ON shops USING GIN (features_config); ``` ### EF Core JSONB Configuration ```csharp public class ShopConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.Property(s => s.Features) .HasColumnName("features_config") .HasColumnType("jsonb") .HasConversion( v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), v => JsonSerializer.Deserialize(v, JsonSerializerOptions.Default)! ); builder.Property(s => s.Settings) .HasColumnName("vertical_settings") .HasColumnType("jsonb"); } } ``` ## Vertical Engines Overview / Tổng Quan Engines ### Inventory Service | Feature | Retail | F&B | |---------|--------|-----| | Stock tracking | ✅ Thành phẩm | ✅ Nguyên liệu | | Deduction logic | 1:1 (Bán 1 trừ 1) | Recipe-based (Định lượng) | | Low stock alerts | ✅ | ✅ | ### Booking Service | Entity | Mô Tả | |--------|-------| | StaffSchedule | Lịch làm việc nhân viên | | Resource | Tài nguyên (Giường, Phòng) | | Appointment | Cuộc hẹn | | TimeSlot | Slot thời gian khả dụng | ### F&B Engine | Entity | Mô Tả | |--------|-------| | Table | Sơ đồ bàn | | Session | Phiên ăn (khách ngồi tại bàn) | | KitchenTicket | Phiếu bếp cho KDS | | Recipe | Định lượng nguyên liệu | ## Common Mistakes / Lỗi Thường Gặp ### 1. Đặt logic nghiệp vụ trong Core Layer ```csharp // ❌ BAD: Logic inventory trong Merchant Service public class ShopService { public async Task ProcessSale(Guid productId, int quantity) { // Logic tồn kho không thuộc về Merchant Service! var stock = await _db.Stocks.FindAsync(productId); stock.Quantity -= quantity; } } // ✅ GOOD: Merchant Service chỉ trả về cấu hình public class ShopService { public async Task GetShopFeaturesAsync(Guid shopId) { var shop = await _shopRepository.GetByIdAsync(shopId); return shop.Features; // Order Service sẽ gọi Inventory Service } } ``` ### 2. Dùng if-else thay vì Strategy Pattern ```csharp // ❌ BAD: If-else spaghetti public async Task ProcessOrder(Order order) { foreach (var item in order.Items) { if (item.Type == "Physical") await _inventory.DeductStock(item); else if (item.Type == "Service") await _booking.CreateAppointment(item); else if (item.Type == "PreparedFood") await _kitchen.CreateTicket(item); // Thêm ngành mới = thêm else if } } // ✅ GOOD: Strategy Pattern public async Task ProcessOrder(Order order) { foreach (var item in order.Items) { var strategy = _strategies.First(s => s.SupportedType == item.ProductType); await strategy.ExecuteAsync(item, order.ShopId); // Thêm ngành mới = thêm Strategy class mới } } ``` ### 3. Không sử dụng Feature Flags ```csharp // ❌ BAD: Gọi service không kiểm tra feature public async Task ProcessOrder(Order order) { await _inventory.DeductStock(order); // Sẽ lỗi nếu shop không có Inventory! } // ✅ GOOD: Kiểm tra feature trước public async Task ProcessOrder(Order order) { var features = await _merchantService.GetShopFeaturesAsync(order.ShopId); if (features.HasInventory) await _inventory.DeductStock(order); } ``` ## Quick Reference / Tham Chiếu Nhanh | Concept | Pattern | Áp Dụng | |---------|---------|---------| | Cấu hình động | JSONB + Value Object | ShopFeatures, Settings | | Xử lý đa loại | Strategy Pattern | ILineItemStrategy | | Giao tiếp service | Integration Events | KitchenTicketCreated | | Mở rộng ngành | Satellite Architecture | Vertical Engines | ## Resources / Tài Nguyên - [Implementation Plan](file:///Users/velikho/.gemini/antigravity/brain/fc592f5c-fd0a-4a19-87d0-d09926a1cdde/implementation_plan.md) - [Architecture Doc (EN)](file:///Users/velikho/Desktop/WORKING/Base/docs/en/architecture/multi-vertical-architecture.md) - [Architecture Doc (VI)](file:///Users/velikho/Desktop/WORKING/Base/docs/vi/architecture/multi-vertical-architecture.md) - [Domain-Driven Design](../domain-driven-design/SKILL.md) - [CQRS + MediatR](../cqrs-mediatr/SKILL.md) - [Inter-Service Communication](../inter-service-communication/SKILL.md) - [Repository Pattern](../repository-pattern/SKILL.md)