feat(deployments): Update local environment configuration for IAM service and Redis integration

- Modified local `.env` and `.env.local` files to include external Redis configuration and IAM service database connection details.
- Updated `docker-compose.yml` to disable local Redis service in favor of an external Redis instance.
- Added JWT configuration parameters for the IAM service, enhancing security and token management.
- Revised example environment file to reflect new configuration options for external services.
- Enhanced documentation to clarify setup instructions for local development with external dependencies.
This commit is contained in:
Ho Ngoc Hai
2026-01-13 01:03:33 +07:00
parent 4a1a0ef79c
commit 71a5d8d4ed
10 changed files with 832 additions and 938 deletions

View File

@@ -15,12 +15,24 @@ JWT_ID_EXPIRES_IN=1h
# ENCRYPTION
ENCRYPTION_KEY='460d261122522a6da8df4b9116a55d97432102a524cf055c04118265f0e51693'
# INFRA
REDIS_HOST=redis
# INFRA - External Redis
REDIS_HOST=167.114.174.113
REDIS_PORT=6379
REDIS_PASSWORD=Velik@2026
DATABASE_URL='postgresql://neondb_owner:npg_Ssfy6HKO0cXI@ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech/iam-service?sslmode=require&channel_binding=require'
# OBSERVABILITY
TRACING_ENABLED=false
JAEGER_ENDPOINT=http://jaeger:14268/api/traces
METRICS_ENABLED=true
# IAM SERVICE .NET
IAM_NET_DATABASE_URL='Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=iam_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require'
REDIS_EXTERNAL_HOST=167.114.174.113
REDIS_EXTERNAL_PORT=6379
REDIS_EXTERNAL_PASSWORD=Velik@2026
REDIS_EXTERNAL_DATABASE=0
JWT_ISSUER=goodgo-platform
JWT_AUDIENCE=goodgo-services
JWT_ACCESS_TOKEN_EXPIRY_MINUTES=15
JWT_REFRESH_TOKEN_EXPIRY_DAYS=7

View File

@@ -59,3 +59,22 @@ METRICS_ENABLED=true
EMAIL_FROM=noreply@goodgo.vn
REDIS_URL=redis://redis:6379
# =============================================================================
# IAM SERVICE .NET CONFIGURATION
# =============================================================================
# Neon PostgreSQL for IAM .NET Service
IAM_NET_DATABASE_URL="Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=iam_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require"
# External Redis
REDIS_EXTERNAL_HOST=167.114.174.113
REDIS_EXTERNAL_PORT=6379
REDIS_EXTERNAL_PASSWORD=Velik@2026
REDIS_EXTERNAL_DATABASE=0
# JWT Configuration for .NET Service
JWT_ISSUER=goodgo-platform
JWT_AUDIENCE=goodgo-services
JWT_ACCESS_TOKEN_EXPIRY_MINUTES=15
JWT_REFRESH_TOKEN_EXPIRY_DAYS=7

View File

@@ -27,24 +27,24 @@ services:
# SHARED INFRASTRUCTURE
# ===========================================================================
# Redis - Shared cache and session store
redis:
image: redis:7-alpine
container_name: redis-cache-local
command: redis-server /etc/redis/redis.conf
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
- ../../infra/databases/redis/redis.conf:/etc/redis/redis.conf
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- microservices-network
restart: unless-stopped
# Redis - DISABLED (using external Redis 167.114.174.113)
# redis:
# image: redis:7-alpine
# container_name: redis-cache-local
# command: redis-server /etc/redis/redis.conf
# ports:
# - "${REDIS_PORT:-6379}:6379"
# volumes:
# - redis_data:/data
# - ../../infra/databases/redis/redis.conf:/etc/redis/redis.conf
# healthcheck:
# test: ["CMD", "redis-cli", "ping"]
# interval: 5s
# timeout: 3s
# retries: 5
# networks:
# - microservices-network
# restart: unless-stopped
# Traefik - API Gateway and Reverse Proxy
traefik:
@@ -81,59 +81,6 @@ services:
# BACKEND SERVICES
# ===========================================================================
# IAM Service - Identity and Access Management
iam-service:
build:
context: ../..
dockerfile: services/iam-service/Dockerfile
container_name: iam-service-local
env_file:
- .env.local
environment:
# Service-specific
- PORT=5001
- SERVICE_NAME=iam-service
- API_VERSION=${API_VERSION:-v1}
# Shared from .env.local (explicit for clarity)
- NODE_ENV=${NODE_ENV:-development}
- LOG_LEVEL=${LOG_LEVEL:-debug}
- DATABASE_URL=${DATABASE_URL}
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PORT=${REDIS_PORT:-6379}
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-15m}
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
- JWT_REFRESH_EXPIRES_IN=${JWT_REFRESH_EXPIRES_IN:-7d}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- CORS_ORIGIN=${CORS_ORIGIN}
- TRACING_ENABLED=${TRACING_ENABLED:-false}
- JAEGER_ENDPOINT=${JAEGER_ENDPOINT}
ports:
- "5001:5001"
depends_on:
redis:
condition: service_healthy
traefik:
condition: service_started
networks:
- microservices-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
# Traefik service discovery
- "traefik.enable=true"
- "traefik.http.routers.iam-service.rule=PathPrefix(`/api/v1/auth`) || PathPrefix(`/api/v1/users`) || PathPrefix(`/api/v1/identity`) || PathPrefix(`/api/v1/access`) || PathPrefix(`/api/v1/governance`) || PathPrefix(`/api/v1/rbac`) || PathPrefix(`/api/v1/mfa`) || PathPrefix(`/api/v1/sessions`)"
- "traefik.http.routers.iam-service.entrypoints=web"
- "traefik.http.services.iam-service.loadbalancer.server.port=5001"
- "traefik.http.services.iam-service.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.iam-service.loadbalancer.healthcheck.interval=10s"
# Storage Service .NET - File Storage Management
storage-service:
build:
@@ -154,8 +101,6 @@ services:
ports:
- "5002:8080"
depends_on:
redis:
condition: service_healthy
minio:
condition: service_healthy
traefik:
@@ -213,8 +158,6 @@ services:
ports:
- "5003:8080"
depends_on:
redis:
condition: service_healthy
traefik:
condition: service_started
networks:
@@ -234,6 +177,57 @@ services:
- "traefik.http.services.social-service.loadbalancer.healthcheck.path=/health/live"
- "traefik.http.services.social-service.loadbalancer.healthcheck.interval=10s"
# IAM Service .NET - Identity and Access Management (Duende IdentityServer)
iam-service-net:
image: goodgo/iam-service-net:latest
container_name: iam-service-net-local
env_file:
- .env.local
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080
# EN: Database - Neon PostgreSQL (from .env)
# VI: Cơ sở dữ liệu - Neon PostgreSQL (từ .env)
- ConnectionStrings__DefaultConnection=${IAM_NET_DATABASE_URL}
# EN: Redis Cache (external)
# VI: Cache Redis (bên ngoài)
- Redis__Host=${REDIS_EXTERNAL_HOST}
- Redis__Port=${REDIS_EXTERNAL_PORT}
- Redis__Password=${REDIS_EXTERNAL_PASSWORD}
- Redis__Database=${REDIS_EXTERNAL_DATABASE}
# EN: JWT Configuration
# VI: Cấu hình JWT
- Jwt__Secret=${JWT_SECRET}
- Jwt__Issuer=${JWT_ISSUER:-goodgo-platform}
- Jwt__Audience=${JWT_AUDIENCE:-goodgo-services}
- Jwt__AccessTokenExpiryMinutes=${JWT_ACCESS_TOKEN_EXPIRY_MINUTES:-15}
- Jwt__RefreshTokenExpiryDays=${JWT_REFRESH_TOKEN_EXPIRY_DAYS:-7}
# EN: Features
# VI: Tính năng
- Features__SwaggerEnabled=true
- Features__DetailedErrors=true
ports:
- "5001:8080"
depends_on:
traefik:
condition: service_started
networks:
- microservices-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health/live"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
labels:
- "traefik.enable=true"
- "traefik.http.routers.iam-service-net.rule=PathPrefix(`/api/v1/iam`) || PathPrefix(`/api/v1/auth`) || PathPrefix(`/api/v1/users`)"
- "traefik.http.routers.iam-service-net.entrypoints=web"
- "traefik.http.services.iam-service-net.loadbalancer.server.port=8080"
- "traefik.http.services.iam-service-net.loadbalancer.healthcheck.path=/health/live"
- "traefik.http.services.iam-service-net.loadbalancer.healthcheck.interval=10s"
# ===========================================================================
# FRONTEND APPLICATIONS (Temporarily disabled)
# ===========================================================================
@@ -335,8 +329,8 @@ services:
# VOLUMES
# =============================================================================
volumes:
redis_data:
driver: local
# redis_data:
# driver: local
minio_data:
driver: local
# prometheus_data:

View File

@@ -66,6 +66,26 @@ JAEGER_ENDPOINT=http://jaeger:14268/api/traces
# Prometheus Metrics (exposed by each service at /metrics)
METRICS_ENABLED=true
# =============================================================================
# IAM SERVICE .NET CONFIGURATION
# =============================================================================
# Neon PostgreSQL for IAM .NET Service
# Get from https://console.neon.tech
IAM_NET_DATABASE_URL=Host=your-neon-host.neon.tech;Port=5432;Database=iam_service;Username=your-user;Password=your-password;SSL Mode=Require
# External Redis (if using external Redis instead of local container)
REDIS_EXTERNAL_HOST=redis
REDIS_EXTERNAL_PORT=6379
REDIS_EXTERNAL_PASSWORD=
REDIS_EXTERNAL_DATABASE=0
# JWT Configuration for .NET Service
JWT_ISSUER=goodgo-platform
JWT_AUDIENCE=goodgo-services
JWT_ACCESS_TOKEN_EXPIRY_MINUTES=15
JWT_REFRESH_TOKEN_EXPIRY_DAYS=7
# =============================================================================
# EXTERNAL SERVICES (Optional)
# =============================================================================

View File

@@ -20,11 +20,11 @@ COPY src/ ./src/
# EN: Build the application
# VI: Build ứng dụng
WORKDIR "/src/src/IamService.API"
RUN dotnet build "IamService.API.csproj" -c Release -o /app/build --no-restore
RUN dotnet build "IamService.API.csproj" -c Release -o /app/build
# Publish stage / Giai đoạn publish
FROM build AS publish
RUN dotnet publish "IamService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
RUN dotnet publish "IamService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Runtime stage / Giai đoạn runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final

View File

@@ -1,7 +1,7 @@
version: '3.8'
# EN: Docker Compose for local development
# VI: Docker Compose cho phát triển local
# EN: Docker Compose for local development with external services
# VI: Docker Compose cho phát triển local với dịch vụ bên ngoài
services:
iamservice-api:
@@ -10,16 +10,34 @@ services:
dockerfile: Dockerfile
container_name: iamservice-api
ports:
- "5001:8080"
- "${API_PORT:-5001}:8080"
env_file:
- .env
environment:
- ASPNETCORE_ENVIRONMENT=Development
- DATABASE_URL=Host=postgres;Port=5432;Database=iamservice_db;Username=postgres;Password=postgres
- REDIS_URL=redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# EN: Override for container environment
# VI: Ghi đè cho môi trường container
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development}
- ASPNETCORE_URLS=http://+:8080
# EN: Database - Neon PostgreSQL
# VI: Cơ sở dữ liệu - Neon PostgreSQL
- ConnectionStrings__DefaultConnection=${DATABASE_URL}
# EN: Redis Cache
# VI: Cache Redis
- Redis__Host=${REDIS_HOST}
- Redis__Port=${REDIS_PORT}
- Redis__Password=${REDIS_PASSWORD}
- Redis__Database=${REDIS_DATABASE:-0}
# EN: JWT Configuration
# VI: Cấu hình JWT
- Jwt__Secret=${JWT_SECRET}
- Jwt__Issuer=${JWT_ISSUER}
- Jwt__Audience=${JWT_AUDIENCE}
- Jwt__AccessTokenExpiryMinutes=${JWT_ACCESS_TOKEN_EXPIRY_MINUTES:-15}
- Jwt__RefreshTokenExpiryDays=${JWT_REFRESH_TOKEN_EXPIRY_DAYS:-7}
# EN: Features
# VI: Tính năng
- Features__SwaggerEnabled=${FEATURE_SWAGGER_ENABLED:-true}
- Features__DetailedErrors=${FEATURE_DETAILED_ERRORS:-true}
networks:
- iamservice-network
healthcheck:
@@ -27,45 +45,8 @@ services:
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
postgres:
image: postgres:16-alpine
container_name: iamservice-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: iamservice_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- iamservice-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: iamservice-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- iamservice-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
redis_data:
start_period: 15s
restart: unless-stopped
networks:
iamservice-network:

View File

@@ -1,271 +1,230 @@
# Architecture Documentation
# Wallet Service Architecture
> Detailed architecture documentation for the .NET 10 Microservice Template.
## Overview
## Architecture Overview
The Wallet Service manages digital wallets and loyalty point accounts for the GoodGo Platform.
```mermaid
graph TB
subgraph "API Layer"
C[Controllers]
CMD[Commands]
Q[Queries]
B[Behaviors]
V[Validations]
end
subgraph "Domain Layer"
AR[Aggregate Roots]
E[Entities]
VO[Value Objects]
DE[Domain Events]
DX[Domain Exceptions]
end
subgraph "Infrastructure Layer"
DB[(PostgreSQL)]
R[Repositories]
CTX[DbContext]
ID[Idempotency]
end
C --> CMD
C --> Q
CMD --> B --> V
CMD --> AR
Q --> R
R --> CTX --> DB
AR --> DE
R --> AR
style C fill:#4a90d9,stroke:#2d5986,color:#fff
style AR fill:#50c878,stroke:#2d8659,color:#fff
style DB fill:#ff6b6b,stroke:#c0392b,color:#fff
```
┌─────────────────────────────────────────────────────────────┐
│ Wallet Service │
├─────────────────────────────────────────────────────────────┤
│ API Layer (Controllers, CQRS) │
├─────────────────────────────────────────────────────────────┤
Domain Layer (Wallet, PointAccount Aggregates) │
├─────────────────────────────────────────────────────────────┤
Infrastructure Layer (EF Core, Repositories) │
├─────────────────────────────────────────────────────────────┤
PostgreSQL Database │
└─────────────────────────────────────────────────────────────┘
```
## Layer Responsibilities
## Architecture Patterns
### 1. Domain Layer (WalletService.Domain)
### Domain-Driven Design (DDD)
The heart of the application containing pure business logic. This layer:
- Has **ZERO** external dependencies (except MediatR.Contracts for events)
- Contains only POCO classes
- Implements DDD tactical patterns
- **Aggregates**: Wallet, PointAccount
- **Entities**: WalletTransaction, PointTransaction
- **Value Objects**: Money, PointBalance
- **Domain Events**: WalletCreated, BalanceChanged, PointsEarned
#### Components
### CQRS Pattern
| Component | Purpose |
|-----------|---------|
| **SeedWork** | Base classes: Entity, ValueObject, Enumeration, IAggregateRoot |
| **AggregatesModel** | Aggregate roots with their entities and value objects |
| **Events** | Domain events for cross-aggregate communication |
| **Exceptions** | Domain-specific exceptions for business rule violations |
### 2. Infrastructure Layer (WalletService.Infrastructure)
Technical implementations and external concerns:
- Database access (EF Core)
- Repository implementations
- External service integrations
### 3. API Layer (WalletService.API)
Application entry point and CQRS implementation:
- Controllers for HTTP handling
- Commands for write operations
- Queries for read operations
- MediatR behaviors for cross-cutting concerns
## CQRS Flow
```mermaid
sequenceDiagram
participant Client
participant Controller
participant MediatR
participant LoggingBehavior
participant ValidatorBehavior
participant TransactionBehavior
participant CommandHandler
participant Repository
participant DbContext
Client->>Controller: HTTP Request
Controller->>MediatR: Send(Command)
MediatR->>LoggingBehavior: Handle
LoggingBehavior->>ValidatorBehavior: Next()
ValidatorBehavior->>TransactionBehavior: Next()
TransactionBehavior->>CommandHandler: Next()
CommandHandler->>Repository: Add/Update/Delete
Repository->>DbContext: SaveEntitiesAsync()
DbContext-->>Repository: Success
Repository-->>CommandHandler: Result
CommandHandler-->>Controller: Response
Controller-->>Client: HTTP Response
```
Commands (Write) Queries (Read)
│ │
▼ ▼
CreateWallet GetWalletQuery
DepositCommand GetTransactionsQuery
WithdrawCommand GetPointAccountQuery
TransferCommand GetBalanceSummaryQuery
EarnPointsCommand
SpendPointsCommand
```
## Domain Events
## Domain Model
### Wallet Aggregate
```mermaid
graph LR
AR[Aggregate Root] -->|Raises| DE[Domain Event]
DE -->|Dispatched by| CTX[DbContext]
CTX -->|Publishes to| M[MediatR]
M -->|Handled by| H1[Handler 1]
M -->|Handled by| H2[Handler 2]
classDiagram
class Wallet {
+Guid Id
+Guid UserId
+Money Balance
+WalletStatus Status
+List~WalletTransaction~ Transactions
+Deposit(Money, description, ref)
+Withdraw(Money, description, ref)
+Freeze()
+Unfreeze()
+Close()
}
style AR fill:#50c878,stroke:#2d8659,color:#fff
style DE fill:#f39c12,stroke:#d68910,color:#fff
style M fill:#9b59b6,stroke:#7d3c98,color:#fff
class WalletTransaction {
+Guid Id
+TransactionType Type
+Money Amount
+Money BalanceAfter
+string Description
+DateTime CreatedAt
}
class Money {
+decimal Amount
+string Currency
}
Wallet "1" --> "*" WalletTransaction
Wallet --> Money
```
### PointAccount Aggregate
```mermaid
classDiagram
class PointAccount {
+Guid Id
+Guid UserId
+int TotalPoints
+int AvailablePoints
+int PendingPoints
+EarnPoints(points, source, desc, expires)
+SpendPoints(points, source, desc)
+AdjustPoints(points, source, desc)
}
class PointTransaction {
+Guid Id
+PointTransactionType Type
+int Points
+int BalanceAfter
+DateTime? ExpiresAt
}
PointAccount "1" --> "*" PointTransaction
```
## Database Schema
### Sample Aggregate
### Tables
```mermaid
erDiagram
samples {
uuid id PK
varchar(200) name
varchar(1000) description
int status_id FK
timestamp created_at
timestamp updated_at
}
sample_statuses {
int id PK
varchar(50) name
}
samples ||--o{ sample_statuses : has
```
| Table | Description |
|-------|-------------|
| `Wallets` | User wallet accounts |
| `WalletTransactions` | Wallet transaction history |
| `PointAccounts` | User point accounts |
| `PointTransactions` | Point transaction history |
## MediatR Pipeline
### Key Indexes
- `IX_Wallets_UserId` - Fast lookup by user
- `IX_WalletTransactions_WalletId` - Transaction history
- `IX_PointAccounts_UserId` - Fast lookup by user
## API Flow
### Deposit Flow
```
Request → LoggingBehavior → ValidatorBehavior → TransactionBehavior → Handler → Response
│ │ │
▼ ▼ ▼
Log start/end Validate Begin/Commit
+ timing with Transaction
FluentValidation
1. Client → POST /api/v1/wallets/deposit
2. Controller → DepositCommand (MediatR)
3. CommandHandler → Validate amount
4. Handler → wallet.Deposit(amount)
5. Domain → Create WalletTransaction + Domain Event
6. Infrastructure → Save to database
7. Response → Updated balance
```
### Behavior Order
1. **LoggingBehavior** - Logs request handling with timing
2. **ValidatorBehavior** - Validates request using FluentValidation
3. **TransactionBehavior** - Wraps command handlers in database transactions
## Error Handling
### Exception Hierarchy
### Transfer Flow
```
Exception
└── DomainException
└── SampleDomainException
1. Client → POST /api/v1/wallets/transfer
2. Controller → TransferCommand
3. Handler → Get source wallet
4. Handler → Get target wallet
5. Domain → source.Withdraw() + target.Deposit()
6. Infrastructure → Transactional save
7. Response → Transfer confirmation
```
### Problem Details (RFC 7807)
## Inter-Service Communication
All errors are returned in Problem Details format:
### IAM Service Integration
```json
{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Validation Error",
"status": 400,
"detail": "One or more validation errors occurred.",
"errors": {
"Name": ["Name is required"]
}
}
```
Wallet Service ──────► IAM Service
User Validation
JWT Verification
```
## Health Checks
### Authentication Flow
```mermaid
graph TD
HC[Health Check Endpoint]
HC --> |/health/live| L[Liveness]
HC --> |/health/ready| R[Readiness]
HC --> |/health| F[Full Status]
R --> PG[(PostgreSQL)]
R --> RD[(Redis)]
style HC fill:#3498db,stroke:#2980b9,color:#fff
style L fill:#2ecc71,stroke:#27ae60,color:#fff
style R fill:#f39c12,stroke:#d68910,color:#fff
```
1. Client sends JWT token in Authorization header
2. Wallet Service validates JWT with IAM Service
3. Extract userId from JWT claims
4. Process wallet operation for user
## Deployment Architecture
## Deployment
### Docker Compose (Local)
### Docker Compose Configuration
```yaml
services:
myservice-api:
build: .
ports: ["5000:8080"]
depends_on:
- postgres
- redis
postgres:
image: postgres:16-alpine
redis:
image: redis:7-alpine
wallet-service:
build:
context: ../..
dockerfile: services/wallet-service-net/Dockerfile
environment:
- DATABASE_URL=${WALLET_DATABASE_URL}
- JWT_AUTHORITY=${IAM_SERVICE_URL}
labels:
- traefik.http.routers.wallet.rule=PathPrefix(`/api/v1/wallets`)
```
### Kubernetes (Production)
### Health Checks
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice-api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: myservice:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
```
| Endpoint | Check |
|----------|-------|
| `/health/live` | Service is running |
| `/health/ready` | Database connected |
| `/health` | Full status |
## Security Considerations
## Security
1. **Authentication**: JWT Bearer token (configure in production)
2. **Authorization**: Role-based access control
3. **Input Validation**: FluentValidation on all requests
4. **SQL Injection**: EF Core parameterized queries
5. **Secrets**: Environment variables, never in code
### Authentication
- JWT Bearer token validation
- IAM Service integration
## Performance Optimization
### Authorization
- User can only access own wallet
- Admin endpoints for system operations
1. **Connection Pooling**: EF Core with Npgsql connection resilience
2. **Async/Await**: All I/O operations are async
3. **Response Caching**: Add caching headers for queries
4. **Database Indexes**: Configure in EntityConfigurations
### Data Protection
- All amounts stored with precision
- Transaction audit trail
- Soft delete for wallets
## References
## Performance
- [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers)
- [.NET Microservices Architecture Guide](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/)
- [Domain-Driven Design](https://martinfowler.com/bliki/DomainDrivenDesign.html)
- [CQRS Pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)
### Optimizations
- Connection pooling (EF Core)
- Index on frequent queries
- Pagination for transaction history
### Scaling
- Horizontal scaling with load balancer
- Read replicas for queries
- Redis caching (future)
## Monitoring
### Metrics
- Request duration
- Transaction counts
- Error rates
### Logging
- Serilog structured logging
- Correlation IDs for tracing
- Seq/Loki integration

View File

@@ -1,193 +1,161 @@
# .NET 10 Microservice Template
# Wallet Service .NET
> Enterprise-grade .NET 10 microservice template following DDD, CQRS, and Clean Architecture patterns.
> **EN**: Wallet and Point Account management service for GoodGo Platform.
> **VI**: Dịch vụ quản lý Ví và Tài khoản Điểm cho GoodGo Platform.
## Overview
This template provides a production-ready structure for .NET microservices based on the eShopOnContainers reference architecture with:
The Wallet Service provides comprehensive wallet and loyalty points management with:
- **Domain-Driven Design (DDD)** - Aggregates, Entities, Value Objects, Domain Events
- **CQRS Pattern** - Separate Commands (write) and Queries (read) with MediatR
- **Clean Architecture** - Domain, Infrastructure, API layered separation
- **EF Core 10** - PostgreSQL with connection resilience
- **FluentValidation** - Request validation
- **API Versioning** - URL segment versioning
- **Health Checks** - Kubernetes-ready probes
- **Structured Logging** - Serilog with console and Seq
- **Wallet Management** - Create, deposit, withdraw, transfer funds
- **Point Account** - Earn, spend, and track loyalty points
- **Transaction History** - Full audit trail of all transactions
- **Multi-Currency Support** - Default VND with currency support
- **Domain-Driven Design** - Clean Architecture with CQRS pattern
## Tech Stack
| Component | Technology |
|-----------|------------|
| Framework | .NET 10 |
| Database | PostgreSQL (EF Core) |
| CQRS | MediatR |
| Validation | FluentValidation |
| API Docs | Swagger/OpenAPI |
| Logging | Serilog |
## Prerequisites
| Requirement | Version |
|-------------|---------|
| .NET SDK | 10.0.101+ |
| Docker | 24.0+ |
| PostgreSQL | 15+ (or use Docker) |
```bash
# Check .NET version
dotnet --version
# Should output: 10.0.xxx
dotnet --version # Should be 10.0.x
```
## Quick Start
### 1. Create New Service
### 1. Configure Environment
```bash
# Copy template to new service
cp -r services/_template_dot_net services/your-service-name
# Navigate to service directory
cd services/your-service-name
# Rename all occurrences of "WalletService" to "YourService"
find . -type f -name "*.cs" -exec sed -i '' 's/WalletService/YourService/g' {} +
find . -type f -name "*.csproj" -exec sed -i '' 's/WalletService/YourService/g' {} +
```
### 2. Configure Environment
```bash
# Copy environment template
cp .env.example .env
# Edit with your configuration
nano .env
# Edit .env with your database connection
```
### 3. Run with Docker
### 2. Run with Docker
```bash
# Start all services (API + PostgreSQL + Redis)
docker-compose up -d
# View logs
docker-compose logs -f myservice-api
cd deployments/local
docker-compose up wallet-service -d
```
### 4. Run Locally
### 3. Run Locally
```bash
# Restore dependencies
cd services/wallet-service-net
dotnet restore
# Build all projects
dotnet build
# Run the API
dotnet run --project src/WalletService.API
```
## Project Structure
```
_template_dot_net/
├── src/
│ ├── WalletService.API/ # Presentation Layer (Controllers, CQRS)
│ │ ├── Controllers/ # API endpoints
│ │ ├── Application/ # CQRS Implementation
│ │ │ ├── Commands/ # Write operations (MediatR)
│ │ │ ├── Queries/ # Read operations
│ │ │ ├── Behaviors/ # MediatR pipeline behaviors
│ │ │ └── Validations/ # FluentValidation validators
│ │ ├── Middleware/ # Custom middleware
│ │ └── Program.cs # Application entry point
│ │
│ ├── WalletService.Domain/ # Domain Layer (Pure business logic)
│ │ ├── AggregatesModel/ # Aggregate roots and entities
│ │ ├── Events/ # Domain events
│ │ ├── Exceptions/ # Domain exceptions
│ │ └── SeedWork/ # Base classes (Entity, ValueObject, etc.)
│ │
│ └── WalletService.Infrastructure/ # Infrastructure Layer (Data access)
│ ├── EntityConfigurations/ # EF Core Fluent API configurations
│ ├── Repositories/ # Repository implementations
│ ├── Idempotency/ # Request idempotency handling
│ └── WalletServiceContext.cs # DbContext with Unit of Work
├── tests/
│ ├── WalletService.UnitTests/ # Unit tests (Domain, Application)
│ └── WalletService.FunctionalTests/ # Integration tests (API endpoints)
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Local development setup
├── global.json # .NET SDK version pinning
└── Directory.Build.props # Common MSBuild properties
```
## API Endpoints
### Wallet APIs
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/samples` | Get all samples |
| `GET` | `/api/v1/samples/{id}` | Get sample by ID |
| `POST` | `/api/v1/samples` | Create new sample |
| `PUT` | `/api/v1/samples/{id}` | Update sample |
| `DELETE` | `/api/v1/samples/{id}` | Delete sample |
| `PATCH` | `/api/v1/samples/{id}/status` | Change status |
| `POST` | `/api/v1/wallets` | Create new wallet |
| `GET` | `/api/v1/wallets/me` | Get current user's wallet |
| `GET` | `/api/v1/wallets/{id}` | Get wallet by ID |
| `POST` | `/api/v1/wallets/deposit` | Deposit funds |
| `POST` | `/api/v1/wallets/withdraw` | Withdraw funds |
| `POST` | `/api/v1/wallets/transfer` | Transfer between wallets |
| `POST` | `/api/v1/wallets/{id}/freeze` | Freeze wallet |
| `POST` | `/api/v1/wallets/{id}/unfreeze` | Unfreeze wallet |
| `GET` | `/api/v1/wallets/transactions` | Get transaction history |
### Points APIs
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/points/me` | Get current user's points |
| `POST` | `/api/v1/points/earn` | Earn points |
| `POST` | `/api/v1/points/spend` | Spend points |
| `GET` | `/api/v1/points/transactions` | Get point transactions |
### Health Endpoints
| Endpoint | Purpose |
|----------|---------|
| `/health` | Full health status |
| `/health/live` | Liveness probe |
| `/health/ready` | Readiness probe |
| `/health/live` | Liveness probe (K8s) |
| `/health/ready` | Readiness probe (K8s) |
## CQRS Pattern
## Project Structure
### Commands (Write Operations)
```csharp
// Define command
public record CreateSampleCommand(string Name, string? Description)
: IRequest<CreateSampleCommandResult>;
// Handle command
public class CreateSampleCommandHandler : IRequestHandler<CreateSampleCommand, CreateSampleCommandResult>
{
public async Task<CreateSampleCommandResult> Handle(CreateSampleCommand request, CancellationToken ct)
{
var sample = new Sample(request.Name, request.Description);
_repository.Add(sample);
await _repository.UnitOfWork.SaveEntitiesAsync(ct);
return new CreateSampleCommandResult(sample.Id);
}
}
```
### Queries (Read Operations)
```csharp
// Define query
public record GetSampleQuery(Guid SampleId) : IRequest<SampleViewModel?>;
wallet-service-net/
├── src/
│ ├── WalletService.API/ # API Layer
│ │ ├── Controllers/ # REST endpoints
│ │ └── Application/ # Commands & Queries
│ │ ├── Commands/ # Write operations
│ │ └── Queries/ # Read operations
│ │
│ ├── WalletService.Domain/ # Domain Layer
│ │ ├── AggregatesModel/
│ │ │ ├── WalletAggregate/ # Wallet, Transaction, Money
│ │ │ └── PointAccountAggregate/ # Points, PointTransaction
│ │ ├── Events/ # Domain events
│ │ └── Exceptions/ # Domain exceptions
│ │
│ └── WalletService.Infrastructure/ # Infrastructure Layer
│ ├── EntityConfigurations/ # EF Core mappings
│ ├── Repositories/ # Data access
│ └── WalletServiceContext.cs # DbContext
├── tests/
│ ├── WalletService.UnitTests/ # Domain & Logic tests
│ └── WalletService.FunctionalTests/ # API integration tests
├── docs/
│ ├── en/ # English docs
│ └── vi/ # Vietnamese docs
└── Dockerfile
```
## Domain Model
### Aggregate Root
### Wallet Aggregate
```csharp
public class Sample : Entity, IAggregateRoot
{
public string Name => _name;
public SampleStatus Status => _status;
public Sample(string name, string? description) {
// Business logic validation
if (string.IsNullOrWhiteSpace(name))
throw new SampleDomainException("Sample name cannot be empty");
// Domain event
AddDomainEvent(new SampleCreatedDomainEvent(this));
}
public void Activate() {
if (_status != SampleStatus.Draft)
throw new SampleDomainException("Only draft samples can be activated");
// State transition
}
}
// Create wallet
var wallet = new Wallet(userId, "VND");
// Deposit funds
wallet.Deposit(new Money(1000000m, "VND"), "Salary", "REF001");
// Withdraw funds
wallet.Withdraw(new Money(500000m, "VND"), "Shopping", "REF002");
// Freeze/Unfreeze
wallet.Freeze();
wallet.Unfreeze();
```
### Point Account Aggregate
```csharp
// Create account
var account = new PointAccount(userId);
// Earn points
account.EarnPoints(100, "ORDER001", "Purchase reward", expiresAt);
// Spend points
account.SpendPoints(50, "ORDER002", "Redeem discount");
// Adjust points (admin)
account.AdjustPoints(10, "ADMIN", "Bonus adjustment");
```
## Testing
@@ -197,68 +165,75 @@ public class Sample : Entity, IAggregateRoot
dotnet test
# Run with coverage
dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura
dotnet test /p:CollectCoverage=true
# Run specific test project
# Unit tests only
dotnet test tests/WalletService.UnitTests
# Functional tests only
dotnet test tests/WalletService.FunctionalTests
```
### Test Results
- **23 tests** total
- 20 unit tests (Wallet, PointAccount domain)
- 3 functional tests (Health endpoints)
## Configuration
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `ASPNETCORE_ENVIRONMENT` | Environment name | `Development` |
| `DATABASE_URL` | PostgreSQL connection string | - |
| `REDIS_URL` | Redis connection string | - |
| `JWT_SECRET` | JWT signing secret (min 32 chars) | - |
| Variable | Description | Required |
|----------|-------------|----------|
| `DATABASE_URL` | PostgreSQL connection | Yes |
| `ASPNETCORE_ENVIRONMENT` | Environment | No (default: Development) |
| `JWT_AUTHORITY` | JWT issuer URL | Yes (for auth) |
### appsettings.json
```json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=myservice;Username=postgres;Password=postgres"
"DefaultConnection": "Host=localhost;Database=wallet_db;Username=postgres;Password=postgres"
},
"Serilog": {
"MinimumLevel": "Information"
"Jwt": {
"Authority": "http://localhost:5001",
"Issuer": "http://localhost:5001"
}
}
```
## Database Migrations
```bash
# Create migration
dotnet ef migrations add InitialCreate \
--project src/WalletService.Infrastructure \
--startup-project src/WalletService.API
# Apply migration
dotnet ef database update \
--project src/WalletService.Infrastructure \
--startup-project src/WalletService.API
```
## Deployment
### Docker Build
```bash
# Build Docker image
docker build -t myservice:latest .
# Run container
docker run -p 5000:8080 --env-file .env myservice:latest
docker build -t goodgo/wallet-service:latest .
```
### Kubernetes
### Docker Compose
See [ARCHITECTURE.md](./ARCHITECTURE.md) for Kubernetes deployment manifests.
## What's New in .NET 10
- **C# 14** language features
- Improved **Native AOT** support
- Better **async/await** performance
- Enhanced **JSON serialization**
- Performance improvements across the board
- 3-year **LTS** support (until November 2028)
Service is registered in `deployments/local/docker-compose.yml` with Traefik routing.
## Resources
- [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers) - Reference architecture
- [.NET 10 Documentation](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10)
- [DDD with .NET](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/)
- [MediatR](https://github.com/jbogard/MediatR) - CQRS library
- [FluentValidation](https://docs.fluentvalidation.net/) - Validation library
- [Architecture Documentation](./ARCHITECTURE.md)
- [GoodGo Platform Docs](../../docs/)
- [eShopOnContainers Reference](https://github.com/dotnet-architecture/eShopOnContainers)
## License

View File

@@ -1,271 +1,230 @@
# Tài Liệu Kiến Trúc
# Kiến Trúc Wallet Service
> Tài liệu kiến trúc chi tiết cho Template Microservice .NET 10.
## Tổng Quan
## Tổng Quan Kiến Trúc
Wallet Service quản lý ví điện tử và tài khoản điểm thưởng cho GoodGo Platform.
```mermaid
graph TB
subgraph "Lớp API"
C[Controllers]
CMD[Commands]
Q[Queries]
B[Behaviors]
V[Validations]
end
subgraph "Lớp Domain"
AR[Aggregate Roots]
E[Entities]
VO[Value Objects]
DE[Domain Events]
DX[Domain Exceptions]
end
subgraph "Lớp Infrastructure"
DB[(PostgreSQL)]
R[Repositories]
CTX[DbContext]
ID[Idempotency]
end
C --> CMD
C --> Q
CMD --> B --> V
CMD --> AR
Q --> R
R --> CTX --> DB
AR --> DE
R --> AR
style C fill:#4a90d9,stroke:#2d5986,color:#fff
style AR fill:#50c878,stroke:#2d8659,color:#fff
style DB fill:#ff6b6b,stroke:#c0392b,color:#fff
```
┌─────────────────────────────────────────────────────────────┐
│ Wallet Service │
├─────────────────────────────────────────────────────────────┤
│ API Layer (Controllers, CQRS) │
├─────────────────────────────────────────────────────────────┤
Domain Layer (Wallet, PointAccount Aggregates) │
├─────────────────────────────────────────────────────────────┤
Infrastructure Layer (EF Core, Repositories) │
├─────────────────────────────────────────────────────────────┤
PostgreSQL Database │
└─────────────────────────────────────────────────────────────┘
```
## Trách Nhiệm Các Lớp
## Các Pattern Kiến Trúc
### 1. Lớp Domain (WalletService.Domain)
### Domain-Driven Design (DDD)
Trái tim của ứng dụng chứa business logic thuần túy. Lớp này:
- **ZERO** phụ thuộc bên ngoài (ngoại trừ MediatR.Contracts cho events)
- Chỉ chứa các class POCO
- Triển khai các tactical patterns của DDD
- **Aggregates**: Wallet, PointAccount
- **Entities**: WalletTransaction, PointTransaction
- **Value Objects**: Money, PointBalance
- **Domain Events**: WalletCreated, BalanceChanged, PointsEarned
#### Thành Phần
### CQRS Pattern
| Thành phần | Mục Đích |
|------------|----------|
| **SeedWork** | Base classes: Entity, ValueObject, Enumeration, IAggregateRoot |
| **AggregatesModel** | Aggregate roots với entities và value objects |
| **Events** | Domain events cho giao tiếp cross-aggregate |
| **Exceptions** | Domain exceptions cho vi phạm business rules |
### 2. Lớp Infrastructure (WalletService.Infrastructure)
Triển khai kỹ thuật và các mối quan tâm bên ngoài:
- Truy cập database (EF Core)
- Triển khai repositories
- Tích hợp external services
### 3. Lớp API (WalletService.API)
Điểm vào ứng dụng và triển khai CQRS:
- Controllers để xử lý HTTP
- Commands cho các thao tác ghi
- Queries cho các thao tác đọc
- MediatR behaviors cho cross-cutting concerns
## Luồng CQRS
```mermaid
sequenceDiagram
participant Client
participant Controller
participant MediatR
participant LoggingBehavior
participant ValidatorBehavior
participant TransactionBehavior
participant CommandHandler
participant Repository
participant DbContext
Client->>Controller: HTTP Request
Controller->>MediatR: Send(Command)
MediatR->>LoggingBehavior: Handle
LoggingBehavior->>ValidatorBehavior: Next()
ValidatorBehavior->>TransactionBehavior: Next()
TransactionBehavior->>CommandHandler: Next()
CommandHandler->>Repository: Add/Update/Delete
Repository->>DbContext: SaveEntitiesAsync()
DbContext-->>Repository: Success
Repository-->>CommandHandler: Result
CommandHandler-->>Controller: Response
Controller-->>Client: HTTP Response
```
Commands (Ghi) Queries (Đọc)
│ │
▼ ▼
CreateWallet GetWalletQuery
DepositCommand GetTransactionsQuery
WithdrawCommand GetPointAccountQuery
TransferCommand GetBalanceSummaryQuery
EarnPointsCommand
SpendPointsCommand
```
## Domain Events
## Domain Model
### Wallet Aggregate
```mermaid
graph LR
AR[Aggregate Root] -->|Phát sinh| DE[Domain Event]
DE -->|Dispatch bởi| CTX[DbContext]
CTX -->|Publish tới| M[MediatR]
M -->|Xử lý bởi| H1[Handler 1]
M -->|Xử lý bởi| H2[Handler 2]
style AR fill:#50c878,stroke:#2d8659,color:#fff
style DE fill:#f39c12,stroke:#d68910,color:#fff
style M fill:#9b59b6,stroke:#7d3c98,color:#fff
```
## Schema Database
### Sample Aggregate
```mermaid
erDiagram
samples {
uuid id PK
varchar(200) name
varchar(1000) description
int status_id FK
timestamp created_at
timestamp updated_at
classDiagram
class Wallet {
+Guid Id
+Guid UserId
+Money Balance
+WalletStatus Status
+List~WalletTransaction~ Transactions
+Deposit(Money, description, ref)
+Withdraw(Money, description, ref)
+Freeze()
+Unfreeze()
+Close()
}
sample_statuses {
int id PK
varchar(50) name
class WalletTransaction {
+Guid Id
+TransactionType Type
+Money Amount
+Money BalanceAfter
+string Description
+DateTime CreatedAt
}
samples ||--o{ sample_statuses : has
class Money {
+decimal Amount
+string Currency
}
Wallet "1" --> "*" WalletTransaction
Wallet --> Money
```
## Pipeline MediatR
```
Request → LoggingBehavior → ValidatorBehavior → TransactionBehavior → Handler → Response
│ │ │
▼ ▼ ▼
Log start/end Validate Begin/Commit
+ timing với Transaction
FluentValidation
```
### Thứ Tự Behaviors
1. **LoggingBehavior** - Ghi log xử lý request với timing
2. **ValidatorBehavior** - Validate request sử dụng FluentValidation
3. **TransactionBehavior** - Bao bọc command handlers trong database transactions
## Xử Lý Lỗi
### Phân Cấp Exceptions
```
Exception
└── DomainException
└── SampleDomainException
```
### Problem Details (RFC 7807)
Tất cả lỗi được trả về theo định dạng Problem Details:
```json
{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Lỗi Validation",
"status": 400,
"detail": "Một hoặc nhiều lỗi validation đã xảy ra.",
"errors": {
"Name": ["Tên là bắt buộc"]
}
}
```
## Health Checks
### PointAccount Aggregate
```mermaid
graph TD
HC[Health Check Endpoint]
HC --> |/health/live| L[Liveness]
HC --> |/health/ready| R[Readiness]
HC --> |/health| F[Full Status]
classDiagram
class PointAccount {
+Guid Id
+Guid UserId
+int TotalPoints
+int AvailablePoints
+int PendingPoints
+EarnPoints(points, source, desc, expires)
+SpendPoints(points, source, desc)
+AdjustPoints(points, source, desc)
}
R --> PG[(PostgreSQL)]
R --> RD[(Redis)]
class PointTransaction {
+Guid Id
+PointTransactionType Type
+int Points
+int BalanceAfter
+DateTime? ExpiresAt
}
style HC fill:#3498db,stroke:#2980b9,color:#fff
style L fill:#2ecc71,stroke:#27ae60,color:#fff
style R fill:#f39c12,stroke:#d68910,color:#fff
PointAccount "1" --> "*" PointTransaction
```
## Kiến Trúc Deployment
## Database Schema
### Docker Compose (Local)
### Các Bảng
| Bảng | Mô Tả |
|------|-------|
| `Wallets` | Tài khoản ví người dùng |
| `WalletTransactions` | Lịch sử giao dịch ví |
| `PointAccounts` | Tài khoản điểm người dùng |
| `PointTransactions` | Lịch sử giao dịch điểm |
### Indexes Chính
- `IX_Wallets_UserId` - Tra cứu theo user
- `IX_WalletTransactions_WalletId` - Lịch sử giao dịch
- `IX_PointAccounts_UserId` - Tra cứu theo user
## API Flow
### Luồng Nạp Tiền
```
1. Client → POST /api/v1/wallets/deposit
2. Controller → DepositCommand (MediatR)
3. CommandHandler → Validate số tiền
4. Handler → wallet.Deposit(amount)
5. Domain → Tạo WalletTransaction + Domain Event
6. Infrastructure → Lưu database
7. Response → Số dư cập nhật
```
### Luồng Chuyển Khoản
```
1. Client → POST /api/v1/wallets/transfer
2. Controller → TransferCommand
3. Handler → Lấy ví nguồn
4. Handler → Lấy ví đích
5. Domain → source.Withdraw() + target.Deposit()
6. Infrastructure → Lưu transactional
7. Response → Xác nhận chuyển khoản
```
## Tích Hợp Giữa Các Service
### Tích Hợp IAM Service
```
Wallet Service ──────► IAM Service
Xác thực User
Kiểm tra JWT
```
### Luồng Xác Thực
1. Client gửi JWT token trong Authorization header
2. Wallet Service xác thực JWT với IAM Service
3. Trích xuất userId từ JWT claims
4. Xử lý thao tác ví cho user
## Triển Khai
### Cấu Hình Docker Compose
```yaml
services:
myservice-api:
build: .
ports: ["5000:8080"]
depends_on:
- postgres
- redis
postgres:
image: postgres:16-alpine
redis:
image: redis:7-alpine
wallet-service:
build:
context: ../..
dockerfile: services/wallet-service-net/Dockerfile
environment:
- DATABASE_URL=${WALLET_DATABASE_URL}
- JWT_AUTHORITY=${IAM_SERVICE_URL}
labels:
- traefik.http.routers.wallet.rule=PathPrefix(`/api/v1/wallets`)
```
### Kubernetes (Production)
### Health Checks
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice-api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: myservice:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
```
| Endpoint | Kiểm Tra |
|----------|----------|
| `/health/live` | Service đang chạy |
| `/health/ready` | Database kết nối |
| `/health` | Trạng thái đầy đủ |
## Cân Nhắc Bảo Mật
## Bảo Mật
1. **Authentication**: JWT Bearer token (cấu hình trong production)
2. **Authorization**: Role-based access control
3. **Input Validation**: FluentValidation trên tất cả requests
4. **SQL Injection**: EF Core parameterized queries
5. **Secrets**: Biến môi trường, không bao giờ trong code
### Xác Thực
- Xác thực JWT Bearer token
- Tích hợp IAM Service
## Tối Ưu Hiệu Năng
### Phân Quyền
- User chỉ truy cập được ví của mình
- Admin endpoints cho system operations
1. **Connection Pooling**: EF Core với Npgsql connection resilience
2. **Async/Await**: Tất cả I/O operations đều async
3. **Response Caching**: Thêm caching headers cho queries
4. **Database Indexes**: Cấu hình trong EntityConfigurations
### Bảo Vệ Dữ Liệu
- Lưu số tiền với độ chính xác cao
- Audit trail giao dịch
- Soft delete cho ví
## Tài Liệu Tham Khảo
## Hiệu Năng
- [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers)
- [Hướng dẫn Kiến trúc .NET Microservices](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/)
- [Domain-Driven Design](https://martinfowler.com/bliki/DomainDrivenDesign.html)
- [CQRS Pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)
### Tối Ưu
- Connection pooling (EF Core)
- Index cho các queries thường xuyên
- Phân trang cho lịch sử giao dịch
### Mở Rộng
- Horizontal scaling với load balancer
- Read replicas cho queries
- Redis caching (tương lai)
## Giám Sát
### Metrics
- Thời gian request
- Số lượng giao dịch
- Tỷ lệ lỗi
### Logging
- Serilog structured logging
- Correlation IDs cho tracing
- Tích hợp Seq/Loki

View File

@@ -1,265 +1,240 @@
# Template Microservice .NET 10
# Wallet Service .NET
> Template microservice .NET 10 cấp doanh nghiệp theo các pattern DDD, CQRS và Clean Architecture.
> **EN**: Wallet and Point Account management service for GoodGo Platform.
> **VI**: Dịch vụ quản lý Ví và Tài khoản Điểm cho GoodGo Platform.
## Tổng Quan
Template này cung cấp cấu trúc sẵn sàng production cho microservices .NET dựa trên kiến trúc tham chiếu eShopOnContainers với:
Wallet Service cung cấp quản lý ví và điểm thưởng toàn diện:
- **Domain-Driven Design (DDD)** - Aggregates, Entities, Value Objects, Domain Events
- **CQRS Pattern** - Tách biệt Commands (ghi) và Queries (đọc) với MediatR
- **Clean Architecture** - Phân tầng Domain, Infrastructure, API
- **EF Core 10** - PostgreSQL với connection resilience
- **FluentValidation** - Validation request
- **API Versioning** - Versioning theo URL segment
- **Health Checks** - Probes sẵn sàng cho Kubernetes
- **Structured Logging** - Serilog với console và Seq
- **Quản Lý Ví** - Tạo, nạp tiền, rút tiền, chuyển khoản
- **Tài Khoản Điểm** - Tích, tiêu và theo dõi điểm thưởng
- **Lịch Sử Giao Dịch** - Audit trail đầy đủ các giao dịch
- **Hỗ Trợ Đa Tiền Tệ** - Mặc định VND với hỗ trợ đa tiền tệ
- **Domain-Driven Design** - Clean Architecture với CQRS pattern
## Yêu Cầu
## Tech Stack
| Yêu cầu | Phiên bản |
|---------|-----------|
| .NET SDK | 10.0.101+ |
| Docker | 24.0+ |
| PostgreSQL | 15+ (hoặc dùng Docker) |
| Thành Phần | Công Nghệ |
|------------|-----------|
| Framework | .NET 10 |
| Database | PostgreSQL (EF Core) |
| CQRS | MediatR |
| Validation | FluentValidation |
| API Docs | Swagger/OpenAPI |
| Logging | Serilog |
## Yêu Cầu Hệ Thống
```bash
# Kiểm tra phiên bản .NET
dotnet --version
# Kết quả nên là: 10.0.xxx
dotnet --version # Phải là 10.0.x
```
## Bắt Đầu Nhanh
### 1. Tạo Service Mới
### 1. Cấu Hình Môi Trường
```bash
# Sao chép template sang service mới
cp -r services/_template_dot_net services/your-service-name
# Di chuyển đến thư mục service
cd services/your-service-name
# Đổi tên tất cả "WalletService" thành "YourService"
find . -type f -name "*.cs" -exec sed -i '' 's/WalletService/YourService/g' {} +
find . -type f -name "*.csproj" -exec sed -i '' 's/WalletService/YourService/g' {} +
```
### 2. Cấu Hình Môi Trường
```bash
# Sao chép template môi trường
cp .env.example .env
# Chỉnh sửa với cấu hình của bạn
nano .env
# Chỉnh sửa .env với connection database của bạn
```
### 3. Chạy với Docker
### 2. Chạy với Docker
```bash
# Khởi động tất cả services (API + PostgreSQL + Redis)
docker-compose up -d
# Xem logs
docker-compose logs -f myservice-api
cd deployments/local
docker-compose up wallet-service -d
```
### 4. Chạy Local
### 3. Chạy Local
```bash
# Khôi phục dependencies
cd services/wallet-service-net
dotnet restore
# Build tất cả projects
dotnet build
# Chạy API
dotnet run --project src/WalletService.API
```
## Cấu Trúc Dự Án
## API Endpoints
```
_template_dot_net/
├── src/
│ ├── WalletService.API/ # Lớp Presentation (Controllers, CQRS)
│ │ ├── Controllers/ # Các API endpoints
│ │ ├── Application/ # Triển khai CQRS
│ │ │ ├── Commands/ # Thao tác ghi (MediatR)
│ │ │ ├── Queries/ # Thao tác đọc
│ │ │ ├── Behaviors/ # MediatR pipeline behaviors
│ │ │ └── Validations/ # FluentValidation validators
│ │ ├── Middleware/ # Custom middleware
│ │ └── Program.cs # Điểm vào ứng dụng
│ │
│ ├── WalletService.Domain/ # Lớp Domain (Business logic thuần túy)
│ │ ├── AggregatesModel/ # Aggregate roots và entities
│ │ ├── Events/ # Domain events
│ │ ├── Exceptions/ # Domain exceptions
│ │ └── SeedWork/ # Base classes (Entity, ValueObject, etc.)
│ │
│ └── WalletService.Infrastructure/ # Lớp Infrastructure (Truy cập dữ liệu)
│ ├── EntityConfigurations/ # Cấu hình EF Core Fluent API
│ ├── Repositories/ # Triển khai repositories
│ ├── Idempotency/ # Xử lý idempotency request
│ └── WalletServiceContext.cs # DbContext với Unit of Work
├── tests/
│ ├── WalletService.UnitTests/ # Unit tests (Domain, Application)
│ └── WalletService.FunctionalTests/ # Integration tests (API endpoints)
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Thiết lập phát triển local
├── global.json # Pin phiên bản .NET SDK
└── Directory.Build.props # Thuộc tính MSBuild chung
```
## Các Endpoint API
### Wallet APIs
| Method | Endpoint | Mô Tả |
|--------|----------|-------|
| `GET` | `/api/v1/samples` | Lấy tất cả samples |
| `GET` | `/api/v1/samples/{id}` | Lấy sample theo ID |
| `POST` | `/api/v1/samples` | Tạo sample mới |
| `PUT` | `/api/v1/samples/{id}` | Cập nhật sample |
| `DELETE` | `/api/v1/samples/{id}` | Xóa sample |
| `PATCH` | `/api/v1/samples/{id}/status` | Thay đổi trạng thái |
| `POST` | `/api/v1/wallets` | Tạo ví mới |
| `GET` | `/api/v1/wallets/me` | Lấy ví của người dùng hiện tại |
| `GET` | `/api/v1/wallets/{id}` | Lấy ví theo ID |
| `POST` | `/api/v1/wallets/deposit` | Nạp tiền vào ví |
| `POST` | `/api/v1/wallets/withdraw` | Rút tiền từ ví |
| `POST` | `/api/v1/wallets/transfer` | Chuyển tiền giữa các ví |
| `POST` | `/api/v1/wallets/{id}/freeze` | Đóng băng ví |
| `POST` | `/api/v1/wallets/{id}/unfreeze` | Mở đóng băng ví |
| `GET` | `/api/v1/wallets/transactions` | Lấy lịch sử giao dịch |
### Points APIs
| Method | Endpoint | Mô Tả |
|--------|----------|-------|
| `GET` | `/api/v1/points/me` | Lấy điểm của người dùng |
| `POST` | `/api/v1/points/earn` | Tích điểm |
| `POST` | `/api/v1/points/spend` | Tiêu điểm |
| `GET` | `/api/v1/points/transactions` | Lấy lịch sử điểm |
### Health Endpoints
| Endpoint | Mục Đích |
|----------|----------|
| `/health` | Trạng thái health đầy đủ |
| `/health/live` | Kiểm tra sống |
| `/health/ready` | Kiểm tra sẵn sàng |
| `/health` | Trạng thái sức khỏe đầy đủ |
| `/health/live` | Liveness probe (K8s) |
| `/health/ready` | Readiness probe (K8s) |
## Pattern CQRS
## Cấu Trúc Dự Án
### Commands (Thao Tác Ghi)
```csharp
// Định nghĩa command
public record CreateSampleCommand(string Name, string? Description)
: IRequest<CreateSampleCommandResult>;
// Xử lý command
public class CreateSampleCommandHandler : IRequestHandler<CreateSampleCommand, CreateSampleCommandResult>
{
public async Task<CreateSampleCommandResult> Handle(CreateSampleCommand request, CancellationToken ct)
{
var sample = new Sample(request.Name, request.Description);
_repository.Add(sample);
await _repository.UnitOfWork.SaveEntitiesAsync(ct);
return new CreateSampleCommandResult(sample.Id);
}
}
```
### Queries (Thao Tác Đọc)
```csharp
// Định nghĩa query
public record GetSampleQuery(Guid SampleId) : IRequest<SampleViewModel?>;
wallet-service-net/
├── src/
│ ├── WalletService.API/ # API Layer
│ │ ├── Controllers/ # REST endpoints
│ │ └── Application/ # Commands & Queries
│ │ ├── Commands/ # Thao tác ghi
│ │ └── Queries/ # Thao tác đọc
│ │
│ ├── WalletService.Domain/ # Domain Layer
│ │ ├── AggregatesModel/
│ │ │ ├── WalletAggregate/ # Wallet, Transaction, Money
│ │ │ └── PointAccountAggregate/ # Points, PointTransaction
│ │ ├── Events/ # Domain events
│ │ └── Exceptions/ # Domain exceptions
│ │
│ └── WalletService.Infrastructure/ # Infrastructure Layer
│ ├── EntityConfigurations/ # EF Core mappings
│ ├── Repositories/ # Data access
│ └── WalletServiceContext.cs # DbContext
├── tests/
│ ├── WalletService.UnitTests/ # Domain & Logic tests
│ └── WalletService.FunctionalTests/ # API integration tests
├── docs/
│ ├── en/ # Tài liệu tiếng Anh
│ └── vi/ # Tài liệu tiếng Việt
└── Dockerfile
```
## Domain Model
### Aggregate Root
### Wallet Aggregate
```csharp
public class Sample : Entity, IAggregateRoot
{
public string Name => _name;
public SampleStatus Status => _status;
public Sample(string name, string? description) {
// Validation business logic
if (string.IsNullOrWhiteSpace(name))
throw new SampleDomainException("Tên sample không được để trống");
// Domain event
AddDomainEvent(new SampleCreatedDomainEvent(this));
}
public void Activate() {
if (_status != SampleStatus.Draft)
throw new SampleDomainException("Chỉ sample draft mới có thể kích hoạt");
// Chuyển đổi trạng thái
}
}
// Tạo ví
var wallet = new Wallet(userId, "VND");
// Nạp tiền
wallet.Deposit(new Money(1000000m, "VND"), "Lương", "REF001");
// Rút tin
wallet.Withdraw(new Money(500000m, "VND"), "Mua sắm", "REF002");
// Đóng băng/Mở đóng băng
wallet.Freeze();
wallet.Unfreeze();
```
## Kiểm Thử
### Point Account Aggregate
```csharp
// Tạo tài khoản điểm
var account = new PointAccount(userId);
// Tích điểm
account.EarnPoints(100, "ORDER001", "Thưởng mua hàng", expiresAt);
// Tiêu điểm
account.SpendPoints(50, "ORDER002", "Đổi giảm giá");
// Điều chỉnh điểm (admin)
account.AdjustPoints(10, "ADMIN", "Điều chỉnh thưởng");
```
## Testing
```bash
# Chạy tất cả tests
dotnet test
# Chạy với coverage
dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura
dotnet test /p:CollectCoverage=true
# Chạy project test cụ thể
# Chỉ unit tests
dotnet test tests/WalletService.UnitTests
# Chỉ functional tests
dotnet test tests/WalletService.FunctionalTests
```
### Kết Quả Test
- **23 tests** tổng cộng
- 20 unit tests (Wallet, PointAccount domain)
- 3 functional tests (Health endpoints)
## Cấu Hình
### Biến Môi Trường
| Biến | Mô Tả | Mặc định |
| Biến | Mô Tả | Bắt Buộc |
|------|-------|----------|
| `ASPNETCORE_ENVIRONMENT` | Tên môi trường | `Development` |
| `DATABASE_URL` | Connection string PostgreSQL | - |
| `REDIS_URL` | Connection string Redis | - |
| `JWT_SECRET` | Secret ký JWT (tối thiểu 32 ký tự) | - |
| `DATABASE_URL` | Connection PostgreSQL | Có |
| `ASPNETCORE_ENVIRONMENT` | Môi trường | Không (mặc định: Development) |
| `JWT_AUTHORITY` | URL JWT issuer | Có (cho auth) |
### appsettings.json
```json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=myservice;Username=postgres;Password=postgres"
"DefaultConnection": "Host=localhost;Database=wallet_db;Username=postgres;Password=postgres"
},
"Serilog": {
"MinimumLevel": "Information"
"Jwt": {
"Authority": "http://localhost:5001",
"Issuer": "http://localhost:5001"
}
}
```
## Database Migrations
```bash
# Tạo migration
dotnet ef migrations add InitialCreate \
--project src/WalletService.Infrastructure \
--startup-project src/WalletService.API
# Áp dụng migration
dotnet ef database update \
--project src/WalletService.Infrastructure \
--startup-project src/WalletService.API
```
## Triển Khai
### Docker Build
```bash
# Build Docker image
docker build -t myservice:latest .
# Chạy container
docker run -p 5000:8080 --env-file .env myservice:latest
docker build -t goodgo/wallet-service:latest .
```
### Kubernetes
### Docker Compose
Xem [ARCHITECTURE.md](./ARCHITECTURE.md) để biết manifests triển khai Kubernetes.
## Có Gì Mới Trong .NET 10
- Tính năng ngôn ngữ **C# 14**
- Hỗ trợ **Native AOT** được cải thiện
- Hiệu suất **async/await** tốt hơn
- **JSON serialization** được nâng cao
- Cải thiện hiệu suất toàn diện
- Hỗ trợ **LTS** 3 năm (đến tháng 11/2028)
Service được đăng ký trong `deployments/local/docker-compose.yml` với Traefik routing.
## Tài Nguyên
- [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers) - Kiến trúc tham chiếu
- [Tài liệu .NET 10](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10)
- [DDD với .NET](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/)
- [MediatR](https://github.com/jbogard/MediatR) - Thư viện CQRS
- [FluentValidation](https://docs.fluentvalidation.net/) - Thư viện validation
- [Tài liệu Kiến trúc](./ARCHITECTURE.md)
- [Tài liệu GoodGo Platform](../../docs/)
- [eShopOnContainers Reference](https://github.com/dotnet-architecture/eShopOnContainers)
## Giấy Phép
Độc quyền - GoodGo Platform
Proprietary - GoodGo Platform