diff --git a/services/ads-manager-service-net/.env.example b/services/ads-manager-service-net/.env.example
new file mode 100644
index 00000000..f9053bc3
--- /dev/null
+++ b/services/ads-manager-service-net/.env.example
@@ -0,0 +1,40 @@
+# Environment / Môi Trường
+ASPNETCORE_ENVIRONMENT=Development
+
+# Database / Cơ Sở Dữ Liệu
+# PostgreSQL connection string (Neon or local)
+DATABASE_URL=Host=localhost;Port=5432;Database=myservice_db;Username=postgres;Password=postgres
+
+# Redis Cache
+REDIS_URL=localhost:6379
+REDIS_PASSWORD=
+
+# JWT Authentication / Xác Thực JWT
+JWT_SECRET=your-secret-key-min-32-characters-long-here
+JWT_ISSUER=goodgo-platform
+JWT_AUDIENCE=goodgo-services
+JWT_ACCESS_TOKEN_EXPIRY_MINUTES=15
+JWT_REFRESH_TOKEN_EXPIRY_DAYS=7
+
+# API Configuration / Cấu Hình API
+API_PORT=5000
+API_BASE_PATH=/api/v1/myservice
+
+# Observability / Quan Sát
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
+OTEL_SERVICE_NAME=myservice
+
+# Logging
+LOG_LEVEL=Information
+SEQ_URL=http://localhost:5341
+
+# Feature Flags
+FEATURE_SWAGGER_ENABLED=true
+FEATURE_DETAILED_ERRORS=true
+
+# Rate Limiting
+RATE_LIMIT_PERMITS_PER_MINUTE=100
+RATE_LIMIT_QUEUE_LIMIT=10
+
+# Health Checks
+HEALTHCHECK_TIMEOUT_SECONDS=5
diff --git a/services/ads-manager-service-net/.gitignore b/services/ads-manager-service-net/.gitignore
new file mode 100644
index 00000000..84b02a53
--- /dev/null
+++ b/services/ads-manager-service-net/.gitignore
@@ -0,0 +1,75 @@
+# Build results
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio
+.vs/
+*.user
+*.userosscache
+*.suo
+*.userprefs
+*.sln.docstates
+
+# Rider
+.idea/
+*.sln.iml
+
+# Visual Studio Code
+.vscode/
+
+# NuGet
+*.nupkg
+*.snupkg
+.nuget/
+packages/
+project.lock.json
+project.fragment.lock.json
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# Coverage
+TestResults/
+*.coverage
+*.coveragexml
+coverage*.json
+coverage*.xml
+
+# Publish output
+publish/
+out/
+
+# Environment files
+.env
+.env.local
+.env.*.local
+*.env
+
+# Secrets
+appsettings.*.json
+!appsettings.json
+!appsettings.Development.json
+
+# macOS
+.DS_Store
+
+# Windows
+Thumbs.db
+ehthumbs.db
+
+# JetBrains
+*.resharper
+
+# dotnet tools
+.config/dotnet-tools.json
+
+# Migration scripts (only keep structure)
+Migrations/
+
+# Temp files
+*.tmp
+*.temp
+~$*
diff --git a/services/ads-manager-service-net/Directory.Build.props b/services/ads-manager-service-net/Directory.Build.props
new file mode 100644
index 00000000..c3b74373
--- /dev/null
+++ b/services/ads-manager-service-net/Directory.Build.props
@@ -0,0 +1,22 @@
+
+
+ net10.0
+ 14.0
+ enable
+ enable
+ true
+ true
+ $(NoWarn);1591;CA2017
+
+
+
+ GoodGo Team
+ GoodGo
+ © 2026 GoodGo. All rights reserved.
+ git
+
+
+
+
+
+
diff --git a/services/ads-manager-service-net/Dockerfile b/services/ads-manager-service-net/Dockerfile
new file mode 100644
index 00000000..192106ab
--- /dev/null
+++ b/services/ads-manager-service-net/Dockerfile
@@ -0,0 +1,66 @@
+# Build stage / Giai đoạn build
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+WORKDIR /src
+
+# EN: Copy project files for layer caching
+# VI: Sao chép các file project để tận dụng layer caching
+COPY ["src/MyService.API/MyService.API.csproj", "src/MyService.API/"]
+COPY ["src/MyService.Domain/MyService.Domain.csproj", "src/MyService.Domain/"]
+COPY ["src/MyService.Infrastructure/MyService.Infrastructure.csproj", "src/MyService.Infrastructure/"]
+COPY ["Directory.Build.props", "./"]
+
+# EN: Restore dependencies
+# VI: Khôi phục dependencies
+RUN dotnet restore "src/MyService.API/MyService.API.csproj"
+
+# EN: Copy all source code
+# VI: Sao chép toàn bộ source code
+COPY src/ ./src/
+
+# EN: Build the application
+# VI: Build ứng dụng
+WORKDIR "/src/src/MyService.API"
+RUN dotnet build "MyService.API.csproj" -c Release -o /app/build --no-restore
+
+# Publish stage / Giai đoạn publish
+FROM build AS publish
+RUN dotnet publish "MyService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
+
+# Runtime stage / Giai đoạn runtime
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
+WORKDIR /app
+
+# EN: Create non-root user for security
+# VI: Tạo user non-root cho bảo mật
+RUN groupadd -g 1001 dotnetuser && \
+ useradd -u 1001 -g dotnetuser -s /bin/sh dotnetuser
+
+# EN: Copy published application
+# VI: Sao chép ứng dụng đã publish
+COPY --from=publish /app/publish .
+
+# EN: Change ownership to non-root user
+# VI: Thay đổi quyền sở hữu sang user non-root
+RUN chown -R dotnetuser:dotnetuser /app
+
+# EN: Switch to non-root user
+# VI: Chuyển sang user non-root
+USER dotnetuser
+
+# EN: Expose port
+# VI: Mở cổng
+EXPOSE 8080
+
+# EN: Set environment variables
+# VI: Thiết lập biến môi trường
+ENV ASPNETCORE_URLS=http://+:8080
+ENV ASPNETCORE_ENVIRONMENT=Production
+
+# EN: Health check
+# VI: Kiểm tra health
+HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
+ CMD curl -f http://localhost:8080/health/live || exit 1
+
+# EN: Start the application
+# VI: Khởi động ứng dụng
+ENTRYPOINT ["dotnet", "MyService.API.dll"]
diff --git a/services/ads-manager-service-net/MyService.slnx b/services/ads-manager-service-net/MyService.slnx
new file mode 100644
index 00000000..1222dbb8
--- /dev/null
+++ b/services/ads-manager-service-net/MyService.slnx
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/services/ads-manager-service-net/docker-compose.yml b/services/ads-manager-service-net/docker-compose.yml
new file mode 100644
index 00000000..254ceb12
--- /dev/null
+++ b/services/ads-manager-service-net/docker-compose.yml
@@ -0,0 +1,72 @@
+version: '3.8'
+
+# EN: Docker Compose for local development
+# VI: Docker Compose cho phát triển local
+
+services:
+ myservice-api:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: myservice-api
+ ports:
+ - "5000:8080"
+ environment:
+ - ASPNETCORE_ENVIRONMENT=Development
+ - DATABASE_URL=Host=postgres;Port=5432;Database=myservice_db;Username=postgres;Password=postgres
+ - REDIS_URL=redis:6379
+ depends_on:
+ postgres:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
+ networks:
+ - myservice-network
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8080/health/live"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 10s
+
+ postgres:
+ image: postgres:16-alpine
+ container_name: myservice-postgres
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_DB: myservice_db
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ networks:
+ - myservice-network
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ redis:
+ image: redis:7-alpine
+ container_name: myservice-redis
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis_data:/data
+ networks:
+ - myservice-network
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ postgres_data:
+ redis_data:
+
+networks:
+ myservice-network:
+ driver: bridge
diff --git a/services/ads-manager-service-net/docs/en/ARCHITECTURE.md b/services/ads-manager-service-net/docs/en/ARCHITECTURE.md
new file mode 100644
index 00000000..9d80ba57
--- /dev/null
+++ b/services/ads-manager-service-net/docs/en/ARCHITECTURE.md
@@ -0,0 +1,271 @@
+# Architecture Documentation
+
+> Detailed architecture documentation for the .NET 10 Microservice Template.
+
+## Architecture Overview
+
+```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
+```
+
+## Layer Responsibilities
+
+### 1. Domain Layer (MyService.Domain)
+
+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
+
+#### Components
+
+| 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 (MyService.Infrastructure)
+
+Technical implementations and external concerns:
+- Database access (EF Core)
+- Repository implementations
+- External service integrations
+
+### 3. API Layer (MyService.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
+```
+
+## Domain Events
+
+```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]
+
+ style AR fill:#50c878,stroke:#2d8659,color:#fff
+ style DE fill:#f39c12,stroke:#d68910,color:#fff
+ style M fill:#9b59b6,stroke:#7d3c98,color:#fff
+```
+
+## Database Schema
+
+### Sample Aggregate
+
+```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
+```
+
+## MediatR Pipeline
+
+```
+Request → LoggingBehavior → ValidatorBehavior → TransactionBehavior → Handler → Response
+ │ │ │
+ ▼ ▼ ▼
+ Log start/end Validate Begin/Commit
+ + timing with Transaction
+ FluentValidation
+```
+
+### 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
+
+```
+Exception
+└── DomainException
+ └── SampleDomainException
+```
+
+### Problem Details (RFC 7807)
+
+All errors are returned in Problem Details format:
+
+```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"]
+ }
+}
+```
+
+## Health Checks
+
+```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
+```
+
+## Deployment Architecture
+
+### Docker Compose (Local)
+
+```yaml
+services:
+ myservice-api:
+ build: .
+ ports: ["5000:8080"]
+ depends_on:
+ - postgres
+ - redis
+
+ postgres:
+ image: postgres:16-alpine
+
+ redis:
+ image: redis:7-alpine
+```
+
+### Kubernetes (Production)
+
+```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
+```
+
+## Security Considerations
+
+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
+
+## Performance Optimization
+
+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
+
+## References
+
+- [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)
diff --git a/services/ads-manager-service-net/docs/en/README.md b/services/ads-manager-service-net/docs/en/README.md
new file mode 100644
index 00000000..4cb53d44
--- /dev/null
+++ b/services/ads-manager-service-net/docs/en/README.md
@@ -0,0 +1,265 @@
+# .NET 10 Microservice Template
+
+> Enterprise-grade .NET 10 microservice template following DDD, CQRS, and Clean Architecture patterns.
+
+## Overview
+
+This template provides a production-ready structure for .NET microservices based on the eShopOnContainers reference architecture 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
+
+## 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
+```
+
+## Quick Start
+
+### 1. Create New Service
+
+```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 "MyService" to "YourService"
+find . -type f -name "*.cs" -exec sed -i '' 's/MyService/YourService/g' {} +
+find . -type f -name "*.csproj" -exec sed -i '' 's/MyService/YourService/g' {} +
+```
+
+### 2. Configure Environment
+
+```bash
+# Copy environment template
+cp .env.example .env
+
+# Edit with your configuration
+nano .env
+```
+
+### 3. Run with Docker
+
+```bash
+# Start all services (API + PostgreSQL + Redis)
+docker-compose up -d
+
+# View logs
+docker-compose logs -f myservice-api
+```
+
+### 4. Run Locally
+
+```bash
+# Restore dependencies
+dotnet restore
+
+# Build all projects
+dotnet build
+
+# Run the API
+dotnet run --project src/MyService.API
+```
+
+## Project Structure
+
+```
+_template_dot_net/
+├── src/
+│ ├── MyService.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
+│ │
+│ ├── MyService.Domain/ # Domain Layer (Pure business logic)
+│ │ ├── AggregatesModel/ # Aggregate roots and entities
+│ │ ├── Events/ # Domain events
+│ │ ├── Exceptions/ # Domain exceptions
+│ │ └── SeedWork/ # Base classes (Entity, ValueObject, etc.)
+│ │
+│ └── MyService.Infrastructure/ # Infrastructure Layer (Data access)
+│ ├── EntityConfigurations/ # EF Core Fluent API configurations
+│ ├── Repositories/ # Repository implementations
+│ ├── Idempotency/ # Request idempotency handling
+│ └── MyServiceContext.cs # DbContext with Unit of Work
+│
+├── tests/
+│ ├── MyService.UnitTests/ # Unit tests (Domain, Application)
+│ └── MyService.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
+
+| 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 |
+
+### Health Endpoints
+
+| Endpoint | Purpose |
+|----------|---------|
+| `/health` | Full health status |
+| `/health/live` | Liveness probe |
+| `/health/ready` | Readiness probe |
+
+## CQRS Pattern
+
+### 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;
+```
+
+## Domain Model
+
+### Aggregate Root
+
+```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
+ }
+}
+```
+
+## Testing
+
+```bash
+# Run all tests
+dotnet test
+
+# Run with coverage
+dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura
+
+# Run specific test project
+dotnet test tests/MyService.UnitTests
+```
+
+## 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) | - |
+
+### appsettings.json
+
+```json
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Host=localhost;Database=myservice;Username=postgres;Password=postgres"
+ },
+ "Serilog": {
+ "MinimumLevel": "Information"
+ }
+}
+```
+
+## Deployment
+
+### Docker Build
+
+```bash
+# Build Docker image
+docker build -t myservice:latest .
+
+# Run container
+docker run -p 5000:8080 --env-file .env myservice:latest
+```
+
+### Kubernetes
+
+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)
+
+## 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
+
+## License
+
+Proprietary - GoodGo Platform
diff --git a/services/ads-manager-service-net/docs/vi/ARCHITECTURE.md b/services/ads-manager-service-net/docs/vi/ARCHITECTURE.md
new file mode 100644
index 00000000..55a5d13b
--- /dev/null
+++ b/services/ads-manager-service-net/docs/vi/ARCHITECTURE.md
@@ -0,0 +1,271 @@
+# Tài Liệu Kiến Trúc
+
+> Tài liệu kiến trúc chi tiết cho Template Microservice .NET 10.
+
+## Tổng Quan Kiến Trúc
+
+```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
+```
+
+## Trách Nhiệm Các Lớp
+
+### 1. Lớp Domain (MyService.Domain)
+
+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
+
+#### Thành Phần
+
+| 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 (MyService.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 (MyService.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
+```
+
+## Domain Events
+
+```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
+ }
+
+ sample_statuses {
+ int id PK
+ varchar(50) name
+ }
+
+ samples ||--o{ sample_statuses : has
+```
+
+## 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
+
+```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
+```
+
+## Kiến Trúc Deployment
+
+### Docker Compose (Local)
+
+```yaml
+services:
+ myservice-api:
+ build: .
+ ports: ["5000:8080"]
+ depends_on:
+ - postgres
+ - redis
+
+ postgres:
+ image: postgres:16-alpine
+
+ redis:
+ image: redis:7-alpine
+```
+
+### Kubernetes (Production)
+
+```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
+```
+
+## Cân Nhắc 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
+
+## Tối Ưu Hiệu Năng
+
+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
+
+## Tài Liệu Tham Khảo
+
+- [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)
diff --git a/services/ads-manager-service-net/docs/vi/README.md b/services/ads-manager-service-net/docs/vi/README.md
new file mode 100644
index 00000000..7d7e48b6
--- /dev/null
+++ b/services/ads-manager-service-net/docs/vi/README.md
@@ -0,0 +1,265 @@
+# Template Microservice .NET 10
+
+> Template microservice .NET 10 cấp doanh nghiệp theo các pattern DDD, CQRS và Clean Architecture.
+
+## 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:
+
+- **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
+
+## Yêu Cầu
+
+| Yêu cầu | Phiên bản |
+|---------|-----------|
+| .NET SDK | 10.0.101+ |
+| Docker | 24.0+ |
+| PostgreSQL | 15+ (hoặc dùng Docker) |
+
+```bash
+# Kiểm tra phiên bản .NET
+dotnet --version
+# Kết quả nên là: 10.0.xxx
+```
+
+## Bắt Đầu Nhanh
+
+### 1. Tạo Service Mới
+
+```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ả "MyService" thành "YourService"
+find . -type f -name "*.cs" -exec sed -i '' 's/MyService/YourService/g' {} +
+find . -type f -name "*.csproj" -exec sed -i '' 's/MyService/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
+```
+
+### 3. 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
+```
+
+### 4. Chạy Local
+
+```bash
+# Khôi phục dependencies
+dotnet restore
+
+# Build tất cả projects
+dotnet build
+
+# Chạy API
+dotnet run --project src/MyService.API
+```
+
+## Cấu Trúc Dự Án
+
+```
+_template_dot_net/
+├── src/
+│ ├── MyService.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
+│ │
+│ ├── MyService.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.)
+│ │
+│ └── MyService.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
+│ └── MyServiceContext.cs # DbContext với Unit of Work
+│
+├── tests/
+│ ├── MyService.UnitTests/ # Unit tests (Domain, Application)
+│ └── MyService.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
+
+| 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 |
+
+### 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 |
+
+## Pattern CQRS
+
+### 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;
+```
+
+## Domain Model
+
+### Aggregate Root
+
+```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
+ }
+}
+```
+
+## Kiểm Thử
+
+```bash
+# Chạy tất cả tests
+dotnet test
+
+# Chạy với coverage
+dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura
+
+# Chạy project test cụ thể
+dotnet test tests/MyService.UnitTests
+```
+
+## Cấu Hình
+
+### Biến Môi Trường
+
+| Biến | Mô Tả | Mặc định |
+|------|-------|----------|
+| `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ự) | - |
+
+### appsettings.json
+
+```json
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Host=localhost;Database=myservice;Username=postgres;Password=postgres"
+ },
+ "Serilog": {
+ "MinimumLevel": "Information"
+ }
+}
+```
+
+## 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
+```
+
+### Kubernetes
+
+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)
+
+## 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
+
+## Giấy Phép
+
+Độc quyền - GoodGo Platform
diff --git a/services/ads-manager-service-net/global.json b/services/ads-manager-service-net/global.json
new file mode 100644
index 00000000..f78eeaf4
--- /dev/null
+++ b/services/ads-manager-service-net/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.101",
+ "rollForward": "latestMinor",
+ "allowPrerelease": false
+ }
+}
\ No newline at end of file
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/LoggingBehavior.cs b/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/LoggingBehavior.cs
new file mode 100644
index 00000000..a724424d
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/LoggingBehavior.cs
@@ -0,0 +1,58 @@
+using System.Diagnostics;
+using MediatR;
+
+namespace MyService.API.Application.Behaviors;
+
+///
+/// EN: MediatR behavior for logging request handling.
+/// VI: MediatR behavior để logging việc xử lý request.
+///
+/// EN: Request type / VI: Loại request
+/// EN: Response type / VI: Loại response
+public class LoggingBehavior : IPipelineBehavior
+ where TRequest : IRequest
+{
+ private readonly ILogger> _logger;
+
+ public LoggingBehavior(ILogger> logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken)
+ {
+ var requestName = typeof(TRequest).Name;
+
+ _logger.LogInformation(
+ "Handling {RequestName} / Đang xử lý {RequestName}",
+ requestName);
+
+ var stopwatch = Stopwatch.StartNew();
+
+ try
+ {
+ var response = await next();
+
+ stopwatch.Stop();
+
+ _logger.LogInformation(
+ "Handled {RequestName} in {ElapsedMs}ms / Đã xử lý {RequestName} trong {ElapsedMs}ms",
+ requestName, stopwatch.ElapsedMilliseconds);
+
+ return response;
+ }
+ catch (Exception ex)
+ {
+ stopwatch.Stop();
+
+ _logger.LogError(ex,
+ "Error handling {RequestName} after {ElapsedMs}ms / Lỗi xử lý {RequestName} sau {ElapsedMs}ms",
+ requestName, stopwatch.ElapsedMilliseconds);
+
+ throw;
+ }
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/TransactionBehavior.cs b/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/TransactionBehavior.cs
new file mode 100644
index 00000000..8675b649
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/TransactionBehavior.cs
@@ -0,0 +1,84 @@
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using MyService.Infrastructure;
+
+namespace MyService.API.Application.Behaviors;
+
+///
+/// EN: MediatR behavior for handling database transactions.
+/// VI: MediatR behavior để xử lý database transactions.
+///
+/// EN: Request type / VI: Loại request
+/// EN: Response type / VI: Loại response
+public class TransactionBehavior : IPipelineBehavior
+ where TRequest : IRequest
+{
+ private readonly MyServiceContext _dbContext;
+ private readonly ILogger> _logger;
+
+ public TransactionBehavior(
+ MyServiceContext dbContext,
+ ILogger> logger)
+ {
+ _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken)
+ {
+ var requestName = typeof(TRequest).Name;
+
+ // EN: Skip transaction for queries (read operations)
+ // VI: Bỏ qua transaction cho queries (các thao tác đọc)
+ if (requestName.EndsWith("Query"))
+ {
+ return await next();
+ }
+
+ // EN: Skip if already in a transaction
+ // VI: Bỏ qua nếu đã trong transaction
+ if (_dbContext.HasActiveTransaction)
+ {
+ return await next();
+ }
+
+ var strategy = _dbContext.Database.CreateExecutionStrategy();
+
+ return await strategy.ExecuteAsync(async () =>
+ {
+ await using var transaction = await _dbContext.BeginTransactionAsync();
+
+ _logger.LogInformation(
+ "Begin transaction {TransactionId} for {RequestName} / Bắt đầu transaction {TransactionId} cho {RequestName}",
+ transaction?.TransactionId, requestName);
+
+ try
+ {
+ var response = await next();
+
+ if (transaction != null)
+ {
+ await _dbContext.CommitTransactionAsync(transaction);
+
+ _logger.LogInformation(
+ "Committed transaction {TransactionId} for {RequestName} / Đã commit transaction {TransactionId} cho {RequestName}",
+ transaction.TransactionId, requestName);
+ }
+
+ return response;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "Error during transaction {TransactionId} for {RequestName} / Lỗi trong transaction {TransactionId} cho {RequestName}",
+ transaction?.TransactionId, requestName);
+
+ _dbContext.RollbackTransaction();
+ throw;
+ }
+ });
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/ValidatorBehavior.cs b/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/ValidatorBehavior.cs
new file mode 100644
index 00000000..0062cd60
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Behaviors/ValidatorBehavior.cs
@@ -0,0 +1,63 @@
+using FluentValidation;
+using MediatR;
+
+namespace MyService.API.Application.Behaviors;
+
+///
+/// EN: MediatR behavior for FluentValidation integration.
+/// VI: MediatR behavior để tích hợp FluentValidation.
+///
+/// EN: Request type / VI: Loại request
+/// EN: Response type / VI: Loại response
+public class ValidatorBehavior : IPipelineBehavior
+ where TRequest : IRequest
+{
+ private readonly IEnumerable> _validators;
+ private readonly ILogger> _logger;
+
+ public ValidatorBehavior(
+ IEnumerable> validators,
+ ILogger> logger)
+ {
+ _validators = validators ?? throw new ArgumentNullException(nameof(validators));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken)
+ {
+ var requestName = typeof(TRequest).Name;
+
+ if (!_validators.Any())
+ {
+ return await next();
+ }
+
+ _logger.LogDebug(
+ "Validating {RequestName} / Đang validate {RequestName}",
+ requestName);
+
+ var context = new ValidationContext(request);
+
+ var validationResults = await Task.WhenAll(
+ _validators.Select(v => v.ValidateAsync(context, cancellationToken)));
+
+ var failures = validationResults
+ .SelectMany(r => r.Errors)
+ .Where(f => f != null)
+ .ToList();
+
+ if (failures.Count != 0)
+ {
+ _logger.LogWarning(
+ "Validation failed for {RequestName} with {ErrorCount} errors / Validation thất bại cho {RequestName} với {ErrorCount} lỗi",
+ requestName, failures.Count);
+
+ throw new ValidationException(failures);
+ }
+
+ return await next();
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/ChangeSampleStatusCommand.cs
new file mode 100644
index 00000000..49825490
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/ChangeSampleStatusCommand.cs
@@ -0,0 +1,14 @@
+using MediatR;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Command to change status of a Sample.
+/// VI: Command để thay đổi trạng thái của Sample.
+///
+/// EN: Sample ID / VI: ID sample
+/// EN: New status (activate, complete, cancel) / VI: Trạng thái mới (activate, complete, cancel)
+public record ChangeSampleStatusCommand(
+ Guid SampleId,
+ string NewStatus
+) : IRequest;
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs
new file mode 100644
index 00000000..76e31030
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs
@@ -0,0 +1,70 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Handler for ChangeSampleStatusCommand.
+/// VI: Handler cho ChangeSampleStatusCommand.
+///
+public class ChangeSampleStatusCommandHandler : IRequestHandler
+{
+ private readonly ISampleRepository _sampleRepository;
+ private readonly ILogger _logger;
+
+ public ChangeSampleStatusCommandHandler(
+ ISampleRepository sampleRepository,
+ ILogger logger)
+ {
+ _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ ChangeSampleStatusCommand request,
+ CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Changing status of sample {SampleId} to {NewStatus} / Thay đổi trạng thái sample {SampleId} thành {NewStatus}",
+ request.SampleId, request.NewStatus);
+
+ // EN: Get existing sample / VI: Lấy sample đã tồn tại
+ var sample = await _sampleRepository.GetAsync(request.SampleId);
+
+ if (sample is null)
+ {
+ _logger.LogWarning(
+ "Sample {SampleId} not found / Sample {SampleId} không tìm thấy",
+ request.SampleId);
+ return false;
+ }
+
+ // EN: Change status based on action / VI: Thay đổi trạng thái dựa trên action
+ switch (request.NewStatus.ToLowerInvariant())
+ {
+ case "activate":
+ sample.Activate();
+ break;
+ case "complete":
+ sample.Complete();
+ break;
+ case "cancel":
+ sample.Cancel();
+ break;
+ default:
+ _logger.LogWarning(
+ "Invalid status action: {NewStatus} / Action trạng thái không hợp lệ: {NewStatus}",
+ request.NewStatus);
+ return false;
+ }
+
+ // EN: Save changes / VI: Lưu thay đổi
+ await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Sample {SampleId} status changed to {NewStatus} / Trạng thái sample {SampleId} đã đổi thành {NewStatus}",
+ request.SampleId, request.NewStatus);
+
+ return true;
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/CreateSampleCommand.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/CreateSampleCommand.cs
new file mode 100644
index 00000000..138cc794
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/CreateSampleCommand.cs
@@ -0,0 +1,21 @@
+using MediatR;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Command to create a new Sample.
+/// VI: Command để tạo một Sample mới.
+///
+/// EN: Sample name / VI: Tên sample
+/// EN: Optional description / VI: Mô tả tùy chọn
+public record CreateSampleCommand(
+ string Name,
+ string? Description
+) : IRequest;
+
+///
+/// EN: Result of CreateSampleCommand.
+/// VI: Kết quả của CreateSampleCommand.
+///
+/// EN: Created sample ID / VI: ID sample đã tạo
+public record CreateSampleCommandResult(Guid Id);
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/CreateSampleCommandHandler.cs
new file mode 100644
index 00000000..d7d0fd7c
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/CreateSampleCommandHandler.cs
@@ -0,0 +1,46 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Handler for CreateSampleCommand.
+/// VI: Handler cho CreateSampleCommand.
+///
+public class CreateSampleCommandHandler : IRequestHandler
+{
+ private readonly ISampleRepository _sampleRepository;
+ private readonly ILogger _logger;
+
+ public CreateSampleCommandHandler(
+ ISampleRepository sampleRepository,
+ ILogger logger)
+ {
+ _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ CreateSampleCommand request,
+ CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Creating new sample with name: {Name} / Tạo sample mới với tên: {Name}",
+ request.Name);
+
+ // EN: Create domain entity / VI: Tạo domain entity
+ var sample = new Sample(request.Name, request.Description);
+
+ // EN: Add to repository / VI: Thêm vào repository
+ _sampleRepository.Add(sample);
+
+ // EN: Save changes (dispatches domain events) / VI: Lưu thay đổi (dispatch domain events)
+ await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Sample created successfully with ID: {SampleId} / Sample đã tạo thành công với ID: {SampleId}",
+ sample.Id);
+
+ return new CreateSampleCommandResult(sample.Id);
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/DeleteSampleCommand.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/DeleteSampleCommand.cs
new file mode 100644
index 00000000..0de392db
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/DeleteSampleCommand.cs
@@ -0,0 +1,10 @@
+using MediatR;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Command to delete a Sample.
+/// VI: Command để xóa một Sample.
+///
+/// EN: Sample ID to delete / VI: ID sample cần xóa
+public record DeleteSampleCommand(Guid SampleId) : IRequest;
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/DeleteSampleCommandHandler.cs
new file mode 100644
index 00000000..c7632189
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/DeleteSampleCommandHandler.cs
@@ -0,0 +1,54 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Handler for DeleteSampleCommand.
+/// VI: Handler cho DeleteSampleCommand.
+///
+public class DeleteSampleCommandHandler : IRequestHandler
+{
+ private readonly ISampleRepository _sampleRepository;
+ private readonly ILogger _logger;
+
+ public DeleteSampleCommandHandler(
+ ISampleRepository sampleRepository,
+ ILogger logger)
+ {
+ _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ DeleteSampleCommand request,
+ CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Deleting sample {SampleId} / Xóa sample {SampleId}",
+ request.SampleId);
+
+ // EN: Get existing sample / VI: Lấy sample đã tồn tại
+ var sample = await _sampleRepository.GetAsync(request.SampleId);
+
+ if (sample is null)
+ {
+ _logger.LogWarning(
+ "Sample {SampleId} not found / Sample {SampleId} không tìm thấy",
+ request.SampleId);
+ return false;
+ }
+
+ // EN: Delete sample / VI: Xóa sample
+ _sampleRepository.Delete(sample);
+
+ // EN: Save changes / VI: Lưu thay đổi
+ await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Sample {SampleId} deleted successfully / Sample {SampleId} đã xóa thành công",
+ request.SampleId);
+
+ return true;
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/UpdateSampleCommand.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/UpdateSampleCommand.cs
new file mode 100644
index 00000000..6fad8514
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/UpdateSampleCommand.cs
@@ -0,0 +1,16 @@
+using MediatR;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Command to update an existing Sample.
+/// VI: Command để cập nhật một Sample đã tồn tại.
+///
+/// EN: Sample ID to update / VI: ID sample cần cập nhật
+/// EN: New name / VI: Tên mới
+/// EN: New description / VI: Mô tả mới
+public record UpdateSampleCommand(
+ Guid SampleId,
+ string Name,
+ string? Description
+) : IRequest;
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/ads-manager-service-net/src/MyService.API/Application/Commands/UpdateSampleCommandHandler.cs
new file mode 100644
index 00000000..e904cf0a
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Commands/UpdateSampleCommandHandler.cs
@@ -0,0 +1,54 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.API.Application.Commands;
+
+///
+/// EN: Handler for UpdateSampleCommand.
+/// VI: Handler cho UpdateSampleCommand.
+///
+public class UpdateSampleCommandHandler : IRequestHandler
+{
+ private readonly ISampleRepository _sampleRepository;
+ private readonly ILogger _logger;
+
+ public UpdateSampleCommandHandler(
+ ISampleRepository sampleRepository,
+ ILogger logger)
+ {
+ _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(
+ UpdateSampleCommand request,
+ CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Updating sample {SampleId} / Cập nhật sample {SampleId}",
+ request.SampleId);
+
+ // EN: Get existing sample / VI: Lấy sample đã tồn tại
+ var sample = await _sampleRepository.GetAsync(request.SampleId);
+
+ if (sample is null)
+ {
+ _logger.LogWarning(
+ "Sample {SampleId} not found / Sample {SampleId} không tìm thấy",
+ request.SampleId);
+ return false;
+ }
+
+ // EN: Update sample using domain method / VI: Cập nhật sample sử dụng domain method
+ sample.Update(request.Name, request.Description);
+
+ // EN: Save changes / VI: Lưu thay đổi
+ await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Sample {SampleId} updated successfully / Sample {SampleId} đã cập nhật thành công",
+ request.SampleId);
+
+ return true;
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSampleQuery.cs b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSampleQuery.cs
new file mode 100644
index 00000000..8b90789c
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSampleQuery.cs
@@ -0,0 +1,23 @@
+using MediatR;
+
+namespace MyService.API.Application.Queries;
+
+///
+/// EN: Query to get a Sample by ID.
+/// VI: Query để lấy một Sample theo ID.
+///
+/// EN: Sample ID / VI: ID sample
+public record GetSampleQuery(Guid SampleId) : IRequest;
+
+///
+/// EN: Sample view model for API responses.
+/// VI: Sample view model cho API responses.
+///
+public record SampleViewModel(
+ Guid Id,
+ string Name,
+ string? Description,
+ string Status,
+ DateTime CreatedAt,
+ DateTime? UpdatedAt
+);
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSampleQueryHandler.cs b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSampleQueryHandler.cs
new file mode 100644
index 00000000..2da10b6d
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSampleQueryHandler.cs
@@ -0,0 +1,39 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.API.Application.Queries;
+
+///
+/// EN: Handler for GetSampleQuery.
+/// VI: Handler cho GetSampleQuery.
+///
+public class GetSampleQueryHandler : IRequestHandler
+{
+ private readonly ISampleRepository _sampleRepository;
+
+ public GetSampleQueryHandler(ISampleRepository sampleRepository)
+ {
+ _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository));
+ }
+
+ public async Task Handle(
+ GetSampleQuery request,
+ CancellationToken cancellationToken)
+ {
+ var sample = await _sampleRepository.GetAsync(request.SampleId);
+
+ if (sample is null)
+ {
+ return null;
+ }
+
+ return new SampleViewModel(
+ sample.Id,
+ sample.Name,
+ sample.Description,
+ sample.Status.Name,
+ sample.CreatedAt,
+ sample.UpdatedAt
+ );
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSamplesQuery.cs b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSamplesQuery.cs
new file mode 100644
index 00000000..d6a98e34
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSamplesQuery.cs
@@ -0,0 +1,9 @@
+using MediatR;
+
+namespace MyService.API.Application.Queries;
+
+///
+/// EN: Query to get all Samples.
+/// VI: Query để lấy tất cả Samples.
+///
+public record GetSamplesQuery : IRequest>;
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSamplesQueryHandler.cs
new file mode 100644
index 00000000..2185302d
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Queries/GetSamplesQueryHandler.cs
@@ -0,0 +1,34 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.API.Application.Queries;
+
+///
+/// EN: Handler for GetSamplesQuery.
+/// VI: Handler cho GetSamplesQuery.
+///
+public class GetSamplesQueryHandler : IRequestHandler>
+{
+ private readonly ISampleRepository _sampleRepository;
+
+ public GetSamplesQueryHandler(ISampleRepository sampleRepository)
+ {
+ _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository));
+ }
+
+ public async Task> Handle(
+ GetSamplesQuery request,
+ CancellationToken cancellationToken)
+ {
+ var samples = await _sampleRepository.GetAllAsync();
+
+ return samples.Select(sample => new SampleViewModel(
+ sample.Id,
+ sample.Name,
+ sample.Description,
+ sample.Status.Name,
+ sample.CreatedAt,
+ sample.UpdatedAt
+ ));
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/ads-manager-service-net/src/MyService.API/Application/Validations/CreateSampleCommandValidator.cs
new file mode 100644
index 00000000..2f339fb3
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Validations/CreateSampleCommandValidator.cs
@@ -0,0 +1,25 @@
+using FluentValidation;
+using MyService.API.Application.Commands;
+
+namespace MyService.API.Application.Validations;
+
+///
+/// EN: Validator for CreateSampleCommand.
+/// VI: Validator cho CreateSampleCommand.
+///
+public class CreateSampleCommandValidator : AbstractValidator
+{
+ public CreateSampleCommandValidator()
+ {
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithMessage("Name is required / Tên là bắt buộc")
+ .MaximumLength(200)
+ .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự");
+
+ RuleFor(x => x.Description)
+ .MaximumLength(1000)
+ .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự")
+ .When(x => x.Description != null);
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/ads-manager-service-net/src/MyService.API/Application/Validations/UpdateSampleCommandValidator.cs
new file mode 100644
index 00000000..7030d5c8
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Application/Validations/UpdateSampleCommandValidator.cs
@@ -0,0 +1,29 @@
+using FluentValidation;
+using MyService.API.Application.Commands;
+
+namespace MyService.API.Application.Validations;
+
+///
+/// EN: Validator for UpdateSampleCommand.
+/// VI: Validator cho UpdateSampleCommand.
+///
+public class UpdateSampleCommandValidator : AbstractValidator
+{
+ public UpdateSampleCommandValidator()
+ {
+ RuleFor(x => x.SampleId)
+ .NotEmpty()
+ .WithMessage("Sample ID is required / ID sample là bắt buộc");
+
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithMessage("Name is required / Tên là bắt buộc")
+ .MaximumLength(200)
+ .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự");
+
+ RuleFor(x => x.Description)
+ .MaximumLength(1000)
+ .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự")
+ .When(x => x.Description != null);
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.API/Controllers/SamplesController.cs b/services/ads-manager-service-net/src/MyService.API/Controllers/SamplesController.cs
new file mode 100644
index 00000000..c87e0ffa
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Controllers/SamplesController.cs
@@ -0,0 +1,200 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using MyService.API.Application.Commands;
+using MyService.API.Application.Queries;
+
+namespace MyService.API.Controllers;
+
+///
+/// EN: Controller for Sample CRUD operations using CQRS pattern.
+/// VI: Controller cho các thao tác CRUD Sample sử dụng pattern CQRS.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/[controller]")]
+[Produces("application/json")]
+public class SamplesController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public SamplesController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get all samples.
+ /// VI: Lấy tất cả samples.
+ ///
+ /// EN: List of samples / VI: Danh sách samples
+ [HttpGet]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task GetSamples()
+ {
+ var samples = await _mediator.Send(new GetSamplesQuery());
+ return Ok(new { success = true, data = samples });
+ }
+
+ ///
+ /// EN: Get a sample by ID.
+ /// VI: Lấy một sample theo ID.
+ ///
+ /// EN: Sample ID / VI: ID sample
+ /// EN: Sample details / VI: Chi tiết sample
+ [HttpGet("{id:guid}")]
+ [ProducesResponseType(typeof(SampleViewModel), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task GetSample(Guid id)
+ {
+ var sample = await _mediator.Send(new GetSampleQuery(id));
+
+ if (sample is null)
+ {
+ return NotFound(new
+ {
+ success = false,
+ error = new
+ {
+ code = "SAMPLE_NOT_FOUND",
+ message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy"
+ }
+ });
+ }
+
+ return Ok(new { success = true, data = sample });
+ }
+
+ ///
+ /// EN: Create a new sample.
+ /// VI: Tạo một sample mới.
+ ///
+ /// EN: Create request / VI: Request tạo
+ /// EN: Created sample ID / VI: ID sample đã tạo
+ [HttpPost]
+ [ProducesResponseType(typeof(CreateSampleCommandResult), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task CreateSample([FromBody] CreateSampleRequest request)
+ {
+ var command = new CreateSampleCommand(request.Name, request.Description);
+ var result = await _mediator.Send(command);
+
+ return CreatedAtAction(
+ nameof(GetSample),
+ new { id = result.Id },
+ new { success = true, data = result });
+ }
+
+ ///
+ /// EN: Update an existing sample.
+ /// VI: Cập nhật một sample đã tồn tại.
+ ///
+ /// EN: Sample ID / VI: ID sample
+ /// EN: Update request / VI: Request cập nhật
+ /// EN: Success status / VI: Trạng thái thành công
+ [HttpPut("{id:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task UpdateSample(Guid id, [FromBody] UpdateSampleRequest request)
+ {
+ var command = new UpdateSampleCommand(id, request.Name, request.Description);
+ var result = await _mediator.Send(command);
+
+ if (!result)
+ {
+ return NotFound(new
+ {
+ success = false,
+ error = new
+ {
+ code = "SAMPLE_NOT_FOUND",
+ message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy"
+ }
+ });
+ }
+
+ return Ok(new { success = true, message = "Sample updated successfully / Sample đã cập nhật thành công" });
+ }
+
+ ///
+ /// EN: Delete a sample.
+ /// VI: Xóa một sample.
+ ///
+ /// EN: Sample ID / VI: ID sample
+ /// EN: Success status / VI: Trạng thái thành công
+ [HttpDelete("{id:guid}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeleteSample(Guid id)
+ {
+ var command = new DeleteSampleCommand(id);
+ var result = await _mediator.Send(command);
+
+ if (!result)
+ {
+ return NotFound(new
+ {
+ success = false,
+ error = new
+ {
+ code = "SAMPLE_NOT_FOUND",
+ message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy"
+ }
+ });
+ }
+
+ return NoContent();
+ }
+
+ ///
+ /// EN: Change sample status.
+ /// VI: Thay đổi trạng thái sample.
+ ///
+ /// EN: Sample ID / VI: ID sample
+ /// EN: Status change request / VI: Request thay đổi trạng thái
+ /// EN: Success status / VI: Trạng thái thành công
+ [HttpPatch("{id:guid}/status")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task ChangeSampleStatus(Guid id, [FromBody] ChangeStatusRequest request)
+ {
+ var command = new ChangeSampleStatusCommand(id, request.Status);
+ var result = await _mediator.Send(command);
+
+ if (!result)
+ {
+ return BadRequest(new
+ {
+ success = false,
+ error = new
+ {
+ code = "STATUS_CHANGE_FAILED",
+ message = "Failed to change sample status / Thay đổi trạng thái sample thất bại"
+ }
+ });
+ }
+
+ return Ok(new { success = true, message = "Sample status changed successfully / Trạng thái sample đã thay đổi thành công" });
+ }
+}
+
+///
+/// EN: Request model for creating a sample.
+/// VI: Model request để tạo sample.
+///
+public record CreateSampleRequest(string Name, string? Description);
+
+///
+/// EN: Request model for updating a sample.
+/// VI: Model request để cập nhật sample.
+///
+public record UpdateSampleRequest(string Name, string? Description);
+
+///
+/// EN: Request model for changing sample status.
+/// VI: Model request để thay đổi trạng thái sample.
+///
+public record ChangeStatusRequest(string Status);
diff --git a/services/ads-manager-service-net/src/MyService.API/MyService.API.csproj b/services/ads-manager-service-net/src/MyService.API/MyService.API.csproj
new file mode 100644
index 00000000..1b5bb222
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/MyService.API.csproj
@@ -0,0 +1,43 @@
+
+
+
+ MyService.API
+ MyService.API
+ Web API layer with CQRS pattern
+ myservice-api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/services/ads-manager-service-net/src/MyService.API/Program.cs b/services/ads-manager-service-net/src/MyService.API/Program.cs
new file mode 100644
index 00000000..bd9b3df4
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Program.cs
@@ -0,0 +1,144 @@
+using Asp.Versioning;
+using FluentValidation;
+using Hellang.Middleware.ProblemDetails;
+using MyService.API.Application.Behaviors;
+using MyService.Infrastructure;
+using Serilog;
+
+// EN: Configure Serilog early / VI: Cấu hình Serilog sớm
+Log.Logger = new LoggerConfiguration()
+ .WriteTo.Console()
+ .CreateBootstrapLogger();
+
+try
+{
+ Log.Information("Starting MyService API / Khởi động MyService API");
+
+ var builder = WebApplication.CreateBuilder(args);
+
+ // EN: Configure Serilog / VI: Cấu hình Serilog
+ builder.Host.UseSerilog((context, services, configuration) => configuration
+ .ReadFrom.Configuration(context.Configuration)
+ .ReadFrom.Services(services)
+ .Enrich.FromLogContext()
+ .WriteTo.Console());
+
+ // EN: Add Infrastructure services / VI: Thêm Infrastructure services
+ builder.Services.AddInfrastructure(builder.Configuration);
+
+ // EN: Add MediatR with behaviors / VI: Thêm MediatR với behaviors
+ builder.Services.AddMediatR(cfg =>
+ {
+ cfg.RegisterServicesFromAssemblyContaining();
+ cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
+ cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>));
+ cfg.AddOpenBehavior(typeof(TransactionBehavior<,>));
+ });
+
+ // EN: Add FluentValidation / VI: Thêm FluentValidation
+ builder.Services.AddValidatorsFromAssemblyContaining();
+
+ // EN: Add API versioning / VI: Thêm API versioning
+ builder.Services.AddApiVersioning(options =>
+ {
+ options.DefaultApiVersion = new ApiVersion(1, 0);
+ options.AssumeDefaultVersionWhenUnspecified = true;
+ options.ReportApiVersions = true;
+ options.ApiVersionReader = ApiVersionReader.Combine(
+ new UrlSegmentApiVersionReader(),
+ new HeaderApiVersionReader("X-Api-Version"));
+ })
+ .AddApiExplorer(options =>
+ {
+ options.GroupNameFormat = "'v'VVV";
+ options.SubstituteApiVersionInUrl = true;
+ });
+
+ // EN: Add controllers / VI: Thêm controllers
+ builder.Services.AddControllers();
+
+ // EN: Add ProblemDetails middleware (RFC 7807) / VI: Thêm ProblemDetails middleware
+ builder.Services.AddProblemDetails(options =>
+ {
+ options.IncludeExceptionDetails = (ctx, ex) =>
+ builder.Environment.IsDevelopment();
+ });
+
+ // EN: Add Swagger / VI: Thêm Swagger
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen(options =>
+ {
+ options.SwaggerDoc("v1", new()
+ {
+ Title = "MyService API",
+ Version = "v1",
+ Description = "MyService microservice API / API microservice MyService"
+ });
+ });
+
+ // EN: Add health checks / VI: Thêm health checks
+ builder.Services.AddHealthChecks()
+ .AddNpgSql(
+ builder.Configuration.GetConnectionString("DefaultConnection")
+ ?? builder.Configuration["DATABASE_URL"]
+ ?? "",
+ name: "postgresql",
+ tags: ["db", "postgresql"]);
+
+ // EN: Add CORS / VI: Thêm CORS
+ builder.Services.AddCors(options =>
+ {
+ options.AddDefaultPolicy(policy =>
+ {
+ policy.AllowAnyOrigin()
+ .AllowAnyMethod()
+ .AllowAnyHeader();
+ });
+ });
+
+ var app = builder.Build();
+
+ // EN: Configure middleware pipeline / VI: Cấu hình middleware pipeline
+ app.UseSerilogRequestLogging();
+ app.UseProblemDetails();
+
+ if (app.Environment.IsDevelopment())
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyService API v1");
+ c.RoutePrefix = "swagger";
+ });
+ }
+
+ app.UseCors();
+ app.UseRouting();
+
+ // EN: Map health check endpoints / VI: Map health check endpoints
+ app.MapHealthChecks("/health");
+ app.MapHealthChecks("/health/live", new()
+ {
+ Predicate = _ => false // EN: Just checks app is running / VI: Chỉ kiểm tra app đang chạy
+ });
+ app.MapHealthChecks("/health/ready");
+
+ // EN: Map controllers / VI: Map controllers
+ app.MapControllers();
+
+ // EN: Run the application / VI: Chạy ứng dụng
+ app.Run();
+}
+catch (Exception ex)
+{
+ Log.Fatal(ex, "Application terminated unexpectedly / Ứng dụng kết thúc bất ngờ");
+ throw;
+}
+finally
+{
+ Log.CloseAndFlush();
+}
+
+// EN: Make Program class accessible for integration tests
+// VI: Làm cho class Program có thể truy cập cho integration tests
+public partial class Program { }
diff --git a/services/ads-manager-service-net/src/MyService.API/Properties/launchSettings.json b/services/ads-manager-service-net/src/MyService.API/Properties/launchSettings.json
new file mode 100644
index 00000000..6355d40b
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/Properties/launchSettings.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/ads-manager-service-net/src/MyService.API/appsettings.Development.json b/services/ads-manager-service-net/src/MyService.API/appsettings.Development.json
new file mode 100644
index 00000000..e407ac85
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/appsettings.Development.json
@@ -0,0 +1,19 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft.AspNetCore": "Information",
+ "Microsoft.EntityFrameworkCore.Database.Command": "Information"
+ }
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Debug",
+ "Override": {
+ "Microsoft": "Information",
+ "Microsoft.EntityFrameworkCore.Database.Command": "Information",
+ "System": "Information"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/ads-manager-service-net/src/MyService.API/appsettings.json b/services/ads-manager-service-net/src/MyService.API/appsettings.json
new file mode 100644
index 00000000..523dc0fc
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.API/appsettings.json
@@ -0,0 +1,46 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Microsoft.EntityFrameworkCore": "Warning"
+ }
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Warning",
+ "Microsoft.EntityFrameworkCore": "Warning",
+ "System": "Warning"
+ }
+ },
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
+ }
+ }
+ ],
+ "Enrich": [
+ "FromLogContext",
+ "WithMachineName",
+ "WithThreadId"
+ ]
+ },
+ "ConnectionStrings": {
+ "DefaultConnection": "Host=localhost;Port=5432;Database=myservice_db;Username=postgres;Password=postgres"
+ },
+ "Redis": {
+ "ConnectionString": "localhost:6379"
+ },
+ "Jwt": {
+ "Secret": "your-super-secret-key-min-32-characters",
+ "Issuer": "goodgo-platform",
+ "Audience": "goodgo-services",
+ "AccessTokenExpiryMinutes": 15,
+ "RefreshTokenExpiryDays": 7
+ },
+ "AllowedHosts": "*"
+}
\ No newline at end of file
diff --git a/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs b/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs
new file mode 100644
index 00000000..40bc8c3a
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs
@@ -0,0 +1,61 @@
+using MyService.Domain.SeedWork;
+
+namespace MyService.Domain.AggregatesModel.SampleAggregate;
+
+///
+/// EN: Repository interface for Sample aggregate.
+/// VI: Interface repository cho Sample aggregate.
+///
+///
+/// EN: Following repository pattern, this interface defines the contract
+/// for data access operations on Sample aggregate.
+/// VI: Theo pattern repository, interface này định nghĩa contract
+/// cho các thao tác truy cập dữ liệu trên Sample aggregate.
+///
+public interface ISampleRepository : IRepository
+{
+ ///
+ /// EN: Get a sample by its ID.
+ /// VI: Lấy một sample theo ID.
+ ///
+ /// EN: The sample ID / VI: ID của sample
+ /// EN: The sample or null if not found / VI: Sample hoặc null nếu không tìm thấy
+ Task GetAsync(Guid sampleId);
+
+ ///
+ /// EN: Get all samples.
+ /// VI: Lấy tất cả samples.
+ ///
+ /// EN: List of samples / VI: Danh sách samples
+ Task> GetAllAsync();
+
+ ///
+ /// EN: Add a new sample.
+ /// VI: Thêm một sample mới.
+ ///
+ /// EN: The sample to add / VI: Sample cần thêm
+ /// EN: The added sample / VI: Sample đã thêm
+ Sample Add(Sample sample);
+
+ ///
+ /// EN: Update an existing sample.
+ /// VI: Cập nhật một sample đã tồn tại.
+ ///
+ /// EN: The sample to update / VI: Sample cần cập nhật
+ void Update(Sample sample);
+
+ ///
+ /// EN: Delete a sample.
+ /// VI: Xóa một sample.
+ ///
+ /// EN: The sample to delete / VI: Sample cần xóa
+ void Delete(Sample sample);
+
+ ///
+ /// EN: Get samples by status.
+ /// VI: Lấy samples theo trạng thái.
+ ///
+ /// EN: The status ID / VI: ID trạng thái
+ /// EN: List of samples with given status / VI: Danh sách samples với trạng thái cho trước
+ Task> GetByStatusAsync(int statusId);
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/Sample.cs b/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/Sample.cs
new file mode 100644
index 00000000..641bb385
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/Sample.cs
@@ -0,0 +1,158 @@
+using MyService.Domain.Events;
+using MyService.Domain.Exceptions;
+using MyService.Domain.SeedWork;
+
+namespace MyService.Domain.AggregatesModel.SampleAggregate;
+
+///
+/// EN: Sample aggregate root demonstrating DDD patterns.
+/// VI: Sample aggregate root minh họa các pattern DDD.
+///
+public class Sample : Entity, IAggregateRoot
+{
+ // EN: Private fields for encapsulation
+ // VI: Fields private để đóng gói
+ private string _name = null!;
+ private string? _description;
+ private SampleStatus _status = null!;
+ private DateTime _createdAt;
+ private DateTime? _updatedAt;
+
+ ///
+ /// EN: Sample name (required).
+ /// VI: Tên sample (bắt buộc).
+ ///
+ public string Name => _name;
+
+ ///
+ /// EN: Optional description.
+ /// VI: Mô tả tùy chọn.
+ ///
+ public string? Description => _description;
+
+ ///
+ /// EN: Current status.
+ /// VI: Trạng thái hiện tại.
+ ///
+ public SampleStatus Status => _status;
+
+ ///
+ /// EN: Status ID for EF Core mapping.
+ /// VI: ID trạng thái cho EF Core mapping.
+ ///
+ public int StatusId { get; private set; }
+
+ ///
+ /// EN: Creation timestamp.
+ /// VI: Thời gian tạo.
+ ///
+ public DateTime CreatedAt => _createdAt;
+
+ ///
+ /// EN: Last update timestamp.
+ /// VI: Thời gian cập nhật cuối.
+ ///
+ public DateTime? UpdatedAt => _updatedAt;
+
+ ///
+ /// EN: Private constructor for EF Core.
+ /// VI: Constructor private cho EF Core.
+ ///
+ protected Sample()
+ {
+ }
+
+ ///
+ /// EN: Create a new Sample with required information.
+ /// VI: Tạo một Sample mới với thông tin bắt buộc.
+ ///
+ /// EN: Sample name / VI: Tên sample
+ /// EN: Optional description / VI: Mô tả tùy chọn
+ public Sample(string name, string? description = null) : this()
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ throw new SampleDomainException("Sample name cannot be empty");
+
+ Id = Guid.NewGuid();
+ _name = name;
+ _description = description;
+ _status = SampleStatus.Draft;
+ StatusId = SampleStatus.Draft.Id;
+ _createdAt = DateTime.UtcNow;
+
+ // EN: Add domain event for creation
+ // VI: Thêm domain event cho việc tạo
+ AddDomainEvent(new SampleCreatedDomainEvent(this));
+ }
+
+ ///
+ /// EN: Update sample information.
+ /// VI: Cập nhật thông tin sample.
+ ///
+ public void Update(string name, string? description)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ throw new SampleDomainException("Sample name cannot be empty");
+
+ if (_status == SampleStatus.Cancelled)
+ throw new SampleDomainException("Cannot update a cancelled sample");
+
+ _name = name;
+ _description = description;
+ _updatedAt = DateTime.UtcNow;
+ }
+
+ ///
+ /// EN: Activate the sample.
+ /// VI: Kích hoạt sample.
+ ///
+ public void Activate()
+ {
+ if (_status != SampleStatus.Draft)
+ throw new SampleDomainException("Only draft samples can be activated");
+
+ var previousStatus = _status;
+ _status = SampleStatus.Active;
+ StatusId = SampleStatus.Active.Id;
+ _updatedAt = DateTime.UtcNow;
+
+ AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status));
+ }
+
+ ///
+ /// EN: Complete the sample.
+ /// VI: Hoàn thành sample.
+ ///
+ public void Complete()
+ {
+ if (_status != SampleStatus.Active)
+ throw new SampleDomainException("Only active samples can be completed");
+
+ var previousStatus = _status;
+ _status = SampleStatus.Completed;
+ StatusId = SampleStatus.Completed.Id;
+ _updatedAt = DateTime.UtcNow;
+
+ AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status));
+ }
+
+ ///
+ /// EN: Cancel the sample.
+ /// VI: Hủy sample.
+ ///
+ public void Cancel()
+ {
+ if (_status == SampleStatus.Completed)
+ throw new SampleDomainException("Cannot cancel a completed sample");
+
+ if (_status == SampleStatus.Cancelled)
+ throw new SampleDomainException("Sample is already cancelled");
+
+ var previousStatus = _status;
+ _status = SampleStatus.Cancelled;
+ StatusId = SampleStatus.Cancelled.Id;
+ _updatedAt = DateTime.UtcNow;
+
+ AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status));
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs b/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs
new file mode 100644
index 00000000..54ce63ba
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs
@@ -0,0 +1,77 @@
+using MyService.Domain.SeedWork;
+
+namespace MyService.Domain.AggregatesModel.SampleAggregate;
+
+///
+/// EN: Sample status enumeration following type-safe enum pattern.
+/// VI: Enumeration trạng thái Sample theo pattern enum an toàn kiểu.
+///
+public class SampleStatus : Enumeration
+{
+ ///
+ /// EN: Draft status - initial state
+ /// VI: Trạng thái nháp - trạng thái ban đầu
+ ///
+ public static SampleStatus Draft = new(1, nameof(Draft));
+
+ ///
+ /// EN: Active status - ready for use
+ /// VI: Trạng thái hoạt động - sẵn sàng sử dụng
+ ///
+ public static SampleStatus Active = new(2, nameof(Active));
+
+ ///
+ /// EN: Completed status - finished processing
+ /// VI: Trạng thái hoàn thành - đã xử lý xong
+ ///
+ public static SampleStatus Completed = new(3, nameof(Completed));
+
+ ///
+ /// EN: Cancelled status - cancelled by user
+ /// VI: Trạng thái đã hủy - bị hủy bởi người dùng
+ ///
+ public static SampleStatus Cancelled = new(4, nameof(Cancelled));
+
+ public SampleStatus(int id, string name) : base(id, name)
+ {
+ }
+
+ ///
+ /// EN: Get all available statuses.
+ /// VI: Lấy tất cả các trạng thái có sẵn.
+ ///
+ public static IEnumerable List() => GetAll();
+
+ ///
+ /// EN: Parse status from name.
+ /// VI: Parse trạng thái từ tên.
+ ///
+ public static SampleStatus FromName(string name)
+ {
+ var status = List().SingleOrDefault(s =>
+ string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase));
+
+ if (status is null)
+ {
+ throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}");
+ }
+
+ return status;
+ }
+
+ ///
+ /// EN: Parse status from ID.
+ /// VI: Parse trạng thái từ ID.
+ ///
+ public static SampleStatus From(int id)
+ {
+ var status = List().SingleOrDefault(s => s.Id == id);
+
+ if (status is null)
+ {
+ throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}");
+ }
+
+ return status;
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/Events/SampleCreatedDomainEvent.cs b/services/ads-manager-service-net/src/MyService.Domain/Events/SampleCreatedDomainEvent.cs
new file mode 100644
index 00000000..7e838214
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/Events/SampleCreatedDomainEvent.cs
@@ -0,0 +1,22 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.Domain.Events;
+
+///
+/// EN: Domain event raised when a new Sample is created.
+/// VI: Domain event được phát ra khi một Sample mới được tạo.
+///
+public class SampleCreatedDomainEvent : INotification
+{
+ ///
+ /// EN: The newly created sample.
+ /// VI: Sample mới được tạo.
+ ///
+ public Sample Sample { get; }
+
+ public SampleCreatedDomainEvent(Sample sample)
+ {
+ Sample = sample;
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/Events/SampleStatusChangedDomainEvent.cs b/services/ads-manager-service-net/src/MyService.Domain/Events/SampleStatusChangedDomainEvent.cs
new file mode 100644
index 00000000..f6d9b422
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/Events/SampleStatusChangedDomainEvent.cs
@@ -0,0 +1,39 @@
+using MediatR;
+using MyService.Domain.AggregatesModel.SampleAggregate;
+
+namespace MyService.Domain.Events;
+
+///
+/// EN: Domain event raised when Sample status changes.
+/// VI: Domain event được phát ra khi trạng thái Sample thay đổi.
+///
+public class SampleStatusChangedDomainEvent : INotification
+{
+ ///
+ /// EN: The sample ID.
+ /// VI: ID của sample.
+ ///
+ public Guid SampleId { get; }
+
+ ///
+ /// EN: Previous status before the change.
+ /// VI: Trạng thái trước khi thay đổi.
+ ///
+ public SampleStatus PreviousStatus { get; }
+
+ ///
+ /// EN: New status after the change.
+ /// VI: Trạng thái mới sau khi thay đổi.
+ ///
+ public SampleStatus NewStatus { get; }
+
+ public SampleStatusChangedDomainEvent(
+ Guid sampleId,
+ SampleStatus previousStatus,
+ SampleStatus newStatus)
+ {
+ SampleId = sampleId;
+ PreviousStatus = previousStatus;
+ NewStatus = newStatus;
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/Exceptions/DomainException.cs b/services/ads-manager-service-net/src/MyService.Domain/Exceptions/DomainException.cs
new file mode 100644
index 00000000..7e737f64
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/Exceptions/DomainException.cs
@@ -0,0 +1,21 @@
+namespace MyService.Domain.Exceptions;
+
+///
+/// EN: Base exception for domain errors.
+/// VI: Exception cơ sở cho các lỗi domain.
+///
+public class DomainException : Exception
+{
+ public DomainException()
+ {
+ }
+
+ public DomainException(string message) : base(message)
+ {
+ }
+
+ public DomainException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/Exceptions/SampleDomainException.cs b/services/ads-manager-service-net/src/MyService.Domain/Exceptions/SampleDomainException.cs
new file mode 100644
index 00000000..c850944c
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/Exceptions/SampleDomainException.cs
@@ -0,0 +1,21 @@
+namespace MyService.Domain.Exceptions;
+
+///
+/// EN: Exception for Sample aggregate domain errors.
+/// VI: Exception cho các lỗi domain của Sample aggregate.
+///
+public class SampleDomainException : DomainException
+{
+ public SampleDomainException()
+ {
+ }
+
+ public SampleDomainException(string message) : base(message)
+ {
+ }
+
+ public SampleDomainException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/MyService.Domain.csproj b/services/ads-manager-service-net/src/MyService.Domain/MyService.Domain.csproj
new file mode 100644
index 00000000..3208317a
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/MyService.Domain.csproj
@@ -0,0 +1,14 @@
+
+
+
+ MyService.Domain
+ MyService.Domain
+ Domain layer containing core business logic and entities
+
+
+
+
+
+
+
+
diff --git a/services/ads-manager-service-net/src/MyService.Domain/SeedWork/Entity.cs b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/Entity.cs
new file mode 100644
index 00000000..b07fdd3b
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/Entity.cs
@@ -0,0 +1,102 @@
+using MediatR;
+
+namespace MyService.Domain.SeedWork;
+
+///
+/// EN: Base class for all domain entities.
+/// VI: Lớp cơ sở cho tất cả các entity trong domain.
+///
+public abstract class Entity
+{
+ private int? _requestedHashCode;
+ private Guid _id;
+ private List _domainEvents = new();
+
+ ///
+ /// EN: Unique identifier for the entity.
+ /// VI: Định danh duy nhất cho entity.
+ ///
+ public virtual Guid Id
+ {
+ get => _id;
+ protected set => _id = value;
+ }
+
+ ///
+ /// EN: Domain events raised by this entity.
+ /// VI: Các domain event được phát ra bởi entity này.
+ ///
+ public IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly();
+
+ ///
+ /// EN: Add a domain event to be dispatched.
+ /// VI: Thêm một domain event để dispatch.
+ ///
+ public void AddDomainEvent(INotification eventItem)
+ {
+ _domainEvents.Add(eventItem);
+ }
+
+ ///
+ /// EN: Remove a domain event.
+ /// VI: Xóa một domain event.
+ ///
+ public void RemoveDomainEvent(INotification eventItem)
+ {
+ _domainEvents.Remove(eventItem);
+ }
+
+ ///
+ /// EN: Clear all domain events.
+ /// VI: Xóa tất cả domain events.
+ ///
+ public void ClearDomainEvents()
+ {
+ _domainEvents.Clear();
+ }
+
+ ///
+ /// EN: Check if entity is transient (not persisted yet).
+ /// VI: Kiểm tra xem entity có phải là transient (chưa lưu) không.
+ ///
+ public bool IsTransient()
+ {
+ return Id == default;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is not Entity item)
+ return false;
+
+ if (ReferenceEquals(this, item))
+ return true;
+
+ if (GetType() != item.GetType())
+ return false;
+
+ if (item.IsTransient() || IsTransient())
+ return false;
+
+ return item.Id == Id;
+ }
+
+ public override int GetHashCode()
+ {
+ if (IsTransient())
+ return base.GetHashCode();
+
+ _requestedHashCode ??= Id.GetHashCode() ^ 31;
+ return _requestedHashCode.Value;
+ }
+
+ public static bool operator ==(Entity? left, Entity? right)
+ {
+ return left?.Equals(right) ?? right is null;
+ }
+
+ public static bool operator !=(Entity? left, Entity? right)
+ {
+ return !(left == right);
+ }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/SeedWork/Enumeration.cs b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/Enumeration.cs
new file mode 100644
index 00000000..6641979c
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/Enumeration.cs
@@ -0,0 +1,95 @@
+using System.Reflection;
+
+namespace MyService.Domain.SeedWork;
+
+///
+/// EN: Base class for enumeration classes (type-safe enum pattern).
+/// VI: Lớp cơ sở cho các lớp enumeration (pattern enum an toàn kiểu).
+///
+///
+/// EN: This provides a type-safe alternative to enums with additional functionality
+/// like validation, parsing, and rich behavior.
+/// VI: Cung cấp một thay thế an toàn kiểu cho enums với các chức năng bổ sung
+/// như validation, parsing, và hành vi phong phú.
+///
+public abstract class Enumeration : IComparable
+{
+ ///
+ /// EN: The name of the enumeration value.
+ /// VI: Tên của giá trị enumeration.
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// EN: The unique identifier of the enumeration value.
+ /// VI: Định danh duy nhất của giá trị enumeration.
+ ///
+ public int Id { get; private set; }
+
+ protected Enumeration(int id, string name) => (Id, Name) = (id, name);
+
+ public override string ToString() => Name;
+
+ ///
+ /// EN: Get all enumeration values of a given type.
+ /// VI: Lấy tất cả các giá trị enumeration của một kiểu cho trước.
+ ///
+ public static IEnumerable GetAll() where T : Enumeration =>
+ typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
+ .Select(f => f.GetValue(null))
+ .Cast();
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is not Enumeration otherValue)
+ return false;
+
+ var typeMatches = GetType() == obj.GetType();
+ var valueMatches = Id.Equals(otherValue.Id);
+
+ return typeMatches && valueMatches;
+ }
+
+ public override int GetHashCode() => Id.GetHashCode();
+
+ ///
+ /// EN: Get absolute difference between two enumeration values.
+ /// VI: Lấy sự khác biệt tuyệt đối giữa hai giá trị enumeration.
+ ///
+ public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
+ {
+ return Math.Abs(firstValue.Id - secondValue.Id);
+ }
+
+ ///
+ /// EN: Parse an integer ID to the corresponding enumeration value.
+ /// VI: Parse một ID integer thành giá trị enumeration tương ứng.
+ ///
+ public static T FromValue(int value) where T : Enumeration
+ {
+ var matchingItem = Parse(value, "value", item => item.Id == value);
+ return matchingItem;
+ }
+
+ ///
+ /// EN: Parse a display name to the corresponding enumeration value.
+ /// VI: Parse một tên hiển thị thành giá trị enumeration tương ứng.
+ ///
+ public static T FromDisplayName(string displayName) where T : Enumeration
+ {
+ var matchingItem = Parse(displayName, "display name", item => item.Name == displayName);
+ return matchingItem;
+ }
+
+ private static T Parse(TValue value, string description, Func predicate) where T : Enumeration
+ {
+ var matchingItem = GetAll().FirstOrDefault(predicate);
+
+ if (matchingItem is null)
+ throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
+
+ return matchingItem;
+ }
+
+ public int CompareTo(object? other) => Id.CompareTo(((Enumeration)other!).Id);
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IAggregateRoot.cs b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IAggregateRoot.cs
new file mode 100644
index 00000000..d361394f
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IAggregateRoot.cs
@@ -0,0 +1,15 @@
+namespace MyService.Domain.SeedWork;
+
+///
+/// EN: Marker interface for aggregate roots.
+/// VI: Interface đánh dấu cho aggregate roots.
+///
+///
+/// EN: Aggregate roots are the entry points to aggregates and are the only objects
+/// that outside code should hold references to.
+/// VI: Aggregate roots là điểm vào của aggregates và là đối tượng duy nhất
+/// mà code bên ngoài nên giữ tham chiếu đến.
+///
+public interface IAggregateRoot
+{
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IRepository.cs b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IRepository.cs
new file mode 100644
index 00000000..2d539e44
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IRepository.cs
@@ -0,0 +1,15 @@
+namespace MyService.Domain.SeedWork;
+
+///
+/// EN: Generic repository interface for aggregate roots.
+/// VI: Interface repository generic cho aggregate roots.
+///
+/// EN: The aggregate root type / VI: Kiểu aggregate root
+public interface IRepository where T : IAggregateRoot
+{
+ ///
+ /// EN: The unit of work for this repository.
+ /// VI: Unit of work cho repository này.
+ ///
+ IUnitOfWork UnitOfWork { get; }
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IUnitOfWork.cs b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IUnitOfWork.cs
new file mode 100644
index 00000000..d37d8fa4
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/IUnitOfWork.cs
@@ -0,0 +1,30 @@
+namespace MyService.Domain.SeedWork;
+
+///
+/// EN: Unit of Work pattern interface.
+/// VI: Interface cho Unit of Work pattern.
+///
+///
+/// EN: Maintains a list of objects affected by a business transaction
+/// and coordinates the writing out of changes.
+/// VI: Duy trì danh sách các đối tượng bị ảnh hưởng bởi một transaction nghiệp vụ
+/// và điều phối việc ghi các thay đổi.
+///
+public interface IUnitOfWork : IDisposable
+{
+ ///
+ /// EN: Save all changes made in this unit of work.
+ /// VI: Lưu tất cả các thay đổi được thực hiện trong unit of work này.
+ ///
+ /// EN: Cancellation token / VI: Token hủy
+ /// EN: Number of entities written / VI: Số entity đã ghi
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// EN: Save all changes and dispatch domain events.
+ /// VI: Lưu tất cả thay đổi và dispatch domain events.
+ ///
+ /// EN: Cancellation token / VI: Token hủy
+ /// EN: True if successful / VI: True nếu thành công
+ Task SaveEntitiesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/services/ads-manager-service-net/src/MyService.Domain/SeedWork/ValueObject.cs b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/ValueObject.cs
new file mode 100644
index 00000000..5cf4188f
--- /dev/null
+++ b/services/ads-manager-service-net/src/MyService.Domain/SeedWork/ValueObject.cs
@@ -0,0 +1,53 @@
+namespace MyService.Domain.SeedWork;
+
+///
+/// EN: Base class for Value Objects following DDD patterns.
+/// VI: Lớp cơ sở cho Value Objects theo mẫu DDD.
+///
+///
+/// EN: Value objects are immutable and compared by their values, not identity.
+/// VI: Value objects là bất biến và được so sánh theo giá trị, không phải định danh.
+///
+public abstract class ValueObject
+{
+ ///
+ /// EN: Get the atomic values that make up this value object.
+ /// VI: Lấy các giá trị nguyên tử tạo nên value object này.
+ ///
+ protected abstract IEnumerable