From 71a5d8d4ed8354a0653cf0817158d4ac95f5210d Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Tue, 13 Jan 2026 01:03:33 +0700 Subject: [PATCH] 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. --- deployments/local/.env | 16 +- deployments/local/.env.local | 19 + deployments/local/docker-compose.yml | 148 +++--- deployments/local/env.local.example | 20 + services/iam-service-net/Dockerfile | 4 +- services/iam-service-net/docker-compose.yml | 81 ++-- .../docs/en/ARCHITECTURE.md | 409 ++++++++--------- services/wallet-service-net/docs/en/README.md | 321 ++++++------- .../docs/vi/ARCHITECTURE.md | 427 ++++++++---------- services/wallet-service-net/docs/vi/README.md | 325 ++++++------- 10 files changed, 832 insertions(+), 938 deletions(-) diff --git a/deployments/local/.env b/deployments/local/.env index dda3570e..fe26c622 100644 --- a/deployments/local/.env +++ b/deployments/local/.env @@ -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 diff --git a/deployments/local/.env.local b/deployments/local/.env.local index a5b19d86..eaeb915c 100644 --- a/deployments/local/.env.local +++ b/deployments/local/.env.local @@ -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 diff --git a/deployments/local/docker-compose.yml b/deployments/local/docker-compose.yml index 128be02f..92348811 100644 --- a/deployments/local/docker-compose.yml +++ b/deployments/local/docker-compose.yml @@ -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: diff --git a/deployments/local/env.local.example b/deployments/local/env.local.example index 9cc115cc..9bb85cca 100644 --- a/deployments/local/env.local.example +++ b/deployments/local/env.local.example @@ -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) # ============================================================================= diff --git a/services/iam-service-net/Dockerfile b/services/iam-service-net/Dockerfile index 60d5c358..043214af 100644 --- a/services/iam-service-net/Dockerfile +++ b/services/iam-service-net/Dockerfile @@ -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 diff --git a/services/iam-service-net/docker-compose.yml b/services/iam-service-net/docker-compose.yml index 4663af07..70509bfa 100644 --- a/services/iam-service-net/docker-compose.yml +++ b/services/iam-service-net/docker-compose.yml @@ -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: diff --git a/services/wallet-service-net/docs/en/ARCHITECTURE.md b/services/wallet-service-net/docs/en/ARCHITECTURE.md index 2bc38df4..60532c62 100644 --- a/services/wallet-service-net/docs/en/ARCHITECTURE.md +++ b/services/wallet-service-net/docs/en/ARCHITECTURE.md @@ -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 diff --git a/services/wallet-service-net/docs/en/README.md b/services/wallet-service-net/docs/en/README.md index adc3ea85..fa2d4375 100644 --- a/services/wallet-service-net/docs/en/README.md +++ b/services/wallet-service-net/docs/en/README.md @@ -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; - -// Handle command -public class CreateSampleCommandHandler : IRequestHandler -{ - public async Task 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; +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 diff --git a/services/wallet-service-net/docs/vi/ARCHITECTURE.md b/services/wallet-service-net/docs/vi/ARCHITECTURE.md index 1fe99cf8..254f17a5 100644 --- a/services/wallet-service-net/docs/vi/ARCHITECTURE.md +++ b/services/wallet-service-net/docs/vi/ARCHITECTURE.md @@ -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: -- Có **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 diff --git a/services/wallet-service-net/docs/vi/README.md b/services/wallet-service-net/docs/vi/README.md index bfb80ac8..1a776e75 100644 --- a/services/wallet-service-net/docs/vi/README.md +++ b/services/wallet-service-net/docs/vi/README.md @@ -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; - -// Xử lý command -public class CreateSampleCommandHandler : IRequestHandler -{ - public async Task 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; +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 tiền +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