373 lines
12 KiB
Markdown
373 lines
12 KiB
Markdown
---
|
|
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<CreateOrderCommand, OrderResponse>
|
|
{
|
|
private readonly IEnumerable<ILineItemStrategy> _strategies;
|
|
|
|
public async Task<OrderResponse> 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<Shop>
|
|
{
|
|
public void Configure(EntityTypeBuilder<Shop> builder)
|
|
{
|
|
builder.Property(s => s.Features)
|
|
.HasColumnName("features_config")
|
|
.HasColumnType("jsonb")
|
|
.HasConversion(
|
|
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
|
|
v => JsonSerializer.Deserialize<ShopFeatures>(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<bool> 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<ShopFeatures> 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)
|